Commit 82d2fae2affde5458e3da0550361c5d0e44a7338

Authored by Anastasia
1 parent 146493f1

- add alias to books

- comments
common/models/Book.php
... ... @@ -2,10 +2,11 @@
2 2  
3 3 namespace common\models;
4 4  
  5 + use artbox\core\models\Alias;
  6 + use frontend\behaviors\SlugBehavior;
5 7 use Yii;
6 8 use yii\behaviors\TimestampBehavior;
7 9 use yii\helpers\FileHelper;
8   - use yii\helpers\Json;
9 10 use yii\web\UploadedFile;
10 11  
11 12 /**
... ... @@ -42,8 +43,15 @@
42 43 [
43 44 'class' => TimestampBehavior::className(),
44 45 'updatedAtAttribute' => false,
  46 + ],
  47 + [
  48 + 'class' => SlugBehavior::className(),
  49 + 'action' => 'book/view',
  50 + 'params' => [
  51 + 'id' => 'id',
  52 + ],
45 53 ]
46   - ];
  54 + ];
47 55  
48 56 }
49 57 /**
... ... @@ -66,6 +74,7 @@
66 74 'author_id',
67 75 'created_at',
68 76 'status',
  77 + 'alias_id'
69 78 ],
70 79 'integer',
71 80 ],
... ... @@ -151,4 +160,12 @@
151 160 return true;
152 161 }
153 162  
  163 + public function getAlias(){
  164 + return $this->hasOne(Alias::className(), ['id' => 'alias_id']);
  165 + }
  166 +
  167 + public function getActiveComments(){
  168 + return $this->hasMany(Comment::className(), ['book_id' => 'id'])->where(['status' => true])->orderBy('created_at');
  169 + }
  170 +
154 171 }
... ...
common/models/Comment.php 0 → 100644
  1 +<?php
  2 +
  3 + namespace common\models;
  4 +
  5 + use Yii;
  6 + use yii\behaviors\TimestampBehavior;
  7 +
  8 + /**
  9 + * This is the model class for table "comment".
  10 + *
  11 + * @property int $id
  12 + * @property int $book_id
  13 + * @property string $name
  14 + * @property string $email
  15 + * @property string $comment
  16 + * @property int $parent_id
  17 + * @property Book $book
  18 + * @property Comment $parent
  19 + * @property Comment[] $comments
  20 + */
  21 + class Comment extends \yii\db\ActiveRecord
  22 + {
  23 + /**
  24 + * {@inheritdoc}
  25 + */
  26 + public static function tableName()
  27 + {
  28 + return 'comment';
  29 + }
  30 + public function behaviors()
  31 + {
  32 + return [
  33 + [
  34 + 'class' => TimestampBehavior::className(),
  35 + 'updatedAtAttribute' => false,
  36 + ],
  37 + ];
  38 +
  39 + }
  40 +
  41 + /**
  42 + * {@inheritdoc}
  43 + */
  44 + public function rules()
  45 + {
  46 + return [
  47 + [
  48 + [
  49 + 'book_id',
  50 + 'parent_id',
  51 + ],
  52 + 'default',
  53 + 'value' => null,
  54 + ],
  55 + [
  56 + [
  57 + 'book_id',
  58 + 'parent_id',
  59 + ],
  60 + 'integer',
  61 + ],
  62 + [
  63 + [ 'comment' ],
  64 + 'string',
  65 + ],
  66 + [
  67 + 'email',
  68 + 'email',
  69 + ],
  70 + [
  71 + [
  72 + 'name',
  73 + 'email',
  74 + ],
  75 + 'string',
  76 + 'max' => 255,
  77 + ],
  78 + [
  79 + [ 'book_id' ],
  80 + 'exist',
  81 + 'skipOnError' => true,
  82 + 'targetClass' => Book::className(),
  83 + 'targetAttribute' => [ 'book_id' => 'id' ],
  84 + ],
  85 + [
  86 + [ 'parent_id' ],
  87 + 'exist',
  88 + 'skipOnError' => true,
  89 + 'targetClass' => Comment::className(),
  90 + 'targetAttribute' => [ 'parent_id' => 'id' ],
  91 + ],
  92 + ];
  93 + }
  94 +
  95 + /**
  96 + * {@inheritdoc}
  97 + */
  98 + public function attributeLabels()
  99 + {
  100 + return [
  101 + 'id' => Yii::t('app', 'ID'),
  102 + 'book_id' => Yii::t('app', 'Book ID'),
  103 + 'name' => Yii::t('app', 'Name'),
  104 + 'email' => Yii::t('app', 'Email'),
  105 + 'comment' => Yii::t('app', 'Comment'),
  106 + 'parent_id' => Yii::t('app', 'Parent ID'),
  107 + ];
  108 + }
  109 +
  110 + /**
  111 + * @return \yii\db\ActiveQuery
  112 + */
  113 + public function getBook()
  114 + {
  115 + return $this->hasOne(Book::className(), [ 'id' => 'book_id' ]);
  116 + }
  117 +
  118 + /**
  119 + * @return \yii\db\ActiveQuery
  120 + */
  121 + public function getParent()
  122 + {
  123 + return $this->hasOne(Comment::className(), [ 'id' => 'parent_id' ]);
  124 + }
  125 +
  126 + /**
  127 + * @return \yii\db\ActiveQuery
  128 + */
  129 + public function getComments()
  130 + {
  131 + return $this->hasMany(Comment::className(), [ 'parent_id' => 'id' ]);
  132 + }
  133 + }
