diff --git a/backend/controllers/BlogCategoryController.php b/backend/controllers/BlogCategoryController.php new file mode 100644 index 0000000..ad21394 --- /dev/null +++ b/backend/controllers/BlogCategoryController.php @@ -0,0 +1,124 @@ + [ + 'class' => VerbFilter::className(), + 'actions' => [ + 'delete' => ['POST'], + ], + ], + ]; + } + + /** + * Lists all BlogCategory models. + * @return mixed + */ + public function actionIndex() + { + $searchModel = new BlogCategorySearch(); + $dataProvider = $searchModel->search(Yii::$app->request->queryParams); + + return $this->render('index', [ + 'searchModel' => $searchModel, + 'dataProvider' => $dataProvider, + ]); + } + + /** + * Displays a single BlogCategory model. + * @param integer $id + * @return mixed + */ + public function actionView($id) + { + return $this->render('view', [ + 'model' => $this->findModel($id), + ]); + } + + /** + * Creates a new BlogCategory model. + * If creation is successful, the browser will be redirected to the 'view' page. + * @return mixed + */ + public function actionCreate() + { + $model = new BlogCategory(); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); + } else { + return $this->render('create', [ + 'model' => $model, + ]); + } + } + + /** + * Updates an existing BlogCategory model. + * If update is successful, the browser will be redirected to the 'view' page. + * @param integer $id + * @return mixed + */ + public function actionUpdate($id) + { + $model = $this->findModel($id); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); + } else { + return $this->render('update', [ + 'model' => $model, + ]); + } + } + + /** + * Deletes an existing BlogCategory model. + * If deletion is successful, the browser will be redirected to the 'index' page. + * @param integer $id + * @return mixed + */ + public function actionDelete($id) + { + $this->findModel($id)->delete(); + + return $this->redirect(['index']); + } + + /** + * Finds the BlogCategory model based on its primary key value. + * If the model is not found, a 404 HTTP exception will be thrown. + * @param integer $id + * @return BlogCategory the loaded model + * @throws NotFoundHttpException if the model cannot be found + */ + protected function findModel($id) + { + if (($model = BlogCategory::findOne($id)) !== null) { + return $model; + } else { + throw new NotFoundHttpException('The requested page does not exist.'); + } + } +} diff --git a/backend/controllers/BlogController.php b/backend/controllers/BlogController.php index c43d680..e627076 100755 --- a/backend/controllers/BlogController.php +++ b/backend/controllers/BlogController.php @@ -3,10 +3,12 @@ namespace backend\controllers; use common\models\Blog; +use common\models\BlogCategory; use common\models\BlogSearch; use Yii; use common\models\Articles; use common\models\ArticlesSearch; +use yii\helpers\VarDumper; use yii\web\Controller; use yii\web\NotFoundHttpException; use yii\filters\VerbFilter; @@ -95,7 +97,17 @@ class BlogController extends Controller if ($model->save() && $image) { $image->saveAs(Yii::getAlias('@storage/blog/' . $image->name)); } - + + if(!empty($model->categoryItems)) + { + foreach($model->categoryItems as $item) + { + if($category = BlogCategory::findOne($item)) { + $model->link('categories', $category); + } + } + } + return $this->redirect(['blog/index']); } else { return $this->render('create', [ @@ -113,6 +125,8 @@ class BlogController extends Controller public function actionUpdate($id) { $model = $this->findModel($id); + $model->categoryItems = $model->getCategories()->indexBy('id') + ->column(); if ($model->load(Yii::$app->request->post())) { @@ -128,7 +142,17 @@ class BlogController extends Controller if ($model->save() && $image) { $image->saveAs(Yii::getAlias('@storage/blog/' . $image->name)); } - + $model->unlinkAll('categories', true); + if(!empty($model->categoryItems)) + { + foreach($model->categoryItems as $item) + { + if($category = BlogCategory::findOne($item)) { + $model->link('categories', $category); + } + } + } + return $this->redirect(['view', 'id' => $model->id]); } else { return $this->render('update', [ diff --git a/backend/views/blog-category/_form.php b/backend/views/blog-category/_form.php new file mode 100644 index 0000000..f9b4fbb --- /dev/null +++ b/backend/views/blog-category/_form.php @@ -0,0 +1,23 @@ + + +
+ + + + field($model, 'name')->textInput(['maxlength' => true]) ?> + +
+ isNewRecord ? Yii::t('app', 'Create') : Yii::t('app', 'Update'), ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?> +
+ + + +
diff --git a/backend/views/blog-category/_search.php b/backend/views/blog-category/_search.php new file mode 100644 index 0000000..df88ac7 --- /dev/null +++ b/backend/views/blog-category/_search.php @@ -0,0 +1,29 @@ + + + diff --git a/backend/views/blog-category/create.php b/backend/views/blog-category/create.php new file mode 100644 index 0000000..d9c4e78 --- /dev/null +++ b/backend/views/blog-category/create.php @@ -0,0 +1,21 @@ +title = Yii::t('app', 'Create Blog Category'); +$this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Blog Categories'), 'url' => ['index']]; +$this->params['breadcrumbs'][] = $this->title; +?> +
+ +

title) ?>

+ + render('_form', [ + 'model' => $model, + ]) ?> + +
diff --git a/backend/views/blog-category/index.php b/backend/views/blog-category/index.php new file mode 100644 index 0000000..5caa36d --- /dev/null +++ b/backend/views/blog-category/index.php @@ -0,0 +1,33 @@ +title = Yii::t('app', 'Blog Categories'); +$this->params['breadcrumbs'][] = $this->title; +?> +
+ +

title) ?>

+ render('_search', ['model' => $searchModel]); ?> + +

+ 'btn btn-success']) ?> +

+ $dataProvider, + 'filterModel' => $searchModel, + 'columns' => [ + ['class' => 'yii\grid\SerialColumn'], + + 'id', + 'name', + + ['class' => 'yii\grid\ActionColumn'], + ], + ]); ?> +
diff --git a/backend/views/blog-category/update.php b/backend/views/blog-category/update.php new file mode 100644 index 0000000..7438663 --- /dev/null +++ b/backend/views/blog-category/update.php @@ -0,0 +1,23 @@ +title = Yii::t('app', 'Update {modelClass}: ', [ + 'modelClass' => 'Blog Category', +]) . $model->name; +$this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Blog Categories'), 'url' => ['index']]; +$this->params['breadcrumbs'][] = ['label' => $model->name, 'url' => ['view', 'id' => $model->id]]; +$this->params['breadcrumbs'][] = Yii::t('app', 'Update'); +?> +
+ +

title) ?>

