These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace SilverStripe\Comments\Model; |
||
4 | |||
5 | use HTMLPurifier_Config; |
||
6 | use HTMLPurifier; |
||
7 | use SilverStripe\Comments\Controllers\CommentingController; |
||
8 | use SilverStripe\Comments\Extensions\CommentsExtension; |
||
9 | use SilverStripe\Comments\Model\Comment\SecurityToken; |
||
10 | use SilverStripe\Control\Controller; |
||
11 | use SilverStripe\Control\Director; |
||
12 | use SilverStripe\Core\Email\Email; |
||
13 | use SilverStripe\Core\Injector\Injector; |
||
14 | use SilverStripe\Forms\CheckboxField; |
||
15 | use SilverStripe\Forms\EmailField; |
||
16 | use SilverStripe\Forms\FieldGroup; |
||
17 | use SilverStripe\Forms\FieldList; |
||
18 | use SilverStripe\Forms\HeaderField; |
||
19 | use SilverStripe\Forms\HTMLEditor\HTMLEditorField; |
||
20 | use SilverStripe\Forms\TextareaField; |
||
21 | use SilverStripe\Forms\TextField; |
||
22 | use SilverStripe\ORM\ArrayList; |
||
23 | use SilverStripe\ORM\DataObject; |
||
24 | use SilverStripe\ORM\DB; |
||
25 | use SilverStripe\ORM\PaginatedList; |
||
26 | use SilverStripe\Security\Member; |
||
27 | use SilverStripe\Security\Permission; |
||
28 | |||
29 | /** |
||
30 | * Represents a single comment object. |
||
31 | * |
||
32 | * @property string $Name |
||
33 | * @property string $Comment |
||
34 | * @property string $Email |
||
35 | * @property string $URL |
||
36 | * @property string $BaseClass |
||
37 | * @property boolean $Moderated |
||
38 | * @property boolean $IsSpam True if the comment is known as spam |
||
39 | * @property integer $ParentID ID of the parent page / dataobject |
||
40 | * @property boolean $AllowHtml If true, treat $Comment as HTML instead of plain text |
||
41 | * @property string $SecretToken Secret admin token required to provide moderation links between sessions |
||
42 | * @property integer $Depth Depth of this comment in the nested chain |
||
43 | * |
||
44 | * @method HasManyList ChildComments() List of child comments |
||
45 | * @method Member Author() Member object who created this comment |
||
46 | * @method Comment ParentComment() Parent comment this is a reply to |
||
47 | * @package comments |
||
48 | */ |
||
49 | class Comment extends DataObject |
||
50 | { |
||
51 | /** |
||
52 | * {@inheritDoc} |
||
53 | */ |
||
54 | private static $db = array( |
||
55 | 'Name' => 'Varchar(200)', |
||
56 | 'Comment' => 'Text', |
||
57 | 'Email' => 'Varchar(200)', |
||
58 | 'URL' => 'Varchar(255)', |
||
59 | 'Moderated' => 'Boolean(0)', |
||
60 | 'IsSpam' => 'Boolean(0)', |
||
61 | 'AllowHtml' => 'Boolean', |
||
62 | 'SecretToken' => 'Varchar(255)', |
||
63 | 'Depth' => 'Int' |
||
64 | ); |
||
65 | |||
66 | /** |
||
67 | * {@inheritDoc} |
||
68 | */ |
||
69 | private static $has_one = array( |
||
70 | 'Author' => Member::class, |
||
71 | 'ParentComment' => self::class, |
||
72 | 'Parent' => DataObject::class |
||
73 | ); |
||
74 | |||
75 | /** |
||
76 | * {@inheritDoc} |
||
77 | */ |
||
78 | private static $has_many = array( |
||
79 | 'ChildComments' => self::class |
||
80 | ); |
||
81 | |||
82 | /** |
||
83 | * {@inheritDoc} |
||
84 | */ |
||
85 | private static $default_sort = '"Created" DESC'; |
||
86 | |||
87 | /** |
||
88 | * {@inheritDoc} |
||
89 | */ |
||
90 | private static $defaults = array( |
||
91 | 'Moderated' => 0, |
||
92 | 'IsSpam' => 0, |
||
93 | ); |
||
94 | |||
95 | /** |
||
96 | * {@inheritDoc} |
||
97 | */ |
||
98 | private static $casting = array( |
||
99 | 'Title' => 'Varchar', |
||
100 | 'ParentTitle' => 'Varchar', |
||
101 | 'ParentClassName' => 'Varchar', |
||
102 | 'AuthorName' => 'Varchar', |
||
103 | 'RSSName' => 'Varchar', |
||
104 | 'DeleteLink' => 'Varchar', |
||
105 | 'SpamLink' => 'Varchar', |
||
106 | 'HamLink' => 'Varchar', |
||
107 | 'ApproveLink' => 'Varchar', |
||
108 | 'Permalink' => 'Varchar' |
||
109 | ); |
||
110 | |||
111 | /** |
||
112 | * {@inheritDoc} |
||
113 | */ |
||
114 | private static $searchable_fields = array( |
||
115 | 'Name', |
||
116 | 'Email', |
||
117 | 'Comment', |
||
118 | 'Created' |
||
119 | ); |
||
120 | |||
121 | /** |
||
122 | * {@inheritDoc} |
||
123 | */ |
||
124 | private static $summary_fields = array( |
||
125 | 'Name' => 'Submitted By', |
||
126 | 'Email' => 'Email', |
||
127 | 'Comment.LimitWordCount' => 'Comment', |
||
128 | 'Created' => 'Date Posted', |
||
129 | 'Parent.Title' => 'Post', |
||
130 | 'IsSpam' => 'Is Spam' |
||
131 | ); |
||
132 | |||
133 | /** |
||
134 | * {@inheritDoc} |
||
135 | */ |
||
136 | private static $field_labels = array( |
||
137 | 'Author' => 'Author Member' |
||
138 | ); |
||
139 | |||
140 | /** |
||
141 | * {@inheritDoc} |
||
142 | */ |
||
143 | private static $table_name = 'Comment'; |
||
144 | |||
145 | /** |
||
146 | * {@inheritDoc} |
||
147 | */ |
||
148 | public function onBeforeWrite() |
||
149 | { |
||
150 | parent::onBeforeWrite(); |
||
151 | |||
152 | // Sanitize HTML, because its expected to be passed to the template unescaped later |
||
153 | if ($this->AllowHtml) { |
||
154 | $this->Comment = $this->purifyHtml($this->Comment); |
||
155 | } |
||
156 | |||
157 | // Check comment depth |
||
158 | $this->updateDepth(); |
||
159 | } |
||
160 | |||
161 | /** |
||
162 | * {@inheritDoc} |
||
163 | */ |
||
164 | public function onBeforeDelete() |
||
165 | { |
||
166 | parent::onBeforeDelete(); |
||
167 | |||
168 | // Delete all children |
||
169 | foreach ($this->ChildComments() as $comment) { |
||
170 | $comment->delete(); |
||
171 | } |
||
172 | } |
||
173 | |||
174 | /** |
||
175 | * @return Comment_SecurityToken |
||
176 | */ |
||
177 | public function getSecurityToken() |
||
178 | { |
||
179 | return Injector::inst()->createWithArgs(SecurityToken::class, array($this)); |
||
180 | } |
||
181 | |||
182 | /** |
||
183 | * Migrates the old {@link PageComment} objects to {@link Comment} |
||
184 | */ |
||
185 | public function requireDefaultRecords() |
||
186 | { |
||
187 | parent::requireDefaultRecords(); |
||
188 | |||
189 | if (DB::get_schema()->hasTable('PageComment')) { |
||
190 | $comments = DB::query('SELECT * FROM "PageComment"'); |
||
191 | |||
192 | if ($comments) { |
||
193 | while ($pageComment = $comments->next()) { |
||
194 | // create a new comment from the older page comment |
||
195 | $comment = new Comment(); |
||
196 | $comment->update($pageComment); |
||
197 | |||
198 | // set the variables which have changed |
||
199 | $comment->BaseClass = SiteTree::class; |
||
200 | $comment->URL = (isset($pageComment['CommenterURL'])) ? $pageComment['CommenterURL'] : ''; |
||
201 | if ((int) $pageComment['NeedsModeration'] == 0) { |
||
202 | $comment->Moderated = true; |
||
203 | } |
||
204 | |||
205 | $comment->write(); |
||
206 | } |
||
207 | } |
||
208 | |||
209 | DB::alteration_message('Migrated PageComment to Comment', 'changed'); |
||
210 | DB::get_schema()->dontRequireTable('PageComment'); |
||
211 | } |
||
212 | } |
||
213 | |||
214 | /** |
||
215 | * Return a link to this comment |
||
216 | * |
||
217 | * @param string $action |
||
218 | * |
||
219 | * @return string link to this comment. |
||
220 | */ |
||
221 | public function Link($action = '') |
||
222 | { |
||
223 | if ($parent = $this->Parent()) { |
||
224 | return $parent->Link($action) . '#' . $this->Permalink(); |
||
225 | } |
||
226 | } |
||
227 | |||
228 | /** |
||
229 | * Returns the permalink for this {@link Comment}. Inserted into |
||
230 | * the ID tag of the comment |
||
231 | * |
||
232 | * @return string |
||
233 | */ |
||
234 | public function Permalink() |
||
235 | { |
||
236 | $prefix = $this->getOption('comment_permalink_prefix'); |
||
237 | return $prefix . $this->ID; |
||
238 | } |
||
239 | |||
240 | /** |
||
241 | * Translate the form field labels for the CMS administration |
||
242 | * |
||
243 | * @param boolean $includerelations |
||
244 | * |
||
245 | * @return array |
||
246 | */ |
||
247 | public function fieldLabels($includerelations = true) |
||
248 | { |
||
249 | $labels = parent::fieldLabels($includerelations); |
||
250 | |||
251 | $labels['Name'] = _t('Comment.NAME', 'Author Name'); |
||
252 | $labels['Comment'] = _t('Comment.COMMENT', 'Comment'); |
||
253 | $labels['Email'] = _t('Comment.EMAIL', 'Email'); |
||
254 | $labels['URL'] = _t('Comment.URL', 'URL'); |
||
255 | $labels['IsSpam'] = _t('Comment.ISSPAM', 'Spam?'); |
||
256 | $labels['Moderated'] = _t('Comment.MODERATED', 'Moderated?'); |
||
257 | $labels['ParentTitle'] = _t('Comment.PARENTTITLE', 'Parent'); |
||
258 | $labels['Created'] = _t('Comment.CREATED', 'Date posted'); |
||
259 | |||
260 | return $labels; |
||
261 | } |
||
262 | |||
263 | /** |
||
264 | * Get the commenting option |
||
265 | * |
||
266 | * @param string $key |
||
267 | * |
||
268 | * @return mixed Result if the setting is available, or null otherwise |
||
269 | */ |
||
270 | public function getOption($key) |
||
271 | { |
||
272 | // If possible use the current record |
||
273 | $record = $this->Parent(); |
||
274 | |||
275 | if (!$record && $this->Parent()) { |
||
276 | // Otherwise a singleton of that record |
||
277 | $record = singleton($this->Parent()->dataClass()); |
||
278 | } elseif (!$record) { |
||
279 | // Otherwise just use the default options |
||
280 | $record = singleton(CommentsExtension::class); |
||
281 | } |
||
282 | |||
283 | return ($record instanceof CommentsExtension || $record->hasExtension(CommentsExtension::class)) |
||
284 | ? $record->getCommentsOption($key) |
||
285 | : null; |
||
286 | } |
||
287 | |||
288 | /** |
||
289 | * Returns the parent {@link DataObject} this comment is attached too |
||
290 | * |
||
291 | * @deprecated 4.0.0 Use $this->Parent() instead |
||
292 | * @return DataObject |
||
293 | */ |
||
294 | public function getParent() |
||
295 | { |
||
296 | return $this->BaseClass && $this->ParentID |
||
297 | ? DataObject::get_by_id($this->BaseClass, $this->ParentID, true) |
||
298 | : null; |
||
299 | } |
||
300 | |||
301 | |||
302 | /** |
||
303 | * Returns a string to help identify the parent of the comment |
||
304 | * |
||
305 | * @return string |
||
306 | */ |
||
307 | public function getParentTitle() |
||
308 | { |
||
309 | if ($parent = $this->Parent()) { |
||
310 | return $parent->Title ?: ($parent->ClassName . ' #' . $parent->ID); |
||
311 | } |
||
312 | } |
||
313 | |||
314 | /** |
||
315 | * Comment-parent classnames obviously vary, return the parent classname |
||
316 | * |
||
317 | * @return string |
||
318 | */ |
||
319 | public function getParentClassName() |
||
320 | { |
||
321 | return $this->Parent()->getClassName(); |
||
322 | } |
||
323 | |||
324 | /** |
||
325 | * {@inheritDoc} |
||
326 | */ |
||
327 | public function castingHelper($field) |
||
328 | { |
||
329 | // Safely escape the comment |
||
330 | if (in_array($field, ['EscapedComment', 'Comment'], true)) { |
||
331 | return $this->AllowHtml ? 'HTMLText' : 'Text'; |
||
332 | } |
||
333 | return parent::castingHelper($field); |
||
334 | } |
||
335 | |||
336 | /** |
||
337 | * Content to be safely escaped on the frontend |
||
338 | * |
||
339 | * @return string |
||
340 | */ |
||
341 | public function getEscapedComment() |
||
342 | { |
||
343 | return $this->Comment; |
||
344 | } |
||
345 | |||
346 | /** |
||
347 | * Return whether this comment is a preview (has not been written to the db) |
||
348 | * |
||
349 | * @return boolean |
||
350 | */ |
||
351 | public function isPreview() |
||
352 | { |
||
353 | return !$this->exists(); |
||
354 | } |
||
355 | |||
356 | /** |
||
357 | * @todo needs to compare to the new {@link Commenting} configuration API |
||
358 | * |
||
359 | * @param Member $member |
||
360 | * @param array $context |
||
361 | * @return bool |
||
362 | */ |
||
363 | public function canCreate($member = null, $context = []) |
||
364 | { |
||
365 | return false; |
||
366 | } |
||
367 | |||
368 | /** |
||
369 | * Checks for association with a page, and {@link SiteTree->ProvidePermission} |
||
370 | * flag being set to true. |
||
371 | * |
||
372 | * @param Member $member |
||
373 | * @return Boolean |
||
374 | */ |
||
375 | public function canView($member = null) |
||
376 | { |
||
377 | $member = $this->getMember($member); |
||
378 | |||
379 | $extended = $this->extendedCan('canView', $member); |
||
380 | if ($extended !== null) { |
||
381 | return $extended; |
||
382 | } |
||
383 | |||
384 | if (Permission::checkMember($member, 'CMS_ACCESS_CommentAdmin')) { |
||
385 | return true; |
||
386 | } |
||
387 | |||
388 | if ($parent = $this->Parent()) { |
||
389 | return $parent->canView($member) |
||
390 | && $parent->hasExtension(CommentsExtension::class) |
||
391 | && $parent->CommentsEnabled; |
||
392 | } |
||
393 | |||
394 | return false; |
||
395 | } |
||
396 | |||
397 | /** |
||
398 | * Checks if the comment can be edited. |
||
399 | * |
||
400 | * @param null|int|Member $member |
||
401 | * @return Boolean |
||
402 | */ |
||
403 | public function canEdit($member = null) |
||
404 | { |
||
405 | $member = $this->getMember($member); |
||
406 | |||
407 | if (!$member) { |
||
408 | return false; |
||
409 | } |
||
410 | |||
411 | $extended = $this->extendedCan('canEdit', $member); |
||
412 | if ($extended !== null) { |
||
413 | return $extended; |
||
414 | } |
||
415 | |||
416 | if (Permission::checkMember($member, 'CMS_ACCESS_CommentAdmin')) { |
||
417 | return true; |
||
418 | } |
||
419 | |||
420 | if ($parent = $this->Parent()) { |
||
421 | return $parent->canEdit($member); |
||
422 | } |
||
423 | |||
424 | return false; |
||
425 | } |
||
426 | |||
427 | /** |
||
428 | * Checks if the comment can be deleted. |
||
429 | * |
||
430 | * @param null|int|Member $member |
||
431 | * @return Boolean |
||
432 | */ |
||
433 | public function canDelete($member = null) |
||
434 | { |
||
435 | $member = $this->getMember($member); |
||
436 | |||
437 | if (!$member) { |
||
438 | return false; |
||
439 | } |
||
440 | |||
441 | $extended = $this->extendedCan('canDelete', $member); |
||
442 | if ($extended !== null) { |
||
443 | return $extended; |
||
444 | } |
||
445 | |||
446 | return $this->canEdit($member); |
||
447 | } |
||
448 | |||
449 | /** |
||
450 | * Resolves Member object. |
||
451 | * |
||
452 | * @param Member|int|null $member |
||
453 | * @return Member|null |
||
454 | */ |
||
455 | protected function getMember($member = null) |
||
456 | { |
||
457 | if (!$member) { |
||
458 | $member = Member::currentUser(); |
||
459 | } |
||
460 | |||
461 | if (is_numeric($member)) { |
||
462 | $member = DataObject::get_by_id(Member::class, $member, true); |
||
463 | } |
||
464 | |||
465 | return $member; |
||
466 | } |
||
467 | |||
468 | /** |
||
469 | * Return the authors name for the comment |
||
470 | * |
||
471 | * @return string |
||
472 | */ |
||
473 | public function getAuthorName() |
||
474 | { |
||
475 | if ($this->Name) { |
||
476 | return $this->Name; |
||
477 | } elseif ($author = $this->Author()) { |
||
478 | return $author->getName(); |
||
479 | } |
||
480 | } |
||
481 | |||
482 | /** |
||
483 | * Generate a secure admin-action link authorised for the specified member |
||
484 | * |
||
485 | * @param string $action An action on CommentingController to link to |
||
486 | * @param Member $member The member authorised to invoke this action |
||
487 | * |
||
488 | * @return string |
||
489 | */ |
||
490 | protected function actionLink($action, $member = null) |
||
491 | { |
||
492 | if (!$member) { |
||
493 | $member = Member::currentUser(); |
||
494 | } |
||
495 | if (!$member) { |
||
496 | return false; |
||
497 | } |
||
498 | |||
499 | /** |
||
500 | * @todo: How do we handle "DataObject" instances that don't have a Link to reject/spam/delete?? This may |
||
501 | * we have to make CMS a hard dependency instead. |
||
502 | */ |
||
503 | // if (!$this->Parent()->hasMethod('Link')) { |
||
504 | // return false; |
||
505 | // } |
||
506 | |||
507 | $url = Controller::join_links( |
||
508 | Director::baseURL(), |
||
509 | 'comments', |
||
510 | $action, |
||
511 | $this->ID |
||
512 | ); |
||
513 | |||
514 | // Limit access for this user |
||
515 | $token = $this->getSecurityToken(); |
||
516 | return $token->addToUrl($url, $member); |
||
517 | } |
||
518 | |||
519 | /** |
||
520 | * Link to delete this comment |
||
521 | * |
||
522 | * @param Member $member |
||
523 | * |
||
524 | * @return string |
||
525 | */ |
||
526 | public function DeleteLink($member = null) |
||
527 | { |
||
528 | if ($this->canDelete($member)) { |
||
529 | return $this->actionLink('delete', $member); |
||
530 | } |
||
531 | } |
||
532 | |||
533 | /** |
||
534 | * Link to mark as spam |
||
535 | * |
||
536 | * @param Member $member |
||
537 | * |
||
538 | * @return string |
||
539 | */ |
||
540 | public function SpamLink($member = null) |
||
541 | { |
||
542 | if ($this->canEdit($member) && !$this->IsSpam) { |
||
543 | return $this->actionLink('spam', $member); |
||
544 | } |
||
545 | } |
||
546 | |||
547 | /** |
||
548 | * Link to mark as not-spam (ham) |
||
549 | * |
||
550 | * @param Member $member |
||
551 | * |
||
552 | * @return string |
||
553 | */ |
||
554 | public function HamLink($member = null) |
||
555 | { |
||
556 | if ($this->canEdit($member) && $this->IsSpam) { |
||
557 | return $this->actionLink('ham', $member); |
||
558 | } |
||
559 | } |
||
560 | |||
561 | /** |
||
562 | * Link to approve this comment |
||
563 | * |
||
564 | * @param Member $member |
||
565 | * |
||
566 | * @return string |
||
567 | */ |
||
568 | public function ApproveLink($member = null) |
||
569 | { |
||
570 | if ($this->canEdit($member) && !$this->Moderated) { |
||
571 | return $this->actionLink('approve', $member); |
||
572 | } |
||
573 | } |
||
574 | |||
575 | /** |
||
576 | * Mark this comment as spam |
||
577 | */ |
||
578 | public function markSpam() |
||
579 | { |
||
580 | $this->IsSpam = true; |
||
581 | $this->Moderated = true; |
||
582 | $this->write(); |
||
583 | $this->extend('afterMarkSpam'); |
||
584 | } |
||
585 | |||
586 | /** |
||
587 | * Mark this comment as approved |
||
588 | */ |
||
589 | public function markApproved() |
||
590 | { |
||
591 | $this->IsSpam = false; |
||
592 | $this->Moderated = true; |
||
593 | $this->write(); |
||
594 | $this->extend('afterMarkApproved'); |
||
595 | } |
||
596 | |||
597 | /** |
||
598 | * Mark this comment as unapproved |
||
599 | */ |
||
600 | public function markUnapproved() |
||
601 | { |
||
602 | $this->Moderated = false; |
||
603 | $this->write(); |
||
604 | $this->extend('afterMarkUnapproved'); |
||
605 | } |
||
606 | |||
607 | /** |
||
608 | * @return string |
||
609 | */ |
||
610 | public function SpamClass() |
||
611 | { |
||
612 | if ($this->IsSpam) { |
||
613 | return 'spam'; |
||
614 | } elseif (!$this->Moderated) { |
||
615 | return 'unmoderated'; |
||
616 | } else { |
||
617 | return 'notspam'; |
||
618 | } |
||
619 | } |
||
620 | |||
621 | /** |
||
622 | * @return string |
||
623 | */ |
||
624 | public function getTitle() |
||
625 | { |
||
626 | $title = sprintf(_t('Comment.COMMENTBY', 'Comment by %s', 'Name'), $this->getAuthorName()); |
||
627 | |||
628 | if ($parent = $this->Parent()) { |
||
629 | if ($parent->Title) { |
||
630 | $title .= sprintf(' %s %s', _t('Comment.ON', 'on'), $parent->Title); |
||
631 | } |
||
632 | } |
||
633 | |||
634 | return $title; |
||
635 | } |
||
636 | |||
637 | /* |
||
638 | * Modify the default fields shown to the user |
||
639 | */ |
||
640 | public function getCMSFields() |
||
641 | { |
||
642 | $commentField = $this->AllowHtml ? HTMLEditorField::class : TextareaField::class; |
||
643 | $fields = new FieldList( |
||
644 | $this |
||
645 | ->obj('Created') |
||
646 | ->scaffoldFormField($this->fieldLabel('Created')) |
||
647 | ->performReadonlyTransformation(), |
||
648 | TextField::create('Name', $this->fieldLabel('Name')), |
||
649 | $commentField::create('Comment', $this->fieldLabel('Comment')), |
||
650 | EmailField::create('Email', $this->fieldLabel('Email')), |
||
651 | TextField::create('URL', $this->fieldLabel('URL')), |
||
652 | FieldGroup::create(array( |
||
653 | CheckboxField::create('Moderated', $this->fieldLabel('Moderated')), |
||
654 | CheckboxField::create('IsSpam', $this->fieldLabel('IsSpam')), |
||
655 | )) |
||
656 | ->setTitle(_t('Comment.OPTIONS', 'Options')) |
||
657 | ->setDescription(_t( |
||
658 | 'Comment.OPTION_DESCRIPTION', |
||
659 | 'Unmoderated and spam comments will not be displayed until approved' |
||
660 | )) |
||
661 | ); |
||
662 | |||
663 | // Show member name if given |
||
664 | if (($author = $this->Author()) && $author->exists()) { |
||
665 | $fields->insertAfter( |
||
666 | TextField::create('AuthorMember', $this->fieldLabel('Author'), $author->Title) |
||
667 | ->performReadonlyTransformation(), |
||
668 | 'Name' |
||
669 | ); |
||
670 | } |
||
671 | |||
672 | // Show parent comment if given |
||
673 | if (($parent = $this->ParentComment()) && $parent->exists()) { |
||
674 | $fields->push(new HeaderField( |
||
675 | 'ParentComment_Title', |
||
676 | _t('Comment.ParentComment_Title', 'This comment is a reply to the below') |
||
677 | )); |
||
678 | // Created date |
||
679 | // FIXME - the method setName in DatetimeField is not chainable, hence |
||
680 | // the lack of chaining here |
||
681 | $createdField = $parent |
||
682 | ->obj('Created') |
||
683 | ->scaffoldFormField($parent->fieldLabel('Created')); |
||
684 | $createdField->setName('ParentComment_Created'); |
||
685 | $createdField->setValue($parent->Created); |
||
686 | $createdField->performReadonlyTransformation(); |
||
687 | $fields->push($createdField); |
||
688 | |||
689 | // Name (could be member or string value) |
||
690 | $fields->push( |
||
691 | $parent |
||
692 | ->obj('AuthorName') |
||
693 | ->scaffoldFormField($parent->fieldLabel('AuthorName')) |
||
694 | ->setName('ParentComment_AuthorName') |
||
695 | ->setValue($parent->getAuthorName()) |
||
696 | ->performReadonlyTransformation() |
||
697 | ); |
||
698 | |||
699 | // Comment body |
||
700 | $fields->push( |
||
701 | $parent |
||
702 | ->obj('EscapedComment') |
||
703 | ->scaffoldFormField($parent->fieldLabel(self::class)) |
||
704 | ->setName('ParentComment_EscapedComment') |
||
705 | ->setValue($parent->Comment) |
||
706 | ->performReadonlyTransformation() |
||
707 | ); |
||
708 | } |
||
709 | |||
710 | $this->extend('updateCMSFields', $fields); |
||
711 | return $fields; |
||
712 | } |
||
713 | |||
714 | /** |
||
715 | * @param string $dirtyHtml |
||
716 | * |
||
717 | * @return string |
||
718 | */ |
||
719 | public function purifyHtml($dirtyHtml) |
||
720 | { |
||
721 | $purifier = $this->getHtmlPurifierService(); |
||
722 | return $purifier->purify($dirtyHtml); |
||
723 | } |
||
724 | |||
725 | /** |
||
726 | * @return HTMLPurifier (or anything with a "purify()" method) |
||
727 | */ |
||
728 | public function getHtmlPurifierService() |
||
729 | { |
||
730 | $config = HTMLPurifier_Config::createDefault(); |
||
731 | $allowedElements = (array) $this->getOption('html_allowed_elements'); |
||
732 | if (!empty($allowedElements)) { |
||
733 | $config->set('HTML.AllowedElements', $allowedElements); |
||
734 | } |
||
735 | |||
736 | // This injector cannot be set unless the 'p' element is allowed |
||
737 | if (in_array('p', $allowedElements)) { |
||
738 | $config->set('AutoFormat.AutoParagraph', true); |
||
739 | } |
||
740 | |||
741 | $config->set('AutoFormat.Linkify', true); |
||
742 | $config->set('URI.DisableExternalResources', true); |
||
743 | $config->set('Cache.SerializerPath', getTempFolder()); |
||
744 | return new HTMLPurifier($config); |
||
745 | } |
||
746 | |||
747 | /** |
||
748 | * Calculate the Gravatar link from the email address |
||
749 | * |
||
750 | * @return string |
||
751 | */ |
||
752 | public function Gravatar() |
||
753 | { |
||
754 | $gravatar = ''; |
||
755 | $use_gravatar = $this->getOption('use_gravatar'); |
||
756 | if ($use_gravatar) { |
||
757 | $gravatar = 'http://www.gravatar.com/avatar/' . md5(strtolower(trim($this->Email))); |
||
758 | $gravatarsize = $this->getOption('gravatar_size'); |
||
759 | $gravatardefault = $this->getOption('gravatar_default'); |
||
760 | $gravatarrating = $this->getOption('gravatar_rating'); |
||
761 | $gravatar .= '?s=' . $gravatarsize . '&d=' . $gravatardefault . '&r=' . $gravatarrating; |
||
762 | } |
||
763 | |||
764 | return $gravatar; |
||
765 | } |
||
766 | |||
767 | /** |
||
768 | * Determine if replies are enabled for this instance |
||
769 | * |
||
770 | * @return boolean |
||
771 | */ |
||
772 | public function getRepliesEnabled() |
||
773 | { |
||
774 | // Check reply option |
||
775 | if (!$this->getOption('nested_comments')) { |
||
776 | return false; |
||
777 | } |
||
778 | |||
779 | // Check if depth is limited |
||
780 | $maxLevel = $this->getOption('nested_depth'); |
||
781 | $notSpam = ($this->SpamClass() == 'notspam'); |
||
782 | return $notSpam && (!$maxLevel || $this->Depth < $maxLevel); |
||
783 | } |
||
784 | |||
785 | /** |
||
786 | * Returns the list of all replies |
||
787 | * |
||
788 | * @return SS_List |
||
789 | */ |
||
790 | public function AllReplies() |
||
791 | { |
||
792 | // No replies if disabled |
||
793 | if (!$this->getRepliesEnabled()) { |
||
794 | return new ArrayList(); |
||
795 | } |
||
796 | |||
797 | // Get all non-spam comments |
||
798 | $order = $this->getOption('order_replies_by') |
||
799 | ?: $this->getOption('order_comments_by'); |
||
800 | $list = $this |
||
801 | ->ChildComments() |
||
802 | ->sort($order); |
||
803 | |||
804 | $this->extend('updateAllReplies', $list); |
||
805 | return $list; |
||
806 | } |
||
807 | |||
808 | /** |
||
809 | * Returns the list of replies, with spam and unmoderated items excluded, for use in the frontend |
||
810 | * |
||
811 | * @return SS_List |
||
812 | */ |
||
813 | public function Replies() |
||
814 | { |
||
815 | // No replies if disabled |
||
816 | if (!$this->getRepliesEnabled()) { |
||
817 | return new ArrayList(); |
||
818 | } |
||
819 | $list = $this->AllReplies(); |
||
820 | |||
821 | // Filter spam comments for non-administrators if configured |
||
822 | $parent = $this->Parent(); |
||
823 | $showSpam = $this->getOption('frontend_spam') && $parent && $parent->canModerateComments(); |
||
824 | if (!$showSpam) { |
||
825 | $list = $list->filter('IsSpam', 0); |
||
826 | } |
||
827 | |||
828 | // Filter un-moderated comments for non-administrators if moderation is enabled |
||
829 | $showUnmoderated = $parent && ( |
||
830 | ($parent->ModerationRequired === 'None') |
||
831 | || ($this->getOption('frontend_moderation') && $parent->canModerateComments()) |
||
832 | ); |
||
833 | if (!$showUnmoderated) { |
||
834 | $list = $list->filter('Moderated', 1); |
||
835 | } |
||
836 | |||
837 | $this->extend('updateReplies', $list); |
||
838 | return $list; |
||
839 | } |
||
840 | |||
841 | /** |
||
842 | * Returns the list of replies paged, with spam and unmoderated items excluded, for use in the frontend |
||
843 | * |
||
844 | * @return PaginatedList |
||
845 | */ |
||
846 | View Code Duplication | public function PagedReplies() |
|
0 ignored issues
–
show
|
|||
847 | { |
||
848 | $list = $this->Replies(); |
||
849 | |||
850 | // Add pagination |
||
851 | $list = new PaginatedList($list, Controller::curr()->getRequest()); |
||
852 | $list->setPaginationGetVar('repliesstart' . $this->ID); |
||
853 | $list->setPageLength($this->getOption('comments_per_page')); |
||
854 | |||
855 | $this->extend('updatePagedReplies', $list); |
||
856 | return $list; |
||
857 | } |
||
858 | |||
859 | /** |
||
860 | * Generate a reply form for this comment |
||
861 | * |
||
862 | * @return Form |
||
863 | */ |
||
864 | public function ReplyForm() |
||
865 | { |
||
866 | // Ensure replies are enabled |
||
867 | if (!$this->getRepliesEnabled()) { |
||
868 | return null; |
||
869 | } |
||
870 | |||
871 | // Check parent is available |
||
872 | $parent = $this->Parent(); |
||
873 | if (!$parent || !$parent->exists()) { |
||
874 | return null; |
||
875 | } |
||
876 | |||
877 | // Build reply controller |
||
878 | $controller = CommentingController::create(); |
||
879 | $controller->setOwnerRecord($parent); |
||
880 | $controller->setParentClass($parent->ClassName); |
||
881 | $controller->setOwnerController(Controller::curr()); |
||
882 | |||
883 | return $controller->ReplyForm($this); |
||
884 | } |
||
885 | |||
886 | /** |
||
887 | * Refresh of this comment in the hierarchy |
||
888 | */ |
||
889 | public function updateDepth() |
||
890 | { |
||
891 | $parent = $this->ParentComment(); |
||
892 | if ($parent && $parent->exists()) { |
||
893 | $parent->updateDepth(); |
||
894 | $this->Depth = $parent->Depth + 1; |
||
895 | } else { |
||
896 | $this->Depth = 1; |
||
897 | } |
||
898 | } |
||
899 | } |
||
900 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.