1 | <?php |
||
2 | |||
3 | namespace yii2mod\comments\models; |
||
4 | |||
5 | use paulzi\adjacencyList\AdjacencyListBehavior; |
||
6 | use Yii; |
||
7 | use yii\behaviors\BlameableBehavior; |
||
8 | use yii\behaviors\TimestampBehavior; |
||
9 | use yii\db\ActiveQuery; |
||
10 | use yii\db\ActiveRecord; |
||
11 | use yii\helpers\ArrayHelper; |
||
12 | use yii2mod\behaviors\PurifyBehavior; |
||
13 | use yii2mod\comments\traits\ModuleTrait; |
||
14 | use yii2mod\moderation\enums\Status; |
||
15 | use yii2mod\moderation\ModerationBehavior; |
||
16 | use yii2mod\moderation\ModerationQuery; |
||
17 | |||
18 | /** |
||
19 | * Class CommentModel |
||
20 | * |
||
21 | * @property int $id |
||
22 | * @property string $entity |
||
23 | * @property int $entityId |
||
24 | * @property string $content |
||
25 | * @property int $parentId |
||
26 | * @property int $level |
||
27 | * @property int $createdBy |
||
28 | * @property int $updatedBy |
||
29 | * @property string $relatedTo |
||
30 | * @property string $url |
||
31 | * @property int $status |
||
32 | * @property int $createdAt |
||
33 | * @property int $updatedAt |
||
34 | * |
||
35 | * @method ActiveRecord makeRoot() |
||
36 | * @method ActiveRecord appendTo($node) |
||
37 | * @method ActiveQuery getDescendants() |
||
38 | * @method ModerationBehavior markRejected() |
||
39 | * @method AdjacencyListBehavior deleteWithChildren() |
||
40 | */ |
||
41 | class CommentModel extends ActiveRecord |
||
42 | { |
||
43 | use ModuleTrait; |
||
44 | |||
45 | const SCENARIO_MODERATION = 'moderation'; |
||
46 | |||
47 | /** |
||
48 | * @var null|array|ActiveRecord[] comment children |
||
49 | */ |
||
50 | protected $children; |
||
51 | |||
52 | /** |
||
53 | * @inheritdoc |
||
54 | */ |
||
55 | public static function tableName() |
||
56 | { |
||
57 | return '{{%comment}}'; |
||
58 | } |
||
59 | |||
60 | /** |
||
61 | * @inheritdoc |
||
62 | */ |
||
63 | public function rules() |
||
64 | { |
||
65 | return [ |
||
66 | [['entity', 'entityId'], 'required'], |
||
67 | ['content', 'required', 'message' => Yii::t('yii2mod.comments', 'Comment cannot be blank.')], |
||
68 | [['content', 'entity', 'relatedTo', 'url'], 'string'], |
||
69 | ['status', 'default', 'value' => Status::APPROVED], |
||
70 | ['status', 'in', 'range' => Status::getConstantsByName()], |
||
71 | ['level', 'default', 'value' => 1], |
||
72 | ['parentId', 'validateParentID', 'except' => static::SCENARIO_MODERATION], |
||
73 | [['entityId', 'parentId', 'status', 'level'], 'integer'], |
||
74 | ]; |
||
75 | } |
||
76 | |||
77 | /** |
||
78 | * @param $attribute |
||
79 | */ |
||
80 | public function validateParentID($attribute) |
||
81 | { |
||
82 | if (null !== $this->{$attribute}) { |
||
83 | $parentCommentExist = static::find() |
||
84 | ->approved() |
||
85 | ->andWhere([ |
||
86 | 'id' => $this->{$attribute}, |
||
87 | 'entity' => $this->entity, |
||
88 | 'entityId' => $this->entityId, |
||
89 | ]) |
||
90 | ->exists(); |
||
91 | |||
92 | if (!$parentCommentExist) { |
||
93 | $this->addError('content', Yii::t('yii2mod.comments', 'Oops, something went wrong. Please try again later.')); |
||
94 | } |
||
95 | } |
||
96 | } |
||
97 | |||
98 | /** |
||
99 | * @inheritdoc |
||
100 | */ |
||
101 | public function behaviors() |
||
102 | { |
||
103 | return [ |
||
104 | 'blameable' => [ |
||
105 | 'class' => BlameableBehavior::class, |
||
106 | 'createdByAttribute' => 'createdBy', |
||
107 | 'updatedByAttribute' => 'updatedBy', |
||
108 | ], |
||
109 | 'timestamp' => [ |
||
110 | 'class' => TimestampBehavior::class, |
||
111 | 'createdAtAttribute' => 'createdAt', |
||
112 | 'updatedAtAttribute' => 'updatedAt', |
||
113 | ], |
||
114 | 'purify' => [ |
||
115 | 'class' => PurifyBehavior::class, |
||
116 | 'attributes' => ['content'], |
||
117 | 'config' => [ |
||
118 | 'HTML.SafeIframe' => true, |
||
119 | 'URI.SafeIframeRegexp' => '%^(https?:)?//(www\.youtube(?:-nocookie)?\.com/embed/|player\.vimeo\.com/video/)%', |
||
120 | 'AutoFormat.Linkify' => true, |
||
121 | 'HTML.TargetBlank' => true, |
||
122 | 'HTML.Allowed' => 'a[href], iframe[src|width|height|frameborder], img[src]', |
||
123 | ], |
||
124 | ], |
||
125 | 'adjacencyList' => [ |
||
126 | 'class' => AdjacencyListBehavior::class, |
||
127 | 'parentAttribute' => 'parentId', |
||
128 | 'sortable' => false, |
||
129 | ], |
||
130 | 'moderation' => [ |
||
131 | 'class' => ModerationBehavior::class, |
||
132 | 'moderatedByAttribute' => false, |
||
133 | ], |
||
134 | ]; |
||
135 | } |
||
136 | |||
137 | /** |
||
138 | * @inheritdoc |
||
139 | */ |
||
140 | public function attributeLabels() |
||
141 | { |
||
142 | return [ |
||
143 | 'id' => Yii::t('yii2mod.comments', 'ID'), |
||
144 | 'content' => Yii::t('yii2mod.comments', 'Content'), |
||
145 | 'entity' => Yii::t('yii2mod.comments', 'Entity'), |
||
146 | 'entityId' => Yii::t('yii2mod.comments', 'Entity ID'), |
||
147 | 'parentId' => Yii::t('yii2mod.comments', 'Parent ID'), |
||
148 | 'status' => Yii::t('yii2mod.comments', 'Status'), |
||
149 | 'level' => Yii::t('yii2mod.comments', 'Level'), |
||
150 | 'createdBy' => Yii::t('yii2mod.comments', 'Created by'), |
||
151 | 'updatedBy' => Yii::t('yii2mod.comments', 'Updated by'), |
||
152 | 'relatedTo' => Yii::t('yii2mod.comments', 'Related to'), |
||
153 | 'url' => Yii::t('yii2mod.comments', 'Url'), |
||
154 | 'createdAt' => Yii::t('yii2mod.comments', 'Created date'), |
||
155 | 'updatedAt' => Yii::t('yii2mod.comments', 'Updated date'), |
||
156 | ]; |
||
157 | } |
||
158 | |||
159 | /** |
||
160 | * @return ModerationQuery |
||
161 | */ |
||
162 | public static function find() |
||
163 | { |
||
164 | return new ModerationQuery(get_called_class()); |
||
165 | } |
||
166 | |||
167 | /** |
||
168 | * @inheritdoc |
||
169 | */ |
||
170 | public function beforeSave($insert) |
||
171 | { |
||
172 | if (parent::beforeSave($insert)) { |
||
173 | if ($this->parentId > 0 && $this->isNewRecord) { |
||
174 | $parentNodeLevel = static::find()->select('level')->where(['id' => $this->parentId])->scalar(); |
||
175 | $this->level += $parentNodeLevel; |
||
176 | } |
||
177 | |||
178 | return true; |
||
179 | } else { |
||
180 | return false; |
||
181 | } |
||
182 | } |
||
183 | |||
184 | /** |
||
185 | * @inheritdoc |
||
186 | */ |
||
187 | public function afterSave($insert, $changedAttributes) |
||
188 | { |
||
189 | parent::afterSave($insert, $changedAttributes); |
||
190 | |||
191 | if (!$insert) { |
||
192 | if (array_key_exists('status', $changedAttributes)) { |
||
193 | $this->beforeModeration(); |
||
194 | } |
||
195 | } |
||
196 | } |
||
197 | |||
198 | /** |
||
199 | * @return bool |
||
200 | */ |
||
201 | public function saveComment() |
||
202 | { |
||
203 | if ($this->validate()) { |
||
204 | if (empty($this->parentId)) { |
||
205 | return $this->makeRoot()->save(); |
||
206 | } else { |
||
207 | $parentComment = static::findOne(['id' => $this->parentId]); |
||
208 | |||
209 | return $this->appendTo($parentComment)->save(); |
||
210 | } |
||
211 | } |
||
212 | |||
213 | return false; |
||
214 | } |
||
215 | |||
216 | /** |
||
217 | * Author relation |
||
218 | * |
||
219 | * @return \yii\db\ActiveQuery |
||
220 | */ |
||
221 | public function getAuthor() |
||
222 | { |
||
223 | return $this->hasOne($this->getModule()->userIdentityClass, ['id' => 'createdBy']); |
||
224 | } |
||
225 | |||
226 | /** |
||
227 | * Get comments tree. |
||
228 | * |
||
229 | * @param string $entity |
||
230 | * @param string $entityId |
||
231 | * @param null $maxLevel |
||
0 ignored issues
–
show
Documentation
Bug
introduced
by
![]() |
|||
232 | * |
||
233 | * @return array|ActiveRecord[] |
||
234 | */ |
||
235 | public static function getTree($entity, $entityId, $maxLevel = null) |
||
236 | { |
||
237 | $query = static::find() |
||
238 | ->alias('c') |
||
239 | ->approved() |
||
240 | ->andWhere([ |
||
241 | 'c.entityId' => $entityId, |
||
242 | 'c.entity' => $entity, |
||
243 | ]) |
||
244 | ->orderBy(['c.parentId' => SORT_ASC, 'c.createdAt' => SORT_ASC]) |
||
245 | ->with(['author']); |
||
246 | |||
247 | if ($maxLevel > 0) { |
||
248 | $query->andWhere(['<=', 'c.level', $maxLevel]); |
||
249 | } |
||
250 | |||
251 | $models = $query->all(); |
||
252 | |||
253 | if (!empty($models)) { |
||
254 | $models = static::buildTree($models); |
||
255 | } |
||
256 | |||
257 | return $models; |
||
258 | } |
||
259 | |||
260 | /** |
||
261 | * Build comments tree. |
||
262 | * |
||
263 | * @param array $data comments list |
||
264 | * @param int $rootID |
||
265 | * |
||
266 | * @return array|ActiveRecord[] |
||
267 | */ |
||
268 | protected static function buildTree(&$data, $rootID = 0) |
||
269 | { |
||
270 | $tree = []; |
||
271 | |||
272 | foreach ($data as $id => $node) { |
||
273 | if ($node->parentId == $rootID) { |
||
274 | unset($data[$id]); |
||
275 | $node->children = self::buildTree($data, $node->id); |
||
276 | $tree[] = $node; |
||
277 | } |
||
278 | } |
||
279 | |||
280 | return $tree; |
||
281 | } |
||
282 | |||
283 | /** |
||
284 | * @return array|null|ActiveRecord[] |
||
285 | */ |
||
286 | public function getChildren() |
||
287 | { |
||
288 | return $this->children; |
||
289 | } |
||
290 | |||
291 | /** |
||
292 | * @param $value |
||
293 | */ |
||
294 | public function setChildren($value) |
||
295 | { |
||
296 | $this->children = $value; |
||
297 | } |
||
298 | |||
299 | /** |
||
300 | * @return bool |
||
301 | */ |
||
302 | public function hasChildren() |
||
303 | { |
||
304 | return !empty($this->children); |
||
305 | } |
||
306 | |||
307 | /** |
||
308 | * @return string |
||
309 | */ |
||
310 | public function getPostedDate() |
||
311 | { |
||
312 | return Yii::$app->formatter->asRelativeTime($this->createdAt); |
||
313 | } |
||
314 | |||
315 | /** |
||
316 | * @return mixed |
||
317 | */ |
||
318 | public function getAuthorName() |
||
319 | { |
||
320 | if ($this->author->hasMethod('getUsername')) { |
||
321 | return $this->author->getUsername(); |
||
322 | } |
||
323 | |||
324 | return $this->author->username; |
||
325 | } |
||
326 | |||
327 | /** |
||
328 | * @return string |
||
329 | */ |
||
330 | public function getContent() |
||
331 | { |
||
332 | return nl2br($this->content); |
||
333 | } |
||
334 | |||
335 | /** |
||
336 | * Get avatar of the user |
||
337 | * |
||
338 | * @return string |
||
339 | */ |
||
340 | public function getAvatar() |
||
341 | { |
||
342 | if ($this->author->hasMethod('getAvatar')) { |
||
343 | return $this->author->getAvatar(); |
||
344 | } |
||
345 | |||
346 | return 'http://www.gravatar.com/avatar?d=mm&f=y&s=50'; |
||
347 | } |
||
348 | |||
349 | /** |
||
350 | * Get list of all authors |
||
351 | * |
||
352 | * @return array |
||
353 | */ |
||
354 | public static function getAuthors() |
||
355 | { |
||
356 | $query = static::find() |
||
357 | ->alias('c') |
||
358 | ->select(['c.createdBy', 'a.username']) |
||
359 | ->joinWith('author a') |
||
360 | ->groupBy(['c.createdBy', 'a.username']) |
||
361 | ->orderBy('a.username') |
||
362 | ->asArray() |
||
363 | ->all(); |
||
364 | |||
365 | return ArrayHelper::map($query, 'createdBy', 'author.username'); |
||
366 | } |
||
367 | |||
368 | /** |
||
369 | * @return int |
||
370 | */ |
||
371 | public function getCommentsCount() |
||
372 | { |
||
373 | return (int) static::find() |
||
374 | ->approved() |
||
375 | ->andWhere(['entity' => $this->entity, 'entityId' => $this->entityId]) |
||
376 | ->count(); |
||
377 | } |
||
378 | |||
379 | /** |
||
380 | * @return string |
||
381 | */ |
||
382 | public function getAnchorUrl() |
||
383 | { |
||
384 | return "#comment-{$this->id}"; |
||
385 | } |
||
386 | |||
387 | /** |
||
388 | * @return null|string |
||
389 | */ |
||
390 | public function getViewUrl() |
||
391 | { |
||
392 | if (!empty($this->url)) { |
||
393 | return $this->url . $this->getAnchorUrl(); |
||
394 | } |
||
395 | |||
396 | return null; |
||
397 | } |
||
398 | |||
399 | /** |
||
400 | * Before moderation event |
||
401 | * |
||
402 | * @return bool |
||
403 | */ |
||
404 | public function beforeModeration() |
||
405 | { |
||
406 | $descendantIds = ArrayHelper::getColumn($this->getDescendants()->asArray()->all(), 'id'); |
||
407 | |||
408 | if (!empty($descendantIds)) { |
||
409 | static::updateAll(['status' => $this->status], ['id' => $descendantIds]); |
||
410 | } |
||
411 | |||
412 | return true; |
||
413 | } |
||
414 | } |
||
415 |