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')) { |
||||
0 ignored issues
–
show
The property
author does not exist on yii2mod\comments\models\CommentModel . Since you implemented __get , consider adding a @property annotation.
![]() The method
hasMethod() does not exist on null .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||
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')) { |
||||
0 ignored issues
–
show
The property
author does not exist on yii2mod\comments\models\CommentModel . Since you implemented __get , consider adding a @property annotation.
![]() |
|||||
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 |