... ...
console/migrations/m180613_133549_create_comment_table.php 0 → 100644
  1 +<?php
  2 +
  3 +use yii\db\Migration;
  4 +
  5 +/**
  6 + * Handles the creation of table `comment`.
  7 + */
  8 +class m180613_133549_create_comment_table extends Migration
  9 +{
  10 + /**
  11 + * {@inheritdoc}
  12 + */
  13 + public function safeUp()
  14 + {
  15 + $this->createTable('comment', [
  16 + 'id' => $this->primaryKey(),
  17 + 'book_id' => $this->integer(),
  18 + 'name' => $this->string(),
  19 + 'email' => $this->string(),
  20 + 'comment' => $this->text(),
  21 + 'parent_id' => $this->integer()
  22 + ]);
  23 +
  24 + $this->addForeignKey('comment_book_fk',
  25 + 'comment',
  26 + 'book_id',
  27 + 'book',
  28 + 'id',
  29 + 'CASCADE',
  30 + 'CASCADE');
  31 +
  32 + $this->addForeignKey('comment_comment_fk',
  33 + 'comment',
  34 + 'parent_id',
  35 + 'comment',
  36 + 'id',
  37 + 'CASCADE',
  38 + 'CASCADE');
  39 + }
  40 +
  41 + /**
  42 + * {@inheritdoc}
  43 + */
  44 + public function safeDown()
  45 + {
  46 + $this->dropForeignKey('comment_book_fk', 'comment');
  47 + $this->dropForeignKey('comment_comment_fk', 'comment');
  48 + $this->dropTable('comment');
  49 + }
  50 +}
... ...
console/migrations/m180613_140138_alter_table_book.php 0 → 100644
  1 +<?php
  2 +
  3 +use yii\db\Migration;
  4 +
  5 +/**
  6 + * Class m180613_140138_alter_table_book
  7 + */
  8 +class m180613_140138_alter_table_book extends Migration
  9 +{
  10 + /**
  11 + * {@inheritdoc}
  12 + */
  13 + public function safeUp()
  14 + {
  15 + $this->addColumn('book', 'alias_id', $this->integer());
  16 +
  17 + $this->addForeignKey('book_alias_fk',
  18 + 'book',
  19 + 'alias_id',
  20 + 'alias',
  21 + 'id',
  22 + 'CASCADE',
  23 + 'CASCADE');
  24 + }
  25 +
  26 + /**
  27 + * {@inheritdoc}
  28 + */
  29 + public function safeDown()
  30 + {
  31 + $this->dropForeignKey('book_alias_fk', 'book');
  32 + $this->dropColumn('book', 'alias_id');
  33 + }
  34 +
  35 +}
