Completed
Push — master ( 4e2d22...187618 )
by Will
14:40
created

tests/CommentsTest.php (4 issues)

Upgrade to new PHP Analysis Engine

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\Tests;
4
5
use ReflectionClass;
6
use SilverStripe\Comments\Extensions\CommentsExtension;
7
use SilverStripe\Comments\Model\Comment;
8
use SilverStripe\Comments\Tests\Stubs\CommentableItem;
9
use SilverStripe\Comments\Tests\Stubs\CommentableItemDisabled;
10
use SilverStripe\Comments\Tests\Stubs\CommentableItemEnabled;
11
use SilverStripe\Control\Controller;
12
use SilverStripe\Control\Director;
13
use SilverStripe\Core\Config\Config;
14
use SilverStripe\Core\Email\Email;
15
use SilverStripe\Dev\FunctionalTest;
16
use SilverStripe\Dev\TestOnly;
17
use SilverStripe\i18n\i18n;
18
use SilverStripe\ORM\DataObject;
19
use SilverStripe\Security\Member;
20
use SilverStripe\Security\Permission;
21
22
/**
23
 * @package comments
24
 */
25
class CommentsTest extends FunctionalTest
26
{
27
    protected static $fixture_file = 'comments/tests/CommentsTest.yml';
28
29
    protected $extraDataObjects = array(
30
        CommentableItem::class,
31
        CommentableItemEnabled::class,
32
        CommentableItemDisabled::class
33
    );
34
35 View Code Duplication
    public function setUp()
0 ignored issues
show
This method seems to be duplicated in your project.

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.

Loading history...
36
    {
37
        parent::setUp();
38
        Config::nest();
39
40
        // Set good default values
41
        Config::inst()->update(CommentsExtension::class, 'comments', array(
42
            'enabled' => true,
43
            'enabled_cms' => false,
44
            'require_login' => false,
45
            'require_login_cms' => false,
46
            'required_permission' => false,
47
            'require_moderation_nonmembers' => false,
48
            'require_moderation' => false,
49
            'require_moderation_cms' => false,
50
            'frontend_moderation' => false,
51
            'frontend_spam' => false,
52
        ));
53
54
        // Configure this dataobject
55
        Config::inst()->update(CommentableItem::class, 'comments', array(
56
            'enabled_cms' => true
57
        ));
58
    }
59
60
    public function tearDown()
61
    {
62
        Config::unnest();
63
        parent::tearDown();
64
    }
65
66
    public function testCommentsList()
67
    {
68
        // comments don't require moderation so unmoderated comments can be
69
        // shown but not spam posts
70
        Config::inst()->update(CommentableItem::class, 'comments', array(
71
            'require_moderation_nonmembers' => false,
72
            'require_moderation' => false,
73
            'require_moderation_cms' => false,
74
        ));
75
76
        $item = $this->objFromFixture(CommentableItem::class, 'spammed');
77
        $this->assertEquals('None', $item->ModerationRequired);
78
79
        $this->assertDOSEquals(array(
80
            array('Name' => 'Comment 1'),
81
            array('Name' => 'Comment 3')
82
        ), $item->Comments(), 'Only 2 non spam posts should be shown');
83
84
        // when moderated, only moderated, non spam posts should be shown.
85
        Config::inst()->update(CommentableItem::class, 'comments', array('require_moderation_nonmembers' => true));
86
        $this->assertEquals('NonMembersOnly', $item->ModerationRequired);
87
88
        // Check that require_moderation overrides this option
89
        Config::inst()->update(CommentableItem::class, 'comments', array('require_moderation' => true));
90
        $this->assertEquals('Required', $item->ModerationRequired);
91
92
        $this->assertDOSEquals(array(
93
            array('Name' => 'Comment 3')
94
        ), $item->Comments(), 'Only 1 non spam, moderated post should be shown');
95
        $this->assertEquals(1, $item->Comments()->Count());
96
97
        // require_moderation_nonmembers still filters out unmoderated comments
98
        Config::inst()->update(CommentableItem::class, 'comments', array('require_moderation' => false));
99
        $this->assertEquals(1, $item->Comments()->Count());
100
101
        Config::inst()->update(CommentableItem::class, 'comments', array('require_moderation_nonmembers' => false));
102
        $this->assertEquals(2, $item->Comments()->Count());
103
104
        // With unmoderated comments set to display in frontend
105
        Config::inst()->update(CommentableItem::class, 'comments', array(
106
            'require_moderation' => true,
107
            'frontend_moderation' => true
108
        ));
109
        $this->assertEquals(1, $item->Comments()->Count());
110
111
        $this->logInWithPermission('ADMIN');
112
        $this->assertEquals(2, $item->Comments()->Count());
113
114
        // With spam comments set to display in frontend
115
        Config::inst()->update(CommentableItem::class, 'comments', array(
116
            'require_moderation' => true,
117
            'frontend_moderation' => false,
118
            'frontend_spam' => true,
119
        ));
120
        if ($member = Member::currentUser()) {
121
            $member->logOut();
122
        }
123
        $this->assertEquals(1, $item->Comments()->Count());
124
125
        $this->logInWithPermission('ADMIN');
126
        $this->assertEquals(2, $item->Comments()->Count());
127
128
129
        // With spam and unmoderated comments set to display in frontend
130
        Config::inst()->update(CommentableItem::class, 'comments', array(
131
            'require_moderation' => true,
132
            'frontend_moderation' => true,
133
            'frontend_spam' => true,
134
        ));
135
        if ($member = Member::currentUser()) {
136
            $member->logOut();
137
        }
138
        $this->assertEquals(1, $item->Comments()->Count());
139
140
        $this->logInWithPermission('ADMIN');
141
        $this->assertEquals(4, $item->Comments()->Count());
142
    }
143
144
    /**
145
     * Test moderation options configured via the CMS
146
     */
147
    public function testCommentCMSModerationList()
148
    {
149
        // comments don't require moderation so unmoderated comments can be
150
        // shown but not spam posts
151
        Config::inst()->update(CommentableItem::class, 'comments', array(
152
            'require_moderation' => true,
153
            'require_moderation_cms' => true,
154
        ));
155
156
        $item = $this->objFromFixture(CommentableItem::class, 'spammed');
157
        $this->assertEquals('None', $item->ModerationRequired);
158
159
        $this->assertDOSEquals(array(
160
            array('Name' => 'Comment 1'),
161
            array('Name' => 'Comment 3')
162
        ), $item->Comments(), 'Only 2 non spam posts should be shown');
163
164
        // when moderated, only moderated, non spam posts should be shown.
165
        $item->ModerationRequired = 'NonMembersOnly';
166
        $item->write();
167
        $this->assertEquals('NonMembersOnly', $item->ModerationRequired);
168
169
        // Check that require_moderation overrides this option
170
        $item->ModerationRequired = 'Required';
171
        $item->write();
172
        $this->assertEquals('Required', $item->ModerationRequired);
173
174
        $this->assertDOSEquals(array(
175
            array('Name' => 'Comment 3')
176
        ), $item->Comments(), 'Only 1 non spam, moderated post should be shown');
177
        $this->assertEquals(1, $item->Comments()->Count());
178
179
        // require_moderation_nonmembers still filters out unmoderated comments
180
        $item->ModerationRequired = 'NonMembersOnly';
181
        $item->write();
182
        $this->assertEquals(1, $item->Comments()->Count());
183
184
        $item->ModerationRequired = 'None';
185
        $item->write();
186
        $this->assertEquals(2, $item->Comments()->Count());
187
    }
188
189
    public function testCanPostComment()
190
    {
191
        Config::inst()->update(CommentableItem::class, 'comments', array(
192
            'require_login' => false,
193
            'require_login_cms' => false,
194
            'required_permission' => false,
195
        ));
196
        $item = $this->objFromFixture(CommentableItem::class, 'first');
197
        $item2 = $this->objFromFixture(CommentableItem::class, 'second');
198
199
        // Test restriction free commenting
200
        if ($member = Member::currentUser()) {
201
            $member->logOut();
202
        }
203
        $this->assertFalse($item->CommentsRequireLogin);
204
        $this->assertTrue($item->canPostComment());
205
206
        // Test permission required to post
207
        Config::inst()->update(CommentableItem::class, 'comments', array(
208
            'require_login' => true,
209
            'required_permission' => 'POSTING_PERMISSION',
210
        ));
211
        $this->assertTrue($item->CommentsRequireLogin);
212
        $this->assertFalse($item->canPostComment());
213
        $this->logInWithPermission('WRONG_ONE');
214
        $this->assertFalse($item->canPostComment());
215
        $this->logInWithPermission('POSTING_PERMISSION');
216
        $this->assertTrue($item->canPostComment());
217
        $this->logInWithPermission('ADMIN');
218
        $this->assertTrue($item->canPostComment());
219
220
        // Test require login to post, but not any permissions
221
        Config::inst()->update(CommentableItem::class, 'comments', array(
222
            'required_permission' => false,
223
        ));
224
        $this->assertTrue($item->CommentsRequireLogin);
225
        if ($member = Member::currentUser()) {
226
            $member->logOut();
227
        }
228
        $this->assertFalse($item->canPostComment());
229
        $this->logInWithPermission('ANY_PERMISSION');
230
        $this->assertTrue($item->canPostComment());
231
232
        // Test options set via CMS
233
        Config::inst()->update(CommentableItem::class, 'comments', array(
234
            'require_login' => true,
235
            'require_login_cms' => true,
236
        ));
237
        $this->assertFalse($item->CommentsRequireLogin);
238
        $this->assertTrue($item2->CommentsRequireLogin);
239
        if ($member = Member::currentUser()) {
240
            $member->logOut();
241
        }
242
        $this->assertTrue($item->canPostComment());
243
        $this->assertFalse($item2->canPostComment());
244
245
        // Login grants permission to post
246
        $this->logInWithPermission('ANY_PERMISSION');
247
        $this->assertTrue($item->canPostComment());
248
        $this->assertTrue($item2->canPostComment());
249
    }
250
    public function testDeleteComment()
251
    {
252
        // Test anonymous user
253
        if ($member = Member::currentUser()) {
254
            $member->logOut();
255
        }
256
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
257
        $commentID = $comment->ID;
258
        $this->assertNull($comment->DeleteLink(), 'No permission to see delete link');
259
        $delete = $this->get('comments/delete/' . $comment->ID . '?ajax=1');
260
        $this->assertEquals(403, $delete->getStatusCode());
261
        $check = DataObject::get_by_id(Comment::class, $commentID);
262
        $this->assertTrue($check && $check->exists());
263
264
        // Test non-authenticated user
265
        $this->logInAs('visitor');
266
        $this->assertNull($comment->DeleteLink(), 'No permission to see delete link');
267
268
        // Test authenticated user
269
        $this->logInAs('commentadmin');
270
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
271
        $commentID = $comment->ID;
272
        $adminComment1Link = $comment->DeleteLink();
273
        $this->assertContains('comments/delete/' . $commentID . '?t=', $adminComment1Link);
274
275
        // Test that this link can't be shared / XSS exploited
276
        $this->logInAs('commentadmin2');
277
        $delete = $this->get($adminComment1Link);
278
        $this->assertEquals(400, $delete->getStatusCode());
279
        $check = DataObject::get_by_id(Comment::class, $commentID);
280
        $this->assertTrue($check && $check->exists());
281
282
        // Test that this other admin can delete the comment with their own link
283
        $adminComment2Link = $comment->DeleteLink();
284
        $this->assertNotEquals($adminComment2Link, $adminComment1Link);
285
        $this->autoFollowRedirection = false;
286
        $delete = $this->get($adminComment2Link);
287
        $this->assertEquals(302, $delete->getStatusCode());
288
        $check = DataObject::get_by_id(Comment::class, $commentID);
289
        $this->assertFalse($check && $check->exists());
290
    }
291
292 View Code Duplication
    public function testSpamComment()
0 ignored issues
show
This method seems to be duplicated in your project.

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.

Loading history...
293
    {
294
        // Test anonymous user
295
        if ($member = Member::currentUser()) {
296
            $member->logOut();
297
        }
298
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
299
        $commentID = $comment->ID;
300
        $this->assertNull($comment->SpamLink(), 'No permission to see mark as spam link');
301
        $spam = $this->get('comments/spam/'.$comment->ID.'?ajax=1');
302
        $this->assertEquals(403, $spam->getStatusCode());
303
        $check = DataObject::get_by_id(Comment::class, $commentID);
304
        $this->assertEquals(0, $check->IsSpam, 'No permission to mark as spam');
305
306
        // Test non-authenticated user
307
        $this->logInAs('visitor');
308
        $this->assertNull($comment->SpamLink(), 'No permission to see mark as spam link');
309
310
        // Test authenticated user
311
        $this->logInAs('commentadmin');
312
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
313
        $commentID = $comment->ID;
314
        $adminComment1Link = $comment->SpamLink();
315
        $this->assertContains('comments/spam/' . $commentID . '?t=', $adminComment1Link);
316
317
        // Test that this link can't be shared / XSS exploited
318
        $this->logInAs('commentadmin2');
319
        $spam = $this->get($adminComment1Link);
320
        $this->assertEquals(400, $spam->getStatusCode());
321
        $check = DataObject::get_by_id(Comment::class, $comment->ID);
322
        $this->assertEquals(0, $check->IsSpam, 'No permission to mark as spam');
323
324
        // Test that this other admin can spam the comment with their own link
325
        $adminComment2Link = $comment->SpamLink();
326
        $this->assertNotEquals($adminComment2Link, $adminComment1Link);
327
        $this->autoFollowRedirection = false;
328
        $spam = $this->get($adminComment2Link);
329
        $this->assertEquals(302, $spam->getStatusCode());
330
        $check = DataObject::get_by_id(Comment::class, $commentID);
331
        $this->assertEquals(1, $check->IsSpam);
332
333
        // Cannot re-spam spammed comment
334
        $this->assertNull($check->SpamLink());
335
    }
336
337 View Code Duplication
    public function testHamComment()
0 ignored issues
show
This method seems to be duplicated in your project.

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.

Loading history...
338
    {
339
        // Test anonymous user
340
        if ($member = Member::currentUser()) {
341
            $member->logOut();
342
        }
343
        $comment = $this->objFromFixture(Comment::class, 'secondComC');
344
        $commentID = $comment->ID;
345
        $this->assertNull($comment->HamLink(), 'No permission to see mark as ham link');
346
        $ham = $this->get('comments/ham/' . $comment->ID . '?ajax=1');
347
        $this->assertEquals(403, $ham->getStatusCode());
348
        $check = DataObject::get_by_id(Comment::class, $commentID);
349
        $this->assertEquals(1, $check->IsSpam, 'No permission to mark as ham');
350
351
        // Test non-authenticated user
352
        $this->logInAs('visitor');
353
        $this->assertNull($comment->HamLink(), 'No permission to see mark as ham link');
354
355
        // Test authenticated user
356
        $this->logInAs('commentadmin');
357
        $comment = $this->objFromFixture(Comment::class, 'secondComC');
358
        $commentID = $comment->ID;
359
        $adminComment1Link = $comment->HamLink();
360
        $this->assertContains('comments/ham/' . $commentID . '?t=', $adminComment1Link);
361
362
        // Test that this link can't be shared / XSS exploited
363
        $this->logInAs('commentadmin2');
364
        $ham = $this->get($adminComment1Link);
365
        $this->assertEquals(400, $ham->getStatusCode());
366
        $check = DataObject::get_by_id(Comment::class, $comment->ID);
367
        $this->assertEquals(1, $check->IsSpam, 'No permission to mark as ham');
368
369
        // Test that this other admin can ham the comment with their own link
370
        $adminComment2Link = $comment->HamLink();
371
        $this->assertNotEquals($adminComment2Link, $adminComment1Link);
372
        $this->autoFollowRedirection = false;
373
        $ham = $this->get($adminComment2Link);
374
        $this->assertEquals(302, $ham->getStatusCode());
375
        $check = DataObject::get_by_id(Comment::class, $commentID);
376
        $this->assertEquals(0, $check->IsSpam);
377
378
        // Cannot re-ham hammed comment
379
        $this->assertNull($check->HamLink());
380
    }
381
382 View Code Duplication
    public function testApproveComment()
0 ignored issues
show
This method seems to be duplicated in your project.

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.

Loading history...
383
    {
384
        // Test anonymous user
385
        if ($member = Member::currentUser()) {
386
            $member->logOut();
387
        }
388
        $comment = $this->objFromFixture(Comment::class, 'secondComB');
389
        $commentID = $comment->ID;
390
        $this->assertNull($comment->ApproveLink(), 'No permission to see approve link');
391
        $approve = $this->get('comments/approve/' . $comment->ID . '?ajax=1');
392
        $this->assertEquals(403, $approve->getStatusCode());
393
        $check = DataObject::get_by_id(Comment::class, $commentID);
394
        $this->assertEquals(0, $check->Moderated, 'No permission to approve');
395
396
        // Test non-authenticated user
397
        $this->logInAs('visitor');
398
        $this->assertNull($comment->ApproveLink(), 'No permission to see approve link');
399
400
        // Test authenticated user
401
        $this->logInAs('commentadmin');
402
        $comment = $this->objFromFixture(Comment::class, 'secondComB');
403
        $commentID = $comment->ID;
404
        $adminComment1Link = $comment->ApproveLink();
405
        $this->assertContains('comments/approve/' . $commentID . '?t=', $adminComment1Link);
406
407
        // Test that this link can't be shared / XSS exploited
408
        $this->logInAs('commentadmin2');
409
        $approve = $this->get($adminComment1Link);
410
        $this->assertEquals(400, $approve->getStatusCode());
411
        $check = DataObject::get_by_id(Comment::class, $comment->ID);
412
        $this->assertEquals(0, $check->Moderated, 'No permission to approve');
413
414
        // Test that this other admin can approve the comment with their own link
415
        $adminComment2Link = $comment->ApproveLink();
416
        $this->assertNotEquals($adminComment2Link, $adminComment1Link);
417
        $this->autoFollowRedirection = false;
418
        $approve = $this->get($adminComment2Link);
419
        $this->assertEquals(302, $approve->getStatusCode());
420
        $check = DataObject::get_by_id(Comment::class, $commentID);
421
        $this->assertEquals(1, $check->Moderated);
422
423
        // Cannot re-approve approved comment
424
        $this->assertNull($check->ApproveLink());
425
    }
426
427
    public function testCommenterURLWrite()
428
    {
429
        $comment = new Comment();
430
        // We only care about the CommenterURL, so only set that
431
        // Check a http and https URL. Add more test urls here as needed.
432
        $protocols = array(
433
            'Http',
434
            'Https',
435
        );
436
        $url = '://example.com';
437
438
        foreach ($protocols as $protocol) {
439
            $comment->CommenterURL = $protocol . $url;
440
            // The protocol should stay as if, assuming it is valid
441
            $comment->write();
442
            $this->assertEquals($comment->CommenterURL, $protocol . $url, $protocol . ':// is a valid protocol');
443
        }
444
    }
445
446
    public function testSanitizesWithAllowHtml()
447
    {
448
        if (!class_exists('\\HTMLPurifier')) {
449
            $this->markTestSkipped('HTMLPurifier class not found');
450
            return;
451
        }
452
453
        // Add p for paragraph
454
        // NOTE: The config method appears to append to the existing array
455
        Config::inst()->update(CommentableItem::class, 'comments', array(
456
            'html_allowed_elements' => array('p'),
457
        ));
458
459
        // Without HTML allowed
460
        $comment1 = new Comment();
461
        $comment1->AllowHtml = false;
462
        $comment1->ParentClass = CommentableItem::class;
463
        $comment1->Comment = '<p><script>alert("w00t")</script>my comment</p>';
464
        $comment1->write();
465
        $this->assertEquals(
466
            '<p><script>alert("w00t")</script>my comment</p>',
467
            $comment1->Comment,
468
            'Does not remove HTML tags with html_allowed=false, ' .
469
            'which is correct behaviour because the HTML will be escaped'
470
        );
471
472
        // With HTML allowed
473
        $comment2 = new Comment();
474
        $comment2->AllowHtml = true;
475
        $comment2->ParentClass = CommentableItem::class;
476
        $comment2->Comment = '<p><script>alert("w00t")</script>my comment</p>';
477
        $comment2->write();
478
        $this->assertEquals(
479
            '<p>my comment</p>',
480
            $comment2->Comment,
481
            'Removes HTML tags which are not on the whitelist'
482
        );
483
    }
484
485
    public function testDefaultTemplateRendersHtmlWithAllowHtml()
486
    {
487
        if (!class_exists('\\HTMLPurifier')) {
488
            $this->markTestSkipped('HTMLPurifier class not found');
489
        }
490
491
        Config::inst()->update(CommentableItem::class, 'comments', array(
492
            'html_allowed_elements' => array('p'),
493
        ));
494
495
        $item = new CommentableItem();
496
        $item->write();
497
498
        // Without HTML allowed
499
        $comment = new Comment();
500
        $comment->Comment = '<p>my comment</p>';
501
        $comment->AllowHtml = false;
502
        $comment->ParentID = $item->ID;
503
        $comment->ParentClass = CommentableItem::class;
504
        $comment->write();
505
506
        $html = $item->customise(array('CommentsEnabled' => true))->renderWith('CommentsInterface');
507
        $this->assertContains(
508
            '&lt;p&gt;my comment&lt;/p&gt;',
509
            $html
510
        );
511
512
        $comment->AllowHtml = true;
513
        $comment->write();
514
        $html = $item->customise(array('CommentsEnabled' => true))->renderWith('CommentsInterface');
515
        $this->assertContains(
516
            '<p>my comment</p>',
517
            $html
518
        );
519
    }
520
521
522
    /**
523
     * Tests whether comments are enabled or disabled by default
524
     */
525
    public function testDefaultEnabled()
526
    {
527
        // Ensure values are set via cms (not via config)
528
        Config::inst()->update(CommentableItem::class, 'comments', array(
529
            'enabled_cms' => true,
530
            'require_moderation_cms' => true,
531
            'require_login_cms' => true
532
        ));
533
534
        // With default = true
535
        $obj = new CommentableItem();
536
        $this->assertTrue((bool)$obj->getCommentsOption('enabled'), "Default setting is enabled");
537
        $this->assertTrue((bool)$obj->ProvideComments);
538
        $this->assertEquals('None', $obj->ModerationRequired);
539
        $this->assertFalse((bool)$obj->CommentsRequireLogin);
540
541
        $obj = new CommentableItemEnabled();
542
        $this->assertTrue((bool)$obj->ProvideComments);
543
        $this->assertEquals('Required', $obj->ModerationRequired);
544
        $this->assertTrue((bool)$obj->CommentsRequireLogin);
545
546
        $obj = new CommentableItemDisabled();
547
        $this->assertFalse((bool)$obj->ProvideComments);
548
        $this->assertEquals('None', $obj->ModerationRequired);
549
        $this->assertFalse((bool)$obj->CommentsRequireLogin);
550
551
        // With default = false
552
        // Because of config rules about falsey values, apply config to object directly
553
        Config::inst()->update(CommentableItem::class, 'comments', array(
554
            'enabled' => false,
555
            'require_login' => true,
556
            'require_moderation' => true
557
        ));
558
        $obj = new CommentableItem();
559
        $this->assertFalse((bool)$obj->getCommentsOption('enabled'), 'Default setting is disabled');
560
        $this->assertFalse((bool)$obj->ProvideComments);
561
        $this->assertEquals('Required', $obj->ModerationRequired);
562
        $this->assertTrue((bool)$obj->CommentsRequireLogin);
563
564
        $obj = new CommentableItemEnabled();
565
        $this->assertTrue((bool)$obj->ProvideComments);
566
        $this->assertEquals('Required', $obj->ModerationRequired);
567
        $this->assertTrue((bool)$obj->CommentsRequireLogin);
568
569
        $obj = new CommentableItemDisabled();
570
        $this->assertFalse((bool)$obj->ProvideComments);
571
        $this->assertEquals('None', $obj->ModerationRequired);
572
        $this->assertFalse((bool)$obj->CommentsRequireLogin);
573
    }
574
575
    /*
576
    When a parent comment is deleted, remove the children
577
     */
578
    public function testOnBeforeDelete()
579
    {
580
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
581
582
        $child = new Comment();
583
        $child->Name = 'Fred Bloggs';
584
        $child->Comment = 'Child of firstComA';
585
        $child->write();
586
        $comment->ChildComments()->add($child);
587
        $this->assertEquals(4, $comment->ChildComments()->count());
588
589
        $commentID = $comment->ID;
590
        $childCommentID = $child->ID;
591
592
        $comment->delete();
593
594
        // assert that the new child been deleted
595
        $this->assertFalse(DataObject::get_by_id(Comment::class, $commentID));
596
        $this->assertFalse(DataObject::get_by_id(Comment::class, $childCommentID));
597
    }
598
599
    public function testRequireDefaultRecords()
600
    {
601
        $this->markTestSkipped('TODO');
602
    }
603
604
    public function testLink()
605
    {
606
        $comment = $this->objFromFixture(Comment::class, 'thirdComD');
607
        $this->assertEquals(
608
            'CommentableItemController#comment-' . $comment->ID,
609
            $comment->Link()
610
        );
611
        $this->assertEquals($comment->ID, $comment->ID);
612
613
        // An orphan comment has no link
614
        $comment->ParentID = 0;
615
        $comment->ParentClass = null;
616
        $comment->write();
617
        $this->assertEquals('', $comment->Link());
618
    }
619
620
    public function testPermalink()
621
    {
622
        $comment = $this->objFromFixture(Comment::class, 'thirdComD');
623
        $this->assertEquals('comment-' . $comment->ID, $comment->Permalink());
624
    }
625
626
    /**
627
     * Test field labels in 2 languages
628
     */
629
    public function testFieldLabels()
630
    {
631
        $locale = i18n::get_locale();
632
        i18n::set_locale('fr');
633
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
634
        $labels = $comment->FieldLabels();
635
        $expected = array(
636
            'Name' => 'Nom de l\'Auteur',
637
            'Comment' => 'Commentaire',
638
            'Email' => 'Email',
639
            'URL' => 'URL',
640
            'Moderated' => 'Modéré?',
641
            'IsSpam' => 'Spam?',
642
            'AllowHtml' => 'Allow Html',
643
            'SecretToken' => 'Secret Token',
644
            'Depth' => 'Depth',
645
            'Author' => 'Author Member',
646
            'ParentComment' => 'Parent Comment',
647
            'ChildComments' => 'Child Comments',
648
            'ParentTitle' => 'Parent',
649
            'Created' => 'Date de publication',
650
            'Parent' => 'Parent'
651
        );
652
        i18n::set_locale($locale);
653
        $this->assertEquals($expected, $labels);
654
        $labels = $comment->FieldLabels();
655
        $expected = array(
656
            'Name' => 'Author Name',
657
            'Comment' => 'Comment',
658
            'Email' => 'Email',
659
            'URL' => 'URL',
660
            'Moderated' => 'Moderated?',
661
            'IsSpam' => 'Spam?',
662
            'AllowHtml' => 'Allow Html',
663
            'SecretToken' => 'Secret Token',
664
            'Depth' => 'Depth',
665
            'Author' => 'Author Member',
666
            'ParentComment' => 'Parent Comment',
667
            'ChildComments' => 'Child Comments',
668
            'ParentTitle' => 'Parent',
669
            'Created' => 'Date posted',
670
            'Parent' => 'Parent'
671
        );
672
        $this->assertEquals($expected, $labels);
673
    }
674
675
    public function testGetOption()
676
    {
677
        $this->markTestSkipped('TODO');
678
    }
679
680
    public function testGetParent()
681
    {
682
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
683
        $item = $this->objFromFixture(CommentableItem::class, 'first');
684
        $parent = $comment->Parent();
685
        $this->assertSame($item->getClassName(), $parent->getClassName());
686
        $this->assertSame($item->ID, $parent->ID);
687
    }
688
689 View Code Duplication
    public function testGetParentTitle()
690
    {
691
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
692
        $title = $comment->getParentTitle();
693
        $this->assertEquals('First', $title);
694
695
        // Title from a comment with no parent is blank
696
        $comment->ParentID = 0;
697
        $comment->ParentClass = null;
698
        $comment->write();
699
        $this->assertEquals('', $comment->getParentTitle());
700
    }
701
702
    public function testGetParentClassName()
703
    {
704
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
705
        $className = $comment->getParentClassName();
706
        $this->assertEquals(CommentableItem::class, $className);
707
    }
708
709
    public function testCastingHelper()
710
    {
711
        $this->markTestSkipped('TODO');
712
    }
713
714
    public function testGetEscapedComment()
715
    {
716
        $this->markTestSkipped('TODO');
717
    }
718
719
    public function testIsPreview()
720
    {
721
        $comment = new Comment();
722
        $comment->Name = 'Fred Bloggs';
723
        $comment->Comment = 'this is a test comment';
724
        $this->assertTrue($comment->isPreview());
725
        $comment->write();
726
        $this->assertFalse($comment->isPreview());
727
    }
728
729
    public function testCanCreate()
730
    {
731
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
732
733
        // admin can create - this is always false
734
        $this->logInAs('commentadmin');
735
        $this->assertFalse($comment->canCreate());
736
737
        // visitor can view
738
        $this->logInAs('visitor');
739
        $this->assertFalse($comment->canCreate());
740
    }
741
742 View Code Duplication
    public function testCanView()
743
    {
744
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
745
746
        // admin can view
747
        $this->logInAs('commentadmin');
748
        $this->assertTrue($comment->canView());
749
750
        // visitor can view
751
        $this->logInAs('visitor');
752
        $this->assertTrue($comment->canView());
753
754
        $comment->ParentID = 0;
755
        $comment->write();
756
        $this->assertFalse($comment->canView());
757
    }
758
759 View Code Duplication
    public function testCanEdit()
760
    {
761
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
762
763
        // admin can edit
764
        $this->logInAs('commentadmin');
765
        $this->assertTrue($comment->canEdit());
766
767
        // visitor cannot
768
        $this->logInAs('visitor');
769
        $this->assertFalse($comment->canEdit());
770
771
        $comment->ParentID = 0;
772
        $comment->write();
773
        $this->assertFalse($comment->canEdit());
774
    }
775
776 View Code Duplication
    public function testCanDelete()
777
    {
778
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
779
780
        // admin can delete
781
        $this->logInAs('commentadmin');
782
        $this->assertTrue($comment->canDelete());
783
784
        // visitor cannot
785
        $this->logInAs('visitor');
786
        $this->assertFalse($comment->canDelete());
787
788
        $comment->ParentID = 0;
789
        $comment->write();
790
        $this->assertFalse($comment->canDelete());
791
    }
792
793
    public function testGetMember()
794
    {
795
        $this->logInAs('visitor');
796
        $current = Member::currentUser();
797
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
798
        $method = $this->getMethod('getMember');
799
800
        // null case
801
        $member = $method->invokeArgs($comment, array());
802
        $this->assertEquals($current, $member);
803
804
        // numeric ID case
805
        $member = $method->invokeArgs($comment, array($current->ID));
806
        $this->assertEquals($current, $member);
807
808
        // identity case
809
        $member = $method->invokeArgs($comment, array($current));
810
        $this->assertEquals($current, $member);
811
    }
812
813
    public function testGetAuthorName()
814
    {
815
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
816
        $this->assertEquals(
817
            'FA',
818
            $comment->getAuthorName()
819
        );
820
821
        $comment->Name = '';
822
        $this->assertEquals(
823
            '',
824
            $comment->getAuthorName()
825
        );
826
827
        $author = $this->objFromFixture(Member::class, 'visitor');
828
        $comment->AuthorID = $author->ID;
829
        $comment->write();
830
        $this->assertEquals(
831
            'visitor',
832
            $comment->getAuthorName()
833
        );
834
835
        // null the names, expect null back
836
        $comment->Name = null;
837
        $comment->AuthorID = 0;
838
        $this->assertNull($comment->getAuthorName());
839
    }
840
841
842
    public function testLinks()
843
    {
844
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
845
        $this->logInAs('commentadmin');
846
847
        $method = $this->getMethod('ActionLink');
848
849
        // test with starts of strings and tokens and salts change each time
850
        $this->assertStringStartsWith(
851
            '/comments/theaction/' . $comment->ID,
852
            $method->invokeArgs($comment, array('theaction'))
853
        );
854
855
        $this->assertStringStartsWith(
856
            '/comments/delete/' . $comment->ID,
857
            $comment->DeleteLink()
858
        );
859
860
        $this->assertStringStartsWith(
861
            '/comments/spam/' . $comment->ID,
862
            $comment->SpamLink()
863
        );
864
865
        $comment->markSpam();
866
        $this->assertStringStartsWith(
867
            '/comments/ham/' . $comment->ID,
868
            $comment->HamLink()
869
        );
870
871
        //markApproved
872
        $comment->markUnapproved();
873
        $this->assertStringStartsWith(
874
            '/comments/approve/' . $comment->ID,
875
            $comment->ApproveLink()
876
        );
877
    }
878
879
    public function testMarkSpam()
880
    {
881
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
882
        $comment->markSpam();
883
        $this->assertTrue($comment->Moderated);
884
        $this->assertTrue($comment->IsSpam);
885
    }
886
887
    public function testMarkApproved()
888
    {
889
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
890
        $comment->markApproved();
891
        $this->assertTrue($comment->Moderated);
892
        $this->assertFalse($comment->IsSpam);
893
    }
894
895
    public function testMarkUnapproved()
896
    {
897
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
898
        $comment->markApproved();
899
        $this->assertTrue($comment->Moderated);
900
    }
901
902 View Code Duplication
    public function testSpamClass()
903
    {
904
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
905
        $this->assertEquals('notspam', $comment->spamClass());
906
        $comment->Moderated = false;
907
        $this->assertEquals('unmoderated', $comment->spamClass());
908
        $comment->IsSpam = true;
909
        $this->assertEquals('spam', $comment->spamClass());
910
    }
911
912
    public function testGetTitle()
913
    {
914
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
915
        $this->assertEquals(
916
            'Comment by FA on First',
917
            $comment->getTitle()
918
        );
919
    }
920
921
    public function testGetCMSFields()
922
    {
923
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
924
        $fields = $comment->getCMSFields();
925
        $names = array();
926
        foreach ($fields as $field) {
927
            $names[] = $field->getName();
928
        }
929
        $expected = array(
930
            'Created',
931
            'Name',
932
            'Comment',
933
            'Email',
934
            'URL',
935
            'Options'
936
        );
937
        $this->assertEquals($expected, $names);
938
    }
939
940
    public function testGetCMSFieldsCommentHasAuthor()
941
    {
942
        $member = Member::get()->filter('FirstName', 'visitor')->first();
943
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
944
        $comment->AuthorID = $member->ID;
945
        $comment->write();
946
947
        $fields = $comment->getCMSFields();
948
        $names = array();
949
        foreach ($fields as $field) {
950
            $names[] = $field->getName();
951
        }
952
        $expected = array(
953
            'Created',
954
            'Name',
955
            'AuthorMember',
956
            'Comment',
957
            'Email',
958
            'URL',
959
            'Options'
960
        );
961
        $this->assertEquals($expected, $names);
962
    }
963
964
    public function testGetCMSFieldsWithParentComment()
965
    {
966
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
967
968
        $child = new Comment();
969
        $child->Name = 'John Smith';
970
        $child->Comment = 'This is yet another test commnent';
971
        $child->ParentCommentID = $comment->ID;
972
        $child->write();
973
974
        $fields = $child->getCMSFields();
975
        $names = array();
976
        foreach ($fields as $field) {
977
            $names[] = $field->getName();
978
        }
979
        $expected = array(
980
            'Created',
981
            'Name',
982
            'Comment',
983
            'Email',
984
            'URL',
985
            'Options',
986
            'ParentComment_Title',
987
            'ParentComment_Created',
988
            'ParentComment_AuthorName',
989
            'ParentComment_EscapedComment'
990
        );
991
        $this->assertEquals($expected, $names);
992
    }
993
994
    public function testPurifyHtml()
995
    {
996
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
997
998
        $dirtyHTML = '<p><script>alert("w00t")</script>my comment</p>';
999
        $this->assertEquals(
1000
            'my comment',
1001
            $comment->purifyHtml($dirtyHTML)
1002
        );
1003
    }
1004
1005
    public function testGravatar()
1006
    {
1007
        // Turn gravatars on
1008
        Config::inst()->update(CommentableItem::class, 'comments', array(
1009
            'use_gravatar' => true
1010
        ));
1011
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
1012
1013
        $this->assertEquals(
1014
            'http://www.gravatar.com/avatar/d41d8cd98f00b204e9800998ecf8427e?s'
1015
            . '=80&d=identicon&r=g',
1016
            $comment->gravatar()
1017
        );
1018
1019
        // Turn gravatars off
1020
        Config::inst()->update(CommentableItem::class, 'comments', array(
1021
            'use_gravatar' => false
1022
        ));
1023
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
1024
1025
        $this->assertEquals(
1026
            '',
1027
            $comment->gravatar()
1028
        );
1029
    }
1030
1031
    public function testGetRepliesEnabled()
1032
    {
1033
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
1034
        Config::inst()->update(CommentableItem::class, 'comments', array(
1035
            'nested_comments' => false
1036
        ));
1037
        $this->assertFalse($comment->getRepliesEnabled());
1038
1039
        Config::inst()->update(CommentableItem::class, 'comments', array(
1040
            'nested_comments' => true,
1041
            'nested_depth' => 4
1042
        ));
1043
        $this->assertTrue($comment->getRepliesEnabled());
1044
1045
        $comment->Depth = 4;
1046
        $this->assertFalse($comment->getRepliesEnabled());
1047
1048
1049
        // 0 indicates no limit for nested_depth
1050
        Config::inst()->update(CommentableItem::class, 'comments', array(
1051
            'nested_comments' => true,
1052
            'nested_depth' => 0
1053
        ));
1054
1055
        $comment->Depth = 234;
1056
        $this->assertTrue($comment->getRepliesEnabled());
1057
        $comment->markUnapproved();
1058
        $this->assertFalse($comment->getRepliesEnabled());
1059
        $comment->markSpam();
1060
        $this->assertFalse($comment->getRepliesEnabled());
1061
1062
        $comment->markApproved();
1063
        $this->assertTrue($comment->getRepliesEnabled());
1064
    }
1065
1066
    public function testAllReplies()
1067
    {
1068
        Config::inst()->update(CommentableItem::class, 'comments', array(
1069
            'nested_comments' => true,
1070
            'nested_depth' => 4
1071
        ));
1072
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
1073
        $this->assertEquals(
1074
            3,
1075
            $comment->allReplies()->count()
1076
        );
1077
        $child = new Comment();
1078
        $child->Name = 'Fred Smith';
1079
        $child->Comment = 'This is a child comment';
1080
        $child->ParentCommentID = $comment->ID;
1081
1082
        // spam should be returned by this method
1083
        $child->markSpam();
1084
        $child->write();
1085
        $replies = $comment->allReplies();
1086
        $this->assertEquals(
1087
            4,
1088
            $comment->allReplies()->count()
1089
        );
1090
1091
        Config::inst()->update(CommentableItem::class, 'comments', array(
1092
            'nested_comments' => false
1093
        ));
1094
1095
        $this->assertEquals(0, $comment->allReplies()->count());
1096
    }
1097
1098
    public function testReplies()
1099
    {
1100
        CommentableItem::add_extension(CommentsExtension::class);
1101
        $this->logInWithPermission('ADMIN');
1102
        Config::inst()->update(CommentableItem::class, 'comments', array(
1103
            'nested_comments' => true,
1104
            'nested_depth' => 4
1105
        ));
1106
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
1107
        $this->assertEquals(
1108
            3,
1109
            $comment->Replies()->count()
1110
        );
1111
1112
        // Test that spam comments are not returned
1113
        $childComment = $comment->Replies()->first();
1114
        $childComment->IsSpam = 1;
1115
        $childComment->write();
1116
        $this->assertEquals(
1117
            2,
1118
            $comment->Replies()->count()
1119
        );
1120
1121
        // Test that unmoderated comments are not returned
1122
        //
1123
        $childComment = $comment->Replies()->first();
1124
1125
        // FIXME - moderation settings scenarios need checked here
1126
        $childComment->Moderated = 0;
1127
        $childComment->IsSpam = 0;
1128
        $childComment->write();
1129
        $this->assertEquals(
1130
            2,
1131
            $comment->Replies()->count()
1132
        );
1133
1134
1135
        // Test moderation required on the front end
1136
        $item = $this->objFromFixture(CommentableItem::class, 'first');
1137
        $item->ModerationRequired = 'Required';
1138
        $item->write();
1139
1140
        Config::inst()->update(CommentableItemDisabled::class, 'comments', array(
1141
            'nested_comments' => true,
1142
            'nested_depth' => 4,
1143
            'frontend_moderation' => true
1144
        ));
1145
1146
        $comment = DataObject::get_by_id(Comment::class, $comment->ID);
1147
1148
        $this->assertEquals(
1149
            2,
1150
            $comment->Replies()->count()
1151
        );
1152
1153
        // Turn off nesting, empty array should be returned
1154
        Config::inst()->update(CommentableItem::class, 'comments', array(
1155
            'nested_comments' => false
1156
        ));
1157
1158
        $this->assertEquals(
1159
            0,
1160
            $comment->Replies()->count()
1161
        );
1162
1163
        CommentableItem::remove_extension(CommentsExtension::class);
1164
    }
1165
1166
    public function testPagedReplies()
1167
    {
1168
        Config::inst()->update(CommentableItem::class, 'comments', array(
1169
            'nested_comments' => true,
1170
            'nested_depth' => 4,
1171
            'comments_per_page' => 2 #Force 2nd page for 3 items
1172
        ));
1173
1174
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
1175
        $pagedList = $comment->pagedReplies();
1176
        $this->assertEquals(
1177
            2,
1178
            $pagedList->TotalPages()
1179
        );
1180
        $this->assertEquals(
1181
            3,
1182
            $pagedList->getTotalItems()
1183
        );
1184
        //TODO - 2nd page requires controller
1185
        //
1186
        Config::inst()->update(CommentableItem::class, 'comments', array(
1187
            'nested_comments' => false
1188
        ));
1189
1190
        $this->assertEquals(0, $comment->PagedReplies()->count());
1191
    }
1192
1193
    public function testReplyForm()
1194
    {
1195
        Config::inst()->update(CommentableItem::class, 'comments', array(
1196
            'nested_comments' => false,
1197
            'nested_depth' => 4
1198
        ));
1199
1200
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
1201
1202
        // No nesting, no reply form
1203
        $form = $comment->replyForm();
1204
        $this->assertNull($form);
1205
1206
        // parent item so show form
1207
        Config::inst()->update(CommentableItem::class, 'comments', array(
1208
            'nested_comments' => true,
1209
            'nested_depth' => 4
1210
        ));
1211
        $form = $comment->replyForm();
1212
1213
        $names = array();
1214
        foreach ($form->Fields() as $field) {
1215
            array_push($names, $field->getName());
1216
        }
1217
1218
        $this->assertEquals(
1219
            array(
1220
                'NameEmailURLComment', // The CompositeField name?
1221
                'ParentID',
1222
                'ParentClassName',
1223
                'ReturnURL',
1224
                'ParentCommentID'
1225
            ),
1226
            $names
1227
        );
1228
1229
        // no parent, no reply form
1230
1231
        $comment->ParentID = 0;
1232
        $comment->ParentClass = null;
1233
        $comment->write();
1234
        $form = $comment->replyForm();
1235
        $this->assertNull($form);
1236
    }
1237
1238
    public function testUpdateDepth()
1239
    {
1240
        Config::inst()->update(CommentableItem::class, 'comments', array(
1241
            'nested_comments' => true,
1242
            'nested_depth' => 4
1243
        ));
1244
1245
        $comment = $this->objFromFixture(Comment::class, 'firstComA');
1246
        $children = $comment->allReplies()->toArray();
1247
        // Make the second child a child of the first
1248
        // Make the third child a child of the second
1249
        $reply1 = $children[0];
1250
        $reply2 = $children[1];
1251
        $reply3 = $children[2];
1252
        $reply2->ParentCommentID = $reply1->ID;
1253
        $reply2->write();
1254
        $this->assertEquals(3, $reply2->Depth);
1255
        $reply3->ParentCommentID = $reply2->ID;
1256
        $reply3->write();
1257
        $this->assertEquals(4, $reply3->Depth);
1258
    }
1259
1260
    public function testGetToken()
1261
    {
1262
        $this->markTestSkipped('TODO');
1263
    }
1264
1265
    public function testMemberSalt()
1266
    {
1267
        $this->markTestSkipped('TODO');
1268
    }
1269
1270
    public function testAddToUrl()
1271
    {
1272
        $this->markTestSkipped('TODO');
1273
    }
1274
1275
    public function testCheckRequest()
1276
    {
1277
        $this->markTestSkipped('TODO');
1278
    }
1279
1280
    public function testGenerate()
1281
    {
1282
        $this->markTestSkipped('TODO');
1283
    }
1284
1285
    protected static function getMethod($name)
1286
    {
1287
        $class = new ReflectionClass(Comment::class);
1288
        $method = $class->getMethod($name);
1289
        $method->setAccessible(true);
1290
        return $method;
1291
    }
1292
}
1293