+ + render('_form', [ + 'model' => $model, + ]) ?> + +
diff --git a/backend/views/blog-category/view.php b/backend/views/blog-category/view.php new file mode 100644 index 0000000..5e2cfe4 --- /dev/null +++ b/backend/views/blog-category/view.php @@ -0,0 +1,36 @@ +title = $model->name; +$this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Blog Categories'), 'url' => ['index']]; +$this->params['breadcrumbs'][] = $this->title; +?> +
+ +

title) ?>

+ +

+ $model->id], ['class' => 'btn btn-primary']) ?> + $model->id], [ + 'class' => 'btn btn-danger', + 'data' => [ + 'confirm' => Yii::t('app', 'Are you sure you want to delete this item?'), + 'method' => 'post', + ], + ]) ?> +

+ + $model, + 'attributes' => [ + 'id', + 'name', + ], + ]) ?> + +
diff --git a/backend/views/blog/_form.php b/backend/views/blog/_form.php index 9ddac0d..61e7bfe 100755 --- a/backend/views/blog/_form.php +++ b/backend/views/blog/_form.php @@ -1,87 +1,120 @@
- + false, - 'options' => ['enctype' => 'multipart/form-data'] + 'options' => [ 'enctype' => 'multipart/form-data' ], ]); ?> - - + + field($model, 'date') - ->widget(DatePicker::className(), [ - 'dateFormat' => 'dd-MM-yyyy', - ]) ?> - + ->widget(DatePicker::className(), [ + 'dateFormat' => 'dd-MM-yyyy', + ]) ?> + field($model, 'date_end') - ->widget(DatePicker::className(), [ - 'dateFormat' => 'dd-MM-yyyy', - ]) ?> - - field($model, 'title')->textInput(['maxlength' => true]) ?> - - field($model, 'body')->widget(CKEditor::className(), - [ - 'editorOptions' => ElFinder::ckeditorOptions('elfinder',[ - 'preset' => 'full', //разработанны стандартные настройки basic, standard, full данную возможность не обязательно использовать - 'inline' => false, //по умолчанию false]), - 'filebrowserUploadUrl'=>Yii::$app->getUrlManager()->createUrl('file/uploader/images-upload') - ] - ) - ]) ?> - - field($model, 'body_preview')->widget(CKEditor::className(), - [ - 'editorOptions' => ElFinder::ckeditorOptions('elfinder',[ - 'preset' => 'full', //разработанны стандартные настройки basic, standard, full данную возможность не обязательно использовать - 'inline' => false, //по умолчанию false]), - 'filebrowserUploadUrl'=>Yii::$app->getUrlManager()->createUrl('file/uploader/images-upload') - ] - ) - ]) ?> - - field($model, 'imageUpload')->widget(\kartik\file\FileInput::classname(), [ - 'language' => 'ru', - 'options' => [ - 'accept' => 'image/*', - 'multiple' => false, - ], - 'pluginOptions' => [ - 'allowedFileExtensions' => ['jpg', 'gif', 'png'], - 'initialPreview' => !empty($model->imageUrl) ? \common\components\artboximage\ArtboxImageHelper::getImage($model->imageUrl, 'list') : '', - 'overwriteInitial' => true, - 'showRemove' => false, - 'showUpload' => false, - 'previewFileType' => 'image', - ], - ]); ?> - - field($model, 'translit')->textInput(['maxlength' => true]) ?> - - field($model, 'meta_title')->textInput(['maxlength' => true]) ?> - - field($model, 'meta_keywords')->textInput(['maxlength' => true]) ?> - - field($model, 'meta_description')->textInput(['maxlength' => true]) ?> - - field($model, 'seo_text')->textarea(['rows' => 6]) ?> - - field($model, 'h1')->textInput(['maxlength' => true]) ?> - + ->widget(DatePicker::className(), [ + 'dateFormat' => 'dd-MM-yyyy', + ]) ?> + + field($model, 'title') + ->textInput([ 'maxlength' => true ]) ?> + + field($model, 'body') + ->widget(CKEditor::className(), [ + 'editorOptions' => ElFinder::ckeditorOptions('elfinder', [ + 'preset' => 'full', + //разработанны стандартные настройки basic, standard, full данную возможность не обязательно использовать + 'inline' => false, + //по умолчанию false]), + 'filebrowserUploadUrl' => Yii::$app->getUrlManager() + ->createUrl('file/uploader/images-upload'), + ]), + ]) ?> + + field($model, 'body_preview') + ->widget(CKEditor::className(), [ + 'editorOptions' => ElFinder::ckeditorOptions('elfinder', [ + 'preset' => 'full', + //разработанны стандартные настройки basic, standard, full данную возможность не обязательно использовать + 'inline' => false, + //по умолчанию false]), + 'filebrowserUploadUrl' => Yii::$app->getUrlManager() + ->createUrl('file/uploader/images-upload'), + ]), + ]) ?> + + field($model, 'imageUpload') + ->widget(\kartik\file\FileInput::classname(), [ + 'language' => 'ru', + 'options' => [ + 'accept' => 'image/*', + 'multiple' => false, + ], + 'pluginOptions' => [ + 'allowedFileExtensions' => [ + 'jpg', + 'gif', + 'png', + ], + 'initialPreview' => !empty( $model->imageUrl ) ? \common\components\artboximage\ArtboxImageHelper::getImage($model->imageUrl, 'list') : '', + 'overwriteInitial' => true, + 'showRemove' => false, + 'showUpload' => false, + 'previewFileType' => 'image', + ], + ]); ?> + + field($model, 'categoryItems') + ->widget(Select2::className(), [ + 'data' => $model->getCategoryItemsAsArray(), + 'language' => 'ru', + 'options' => [ + 'placeholder' => 'Выберите категории ...', + 'multiple' => true, + ], + 'pluginOptions' => [ + 'allowClear' => true, + ], + ]); + ?> + + field($model, 'translit') + ->textInput([ 'maxlength' => true ]) ?> + + field($model, 'meta_title') + ->textInput([ 'maxlength' => true ]) ?> + + field($model, 'meta_keywords') + ->textInput([ 'maxlength' => true ]) ?> + + field($model, 'meta_description') + ->textInput([ 'maxlength' => true ]) ?> + + field($model, 'seo_text') + ->textarea([ 'rows' => 6 ]) ?> + + field($model, 'h1') + ->textInput([ 'maxlength' => true ]) ?> +
- isNewRecord ? 'Create' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?> + isNewRecord ? 'Create' : 'Update', [ 'class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary' ]) ?>
- +
diff --git a/common/behaviors/ManyToManyBehavior.php b/common/behaviors/ManyToManyBehavior.php new file mode 100644 index 0000000..6e9d57d --- /dev/null +++ b/common/behaviors/ManyToManyBehavior.php @@ -0,0 +1,407 @@ + 'saveRelations', + ActiveRecord::EVENT_AFTER_UPDATE => 'saveRelations', + ]; + } + /** + * Invokes init of parent class and assigns proper values to internal _fields variable + */ + public function init() + { + parent::init(); + //configure _fields + foreach ($this->relations as $attributeName => $params) { + //add primary field + $this->_fields[$attributeName] = [ + 'attribute' => $attributeName, + ]; + if (isset($params['get'])) { + $this->_fields[$attributeName]['get'] = $params['get']; + } + if (isset($params['set'])) { + $this->_fields[$attributeName]['set'] = $params['set']; + } + // Add secondary fields + if (isset($params['fields'])) { + foreach ($params['fields'] as $fieldName => $adjustments) { + $fullFieldName = $attributeName.'_'.$fieldName; + if (isset($this->_fields[$fullFieldName])) { + throw new ErrorException("Ambiguous field name definition: {$fullFieldName}"); + } + $this->_fields[$fullFieldName] = [ + 'attribute' => $attributeName, + ]; + if (isset($adjustments['get'])) { + $this->_fields[$fullFieldName]['get'] = $adjustments['get']; + } + if (isset($adjustments['set'])) { + $this->_fields[$fullFieldName]['set'] = $adjustments['set']; + } + } + } + } + } + /** + * Save all dirty (changed) relation values ($this->_values) to the database + * @throws ErrorException + * @throws Exception + */ + public function saveRelations() + { + /** @var ActiveRecord $primaryModel */ + $primaryModel = $this->owner; + if (is_array($primaryModelPk = $primaryModel->getPrimaryKey())) { + throw new ErrorException('This behavior does not support composite primary keys'); + } + foreach ($this->relations as $attributeName => $params) { + $relationName = $this->getRelationName($attributeName); + $relation = $primaryModel->getRelation($relationName); + if (!$this->hasNewValue($attributeName)) { + continue; + } + if (!empty($relation->via) && $relation->multiple) { + // Many-to-many + $this->saveManyToManyRelation($relation, $attributeName); + } elseif (!empty($relation->link) && $relation->multiple) { + // One-to-many on the many side + $this->saveOneToManyRelation($relation, $attributeName); + } else { + throw new ErrorException('Relationship type not supported.'); + } + } + } + /** + * @param ActiveQuery $relation + * @param string $attributeName + * @throws Exception + */ + private function saveManyToManyRelation($relation, $attributeName) + { + /** @var ActiveRecord $primaryModel */ + $primaryModel = $this->owner; + $primaryModelPk = $primaryModel->getPrimaryKey(); + $bindingKeys = $this->getNewValue($attributeName); + // Assuming junction column is visible from the primary model connection + if (is_array($relation->via)) { + // via() + $via = $relation->via[1]; + /** @var ActiveRecord $junctionModelClass */ + $junctionModelClass = $via->modelClass; + $junctionTable = $junctionModelClass::tableName(); + list($junctionColumn) = array_keys($via->link); + } else { + // viaTable() + list($junctionTable) = array_values($relation->via->from); + list($junctionColumn) = array_keys($relation->via->link); + } + list($relatedColumn) = array_values($relation->link); + $connection = $primaryModel::getDb(); + $transaction = $connection->beginTransaction(); + try { + // Remove old relations + $connection->createCommand() + ->delete($junctionTable, ArrayHelper::merge( + [$junctionColumn => $primaryModelPk], + $this->getCustomDeleteCondition($attributeName) + )) + ->execute(); + // Write new relations + if (!empty($bindingKeys)) { + $junctionRows = []; + $viaTableParams = $this->getViaTableParams($attributeName); + foreach ($bindingKeys as $relatedPk) { + $row = [$primaryModelPk, $relatedPk]; + // Calculate additional viaTable values + foreach (array_keys($viaTableParams) as $viaTableColumn) { + $row[] = $this->getViaTableValue($attributeName, $viaTableColumn, $relatedPk); + } + array_push($junctionRows, $row); + } + $cols = [$junctionColumn, $relatedColumn]; + // Additional viaTable columns + foreach (array_keys($viaTableParams) as $viaTableColumn) { + $cols[] = $viaTableColumn; + } + $connection->createCommand() + ->batchInsert($junctionTable, $cols, $junctionRows) + ->execute(); + } + $transaction->commit(); + } catch (Exception $ex) { + $transaction->rollback(); + throw $ex; + } + } + /** + * @param ActiveQuery $relation + * @param string $attributeName + * @throws Exception + */ + private function saveOneToManyRelation($relation, $attributeName) + { + /** @var ActiveRecord $primaryModel */ + $primaryModel = $this->owner; + $primaryModelPk = $primaryModel->getPrimaryKey(); + $bindingKeys = $this->getNewValue($attributeName); + // HasMany, primary model HAS MANY foreign models, must update foreign model table + /** @var ActiveRecord $foreignModel */ + $foreignModel = new $relation->modelClass(); + $manyTable = $foreignModel->tableName(); + list($manyTableFkColumn) = array_keys($relation->link); + $manyTableFkValue = $primaryModelPk; + list($manyTablePkColumn) = ($foreignModel->primaryKey()); + $connection = $foreignModel::getDb(); + $transaction = $connection->beginTransaction(); + $defaultValue = $this->getDefaultValue($attributeName); + try { + // Remove old relations + $connection->createCommand() + ->update( + $manyTable, + [$manyTableFkColumn => $defaultValue], + [$manyTableFkColumn => $manyTableFkValue]) + ->execute(); + // Write new relations + if (!empty($bindingKeys)) { + $connection->createCommand() + ->update( + $manyTable, + [$manyTableFkColumn => $manyTableFkValue], + ['in', $manyTablePkColumn, $bindingKeys]) + ->execute(); + } + $transaction->commit(); + } catch (Exception $ex) { + $transaction->rollback(); + throw $ex; + } + } + /** + * Call user function + * @param $function + * @param $value + * @return mixed + * @throws ErrorException + */ + private function callUserFunction($function, $value) + { + if (!is_array($function) && !$function instanceof \Closure) { + throw new ErrorException('This value is not a function'); + } + return call_user_func($function, $value); + } + /** + * Check if an attribute is dirty and must be saved (its new value exists) + * @param string $attributeName + * @return null + */ + private function hasNewValue($attributeName) + { + return isset($this->_values[$attributeName]); + } + /** + * Get value of a dirty attribute by name + * @param string $attributeName + * @return null + */ + private function getNewValue($attributeName) + { + return $this->_values[$attributeName]; + } + /** + * Get default value for an attribute (used for 1-N relations) + * @param string $attributeName + * @return mixed + */ + private function getDefaultValue($attributeName) + { + $relationParams = $this->getRelationParams($attributeName); + if (!isset($relationParams['default'])) { + return null; + } + if ($relationParams['default'] instanceof \Closure) { + $closure = $relationParams['default']; + $relationName = $this->getRelationName($attributeName); + return call_user_func($closure, $this->owner, $relationName, $attributeName); + } + return $relationParams['default']; + } + /** + * Calculate additional value of viaTable + * @param string $attributeName + * @param string $viaTableAttribute + * @param integer $relatedPk + * @return mixed + */ + private function getViaTableValue($attributeName, $viaTableAttribute, $relatedPk) + { + $viaTableParams = $this->getViaTableParams($attributeName); + if (!isset($viaTableParams[$viaTableAttribute])) { + return null; + } + if ($viaTableParams[$viaTableAttribute] instanceof \Closure) { + $closure = $viaTableParams[$viaTableAttribute]; + $relationName = $this->getRelationName($attributeName); + return call_user_func($closure, $this->owner, $relationName, $attributeName, $relatedPk); + } + return $viaTableParams[$viaTableAttribute]; + } + /** + * Get additional parameters of viaTable + * @param string $attributeName + * @return array + */ + private function getViaTableParams($attributeName) + { + $params = $this->getRelationParams($attributeName); + return isset($params['viaTableValues']) + ? $params['viaTableValues'] + : []; + } + /** + * Get custom condition used to delete old records. + * @param string $attributeName + * @return array + */ + private function getCustomDeleteCondition($attributeName) + { + $params = $this->getRelationParams($attributeName); + return isset($params['customDeleteCondition']) + ? $params['customDeleteCondition'] + : []; + } + /** + * Get parameters of a field + * @param string $fieldName + * @return mixed + * @throws ErrorException + */ + private function getFieldParams($fieldName) + { + if (empty($this->_fields[$fieldName])) { + throw new ErrorException('Parameter "' . $fieldName . '" does not exist'); + } + return $this->_fields[$fieldName]; + } + /** + * Get parameters of a relation + * @param string $attributeName + * @return mixed + * @throws ErrorException + */ + private function getRelationParams($attributeName) + { + if (empty($this->relations[$attributeName])) { + throw new ErrorException('Parameter "' . $attributeName . '" does not exist.'); + } + return $this->relations[$attributeName]; + } + /** + * Get name of a relation + * @param string $attributeName + * @return null + */ + private function getRelationName($attributeName) + { + $params = $this->getRelationParams($attributeName); + if (is_string($params)) { + return $params; + } + if (is_array($params) && !empty($params[0])) { + return $params[0]; + } + return null; + } + /** + * @inheritdoc + */ + public function canGetProperty($name, $checkVars = true) + { + return array_key_exists($name, $this->_fields) ? + true : parent::canGetProperty($name, $checkVars); + } + /** + * @inheritdoc + */ + public function canSetProperty($name, $checkVars = true) + { + return array_key_exists($name, $this->_fields) ? + true : parent::canSetProperty($name, $checkVars = true); + } + /** + * @inheritdoc + */ + public function __get($name) + { + $fieldParams = $this->getFieldParams($name); + $attributeName = $fieldParams['attribute']; + $relationName = $this->getRelationName($attributeName); + if ($this->hasNewValue($attributeName)) { + $value = $this->getNewValue($attributeName); + } else { + /** @var ActiveRecord $owner */ + $owner = $this->owner; + $relation = $owner->getRelation($relationName); + /** @var ActiveRecord $foreignModel */ + $foreignModel = new $relation->modelClass(); + $value = $relation->select($foreignModel->getPrimaryKey())->column(); + } + if (empty($fieldParams['get'])) { + return $value; + } + return $this->callUserFunction($fieldParams['get'], $value); + } + /** + * @inheritdoc + */ + public function __set($name, $value) + { + $fieldParams = $this->getFieldParams($name); + $attributeName = $fieldParams['attribute']; + if (!empty($fieldParams['set'])) { + $this->_values[$attributeName] = $this->callUserFunction($fieldParams['set'], $value); + } else { + $this->_values[$attributeName] = $value; + } + } +} \ No newline at end of file diff --git a/common/models/Blog.php b/common/models/Blog.php index 148d23b..ed8728f 100755 --- a/common/models/Blog.php +++ b/common/models/Blog.php @@ -1,108 +1,197 @@ [ + 'class' => 'common\behaviors\Slug', + 'in_attribute' => 'title', + 'out_attribute' => 'translit', + 'translit' => true, + ], + [ + 'class' => SaveImgBehavior::className(), + ], + ]; + } + + /** + * @inheritdoc + */ + public function rules() + { + return [ + [ + [ 'date' ], + 'default', + 'value' => function() { + return time(); + }, + ], + [ + [ + 'date', + 'date_end', + ], + 'safe', + ], + [ + [ + 'title', + 'body', + ], + 'required', + ], + [ + [ + 'body', + 'body_preview', + 'seo_text', + ], + 'string', + ], + [ + [ + 'title', + 'image', + 'translit', + 'meta_title', + 'meta_keywords', + 'meta_description', + 'h1', + ], + 'string', + 'max' => 255, + ], + [ + [ 'imageUpload' ], + 'safe', + ], + [ + [ 'imageUpload' ], + 'file', + 'extensions' => 'jpg, gif, png', + ], + [ + [ + 'date', + 'date_end', + ], + 'filter', + 'filter' => function($value) { + return strtotime($value) ? : time(); + }, + ], + [ + [ + 'categoryItems', + ], + 'safe', + ], + ]; + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return [ + 'id' => 'ID', + 'date' => 'Date', + 'date_end' => 'Date end', + 'title' => 'Title', + 'body' => 'Body', + 'body_preview' => 'Body preview', + 'image' => 'Image', + 'imageUrl' => Yii::t('app', 'Image'), + 'translit' => 'Translit', + 'meta_title' => 'Meta Title', + 'meta_keywords' => 'Meta Keywords', + 'meta_description' => 'Meta Description', + 'seo_text' => 'Seo Text', + 'h1' => 'H1', + ]; + } + + public function getImageFile() + { + return empty( $this->image ) ? NULL : Yii::getAlias('@imagesDir/blog/' . $this->image); + } + + public function getImageUrl() + { + return empty( $this->image ) ? NULL : Yii::getAlias('@imagesUrl/blog/' . $this->image); + } + + /** + * @return ActiveQuery + */ + public function getCategories() + { + return $this->hasMany(BlogCategory::className(), [ 'id' => 'category_id' ]) + ->viaTable('blog_to_category', [ 'blog_id' => 'id' ]); + } + + public function getCategoryItemsAsArray() + { + return BlogCategory::find() + ->select('name') + ->indexBy('id') + ->asArray() + ->column(); + } + + public function getCategoryItems() + { + return $this->_categoryItems; + } + + public function setCategoryItems($items) + { + $this->_categoryItems = $items; + } } - - /** - * @inheritdoc - */ - public function behaviors() - { - return [ - 'slug' => [ - 'class' => 'common\behaviors\Slug', - 'in_attribute' => 'title', - 'out_attribute' => 'translit', - 'translit' => true - ], - [ - 'class' => SaveImgBehavior::className(), - ], - ]; - } - - /** - * @inheritdoc - */ - public function rules() - { - return [ - [['date'], 'default', 'value' => function() { - return time(); - }], - [['date', 'date_end'], 'safe'], - [['title', 'body'], 'required'], - [['body', 'body_preview', 'seo_text'], 'string'], - [['title', 'image', 'translit', 'meta_title', 'meta_keywords', 'meta_description', 'h1'], 'string', 'max' => 255], - [['imageUpload'], 'safe'], - [['imageUpload'], 'file', 'extensions' => 'jpg, gif, png'], - [['date', 'date_end'], 'filter', 'filter' => function($value) { - return strtotime($value)?:time(); - }], - ]; - } - - /** - * @inheritdoc - */ - public function attributeLabels() - { - return [ - 'id' => 'ID', - 'date' => 'Date', - 'date_end' => 'Date end', - 'title' => 'Title', - 'body' => 'Body', - 'body_preview' => 'Body preview', - 'image' => 'Image', - 'imageUrl' => Yii::t('app', 'Image'), - 'translit' => 'Translit', - 'meta_title' => 'Meta Title', - 'meta_keywords' => 'Meta Keywords', - 'meta_description' => 'Meta Description', - 'seo_text' => 'Seo Text', - 'h1' => 'H1', - ]; - } - - public function getImageFile() { - return empty($this->image) ? null : Yii::getAlias('@imagesDir/blog/'. $this->image); - } - - public function getImageUrl() - { - return empty($this->image) ? null : Yii::getAlias('@imagesUrl/blog/' . $this->image); - } -} diff --git a/common/models/BlogCategory.php b/common/models/BlogCategory.php new file mode 100644 index 0000000..548e725 --- /dev/null +++ b/common/models/BlogCategory.php @@ -0,0 +1,39 @@ +hasMany(Blog::className(), [ 'id' => 'blog_id' ]) + ->viaTable('blog_to_category', [ 'category_id' => 'id' ]); + } + } diff --git a/common/models/BlogCategorySearch.php b/common/models/BlogCategorySearch.php new file mode 100644 index 0000000..50ab904 --- /dev/null +++ b/common/models/BlogCategorySearch.php @@ -0,0 +1,69 @@ + $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, + ]); + + $query->andFilterWhere(['like', 'name', $this->name]); + + return $dataProvider; + } +} diff --git a/common/models/BlogToCategory.php b/common/models/BlogToCategory.php new file mode 100644 index 0000000..6849b8e --- /dev/null +++ b/common/models/BlogToCategory.php @@ -0,0 +1,13 @@ + Yii::t('product', 'Select brand') ] ) ?> - + field($model, 'categories')->widget(Select2::className(), [ 'data' => ArtboxTreeHelper::treeMap(ProductHelper::getCategories(), 'category_id', 'name'), 'language' => 'ru', diff --git a/console/migrations/m160930_082338_add_categories_to_blog.php b/console/migrations/m160930_082338_add_categories_to_blog.php new file mode 100644 index 0000000..f475708 --- /dev/null +++ b/console/migrations/m160930_082338_add_categories_to_blog.php @@ -0,0 +1,39 @@ +createTable('blog_category', [ + 'id' => $this->primaryKey(), + 'name' => $this->string(), + ]); + + $this->createTable('blog_to_category', [ + 'blog_id' => $this->integer(), + 'category_id' => $this->integer(), + ]); + + $this->createIndex('blog_to_category_key', 'blog_to_category', [ + 'blog_id', + 'category_id', + ], true); + + $this->addForeignKey('blog_fk', 'blog_to_category', 'blog_id', 'blog', 'id', 'CASCADE', 'CASCADE'); + $this->addForeignKey('blog_category_fk', 'blog_to_category', 'category_id', 'blog_category', 'id', 'CASCADE', 'CASCADE'); + + } + + public function down() + { + $this->dropForeignKey('blog_category_fk', 'blog_to_category'); + $this->dropForeignKey('blog_fk', 'blog_to_category'); + + $this->dropIndex('blog_to_category_key', 'blog_to_category'); + + $this->dropTable('blog_to_category'); + $this->dropTable('blog_category'); + } +} diff --git a/frontend/controllers/BlogController.php b/frontend/controllers/BlogController.php index c27415b..3487a0e 100755 --- a/frontend/controllers/BlogController.php +++ b/frontend/controllers/BlogController.php @@ -3,7 +3,9 @@ namespace frontend\controllers; use common\models\Blog; + use common\models\BlogCategory; use yii\data\ActiveDataProvider; + use yii\helpers\VarDumper; use yii\web\Controller; use yii\web\NotFoundHttpException; @@ -45,11 +47,38 @@ public function actionView($id) { $model = $this->findModel($id); + $categories = $model->categories; return $this->render('view', [ 'model' => $model, ]); } + public function actionCategory($id) + { + $model = BlogCategory::find() + ->where([ + 'id' => $id, + ]) + ->one(); + $query = $model->getBlogs(); + $dataProvider = new ActiveDataProvider([ + 'query' => $query, + 'pagination' => [ + 'pageSize' => 4 + , + ], + 'sort' => [ + 'defaultOrder' => [ + 'date' => SORT_DESC, + ] + ], + ]); + return $this->render('category', [ + 'model' => $model, + 'dataProvider' => $dataProvider, + ]); + } + private function findModel($id) { $model = Blog::findOne($id); if(!empty($model)) { diff --git a/frontend/views/blog/category.php b/frontend/views/blog/category.php new file mode 100755 index 0000000..7243a2e --- /dev/null +++ b/frontend/views/blog/category.php @@ -0,0 +1,38 @@ +title = "Блог"; +$this->params[ 'breadcrumbs' ][] = $this->title; + +?> + +
+ $dataProvider, + 'options' => [ + 'tag' => false, + ], + 'pager' => [ + 'prevPageCssClass' => 'left_pg', + 'nextPageCssClass' => 'right_pg', + 'activePageCssClass' => 'active', + 'disabledPageCssClass' => '', + 'firstPageLabel' => false, + ], + 'itemView' => '_blog_item', + 'layout' => '{items}{pager}', + ]); + + ?> +
diff --git a/frontend/views/blog/index.php b/frontend/views/blog/index.php index 639c962..e9104ef 100755 --- a/frontend/views/blog/index.php +++ b/frontend/views/blog/index.php @@ -36,12 +36,4 @@ $this->params[ 'breadcrumbs' ][] = $this->title; ]); ?> - - - - - - - - diff --git a/frontend/views/comments/_form.php b/frontend/views/comments/_form.php new file mode 100755 index 0000000..aeafc28 --- /dev/null +++ b/frontend/views/comments/_form.php @@ -0,0 +1,40 @@ + $formId, + 'action' => Url::to([ + 'artbox-comment/default/create', + 'entity' => $comment_model->encryptedEntity, + ]), + ]); +?> +
+ field($comment_model, 'username', [ 'options' => [ 'class' => 'form-group input_bl' ] ]) + ->textInput(); + echo $form->field($comment_model, 'email', [ 'options' => [ 'class' => 'form-group input_bl' ] ]) + ->textInput(); + + echo $form->field($comment_model, 'text', [ 'options' => [ 'class' => 'form-group input_bl area_bl' ] ]) + ->textarea(); + echo Html::tag('div', Html::submitButton(Yii::t('artbox-comment', 'Submit')), [ 'class' => 'input_bl submit_btn' ]); + ?> +
+ \ No newline at end of file diff --git a/frontend/views/comments/_item.php b/frontend/views/comments/_item.php new file mode 100755 index 0000000..59a4841 --- /dev/null +++ b/frontend/views/comments/_item.php @@ -0,0 +1,153 @@ + +
+
+
+ +
+ + + rating )) { + ?> +
+ 1 + rating->value; ?> + 5 +
+
+ +
+ text; + ?> +
+
+
+ user->isGuest) { + ?> + Ответить + user->isGuest && \Yii::$app->user->id == $model->user_id) { + ?> + Удалить + + + Dislike + +
+
+
+
+ children )) { + foreach($model->children as $index => $child) { + ?> +
+
+
+ +
+
+ date_add); + ?> +
+
+ user )) { + echo $child->user->username; + } else { + echo $child->username . ' (' . Yii::t('artbox-comment', 'Guest') . ')'; + } + ?> +
+
+ text; + ?> +
+
+
+ user->isGuest) { + ?> + Ответить + user->isGuest && \Yii::$app->user->id == $child->user_id) { + ?> + Удалить + + + Dislike + +
+
+
+ +
+ diff --git a/frontend/views/comments/_list.php b/frontend/views/comments/_list.php new file mode 100755 index 0000000..2f80e45 --- /dev/null +++ b/frontend/views/comments/_list.php @@ -0,0 +1,30 @@ +session->getFlash('artbox_comment_success')) != null) { + echo Html::tag('p', $success); + } + echo ListView::widget([ + 'dataProvider' => $comments, + 'itemOptions' => $item_options, + 'itemView' => $item_view, + 'summary' => '', + ]); + Pjax::end(); + \ No newline at end of file diff --git a/frontend/views/comments/index.php b/frontend/views/comments/index.php index 614a09d..eae3c1c 100644 --- a/frontend/views/comments/index.php +++ b/frontend/views/comments/index.php @@ -1,19 +1,92 @@ - + 1, -]); -echo CommentWidget::widget([ - 'model' => $comments, - 'entityIdAttribute' => 'id' -]); -?> \ No newline at end of file + $comments = new Comments([ + 'id' => 1, + ]); + echo CommentWidget::widget([ + 'model' => $comments, + 'entityIdAttribute' => 'id', + 'layout' => '{list}{form}', + 'formView' => '@frontend/views/comments/_form', + 'listView' => '@frontend/views/comments/_list', + 'itemView' => '@frontend/views/comments/_item', + ]); +?> + +
+
Отзывы
+
+
Уважаемые клиенты и партнеры,
+
мы с благодарностью принимаем ваши пожелания и замечания относительно проведенных мероприятий и качества обслуживания в шоу-руме "Baccara Home"
+
+
+ +
+
+
+ + +
Заказали на всю квартиру обои. Все хорошо, но был задержан один вид обоев к установленному сроку. Приятно, что это компенсировали доставкой. В целом мы остались довольны.
+
+
+
+
+
+
+
+
+ +
+
+ Больше отзывов + Написать отзыв +
+
+
Мы обязательно рассмотрим все отзывы!
+
+
+
+
+
+ +
+
+ + +
+
+
+ + +
+
+ +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ diff --git a/frontend/web/css/style.css b/frontend/web/css/style.css old mode 100755 new mode 100644 index fb6f5e6..3f52558 --- a/frontend/web/css/style.css +++ b/frontend/web/css/style.css @@ -202,45 +202,6 @@ footer .socbuts ul li{ footer .socbuts a:hover{ opacity:0.6; } -.pagination{ - display: flex; - justify-content: center; - margin: 56px 0px; -} -.pagination, .pagination a, .pagination span{ - font-family: Arial, sans-serif!important; - font-weight: bold; - color:#9b9999!important; - font-size: 12px; - background-position: center; - background-repeat: no-repeat; -} -.pagination li a, .pagination li span{ - padding: 1px 8.5px; -} - -.pagination .active a{ - background-color: initial!important; - border-color: #d2d2d2!important; - color:#000!important; -} -.pagination .left_pg span, .pagination .right_pg span, .pagination .left_pg a, .pagination .right_pg a{ - background-color: #d2d2d2; - padding: 1px 9.5px; -} -.pagination .left_pg a, .pagination .left_pg span{ - background-image: url(../images/left_ar.png); - font-size:0px; - width: 28px; - height: 21px; -} -.pagination .right_pg a, .pagination .right_pg span{ - background-image: url(../images/right_ar.png); - font-size:0px; - width: 28px; - height: 21px; -} - .main-box1{ min-height:300px; text-align: center; @@ -754,14 +715,11 @@ footer .socbuts a:hover{ } .post_1.contract img{max-height:222px;} .blocks{text-align: center;letter-spacing: normal;text-transform: uppercase;margin-top: 36px;margin-left: 9px;} -.blocks .title{ - - padding: 17px 0px 9px 0px; -} -.blocks .title a{ +.blocks a .title{ font-size: 14px; font-family: 'Lato-Medium'; font-weight: bold; + padding: 17px 0px 9px 0px; } .blocks a:hover{color:black!important;} .b1{padding-bottom: 44px;} @@ -1028,18 +986,18 @@ footer .socbuts a:hover{ } .spoiler .control-label{display: none;} .spoiler input[type=checkbox], input[type=radio] { - /*margin: 1px 0 0;*/ - /*opacity: 0;*/ - /*margin-right: 7px;*/ + margin: 1px 0 0; + opacity: 0; + margin-right: 7px; } .spoiler label { - display: inline; + display: inline-block; max-width: 100%; font-weight: 700; width: 100%; letter-spacing: 0.4px; margin-bottom: 0px; - /*background: url('../images/knopka.png');*/ + background: url('../images/knopka.png'); background-position: left 5px; background-repeat: no-repeat; } @@ -1125,9 +1083,47 @@ footer .socbuts a:hover{ line-height: 18px; margin-bottom: 28px; } +.pagination{ + text-align: center; + list-style: none; + display: flex; + justify-content: center; + margin: 56px 0px; +} a.active{ cursor: default; } +.pagination li a{ + width: 21px; + height: 21px; + display: flex; + text-align: center; + font-family: 'Arial Bold', Arial, sans-serif; + font-weight: bold; + line-height: 2px; + justify-content: center; + align-items: center; + border: 1px solid #d2d2d2; + margin-right: -1px; + color: #9b9999; + font-size: 12px; +} +.pagination .left_pg{ + background-image: url('../images/left_ar.png'); +} +.pagination .right_pg{ + background-image: url('../images/right_ar.png'); +} +.pagination .left_pg, .pagination .right_pg{ + background-color: #d2d2d2; + padding: 0px 14px; + background-repeat: no-repeat; + background-position: center center; +} +.pagination .left_pg, .pagination .right_pg:hover{ + border:1px solid rgba(0,0,0,0); +} +.pagination li a.active, .pagination li a.active:hover{color:black;} .more_colls{ background-color: #decfc8; text-transform: uppercase; @@ -1513,7 +1509,7 @@ p.right{text-align: right;} } .collection .head img{ max-width:960px; - /*width:100%;*/ + width:100%; } .cols{max-width: 960px;margin: 40px auto 25px;} .cols a{ @@ -1604,7 +1600,58 @@ p.right{text-align: right;} margin: 0px; } .dropd_menu div:first-child .title:after{display: none;} +.pagination li a { + width: 21px!important; + height: 21px!important; + display: flex!important; + text-align: center!important; + font-family: 'Arial Bold', Arial, sans-serif!important; + font-weight: bold!important; + line-height: 2px!important; + justify-content: center!important; + align-items: center!important; + border: 1px solid #d2d2d2!important; + margin-right: -1px!important; + color: #9b9999!important; + font-size: 12px!important; + background-position: center; + background-repeat: no-repeat; +} +.pagination span{ + width: 28px; + height: 21px!important; + display: flex!important; + text-align: center!important; + font-family: 'Arial Bold', Arial, sans-serif!important; + font-weight: bold!important; + line-height: 2px!important; + justify-content: center!important; + align-items: center!important; + border: 1px solid #d2d2d2!important; + margin-right: -1px!important; + color: #9b9999!important; + font-size: 12px!important; + background-color: #d2d2d2; + text-indent: -999px; + background-position: center; + background-repeat: no-repeat; +} +.pagination .prev a, .pagination .prev span{ + background-image: url(../images/left_ar.png); + width: 30px!important; + text-indent: 100px; + overflow: hidden; + background-color: #d2d2d2; +} +.pagination .next a, .pagination .next span{ + background-image: url('../images/right_ar.png'); + width: 30px!important; + text-indent: 100px; + overflow: hidden; + background-color: #d2d2d2; +} +.pagination .active a{color:#000!important;background-color:white!important;} .action{max-width:260px;margin-bottom: 10px;} .action img{width:100%; max-width:227px;} @@ -1649,7 +1696,25 @@ p.right{text-align: right;} font-weight: 600; } .act_d{margin-top:66px;} -.act_d p{color:#878686;font-family: 'Lato-Light';font-weight: 600;font-size:15px;letter-spacing: 0.4px;} +.act_d p{ + color: #878686; + font-family: 'Lato-Light'; + font-weight: 600; + font-size: 15px; + letter-spacing: 0.4px; + height: 24px; + overflow: hidden; + transition:0.2s; +} +.act_d p.opened{ + height: initial; +} + +.left_pg, .right_pg{ + max-height: 21px; + width: 28px; + overflow: hidden; +} /**/ .dropd_menu:before { @@ -1746,4 +1811,162 @@ p.right{text-align: right;} @media (max-width: 379px) { .main-box1 img{width:100%;} .blog_link img{width: 100%;} +} + + +/* OTZIVI */ +.form-comm-wr .input_bl, .form-comm-wr .help-block{ + margin: 0; + padding: 0; +} +.artbox_item_info .user_data, .artbox_item_info .user_name{ + color: #7e7e82; + font-size:14px; + display: inline-block; +} +.artbox_item_info .user_name:before{ + content:'/ '; +} +.artbox_item_info .user_txt{ + color: #343333; + font-size: 15px; + font-family: 'Lato-Light'; + font-weight: 600; + padding-top: 11px; + line-height: 20px; +} +.otzivi_block{ + width:100%; + max-width: 865px; + float: none; + margin: 0 auto; + padding-bottom: 140px; +} +.artbox_item_info{ + border-top: 1px solid #d9d9d9; + padding-top: 18px; + padding-bottom: 27px; +} +.comments_block{ + border-bottom: 1px solid #d9d9d9; +} +.title9{ + color: #847e7e; + font-size: 24px; + text-transform: uppercase; + font-family: 'Lato-Light'; + font-weight: 600; + margin-top: 10px; + margin-bottom: 11px; +} +.start_otzivi{ + text-align: center; + text-transform: uppercase; + color: #000000; + font-size: 15px; + line-height: 26px; + font-family: 'Lato-Light'; + font-weight: 600; + margin-bottom: 15px; + max-width: 640px; + margin-left: auto; + margin-right: auto; +} +.start_otzivi .title{ + font-size:18px; +} +.form-comm-wr input, .form-comm-wr textarea{ + text-transform: uppercase; + font-size: 11px; + font-weight: 900; + border-radius: 0px; + margin-top:-1px; + outline: none; + resize: none; + padding:14px; + letter-spacing: 0.5px; + box-shadow: none!important; + border: 1px solid rgb(204, 204, 204)!important; +} +.input_bl.submit_btn{ + text-align:center; + padding: 15px 0px; +} +.input_bl.submit_btn button{ + border: 1px solid #9e9e9e; + background-color: #fff; + outline: none; + text-transform: uppercase; + font-size: 11px; + font-weight: 600; + color: #9e9e9e; + letter-spacing: 1px; + padding: 5px 17px; + transition:0.1s; +} +.input_bl.submit_btn button:hover{ + background-color:#9e9e9e; + color:#fff; +} +.add_comment{ + display: none; + margin-top: 40px!important; +} +.add_comment, .add_comment .artbox_comment_container.comments-start{ + position: relative; + max-width: 705px; + margin: 0 auto; +} +.otz_buttons{ + text-align: center; + text-transform: uppercase; + font-size: 11px; + font-weight: 600; + letter-spacing: 1px; + margin: 44px 0px; +} +.otz_buttons a{ + background-color:#e4e4e4; + padding: 11px; + padding-bottom: 10px; + margin: 10px; + background-image: url(../images/arrow_down_bold.png); + background-repeat: no-repeat; + background-position-y: center; +} +.otz_buttons .btn_otz.more1{ + padding-right: 15px; + padding-left: 38px; + background-position-x: 17px; +} +.otz_buttons .btn_otz.write{ + padding-right: 42px; + padding-left: 16px; + background-position-x: calc(100% - 16px); +} +.otz_buttons a:hover{ + background-color:#ebdbd4!important; + color:#000; +} +.add_comment .title{ + text-align: center; + text-transform: uppercase; + font-size: 15px; + color: #000000; + font-family: 'Lato-Light'; + font-weight: 600; + padding: 14px; +} +.close_comm{ + position: absolute; + top: 16px; + right: 0; + width: 27px; + height: 27px; + background-image:url('../images/close.png'); + transition:0.1s; + cursor: pointer; +} +.close_comm:hover{ + background-image:url('../images/close_hover.png'); } \ No newline at end of file -- libgit2 0.21.4