... ...
console/migrations/m180613_144215_alter_table_comment.php 0 → 100644
  1 +<?php
  2 +
  3 +use yii\db\Migration;
  4 +
  5 +/**
  6 + * Class m180613_144215_alter_table_comment
  7 + */
  8 +class m180613_144215_alter_table_comment extends Migration
  9 +{
  10 + /**
  11 + * {@inheritdoc}
  12 + */
  13 + public function safeUp()
  14 + {
  15 + $this->addColumn('comment', 'status', $this->boolean());
  16 + $this->addColumn('comment', 'created_at', $this->integer());
  17 + }
  18 +
  19 + /**
  20 + * {@inheritdoc}
  21 + */
  22 + public function safeDown()
  23 + {
  24 + $this->dropColumn('comment', 'status');
  25 + $this->dropColumn('comment', 'created_at');
  26 + }
  27 +
  28 + /*
  29 + // Use up()/down() to run migration code without a transaction.
  30 + public function up()
  31 + {
  32 +
  33 + }
  34 +
  35 + public function down()
  36 + {
  37 + echo "m180613_144215_alter_table_comment cannot be reverted.\n";
  38 +
  39 + return false;
  40 + }
  41 + */
  42 +}
... ...
frontend/behaviors/SlugBehavior.php 0 → 100755
  1 +<?php
  2 +
  3 + namespace frontend\behaviors;
  4 +
  5 + use artbox\core\helpers\SlugHelper;
  6 + use artbox\core\models\Alias;
  7 + use artbox\core\models\Language;
  8 + use yii;
  9 + use yii\base\Behavior;
  10 + use yii\base\ErrorException;
  11 + use yii\db\ActiveRecord;
  12 + use yii\helpers\Json;
  13 +
  14 + /**
  15 + * Class SlugBehavior
  16 + * Behavior to generate and maintain connection with aliases.
  17 + *
  18 + * @property ActiveRecord $owner
  19 + */
  20 + class SlugBehavior extends Behavior
  21 + {
  22 +
  23 + /**
  24 + * Attribute from which slug should be generated
  25 + *
  26 + * @var string
  27 + */
  28 + public $inAttribute = 'title';
  29 +
  30 + /**
  31 + * Attribute which holds link to Alias
  32 + *
  33 + * @var int
  34 + */
  35 + public $outAttribute = 'alias_id';
  36 +
  37 + /**
  38 + * Action which should be called after parsing Url
  39 + *
  40 + * @var string
  41 + */
  42 + public $action;
  43 +
  44 + /**
  45 + * Params which should be passed to controller action
  46 + *
  47 + * @var array
  48 + */
  49 + public $params;
  50 +
  51 + /**
  52 + * Whether to use transit or just replace non-alphabetic symbols.
  53 + *
  54 + * @var bool
  55 + */
  56 + public $translit = true;
  57 +
  58 + /**
  59 + * Fields available in for SEO.
  60 + *
  61 + * @var array
  62 + */
  63 + public $fields = [];
  64 +
  65 + /**
  66 + * Get Alias $value for $owner.
  67 + * If needed db request would be used only once.
  68 + *
  69 + * @var string $aliasValue
  70 + */
  71 + protected $aliasValue = null;
  72 +
  73 + /**
  74 + * Current Alias model for $owner.
  75 + *
  76 + * @var Alias|null $currentAlias
  77 + */
  78 + private $currentAlias;
  79 +
  80 + /**
  81 + * Whether db query to get Alias already executed, means that request won't be made until force flag is set.
  82 + *
  83 + * @var bool $executed
  84 + */
  85 + private $executed = false;
  86 +
  87 + /**
  88 + * Template for generate meta-tags
  89 + *
  90 + * @var array
  91 + */
  92 + public $meta = [];
  93 + /**
  94 + * @inheritdoc
  95 + */
  96 + public $alwaysGenerate = false;
  97 + public $attributes = [];
  98 + public function events()
  99 + {
  100 + return [
  101 + ActiveRecord::EVENT_AFTER_INSERT => 'getSlug',
  102 + ActiveRecord::EVENT_AFTER_UPDATE => 'getSlug',
  103 + ActiveRecord::EVENT_AFTER_DELETE => 'deleteSlug',
  104 + ];
  105 + }
  106 +
  107 + /**
  108 + * @inheritdoc
  109 + */
  110 + public function attach($owner)
  111 + {
  112 + parent::attach($owner);
  113 + if (!$owner instanceof ActiveRecord) {
  114 + throw new yii\base\InvalidConfigException(
  115 + \Yii::t('core', 'SlugBeahvior can only be attached to the instance of ActiveRecord')
  116 + );
  117 + }
  118 + }
  119 +
  120 + /**
  121 + * Generate slug
  122 + *
  123 + * @param yii\base\Event $event
  124 + *
  125 + * @return void
  126 + */
  127 + public function getSlug($event)
  128 + {
  129 + /**
  130 + * @var string $attribute_value
  131 + */
  132 + $attribute_value = $this->owner->getAttribute($this->inAttribute);
  133 + if (!empty($attribute_value)) {
  134 + /**
  135 + * @var string $alias_value
  136 + */
  137 + $alias_value = $this->aliasValue;
  138 + $this->currentAlias = $this->findCurrentAlias();
  139 + if (empty($alias_value)) {
  140 + $result = $this->generateSlug($attribute_value);
  141 + } else {
  142 + $result = $this->generateSlug($alias_value);
  143 + }
  144 + $suffix = 2;
  145 + $new_result = $result;
  146 + while (!empty(Alias::find()->where(['value' => $new_result])->one())) {
  147 + $new_result = $result . '-' . $suffix;
  148 + $suffix++;
  149 + }
  150 + $this->saveSlug($new_result);
  151 + }
  152 + }
  153 +
  154 + /**
  155 + * Delete slug
  156 + *
  157 + * @param yii\base\Event $event
  158 + *
  159 + * @return void
  160 + */
  161 + public function deleteSlug($event)
  162 + {
  163 + if (!empty($current = $this->findCurrentAlias())) {
  164 +
  165 + $current->delete();
  166 + }
  167 + }
  168 +
  169 + /**
  170 + * Get current Alias. Db query will be made first time or if $force flag is set.
  171 + *
  172 + * @param bool $force
  173 + *
  174 + * @return \artbox\core\models\Alias|null
  175 + */
  176 + public function findCurrentAlias($force = false)
  177 + {
  178 + if ($force || !$this->executed) {
  179 + /**
  180 + * @var int $alias_id
  181 + */
  182 + $alias_id = $this->owner->getAttribute($this->outAttribute);
  183 + $currentAlias = null;
  184 + if (!empty($alias_id)) {
  185 + /**
  186 + * @var Alias|null $currentAlias
  187 + */
  188 + $currentAlias = Alias::find()
  189 + ->where(
  190 + [
  191 + 'id' => $alias_id,
  192 + ]
  193 + )
  194 + ->one();
  195 + }
  196 + $this->executed = true;
  197 + return $currentAlias;
  198 + } else {
  199 + return $this->currentAlias;
  200 + }
  201 + }
  202 +
  203 + /**
  204 + * Generate unique slug.
  205 + *
  206 + * @param string $slug
  207 + *
  208 + * @return string
  209 + */
  210 + private function generateSlug($slug)
  211 + {
  212 + $slug = $this->slugify($slug);
  213 + $languageId = null;
  214 + if ($this->owner->hasAttribute('language_id')) {
  215 + $languageId = $this->owner->getAttribute('language_id');
  216 + }
  217 + return SlugHelper::unify($slug, $this->currentAlias, $languageId);
  218 + }
  219 +
  220 + /**
  221 + * Generate slug from string according to $translit flag.
  222 + *
  223 + * @param string $slug
  224 + *
  225 + * @return string
  226 + */
  227 + private function slugify($slug)
  228 + {
  229 + if ($this->translit) {
  230 + return SlugHelper::slugify($slug);
  231 + } else {
  232 + return $this->slug($slug, '-', true);
  233 + }
  234 + }
  235 +
  236 + /**
  237 + * Replace non-alphabetic symbols with $replcement symbol.
  238 + *
  239 + * @param string $string
  240 + * @param string $replacement
  241 + * @param bool $lowercase
  242 + *
  243 + * @return string
  244 + */
  245 + private function slug($string, $replacement = '-', $lowercase = true)
  246 + {
  247 + $string = preg_replace('/[^\p{L}\p{Nd}]+/u', $replacement, $string);
  248 + $string = trim($string, $replacement);
  249 + return $lowercase ? strtolower($string) : $string;
  250 + }
  251 +
  252 + /**
  253 + * Check if $slug differs from current Alias value
  254 + *
  255 + * @param $slug
  256 + *
  257 + * @return bool
  258 + */
  259 + private function hasChanged($slug)
  260 + {
  261 + if (!empty($this->currentAlias) && $this->currentAlias->value === $slug) {
  262 + return false;
  263 + } else {
  264 + return true;
  265 + }
  266 + }
  267 +
  268 + /**
  269 + * If $slug differs from Db Alias value, updates current if exists or create new Alias row.
  270 + *
  271 + * @param $slug
  272 + *
  273 + * @throws \yii\base\ErrorException
  274 + */
  275 + protected function saveSlug($slug)
  276 + {
  277 + if (!$this->hasChanged($slug)) {
  278 + return;
  279 + } else {
  280 + $this->populateFields();
  281 + $route = $this->buildRoute();
  282 + $owner = $this->owner;
  283 + if (empty($alias = $this->findCurrentAlias())) {
  284 +
  285 + $alias = \Yii::createObject(
  286 + Alias::className(),
  287 + [
  288 + [
  289 + 'value' => $slug,
  290 + 'route' => $route,
  291 + 'entity' => $owner::className(),
  292 + 'title' => ( !empty($title) ) ? $title : $owner->getAttribute($this->inAttribute),
  293 + 'h1' => ( !empty($h1) ) ? $h1 : $owner->getAttribute($this->inAttribute),
  294 + 'fields' => Json::encode($this->fields),
  295 + 'language_id' => Language::getCurrent()->id,
  296 + ],
  297 + ]
  298 + );
  299 +
  300 + } else {
  301 + $alias->value = $slug;
  302 + $alias->route = $route;
  303 + $alias->entity = $owner::className();
  304 + $alias->language_id = Language::getCurrent()->id;
  305 + }
  306 + if ($alias->save()) {
  307 + $owner->{$this->outAttribute} = $alias->id;
  308 + $this->detach();
  309 + $owner->save(false, [ $this->outAttribute ]);
  310 + } else {
  311 + $alias = Alias::find()
  312 + ->where(
  313 + [
  314 + 'route' => $alias->route,
  315 + 'language_id' => $alias->language_id,
  316 + ]
  317 + )
  318 + ->one();
  319 + if ($alias) {
  320 + $owner->{$this->outAttribute} = $alias->id;
  321 + $this->detach();
  322 + $owner->save(false, [ $this->outAttribute ]);
  323 + } else {
  324 + throw new ErrorException(
  325 + \Yii::t(
  326 + 'core',
  327 + 'Alias cannot be saved: ' . Json::encode($alias) . '. Errors: ' . Json::encode(
  328 + $alias->getErrors()
  329 + )
  330 + )
  331 + );
  332 + }
  333 + }
  334 + }
  335 + }
  336 + protected function generateData(string $attribute, int $lang)
  337 + {
  338 + if (!isset($this->meta[ $lang ][ $attribute ])) {
  339 + return null;
  340 + }
  341 + $value = $this->meta[ $lang ][ $attribute ];
  342 + foreach ($this->attributes as $search => $replace) {
  343 + $value = str_replace($search, $replace, $value);
  344 +
  345 + }
  346 + return $value;
  347 + }
  348 +
  349 + protected function populateFields()
  350 + {
  351 + $fields = array_keys($this->owner->attributes);
  352 + foreach ($fields as $field) {
  353 +
  354 + $chain = $this->owner;
  355 +
  356 + if (!empty($chain->$field)) {
  357 + $chain = $chain->$field;
  358 + } else {
  359 + $chain = '';
  360 + }
  361 + $this->attributes[ '{{' . $field . '}}' ] = $chain;
  362 + }
  363 + }
  364 +
  365 +
  366 + /**
  367 + * Get $aliasValue. Try to get from cached value and tries to get from Db if empty.
  368 + *
  369 + * @return string
  370 + */
  371 + public function getAliasValue()
  372 + {
  373 + if (empty($this->aliasValue) && !$this->executed) {
  374 + $currentAlias = $this->findCurrentAlias();
  375 + if (!empty($currentAlias)) {
  376 + $this->aliasValue = $currentAlias->value;
  377 + return $currentAlias->value;
  378 + }
  379 + }
  380 + return $this->aliasValue;
  381 + }
  382 +
  383 + /**
  384 + * Set $aliasValue
  385 + *
  386 + * @param $value
  387 + */
  388 + public function setAliasValue($value)
  389 + {
  390 + $this->aliasValue = $value;
  391 + }
  392 +
  393 + /**
  394 + * Build Json route from $action and $params fields.
  395 + *
  396 + * @return null|string
  397 + */
  398 + protected function buildRoute()
  399 + {
  400 + $owner = $this->owner;
  401 + $route = [];
  402 + $action = $this->action;
  403 + if (empty($action)) {
  404 + return null;
  405 + } else {
  406 + $route[ 0 ] = $action;
  407 + }
  408 + $params = $this->params;
  409 + if (!empty($params) && is_array($params)) {
  410 + foreach ($params as $param => $attribute) {
  411 + $attributeValue = $owner->getAttribute($attribute);
  412 + if (!empty($attributeValue)) {
  413 + $route[ $param ] = $attributeValue;
  414 + }
  415 + }
  416 + }
  417 + return Json::encode($route);
  418 + }
  419 + }
  420 +
0 421 \ No newline at end of file
... ...
frontend/controllers/BookController.php
... ... @@ -38,7 +38,6 @@
38 38 }
39 39 $model = new Book();
40 40 if (\Yii::$app->request->isPost) {
41   -
42 41 if ($model->load(\Yii::$app->request->post(), '') and $model->validate()) {
43 42 $model->author_id = $user->id;
44 43 $model->status = $model::STATUS_MODERATION;
... ... @@ -84,7 +83,7 @@
84 83 $dataProvider = new ActiveDataProvider(
85 84 [
86 85 'query' => Book::find()
87   - ->with('author')
  86 + ->with(['author', 'alias'])
88 87 ->where([ 'status' => Book::STATUS_ACTIVE ]),
89 88 'pagination' => [
90 89 'pageSize' => 10,
... ...
frontend/views/book/_book.php
... ... @@ -2,7 +2,7 @@
2 2 /**
3 3 * @var \common\models\Book $model;
4 4 */
5   - use yii\helpers\Url;
  5 + use artbox\core\helpers\Url;
6 6  
7 7 ?>
8 8 <div class="row">
... ... @@ -12,7 +12,7 @@
12 12 <?php } ?>
13 13 </div>
14 14 <div class="col-xs-12 col-sm-8 col-md-7 col-lg-6 books-wrapp-text">
15   - <div class="style books-title"><a href="<?=Url::to(['book/view', 'id' => $model->id])?>"><?=$model->title?></a></div>
  15 + <div class="style books-title"><a href="<?=Url::to(['alias' => $model->alias])?>"><?=$model->title?></a></div>
16 16 <div class="style books-autor">Автор: <?=$model->author->name?> <?=$model->author->secondname?></div>
17 17 <div class="style books-text">
18 18 <?=$model->preview?>
... ...
frontend/views/book/view.php
... ... @@ -142,9 +142,6 @@
142 142 <div class="comments-card-text style">
143 143 Доброго дня! Це не обкладинка, а просто футуристична картинка "для ілюстрації". Обкладинку ми зараз готуємо і незабаром додамо її на сайт.
144 144 </div>
145   - <div class="style answers-wr">
146   - <span>відповісти</span>
147   - </div>
148 145 </div>
149 146 </div>
150 147  
... ...
frontend/web/js/book.js
... ... @@ -170,7 +170,9 @@
170 170 data.append('description', description);
171 171 data.append('preview', preview);
172 172 data.append('file', imageFile);
173   - data.append('image', image);
  173 + if (image != null){
  174 + data.append('image', image);
  175 + }
174 176 xhr.onreadystatechange = function() {
175 177 if (xhr.readyState === XMLHttpRequest.DONE) {
176 178 // window.location = '/author/index';
... ...