Passed
Push — master ( 844821...5dc482 )
by Daniel
05:38
created

SiteTreeTest::testAbsoluteLiveLink()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 11
nc 1
nop 0
dl 0
loc 16
rs 9.9
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\CMS\Tests\Model;
4
5
use LogicException;
6
use Page;
0 ignored issues
show
Bug introduced by
The type Page was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
7
use ReflectionMethod;
8
use SilverStripe\CMS\Model\RedirectorPage;
9
use SilverStripe\CMS\Model\SiteTree;
10
use SilverStripe\CMS\Model\VirtualPage;
11
use SilverStripe\Control\ContentNegotiator;
12
use SilverStripe\Control\Controller;
13
use SilverStripe\Control\Director;
14
use SilverStripe\Core\Config\Config;
15
use SilverStripe\Core\Injector\Injector;
16
use SilverStripe\Dev\SapphireTest;
17
use SilverStripe\i18n\i18n;
18
use SilverStripe\ORM\DB;
19
use SilverStripe\ORM\DataObject;
20
use SilverStripe\ORM\ValidationException;
21
use SilverStripe\Security\Group;
22
use SilverStripe\Security\InheritedPermissions;
23
use SilverStripe\Security\Member;
24
use SilverStripe\Security\Permission;
25
use SilverStripe\Security\Security;
26
use SilverStripe\SiteConfig\SiteConfig;
27
use SilverStripe\Subsites\Extensions\SiteTreeSubsites;
0 ignored issues
show
Bug introduced by
The type SilverStripe\Subsites\Extensions\SiteTreeSubsites was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
28
use SilverStripe\Versioned\Versioned;
29
use SilverStripe\View\Parsers\Diff;
30
use SilverStripe\View\Parsers\ShortcodeParser;
31
use SilverStripe\View\Parsers\URLSegmentFilter;
32
use TractorCow\Fluent\Extension\FluentSiteTreeExtension;
0 ignored issues
show
Bug introduced by
The type TractorCow\Fluent\Extens...FluentSiteTreeExtension was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
33
34
class SiteTreeTest extends SapphireTest
35
{
36
    protected static $fixture_file = 'SiteTreeTest.yml';
37
38
    protected static $illegal_extensions = [
39
        SiteTree::class => [
40
            SiteTreeSubsites::class,
41
            FluentSiteTreeExtension::class,
42
        ],
43
    ];
44
45
    protected static $extra_dataobjects = [
46
        SiteTreeTest_ClassA::class,
47
        SiteTreeTest_ClassB::class,
48
        SiteTreeTest_ClassC::class,
49
        SiteTreeTest_ClassD::class,
50
        SiteTreeTest_ClassCext::class,
51
        SiteTreeTest_NotRoot::class,
52
        SiteTreeTest_StageStatusInherit::class,
53
        SiteTreeTest_DataObject::class,
54
    ];
55
56
    public function reservedSegmentsProvider()
57
    {
58
        return [
59
            // segments reserved by rules
60
            ['Admin', 'admin-2'],
61
            ['Dev', 'dev-2'],
62
            ['Robots in disguise', 'robots-in-disguise'],
63
            // segments reserved by folder name
64
            ['resources', 'resources-2'],
65
            ['assets', 'assets-2'],
66
            ['notafoldername', 'notafoldername'],
67
        ];
68
    }
69
70
    public function testCreateDefaultpages()
71
    {
72
        $remove = SiteTree::get();
73
        if ($remove) {
0 ignored issues
show
introduced by
$remove is of type SilverStripe\ORM\DataList, thus it always evaluated to true.
Loading history...
74
            foreach ($remove as $page) {
75
                $page->delete();
76
            }
77
        }
78
        // Make sure the table is empty
79
        $this->assertEquals(DB::query('SELECT COUNT("ID") FROM "SiteTree"')->value(), 0);
80
81
        // Disable the creation
82
        SiteTree::config()->create_default_pages = false;
83
        singleton(SiteTree::class)->requireDefaultRecords();
84
85
        // The table should still be empty
86
        $this->assertEquals(DB::query('SELECT COUNT("ID") FROM "SiteTree"')->value(), 0);
87
88
        // Enable the creation
89
        SiteTree::config()->create_default_pages = true;
90
        singleton(SiteTree::class)->requireDefaultRecords();
91
92
        // The table should now have three rows (home, about-us, contact-us)
93
        $this->assertEquals(DB::query('SELECT COUNT("ID") FROM "SiteTree"')->value(), 3);
94
    }
95
96
    /**
97
     * Test generation of the URLSegment values.
98
     *  - Turns things into lowercase-hyphen-format
99
     *  - Generates from Title by default, unless URLSegment is explicitly set
100
     *  - Resolves duplicates by appending a number
101
     *  - renames classes with a class name conflict
102
     */
103
    public function testURLGeneration()
104
    {
105
        $expectedURLs = [
106
            'home' => 'home',
107
            'staff' => 'my-staff',
108
            'about' => 'about-us',
109
            'staffduplicate' => 'my-staff-2',
110
            'product1' => '1-1-test-product',
111
            'product2' => 'another-product',
112
            'product3' => 'another-product-2',
113
            'product4' => 'another-product-3',
114
            'object'   => 'object',
115
            'controller' => 'controller',
116
            'numericonly' => '1930',
117
        ];
118
119
        foreach ($expectedURLs as $fixture => $urlSegment) {
120
            $obj = $this->objFromFixture(SiteTree::class, $fixture);
121
            $this->assertEquals($urlSegment, $obj->URLSegment);
122
        }
123
    }
124
125
    /**
126
     * Check if reserved URL's are properly appended with a number at top level
127
     * @dataProvider reservedSegmentsProvider
128
     */
129
    public function testDisallowedURLGeneration($title, $urlSegment)
130
    {
131
        $page = SiteTree::create(['Title' => $title]);
132
        $id = $page->write();
133
        $page = SiteTree::get()->byID($id);
134
        $this->assertEquals($urlSegment, $page->URLSegment);
135
    }
136
137
    /**
138
     * Check if reserved URL's are not appended with a number on a child page
139
     * It's okay to have a URL like domain.com/my-page/admin as it won't interfere with domain.com/admin
140
     * @dataProvider reservedSegmentsProvider
141
     */
142
    public function testDisallowedChildURLGeneration($title, $urlSegment)
143
    {
144
        Config::modify()->set(SiteTree::class, 'nested_urls', true);
145
        // Using the same dataprovider, strip out the -2 from the admin and dev segment
146
        $urlSegment = str_replace('-2', '', $urlSegment);
147
        $page = SiteTree::create(['Title' => $title, 'ParentID' => 1]);
148
        $id = $page->write();
149
        $page = SiteTree::get()->byID($id);
150
        $this->assertEquals($urlSegment, $page->URLSegment);
151
    }
152
153
    /**
154
     * Test that publication copies data to SiteTree_Live
155
     */
156
    public function testPublishCopiesToLiveTable()
157
    {
158
        $obj = $this->objFromFixture(SiteTree::class, 'about');
159
        $obj->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
160
161
        $createdID = DB::query(
162
            "SELECT \"ID\" FROM \"SiteTree_Live\" WHERE \"URLSegment\" = '$obj->URLSegment'"
163
        )->value();
164
        $this->assertEquals($obj->ID, $createdID);
165
    }
166
167
    /**
168
     * Test that field which are set and then cleared are also transferred to the published site.
169
     */
170
    public function testPublishDeletedFields()
171
    {
172
        $this->logInWithPermission('ADMIN');
173
174
        $obj = $this->objFromFixture(SiteTree::class, 'about');
175
        $obj->Title = "asdfasdf";
176
        $obj->write();
177
        $this->assertTrue($obj->publishRecursive());
178
179
        $this->assertEquals(
180
            'asdfasdf',
181
            DB::query("SELECT \"Title\" FROM \"SiteTree_Live\" WHERE \"ID\" = '$obj->ID'")->value()
182
        );
183
184
        $obj->Title = null;
185
        $obj->write();
186
        $this->assertTrue($obj->publishRecursive());
187
188
        $this->assertNull(DB::query("SELECT \"Title\" FROM \"SiteTree_Live\" WHERE \"ID\" = '$obj->ID'")->value());
189
    }
190
191
    public function testParentNodeCachedInMemory()
192
    {
193
        $parent = SiteTree::create();
194
        $parent->Title = 'Section Title';
195
        $child = SiteTree::create();
196
        $child->Title = 'Page Title';
197
        $child->setParent($parent);
198
199
        $this->assertInstanceOf(SiteTree::class, $child->Parent);
200
        $this->assertEquals("Section Title", $child->Parent->Title);
201
    }
202
203
    public function testParentModelReturnType()
204
    {
205
        $parent = new SiteTreeTest_PageNode();
206
        $child = new SiteTreeTest_PageNode();
207
208
        $child->setParent($parent);
209
        $this->assertInstanceOf(SiteTreeTest_PageNode::class, $child->Parent);
0 ignored issues
show
Bug Best Practice introduced by
The property Parent does not exist on SilverStripe\CMS\Tests\Model\SiteTreeTest_PageNode. Since you implemented __get, consider adding a @property annotation.
Loading history...
210
    }
211
212
    /**
213
     * Confirm that DataObject::get_one() gets records from SiteTree_Live
214
     */
215
    public function testGetOneFromLive()
216
    {
217
        $s = SiteTree::create();
218
        $s->Title = "V1";
219
        $s->URLSegment = "get-one-test-page";
220
        $s->write();
221
        $s->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
222
        $s->Title = "V2";
223
        $s->write();
224
225
        $oldMode = Versioned::get_reading_mode();
226
        Versioned::set_stage(Versioned::LIVE);
227
228
        $checkSiteTree = DataObject::get_one(SiteTree::class, [
229
            '"SiteTree"."URLSegment"' => 'get-one-test-page',
230
        ]);
231
        $this->assertEquals("V1", $checkSiteTree->Title);
232
233
        Versioned::set_reading_mode($oldMode);
234
    }
235
236
    public function testChidrenOfRootAreTopLevelPages()
237
    {
238
        $pages = SiteTree::get();
239
        foreach ($pages as $page) {
240
            $page->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
241
        }
242
        unset($pages);
243
244
        /* If we create a new SiteTree object with ID = 0 */
245
        $obj = SiteTree::create();
246
        /* Then its children should be the top-level pages */
247
        $stageChildren = $obj->stageChildren()->map('ID', 'Title');
248
        $liveChildren = $obj->liveChildren()->map('ID', 'Title');
249
        $allChildren = $obj->AllChildrenIncludingDeleted()->map('ID', 'Title');
250
251
        $this->assertContains('Home', $stageChildren);
252
        $this->assertContains('Products', $stageChildren);
253
        $this->assertNotContains('Staff', $stageChildren);
254
255
        $this->assertContains('Home', $liveChildren);
256
        $this->assertContains('Products', $liveChildren);
257
        $this->assertNotContains('Staff', $liveChildren);
258
259
        $this->assertContains('Home', $allChildren);
260
        $this->assertContains('Products', $allChildren);
261
        $this->assertNotContains('Staff', $allChildren);
262
    }
263
264
    public function testCanSaveBlankToHasOneRelations()
265
    {
266
        /* DataObject::write() should save to a has_one relationship if you set a field called (relname)ID */
267
        $page = SiteTree::create();
268
        $parentID = $this->idFromFixture(SiteTree::class, 'home');
269
        $page->ParentID = $parentID;
270
        $page->write();
271
        $this->assertEquals(
272
            $parentID,
273
            DB::query("SELECT \"ParentID\" FROM \"SiteTree\" WHERE \"ID\" = $page->ID")->value()
274
        );
275
276
        /* You should then be able to save a null/0/'' value to the relation */
277
        $page->ParentID = null;
278
        $page->write();
279
        $this->assertEquals(0, DB::query("SELECT \"ParentID\" FROM \"SiteTree\" WHERE \"ID\" = $page->ID")->value());
280
    }
281
282
    public function testStageStates()
283
    {
284
        // newly created page
285
        $createdPage = SiteTree::create();
286
        $createdPage->write();
287
        $this->assertTrue($createdPage->isOnDraft());
288
        $this->assertFalse($createdPage->isPublished());
289
        $this->assertTrue($createdPage->isOnDraftOnly());
290
        $this->assertTrue($createdPage->isModifiedOnDraft());
291
292
        // published page
293
        $publishedPage = SiteTree::create();
294
        $publishedPage->write();
295
        $publishedPage->copyVersionToStage('Stage', 'Live');
296
        $this->assertTrue($publishedPage->isOnDraft());
297
        $this->assertTrue($publishedPage->isPublished());
298
        $this->assertFalse($publishedPage->isOnDraftOnly());
299
        $this->assertFalse($publishedPage->isOnLiveOnly());
300
        $this->assertFalse($publishedPage->isModifiedOnDraft());
301
302
        // published page, deleted from stage
303
        $deletedFromDraftPage = SiteTree::create();
304
        $deletedFromDraftPage->write();
305
        $deletedFromDraftPage->copyVersionToStage('Stage', 'Live');
306
        $deletedFromDraftPage->deleteFromStage('Stage');
307
        $this->assertFalse($deletedFromDraftPage->isArchived());
308
        $this->assertFalse($deletedFromDraftPage->isOnDraft());
309
        $this->assertTrue($deletedFromDraftPage->isPublished());
310
        $this->assertFalse($deletedFromDraftPage->isOnDraftOnly());
311
        $this->assertTrue($deletedFromDraftPage->isOnLiveOnly());
312
        $this->assertFalse($deletedFromDraftPage->isModifiedOnDraft());
313
314
        // published page, deleted from live
315
        $deletedFromLivePage = SiteTree::create();
316
        $deletedFromLivePage->write();
317
        $deletedFromLivePage->copyVersionToStage('Stage', 'Live');
318
        $deletedFromLivePage->deleteFromStage('Live');
319
        $this->assertFalse($deletedFromLivePage->isArchived());
320
        $this->assertTrue($deletedFromLivePage->isOnDraft());
321
        $this->assertFalse($deletedFromLivePage->isPublished());
322
        $this->assertTrue($deletedFromLivePage->isOnDraftOnly());
323
        $this->assertFalse($deletedFromLivePage->isOnLiveOnly());
324
        $this->assertTrue($deletedFromLivePage->isModifiedOnDraft());
325
326
        // published page, deleted from both stages
327
        $deletedFromAllStagesPage = SiteTree::create();
328
        $deletedFromAllStagesPage->write();
329
        $deletedFromAllStagesPage->copyVersionToStage('Stage', 'Live');
330
        $deletedFromAllStagesPage->deleteFromStage('Stage');
331
        $deletedFromAllStagesPage->deleteFromStage('Live');
332
        $this->assertTrue($deletedFromAllStagesPage->isArchived());
333
        $this->assertFalse($deletedFromAllStagesPage->isOnDraft());
334
        $this->assertFalse($deletedFromAllStagesPage->isPublished());
335
        $this->assertFalse($deletedFromAllStagesPage->isOnDraftOnly());
336
        $this->assertFalse($deletedFromAllStagesPage->isOnLiveOnly());
337
        $this->assertFalse($deletedFromAllStagesPage->isModifiedOnDraft());
338
339
        // published page, modified
340
        $modifiedOnDraftPage = SiteTree::create();
341
        $modifiedOnDraftPage->write();
342
        $modifiedOnDraftPage->copyVersionToStage('Stage', 'Live');
343
        $modifiedOnDraftPage->Content = 'modified';
344
        $modifiedOnDraftPage->write();
345
        $this->assertFalse($modifiedOnDraftPage->isArchived());
346
        $this->assertTrue($modifiedOnDraftPage->isOnDraft());
347
        $this->assertTrue($modifiedOnDraftPage->isPublished());
348
        $this->assertFalse($modifiedOnDraftPage->isOnDraftOnly());
349
        $this->assertFalse($modifiedOnDraftPage->isOnLiveOnly());
350
        $this->assertTrue($modifiedOnDraftPage->isModifiedOnDraft());
351
    }
352
353
    /**
354
     * Test that a page can be completely deleted and restored to the stage site
355
     */
356
    public function testRestoreToStage()
357
    {
358
        $page = $this->objFromFixture(SiteTree::class, 'about');
359
        $pageID = $page->ID;
360
        $page->delete();
361
        $this->assertTrue(!DataObject::get_by_id(SiteTree::class, $pageID));
362
363
        $deletedPage = Versioned::get_latest_version(SiteTree::class, $pageID);
364
        $resultPage = $deletedPage->doRestoreToStage();
365
366
        $requeriedPage = DataObject::get_by_id(SiteTree::class, $pageID);
367
368
        $this->assertEquals($pageID, $resultPage->ID);
369
        $this->assertEquals($pageID, $requeriedPage->ID);
370
        $this->assertEquals('About Us', $requeriedPage->Title);
371
        $this->assertInstanceOf(SiteTree::class, $requeriedPage);
372
373
374
        $page2 = $this->objFromFixture(SiteTree::class, 'products');
375
        $page2ID = $page2->ID;
376
        $page2->doUnpublish();
377
        $page2->delete();
378
379
        // Check that if we restore while on the live site that the content still gets pushed to
380
        // stage
381
        Versioned::set_stage(Versioned::LIVE);
382
        $deletedPage = Versioned::get_latest_version(SiteTree::class, $page2ID);
383
        $deletedPage->doRestoreToStage();
384
        $this->assertFalse(
385
            (bool)Versioned::get_one_by_stage(SiteTree::class, Versioned::LIVE, "\"SiteTree\".\"ID\" = " . $page2ID)
386
        );
387
388
        Versioned::set_stage(Versioned::DRAFT);
389
        $requeriedPage = DataObject::get_by_id(SiteTree::class, $page2ID);
390
        $this->assertEquals('Products', $requeriedPage->Title);
391
        $this->assertInstanceOf(SiteTree::class, $requeriedPage);
392
    }
393
394
    public function testNoCascadingDeleteWithoutID()
395
    {
396
        Config::inst()->update('SiteTree', 'enforce_strict_hierarchy', true);
0 ignored issues
show
Bug introduced by
The method update() does not exist on SilverStripe\Config\Coll...nfigCollectionInterface. It seems like you code against a sub-type of said class. However, the method does not exist in SilverStripe\Config\Coll...nfigCollectionInterface. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

396
        Config::inst()->/** @scrutinizer ignore-call */ update('SiteTree', 'enforce_strict_hierarchy', true);
Loading history...
397
        $count = SiteTree::get()->count();
398
        $this->assertNotEmpty($count);
399
        $obj = SiteTree::create();
400
        $this->assertFalse($obj->exists());
401
        $fail = true;
402
        try {
403
            $obj->delete();
404
        } catch (LogicException $e) {
405
            $fail = false;
406
        }
407
        if ($fail) {
408
            $this->fail('Failed to throw delete exception');
409
        }
410
        $this->assertCount($count, SiteTree::get());
411
    }
412
413
    public function testGetByLink()
414
    {
415
        $home     = $this->objFromFixture(SiteTree::class, 'home');
416
        $about    = $this->objFromFixture(SiteTree::class, 'about');
417
        $staff    = $this->objFromFixture(SiteTree::class, 'staff');
418
        $product  = $this->objFromFixture(SiteTree::class, 'product1');
419
420
        SiteTree::config()->nested_urls = false;
421
422
        $this->assertEquals($home->ID, SiteTree::get_by_link('/', false)->ID);
423
        $this->assertEquals($home->ID, SiteTree::get_by_link('/home/', false)->ID);
424
        $this->assertEquals($about->ID, SiteTree::get_by_link($about->Link(), false)->ID);
425
        $this->assertEquals($staff->ID, SiteTree::get_by_link($staff->Link(), false)->ID);
426
        $this->assertEquals($product->ID, SiteTree::get_by_link($product->Link(), false)->ID);
427
428
        Config::modify()->set(SiteTree::class, 'nested_urls', true);
429
430
        $this->assertEquals($home->ID, SiteTree::get_by_link('/', false)->ID);
431
        $this->assertEquals($home->ID, SiteTree::get_by_link('/home/', false)->ID);
432
        $this->assertEquals($about->ID, SiteTree::get_by_link($about->Link(), false)->ID);
433
        $this->assertEquals($staff->ID, SiteTree::get_by_link($staff->Link(), false)->ID);
434
        $this->assertEquals($product->ID, SiteTree::get_by_link($product->Link(), false)->ID);
435
436
        $this->assertEquals(
437
            $staff->ID,
438
            SiteTree::get_by_link('/my-staff/', false)->ID,
439
            'Assert a unique URLSegment can be used for b/c.'
440
        );
441
    }
442
443
    public function testRelativeLink()
444
    {
445
        $about    = $this->objFromFixture(SiteTree::class, 'about');
446
        $staff    = $this->objFromFixture(SiteTree::class, 'staff');
447
448
        Config::modify()->set(SiteTree::class, 'nested_urls', true);
449
450
        $this->assertEquals(
451
            'about-us/',
452
            $about->RelativeLink(),
453
            'Matches URLSegment on top level without parameters'
454
        );
455
        $this->assertEquals(
456
            'about-us/my-staff/',
457
            $staff->RelativeLink(),
458
            'Matches URLSegment plus parent on second level without parameters'
459
        );
460
        $this->assertEquals(
461
            'about-us/edit',
462
            $about->RelativeLink('edit'),
463
            'Matches URLSegment plus parameter on top level'
464
        );
465
        $this->assertEquals(
466
            'about-us/tom&jerry',
467
            $about->RelativeLink('tom&jerry'),
468
            'Doesnt url encode parameter'
469
        );
470
    }
471
472
    public function testPageLevel()
473
    {
474
        $about = $this->objFromFixture(SiteTree::class, 'about');
475
        $staff = $this->objFromFixture(SiteTree::class, 'staff');
476
        $this->assertEquals(1, $about->getPageLevel());
477
        $this->assertEquals(2, $staff->getPageLevel());
478
    }
479
480
    public function testAbsoluteLiveLink()
481
    {
482
        $parent = $this->objFromFixture(SiteTree::class, 'about');
483
        $child = $this->objFromFixture(SiteTree::class, 'staff');
484
485
        Config::modify()->set(SiteTree::class, 'nested_urls', true);
486
487
        $child->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
488
        $parent->URLSegment = 'changed-on-live';
489
        $parent->write();
490
        $parent->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
491
        $parent->URLSegment = 'changed-on-draft';
492
        $parent->write();
493
494
        $this->assertStringEndsWith('changed-on-live/my-staff/', $child->getAbsoluteLiveLink(false));
495
        $this->assertStringEndsWith('changed-on-live/my-staff/?stage=Live', $child->getAbsoluteLiveLink());
496
    }
497
498
    public function testDuplicateChildrenRetainSort()
499
    {
500
        $parent = SiteTree::create();
501
        $parent->Title = 'Parent';
502
        $parent->write();
503
504
        $child1 = SiteTree::create();
505
        $child1->ParentID = $parent->ID;
506
        $child1->Title = 'Child 1';
507
        $child1->Sort = 2;
508
        $child1->write();
509
510
        $child2 = SiteTree::create();
511
        $child2->ParentID = $parent->ID;
512
        $child2->Title = 'Child 2';
513
        $child2->Sort = 1;
514
        $child2->write();
515
516
        $duplicateParent = $parent->duplicateWithChildren();
517
        $duplicateChildren = $duplicateParent->AllChildren()->toArray();
518
        $this->assertCount(2, $duplicateChildren);
519
520
        $duplicateChild2 = array_shift($duplicateChildren);
521
        $duplicateChild1 = array_shift($duplicateChildren);
522
523
524
        $this->assertEquals('Child 1', $duplicateChild1->Title);
525
        $this->assertEquals('Child 2', $duplicateChild2->Title);
526
527
        // assertGreaterThan works by having the LOWER value first
528
        $this->assertGreaterThan($duplicateChild2->Sort, $duplicateChild1->Sort);
529
    }
530
531
    public function testDeleteFromStageOperatesRecursively()
532
    {
533
        Config::modify()->set(SiteTree::class, 'enforce_strict_hierarchy', false);
534
        $pageAbout = $this->objFromFixture(SiteTree::class, 'about');
535
        $pageStaff = $this->objFromFixture(SiteTree::class, 'staff');
536
        $pageStaffDuplicate = $this->objFromFixture(SiteTree::class, 'staffduplicate');
537
538
        $pageAbout->delete();
539
540
        $this->assertNull(DataObject::get_by_id(SiteTree::class, $pageAbout->ID));
541
        $this->assertTrue(DataObject::get_by_id(SiteTree::class, $pageStaff->ID) instanceof SiteTree);
542
        $this->assertTrue(DataObject::get_by_id(SiteTree::class, $pageStaffDuplicate->ID) instanceof SiteTree);
543
        Config::modify()->set(SiteTree::class, 'enforce_strict_hierarchy', true);
544
    }
545
546
    public function testDeleteFromStageOperatesRecursivelyStrict()
547
    {
548
        Config::modify()->set(SiteTree::class, 'enforce_strict_hierarchy', true);
549
        $pageAbout = $this->objFromFixture(SiteTree::class, 'about');
550
        $pageStaff = $this->objFromFixture(SiteTree::class, 'staff');
551
        $pageStaffDuplicate = $this->objFromFixture(SiteTree::class, 'staffduplicate');
552
553
        $pageAbout->delete();
554
555
        $this->assertNull(DataObject::get_by_id(SiteTree::class, $pageAbout->ID));
556
        $this->assertNull(DataObject::get_by_id(SiteTree::class, $pageStaff->ID));
557
        $this->assertNull(DataObject::get_by_id(SiteTree::class, $pageStaffDuplicate->ID));
558
    }
559
560
    public function testDuplicate()
561
    {
562
        $pageAbout = $this->objFromFixture(SiteTree::class, 'about');
563
        $dupe = $pageAbout->duplicate();
564
        $this->assertEquals($pageAbout->Title, $dupe->Title);
565
        $this->assertNotEquals($pageAbout->URLSegment, $dupe->URLSegment);
566
        $this->assertNotEquals($pageAbout->Sort, $dupe->Sort);
567
    }
568
569
    public function testDeleteFromLiveOperatesRecursively()
570
    {
571
        Config::modify()->set(SiteTree::class, 'enforce_strict_hierarchy', false);
572
        $this->logInWithPermission('ADMIN');
573
574
        $pageAbout = $this->objFromFixture(SiteTree::class, 'about');
575
        $pageAbout->publishRecursive();
576
        $pageStaff = $this->objFromFixture(SiteTree::class, 'staff');
577
        $pageStaff->publishRecursive();
578
        $pageStaffDuplicate = $this->objFromFixture(SiteTree::class, 'staffduplicate');
579
        $pageStaffDuplicate->publishRecursive();
580
581
        $parentPage = $this->objFromFixture(SiteTree::class, 'about');
582
583
        $parentPage->doUnpublish();
584
585
        Versioned::set_stage(Versioned::LIVE);
586
587
        $this->assertNull(DataObject::get_by_id(SiteTree::class, $pageAbout->ID));
588
        $this->assertTrue(DataObject::get_by_id(SiteTree::class, $pageStaff->ID) instanceof SiteTree);
589
        $this->assertTrue(DataObject::get_by_id(SiteTree::class, $pageStaffDuplicate->ID) instanceof SiteTree);
590
        Versioned::set_stage(Versioned::DRAFT);
591
        Config::modify()->set(SiteTree::class, 'enforce_strict_hierarchy', true);
592
    }
593
594
    public function testUnpublishDoesNotDeleteChildrenWithLooseHierachyOn()
595
    {
596
        Config::modify()->set(SiteTree::class, 'enforce_strict_hierarchy', false);
597
        $this->logInWithPermission('ADMIN');
598
599
        $pageAbout = $this->objFromFixture(SiteTree::class, 'about');
600
        $pageAbout->publishRecursive();
601
        $pageStaff = $this->objFromFixture(SiteTree::class, 'staff');
602
        $pageStaff->publishRecursive();
603
        $pageStaffDuplicate = $this->objFromFixture(SiteTree::class, 'staffduplicate');
604
        $pageStaffDuplicate->publishRecursive();
605
606
        $parentPage = $this->objFromFixture(SiteTree::class, 'about');
607
        $parentPage->doUnpublish();
608
609
        Versioned::set_stage(Versioned::LIVE);
610
        $this->assertNull(DataObject::get_by_id(SiteTree::class, $pageAbout->ID));
611
        $this->assertTrue(DataObject::get_by_id(SiteTree::class, $pageStaff->ID) instanceof SiteTree);
612
        $this->assertTrue(DataObject::get_by_id(SiteTree::class, $pageStaffDuplicate->ID) instanceof SiteTree);
613
        Versioned::set_stage(Versioned::DRAFT);
614
        Config::modify()->set(SiteTree::class, 'enforce_strict_hierarchy', true);
615
    }
616
617
    public function testDeleteFromLiveOperatesRecursivelyStrict()
618
    {
619
        Config::inst()->update(SiteTree::class, 'enforce_strict_hierarchy', true);
620
        $this->logInWithPermission('ADMIN');
621
622
        $pageAbout = $this->objFromFixture(SiteTree::class, 'about');
623
        $pageAbout->publishRecursive();
624
        $pageStaff = $this->objFromFixture(SiteTree::class, 'staff');
625
        $pageStaff->publishRecursive();
626
        $pageStaffDuplicate = $this->objFromFixture(SiteTree::class, 'staffduplicate');
627
        $pageStaffDuplicate->publishRecursive();
628
629
        $parentPage = $this->objFromFixture(SiteTree::class, 'about');
630
        $parentPage->doUnpublish();
631
632
        Versioned::set_stage(Versioned::LIVE);
633
        $this->assertNull(DataObject::get_by_id(SiteTree::class, $pageAbout->ID));
634
        $this->assertNull(DataObject::get_by_id(SiteTree::class, $pageStaff->ID));
635
        $this->assertNull(DataObject::get_by_id(SiteTree::class, $pageStaffDuplicate->ID));
636
        Versioned::set_stage(Versioned::DRAFT);
637
    }
638
639
    /**
640
     * Simple test to confirm that querying from a particular archive date doesn't throw
641
     * an error
642
     */
643
    public function testReadArchiveDate()
644
    {
645
        $date = '2009-07-02 14:05:07';
646
        Versioned::reading_archived_date($date);
647
        SiteTree::get()->where([
648
            '"SiteTree"."ParentID"' => 0,
649
        ])->sql($args);
650
        $this->assertContains($date, $args);
651
    }
652
653
    public function testEditPermissions()
654
    {
655
        $editor = $this->objFromFixture(Member::class, "editor");
656
657
        $home = $this->objFromFixture(SiteTree::class, "home");
658
        $staff = $this->objFromFixture(SiteTree::class, "staff");
659
        $products = $this->objFromFixture(SiteTree::class, "products");
660
        $product1 = $this->objFromFixture(SiteTree::class, "product1");
661
        $product4 = $this->objFromFixture(SiteTree::class, "product4");
662
663
        // Test logged out users cannot edit
664
        $this->logOut();
665
        $this->assertFalse($staff->canEdit());
666
667
        // Can't edit a page that is locked to admins
668
        $this->assertFalse($home->canEdit($editor));
669
670
        // Can edit a page that is locked to editors
671
        $this->assertTrue($products->canEdit($editor));
672
673
        // Can edit a child of that page that inherits
674
        $this->assertTrue($product1->canEdit($editor));
675
676
        // Can't edit a child of that page that has its permissions overridden
677
        $this->assertFalse($product4->canEdit($editor));
678
    }
679
680
    public function testCanEditWithAccessToAllSections()
681
    {
682
        $page = SiteTree::create();
683
        $page->write();
684
        $allSectionMember = $this->objFromFixture(Member::class, 'allsections');
685
        $securityAdminMember = $this->objFromFixture(Member::class, 'securityadmin');
686
687
        $this->assertTrue($page->canEdit($allSectionMember));
688
        $this->assertFalse($page->canEdit($securityAdminMember));
689
    }
690
691
    public function testCreatePermissions()
692
    {
693
        // Test logged out users cannot create
694
        $this->logOut();
695
        $this->assertFalse(singleton(SiteTree::class)->canCreate());
696
697
        // Login with another permission
698
        $this->logInWithPermission('DUMMY');
699
        $this->assertFalse(singleton(SiteTree::class)->canCreate());
700
701
        // Login with basic CMS permission
702
        $perms = SiteConfig::config()->required_permission;
703
        $this->logInWithPermission(reset($perms));
704
        $this->assertTrue(singleton(SiteTree::class)->canCreate());
705
706
        // Test creation underneath a parent which this user doesn't have access to
707
        $parent = $this->objFromFixture(SiteTree::class, 'about');
708
        $this->assertFalse(singleton(SiteTree::class)->canCreate(null, ['Parent' => $parent]));
709
710
        // Test creation underneath a parent which doesn't allow a certain child
711
        $parentB = new SiteTreeTest_ClassB();
712
        $parentB->Title = 'Only Allows SiteTreeTest_ClassC';
713
        $parentB->write();
714
        $this->assertTrue(singleton(SiteTreeTest_ClassA::class)->canCreate(null));
715
        $this->assertFalse(singleton(SiteTreeTest_ClassA::class)->canCreate(null, ['Parent' => $parentB]));
716
        $this->assertTrue(singleton(SiteTreeTest_ClassC::class)->canCreate(null, ['Parent' => $parentB]));
717
718
        // Test creation underneath a parent which doesn't exist in the database. This should
719
        // fall back to checking whether the user can create pages at the root of the site
720
        $this->assertTrue(singleton(SiteTree::class)->canCreate(null, ['Parent' => singleton(SiteTree::class)]));
721
722
        //Test we don't check for allowedChildren on parent context if it's not SiteTree instance
723
        $this->assertTrue(
724
            singleton(SiteTree::class)->canCreate(
725
                null,
726
                ['Parent' => $this->objFromFixture(SiteTreeTest_DataObject::class, 'relations')]
727
            )
728
        );
729
    }
730
731
    public function testEditPermissionsOnDraftVsLive()
732
    {
733
        // Create an inherit-permission page
734
        $page = SiteTree::create();
735
        $page->write();
736
        $page->CanEditType = "Inherit";
737
        $page->publishRecursive();
738
        $pageID = $page->ID;
0 ignored issues
show
Unused Code introduced by
The assignment to $pageID is dead and can be removed.
Loading history...
739
740
        // Lock down the site config
741
        $sc = $page->SiteConfig;
742
        $sc->CanEditType = 'OnlyTheseUsers';
743
        $sc->EditorGroups()->add($this->idFromFixture(Group::class, 'admins'));
744
        $sc->write();
745
746
        // Confirm that Member.editor can't edit the page
747
        $member = $this->objFromFixture(Member::class, 'editor');
748
        Security::setCurrentUser($member);
749
        $this->assertFalse($page->canEdit());
750
751
        // Change the page to be editable by Group.editors, but do not publish
752
        $admin = $this->objFromFixture(Member::class, 'admin');
753
        Security::setCurrentUser($admin);
754
        $page->CanEditType = 'OnlyTheseUsers';
755
        $page->EditorGroups()->add($this->idFromFixture(Group::class, 'editors'));
756
        $page->write();
757
758
        // Clear permission cache
759
        /** @var InheritedPermissions $checker */
760
        $checker = SiteTree::getPermissionChecker();
761
        $checker->clearCache();
762
763
        // Confirm that Member.editor can now edit the page
764
        $member = $this->objFromFixture(Member::class, 'editor');
765
        Security::setCurrentUser($member);
766
        $this->assertTrue($page->canEdit());
767
768
        // Publish the changes to the page
769
        $admin = $this->objFromFixture(Member::class, 'admin');
770
        Security::setCurrentUser($admin);
771
        $page->publishRecursive();
772
773
        // Confirm that Member.editor can still edit the page
774
        $member = $this->objFromFixture(Member::class, 'editor');
775
        Security::setCurrentUser($member);
776
        $this->assertTrue($page->canEdit());
777
    }
778
779
    public function testCompareVersions()
780
    {
781
        // Necessary to avoid
782
        $oldCleanerClass = Diff::$html_cleaner_class;
783
        Diff::$html_cleaner_class = SiteTreeTest_NullHtmlCleaner::class;
784
785
        $page = SiteTree::create();
786
        $page->write();
787
        $this->assertEquals(1, $page->Version);
788
789
        // Use inline element to avoid double wrapping applied to
790
        // blocklevel elements depending on HTMLCleaner implementation:
791
        // <ins><p> gets converted to <ins><p><inst>
792
        $page->Content = "<span>This is a test</span>";
793
        $page->write();
794
        $this->assertEquals(2, $page->Version);
795
796
        $diff = $page->compareVersions(1, 2);
797
798
        $processedContent = trim($diff->Content);
799
        $processedContent = preg_replace('/\s*</', '<', $processedContent);
800
        $processedContent = preg_replace('/>\s*/', '>', $processedContent);
801
        $this->assertEquals("<ins><span>This is a test</span></ins>", $processedContent);
802
803
        Diff::$html_cleaner_class = $oldCleanerClass;
804
    }
805
806
    public function testAuthorIDAndPublisherIDFilledOutOnPublish()
807
    {
808
        // Ensure that we have a member ID who is doing all this work
809
        $member = $this->objFromFixture(Member::class, "admin");
810
        $this->logInAs($member);
811
812
        // Write the page
813
        $about = $this->objFromFixture(SiteTree::class, 'about');
814
        $about->Title = "Another title";
815
        $about->write();
816
817
        // Check the version created
818
        $savedVersion = DB::prepared_query(
0 ignored issues
show
Deprecated Code introduced by
The function SilverStripe\ORM\Connect\Query::first() has been deprecated: Use record() instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

818
        $savedVersion = /** @scrutinizer ignore-deprecated */ DB::prepared_query(

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
819
            "SELECT \"AuthorID\", \"PublisherID\" FROM \"SiteTree_Versions\"
820
            WHERE \"RecordID\" = ? ORDER BY \"Version\" DESC",
821
            [$about->ID]
822
        )->first();
823
        $this->assertEquals($member->ID, $savedVersion['AuthorID']);
824
        $this->assertEquals(0, $savedVersion['PublisherID']);
825
826
        // Publish the page
827
        $about->publishRecursive();
828
        $publishedVersion = DB::prepared_query(
0 ignored issues
show
Deprecated Code introduced by
The function SilverStripe\ORM\Connect\Query::first() has been deprecated: Use record() instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

828
        $publishedVersion = /** @scrutinizer ignore-deprecated */ DB::prepared_query(

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
829
            "SELECT \"AuthorID\", \"PublisherID\" FROM \"SiteTree_Versions\"
830
            WHERE \"RecordID\" = ? ORDER BY \"Version\" DESC",
831
            [$about->ID]
832
        )->first();
833
834
        // Check the version created
835
        $this->assertEquals($member->ID, $publishedVersion['AuthorID']);
836
        $this->assertEquals($member->ID, $publishedVersion['PublisherID']);
837
    }
838
839
    public function testLinkShortcodeHandler()
840
    {
841
        $aboutPage = $this->objFromFixture(SiteTree::class, 'about');
842
        $redirectPage = $this->objFromFixture(RedirectorPage::class, 'external');
843
844
        $parser = new ShortcodeParser();
845
        $parser->register('sitetree_link', [SiteTree::class, 'link_shortcode_handler']);
846
847
        $aboutShortcode = sprintf('[sitetree_link,id=%d]', $aboutPage->ID);
848
        $aboutEnclosed  = sprintf('[sitetree_link,id=%d]Example Content[/sitetree_link]', $aboutPage->ID);
849
850
        $aboutShortcodeExpected = $aboutPage->Link();
851
        $aboutEnclosedExpected  = sprintf('<a href="%s">Example Content</a>', $aboutPage->Link());
852
853
        $this->assertEquals(
854
            $aboutShortcodeExpected,
855
            $parser->parse($aboutShortcode),
856
            'Test that simple linking works.'
857
        );
858
        $this->assertEquals(
859
            $aboutEnclosedExpected,
860
            $parser->parse($aboutEnclosed),
861
            'Test enclosed content is linked.'
862
        );
863
864
        $aboutPage->delete();
865
866
        $this->assertEquals(
867
            $aboutShortcodeExpected,
868
            $parser->parse($aboutShortcode),
869
            'Test that deleted pages still link.'
870
        );
871
        $this->assertEquals($aboutEnclosedExpected, $parser->parse($aboutEnclosed));
872
873
        $aboutShortcode = '[sitetree_link,id="-1"]';
874
        $aboutEnclosed  = '[sitetree_link,id="-1"]Example Content[/sitetree_link]';
875
876
        $this->assertEquals('', $parser->parse($aboutShortcode), 'Test empty result if no suitable matches.');
877
        $this->assertEquals('', $parser->parse($aboutEnclosed));
878
879
        $redirectShortcode = sprintf('[sitetree_link,id=%d]', $redirectPage->ID);
880
        $redirectEnclosed  = sprintf('[sitetree_link,id=%d]Example Content[/sitetree_link]', $redirectPage->ID);
881
        $redirectExpected = 'http://www.google.com?a&amp;b';
882
883
        $this->assertEquals($redirectExpected, $parser->parse($redirectShortcode));
884
        $this->assertEquals(
885
            sprintf('<a href="%s">Example Content</a>', $redirectExpected),
886
            $parser->parse($redirectEnclosed)
887
        );
888
889
        $this->assertEquals('', $parser->parse('[sitetree_link]'), 'Test that invalid ID attributes are not parsed.');
890
        $this->assertEquals('', $parser->parse('[sitetree_link,id="text"]'));
891
        $this->assertEquals('', $parser->parse('[sitetree_link]Example Content[/sitetree_link]'));
892
    }
893
894
    public function testIsCurrent()
895
    {
896
        $aboutPage = $this->objFromFixture(SiteTree::class, 'about');
897
        $productPage = $this->objFromFixture(SiteTree::class, 'products');
898
899
        Director::set_current_page($aboutPage);
900
        $this->assertTrue($aboutPage->isCurrent(), 'Assert that basic isCurrent checks works.');
901
        $this->assertFalse($productPage->isCurrent());
902
903
        $this->assertTrue(
904
            DataObject::get_one(SiteTree::class, [
905
                '"SiteTree"."Title"' => 'About Us',
906
            ])->isCurrent(),
907
            'Assert that isCurrent works on another instance with the same ID.'
908
        );
909
910
        Director::set_current_page($newPage = SiteTree::create());
911
        $this->assertTrue($newPage->isCurrent(), 'Assert that isCurrent works on unsaved pages.');
912
    }
913
914
    public function testIsSection()
915
    {
916
        $about = $this->objFromFixture(SiteTree::class, 'about');
917
        $staff = $this->objFromFixture(SiteTree::class, 'staff');
918
        $ceo   = $this->objFromFixture(SiteTree::class, 'ceo');
919
920
        Director::set_current_page($about);
921
        $this->assertTrue($about->isSection());
922
        $this->assertFalse($staff->isSection());
923
        $this->assertFalse($ceo->isSection());
924
925
        Director::set_current_page($staff);
926
        $this->assertTrue($about->isSection());
927
        $this->assertTrue($staff->isSection());
928
        $this->assertFalse($ceo->isSection());
929
930
        Director::set_current_page($ceo);
931
        $this->assertTrue($about->isSection());
932
        $this->assertTrue($staff->isSection());
933
        $this->assertTrue($ceo->isSection());
934
    }
935
936
    public function testURLSegmentReserved()
937
    {
938
        $siteTree = SiteTree::create(['URLSegment' => 'admin']);
939
        $segment = $siteTree->validURLSegment();
940
941
        $this->assertFalse($segment);
942
    }
943
944
    public function testURLSegmentAutoUpdate()
945
    {
946
        $sitetree = SiteTree::create();
947
        $sitetree->Title = _t(
948
            'SilverStripe\\CMS\\Controllers\\CMSMain.NEWPAGE',
949
            'New {pagetype}',
950
            ['pagetype' => $sitetree->i18n_singular_name()]
951
        );
952
        $sitetree->write();
953
        $this->assertEquals(
954
            'new-page',
955
            $sitetree->URLSegment,
956
            'Sets based on default title on first save'
957
        );
958
959
        $sitetree->Title = 'Changed';
960
        $sitetree->write();
961
        $this->assertEquals(
962
            'changed',
963
            $sitetree->URLSegment,
964
            'Auto-updates when set to default title'
965
        );
966
967
        $sitetree->Title = 'Changed again';
968
        $sitetree->write();
969
        $this->assertEquals(
970
            'changed',
971
            $sitetree->URLSegment,
972
            'Does not auto-update once title has been changed'
973
        );
974
    }
975
976
    public function testURLSegmentAutoUpdateLocalized()
977
    {
978
        $oldLocale = i18n::get_locale();
979
        i18n::set_locale('de_DE');
980
981
        $sitetree = SiteTree::create();
982
        $sitetree->Title = _t(
983
            'SilverStripe\\CMS\\Controllers\\CMSMain.NEWPAGE',
984
            'New {pagetype}',
985
            ['pagetype' => $sitetree->i18n_singular_name()]
986
        );
987
        $sitetree->write();
988
        $this->assertEquals(
989
            'neue-seite',
990
            $sitetree->URLSegment,
991
            'Sets based on default title on first save'
992
        );
993
994
        $sitetree->Title = 'Changed';
995
        $sitetree->write();
996
        $this->assertEquals(
997
            'changed',
998
            $sitetree->URLSegment,
999
            'Auto-updates when set to default title'
1000
        );
1001
1002
        $sitetree->Title = 'Changed again';
1003
        $sitetree->write();
1004
        $this->assertEquals(
1005
            'changed',
1006
            $sitetree->URLSegment,
1007
            'Does not auto-update once title has been changed'
1008
        );
1009
1010
        i18n::set_locale($oldLocale);
1011
    }
1012
1013
    /**
1014
     * @covers \SilverStripe\CMS\Model\SiteTree::validURLSegment
1015
     */
1016
    public function testValidURLSegmentURLSegmentConflicts()
1017
    {
1018
        $sitetree = SiteTree::create();
1019
        SiteTree::config()->nested_urls = false;
1020
1021
        $sitetree->URLSegment = 'home';
1022
        $this->assertFalse($sitetree->validURLSegment(), 'URLSegment conflicts are recognised');
1023
        $sitetree->URLSegment = 'home-noconflict';
1024
        $this->assertTrue($sitetree->validURLSegment());
1025
1026
        $sitetree->ParentID   = $this->idFromFixture(SiteTree::class, 'about');
1027
        $sitetree->URLSegment = 'home';
1028
        $this->assertFalse($sitetree->validURLSegment(), 'Conflicts are still recognised with a ParentID value');
1029
1030
        Config::modify()->set(SiteTree::class, 'nested_urls', true);
1031
1032
        $sitetree->ParentID   = 0;
1033
        $sitetree->URLSegment = 'home';
1034
        $this->assertFalse($sitetree->validURLSegment(), 'URLSegment conflicts are recognised');
1035
1036
        $sitetree->ParentID = $this->idFromFixture(SiteTree::class, 'about');
1037
        $this->assertTrue($sitetree->validURLSegment(), 'URLSegments can be the same across levels');
1038
1039
        $sitetree->URLSegment = 'my-staff';
1040
        $this->assertFalse($sitetree->validURLSegment(), 'Nested URLSegment conflicts are recognised');
1041
        $sitetree->URLSegment = 'my-staff-noconflict';
1042
        $this->assertTrue($sitetree->validURLSegment());
1043
    }
1044
1045
    /**
1046
     * @covers \SilverStripe\CMS\Model\SiteTree::validURLSegment
1047
     */
1048
    public function testValidURLSegmentClassNameConflicts()
1049
    {
1050
        $sitetree = SiteTree::create();
1051
        $sitetree->URLSegment = Controller::class;
1052
1053
        $this->assertTrue($sitetree->validURLSegment(), 'Class names are no longer conflicts');
1054
    }
1055
1056
    /**
1057
     * @covers \SilverStripe\CMS\Model\SiteTree::validURLSegment
1058
     */
1059
    public function testValidURLSegmentControllerConflicts()
1060
    {
1061
        Config::modify()->set(SiteTree::class, 'nested_urls', true);
1062
1063
        $sitetree = SiteTree::create();
1064
        $sitetree->ParentID = $this->idFromFixture(SiteTreeTest_Conflicted::class, 'parent');
1065
1066
        $sitetree->URLSegment = 'index';
1067
        $this->assertFalse($sitetree->validURLSegment(), 'index is not a valid URLSegment');
1068
1069
        $sitetree->URLSegment = 'conflicted-action';
1070
        $this->assertFalse($sitetree->validURLSegment(), 'allowed_actions conflicts are recognised');
1071
1072
        $sitetree->URLSegment = 'conflicted-template';
1073
        $this->assertFalse($sitetree->validURLSegment(), 'Action-specific template conflicts are recognised');
1074
1075
        $sitetree->URLSegment = 'valid';
1076
        $this->assertTrue($sitetree->validURLSegment(), 'Valid URLSegment values are allowed');
1077
    }
1078
1079
    public function testURLSegmentPrioritizesExtensionVotes()
1080
    {
1081
        $sitetree = SiteTree::create();
1082
        $sitetree->URLSegment = 'unique-segment';
1083
        $this->assertTrue($sitetree->validURLSegment());
1084
1085
        SiteTree::add_extension(SiteTreeTest_Extension::class);
1086
        $sitetree = SiteTree::create();
1087
        $sitetree->URLSegment = 'unique-segment';
1088
        $this->assertFalse($sitetree->validURLSegment());
1089
        SiteTree::remove_extension(SiteTreeTest_Extension::class);
1090
    }
1091
1092
    public function testURLSegmentMultiByte()
1093
    {
1094
        URLSegmentFilter::config()->set('default_allow_multibyte', true);
1095
        $sitetree = SiteTree::create();
1096
        $sitetree->write();
1097
1098
        $sitetree->URLSegment = 'brötchen';
1099
        $sitetree->write();
1100
        $sitetree = DataObject::get_by_id(SiteTree::class, $sitetree->ID, false);
1101
        $this->assertEquals($sitetree->URLSegment, rawurlencode('brötchen'));
1102
1103
        $sitetree->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
1104
        $sitetree = DataObject::get_by_id(SiteTree::class, $sitetree->ID, false);
1105
        $this->assertEquals($sitetree->URLSegment, rawurlencode('brötchen'));
1106
        $sitetreeLive = Versioned::get_one_by_stage(
1107
            SiteTree::class,
1108
            Versioned::LIVE,
1109
            '"SiteTree"."ID" = ' . $sitetree->ID,
1110
            false
1111
        );
1112
        $this->assertEquals($sitetreeLive->URLSegment, rawurlencode('brötchen'));
1113
    }
1114
1115
    public function testVersionsAreCreated()
1116
    {
1117
        $p = SiteTree::create();
1118
        $p->Content = "one";
1119
        $p->write();
1120
        $this->assertEquals(1, $p->Version);
1121
1122
        // No changes don't bump version
1123
        $p->write();
1124
        $this->assertEquals(1, $p->Version);
1125
1126
        $p->Content = "two";
1127
        $p->write();
1128
        $this->assertEquals(2, $p->Version);
1129
1130
        // Only change meta-data don't bump version
1131
        $p->HasBrokenLink = true;
1132
        $p->write();
1133
        $p->HasBrokenLink = false;
1134
        $p->write();
1135
        $this->assertEquals(2, $p->Version);
1136
1137
        $p->Content = "three";
1138
        $p->write();
1139
        $this->assertEquals(3, $p->Version);
1140
    }
1141
1142
    public function testPageTypeClasses()
1143
    {
1144
        $classes = SiteTree::page_type_classes();
1145
        $this->assertNotContains(SiteTree::class, $classes, 'Page types do not include base class');
1146
        $this->assertContains('Page', $classes, 'Page types do contain subclasses');
1147
1148
        // Testing what happens in an incorrect config value is set - hide_ancestor should be a string
1149
        Config::modify()->set(SiteTreeTest_ClassA::class, 'hide_ancestor', true);
1150
        $newClasses = SiteTree::page_type_classes();
1151
        $this->assertEquals(
1152
            $classes,
1153
            $newClasses,
1154
            'Setting hide_ancestor to a boolean (incorrect) value caused a page class to be hidden'
1155
        );
1156
    }
1157
1158
    /**
1159
     * Tests that core subclasses of SiteTree are included in allowedChildren() by default, but not instances of
1160
     * HiddenClass
1161
     */
1162
    public function testAllowedChildrenContainsCoreSubclassesButNotHiddenClass()
1163
    {
1164
        $page = SiteTree::create();
1165
        $allowedChildren = $page->allowedChildren();
1166
1167
        $this->assertContains(
1168
            VirtualPage::class,
1169
            $allowedChildren,
1170
            'Includes core subclasses by default'
1171
        );
1172
1173
        $this->assertNotContains(
1174
            SiteTreeTest_ClassE::class,
1175
            $allowedChildren,
1176
            'HiddenClass instances should not be returned'
1177
        );
1178
    }
1179
1180
    /**
1181
     * Tests that various types of SiteTree classes will or will not be returned from the allowedChildren method
1182
     * @dataProvider allowedChildrenProvider
1183
     * @param string $className
1184
     * @param array  $expected
1185
     * @param string $assertionMessage
1186
     */
1187
    public function testAllowedChildren($className, $expected, $assertionMessage)
1188
    {
1189
        $class = new $className;
1190
        $this->assertEquals($expected, $class->allowedChildren(), $assertionMessage);
1191
    }
1192
1193
    /**
1194
     * @return array
1195
     */
1196
    public function allowedChildrenProvider()
1197
    {
1198
        return [
1199
            [
1200
                // Class name
1201
                SiteTreeTest_ClassA::class,
1202
                // Expected
1203
                [ SiteTreeTest_ClassB::class ],
1204
                // Assertion message
1205
                'Direct setting of allowed children',
1206
            ],
1207
            [
1208
                SiteTreeTest_ClassB::class,
1209
                [ SiteTreeTest_ClassC::class, SiteTreeTest_ClassCext::class ],
1210
                'Includes subclasses',
1211
            ],
1212
            [
1213
                SiteTreeTest_ClassC::class,
1214
                [],
1215
                'Null setting',
1216
            ],
1217
            [
1218
                SiteTreeTest_ClassD::class,
1219
                [SiteTreeTest_ClassC::class],
1220
                'Excludes subclasses if class is prefixed by an asterisk',
1221
            ],
1222
        ];
1223
    }
1224
1225
    public function testAllowedChildrenValidation()
1226
    {
1227
        $page = SiteTree::create();
1228
        $page->write();
1229
        $classA = new SiteTreeTest_ClassA();
1230
        $classA->write();
1231
        $classB = new SiteTreeTest_ClassB();
1232
        $classB->write();
1233
        $classC = new SiteTreeTest_ClassC();
1234
        $classC->write();
1235
        $classD = new SiteTreeTest_ClassD();
1236
        $classD->write();
1237
        $classCext = new SiteTreeTest_ClassCext();
1238
        $classCext->write();
1239
1240
        $classB->ParentID = $page->ID;
0 ignored issues
show
Bug Best Practice introduced by
The property ParentID does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1241
        $valid = $classB->doValidate();
1242
        $this->assertTrue($valid->isValid(), "Does allow children on unrestricted parent");
1243
1244
        $classB->ParentID = $classA->ID;
1245
        $valid = $classB->doValidate();
1246
        $this->assertTrue($valid->isValid(), "Does allow child specifically allowed by parent");
1247
1248
        $classC->ParentID = $classA->ID;
1249
        $valid = $classC->doValidate();
1250
        $this->assertFalse($valid->isValid(), "Doesnt allow child on parents specifically restricting children");
1251
1252
        $classB->ParentID = $classC->ID;
1253
        $valid = $classB->doValidate();
1254
        $this->assertFalse($valid->isValid(), "Doesnt allow child on parents disallowing all children");
1255
1256
        $classB->ParentID = $classCext->ID;
1257
        $valid = $classB->doValidate();
1258
        $this->assertTrue($valid->isValid(), "Extensions of allowed classes are incorrectly reported as invalid");
1259
1260
        $classCext->ParentID = $classD->ID;
1261
        $valid = $classCext->doValidate();
1262
        $this->assertFalse(
1263
            $valid->isValid(),
1264
            "Doesnt allow child where only parent class is allowed on parent node, and asterisk prefixing is used"
1265
        );
1266
    }
1267
1268
    public function testClassDropdown()
1269
    {
1270
        $sitetree = SiteTree::create();
1271
        $method = new ReflectionMethod($sitetree, 'getClassDropdown');
1272
        $method->setAccessible(true);
1273
1274
        Security::setCurrentUser(null);
1275
        $this->assertArrayNotHasKey(SiteTreeTest_ClassA::class, $method->invoke($sitetree));
1276
1277
        $this->loginWithPermission('ADMIN');
1278
        $this->assertArrayHasKey(SiteTreeTest_ClassA::class, $method->invoke($sitetree));
1279
1280
        $this->loginWithPermission('CMS_ACCESS_CMSMain');
1281
        $this->assertArrayHasKey(SiteTreeTest_ClassA::class, $method->invoke($sitetree));
1282
1283
        Security::setCurrentUser(null);
1284
    }
1285
1286
    public function testCanBeRoot()
1287
    {
1288
        $page = SiteTree::create();
1289
        $page->ParentID = 0;
1290
        $page->write();
1291
1292
        $notRootPage = new SiteTreeTest_NotRoot();
1293
        $notRootPage->ParentID = 0;
0 ignored issues
show
Bug Best Practice introduced by
The property ParentID does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1294
        $isDetected = false;
1295
        try {
1296
            $notRootPage->write();
1297
        } catch (ValidationException $e) {
1298
            $this->assertContains('is not allowed on the root level', $e->getMessage());
1299
            $isDetected = true;
1300
        }
1301
1302
        if (!$isDetected) {
1303
            $this->fail('Fails validation with $can_be_root=false');
1304
        }
1305
    }
1306
1307
    public function testModifyStatusFlagByInheritance()
1308
    {
1309
        $node = new SiteTreeTest_StageStatusInherit();
1310
        $treeTitle = $node->getTreeTitle();
1311
        $this->assertContains('InheritedTitle', $treeTitle);
1312
        $this->assertContains('inherited-class', $treeTitle);
1313
    }
1314
1315
    public function testMenuTitleIsUnsetWhenEqualsTitle()
1316
    {
1317
        $page = SiteTree::create();
1318
        $page->Title = 'orig';
1319
        $page->MenuTitle = 'orig';
1320
        $page->write();
1321
1322
        // change menu title
1323
        $page->MenuTitle = 'changed';
1324
        $page->write();
1325
        $page = SiteTree::get()->byID($page->ID);
1326
        $this->assertEquals('changed', $page->getField('MenuTitle'));
1327
1328
        // change menu title back
1329
        $page->MenuTitle = 'orig';
1330
        $page->write();
1331
        $page = SiteTree::get()->byID($page->ID);
1332
        $this->assertEquals(null, $page->getField('MenuTitle'));
1333
    }
1334
1335
    public function testMetaTagGeneratorDisabling()
1336
    {
1337
        $generator = Config::inst()->get(SiteTree::class, 'meta_generator');
1338
1339
        $page = new SiteTreeTest_PageNode();
1340
1341
        $meta = $page->MetaTags();
1342
        $this->assertEquals(
1343
            1,
1344
            preg_match('/.*meta name="generator" content="SilverStripe - http:\/\/silverstripe.org".*/', $meta),
1345
            'test default functionality - uses value from Config'
1346
        );
1347
1348
        // test proper escaping of quotes in attribute value
1349
        Config::modify()->set(SiteTree::class, 'meta_generator', 'Generator with "quotes" in it');
1350
        $meta = $page->MetaTags();
1351
        $this->assertEquals(
1352
            1,
1353
            preg_match('/.*meta name="generator" content="Generator with &quot;quotes&quot; in it".*/', $meta),
1354
            'test proper escaping of values from Config'
1355
        );
1356
1357
        // test empty generator - no tag should appear at all
1358
        Config::modify()->set(SiteTree::class, 'meta_generator', '');
1359
        $meta = $page->MetaTags();
1360
        $this->assertEquals(
1361
            0,
1362
            preg_match('/.*meta name=.generator..*/', $meta),
1363
            'test blank value means no tag generated'
1364
        );
1365
1366
        // reset original value
1367
        Config::modify()->set(SiteTree::class, 'meta_generator', $generator);
1368
    }
1369
1370
1371
    public function testGetBreadcrumbItems()
1372
    {
1373
        $page = $this->objFromFixture(SiteTree::class, "breadcrumbs");
1374
        $this->assertEquals(1, $page->getBreadcrumbItems()->count(), "Only display current page.");
1375
1376
        // Test breadcrumb order
1377
        $page = $this->objFromFixture(SiteTree::class, "breadcrumbs5");
1378
        $breadcrumbs = $page->getBreadcrumbItems();
1379
        $this->assertEquals($breadcrumbs->count(), 5, "Display all breadcrumbs");
1380
        $this->assertEquals($breadcrumbs->first()->Title, "Breadcrumbs", "Breadcrumbs should be the first item.");
1381
        $this->assertEquals($breadcrumbs->last()->Title, "Breadcrumbs 5", "Breadcrumbs 5 should be last item.");
1382
1383
        // Test breadcrumb max depth
1384
        $breadcrumbs = $page->getBreadcrumbItems(2);
1385
        $this->assertEquals($breadcrumbs->count(), 2, "Max depth should limit the breadcrumbs to 2 items.");
1386
        $this->assertEquals($breadcrumbs->first()->Title, "Breadcrumbs 4", "First item should be Breadrcumbs 4.");
1387
        $this->assertEquals($breadcrumbs->last()->Title, "Breadcrumbs 5", "Breadcrumbs 5 should be last.");
1388
    }
1389
1390
    /**
1391
     * Tests SiteTree::MetaTags
1392
     * Note that this test makes no assumption on the closing of tags (other than <title></title>)
1393
     */
1394
    public function testMetaTags()
1395
    {
1396
        $this->logInWithPermission('ADMIN');
1397
        $page = $this->objFromFixture(SiteTree::class, 'metapage');
1398
1399
        // Test with title
1400
        $meta = $page->MetaTags();
1401
        $charset = Config::inst()->get(ContentNegotiator::class, 'encoding');
1402
        $this->assertContains('<meta http-equiv="Content-Type" content="text/html; charset=' . $charset . '"', $meta);
1403
        $this->assertContains('<meta name="description" content="The &lt;br /&gt; and &lt;br&gt; tags"', $meta);
1404
        $this->assertContains('<link rel="canonical" href="http://www.mysite.com/html-and-xml"', $meta);
1405
        $this->assertContains('<meta name="x-page-id" content="' . $page->ID . '"', $meta);
1406
        $this->assertContains('<meta name="x-cms-edit-link" content="' . $page->CMSEditLink() . '"', $meta);
1407
        $this->assertContains('<title>HTML &amp; XML</title>', $meta);
1408
1409
        // Test without title
1410
        $meta = $page->MetaTags(false);
1411
        $this->assertNotContains('<title>', $meta);
1412
    }
1413
1414
    /**
1415
     * Test that orphaned pages are handled correctly
1416
     */
1417
    public function testOrphanedPages()
1418
    {
1419
        $origStage = Versioned::get_reading_mode();
1420
1421
        // Setup user who can view draft content, but lacks cms permission.
1422
        // To users such as this, orphaned pages should be inaccessible. canView for these pages is only
1423
        // necessary for admin / cms users, who require this permission to edit / rearrange these pages.
1424
        $permission = new Permission();
1425
        $permission->Code = 'VIEW_DRAFT_CONTENT';
1426
        $group = new Group(['Title' => 'Staging Users']);
1427
        $group->write();
1428
        $group->Permissions()->add($permission);
1429
        $member = new Member();
1430
        $member->Email = '[email protected]';
1431
        $member->write();
1432
        $member->Groups()->add($group);
1433
1434
        // both pages are viewable in stage
1435
        Versioned::set_stage(Versioned::DRAFT);
1436
        $about = $this->objFromFixture(SiteTree::class, 'about');
1437
        $staff = $this->objFromFixture(SiteTree::class, 'staff');
1438
        $this->assertFalse($about->isOrphaned());
1439
        $this->assertFalse($staff->isOrphaned());
1440
        $this->assertTrue($about->canView($member));
1441
        $this->assertTrue($staff->canView($member));
1442
1443
        // Publishing only the child page to live should orphan the live record, but not the staging one
1444
        $staff->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
1445
        $this->assertFalse($staff->isOrphaned());
1446
        $this->assertTrue($staff->canView($member));
1447
        Versioned::set_stage(Versioned::LIVE);
1448
        $staff = $this->objFromFixture(SiteTree::class, 'staff'); // Live copy of page
1449
        $this->assertTrue($staff->isOrphaned()); // because parent isn't published
1450
        $this->assertFalse($staff->canView($member));
1451
1452
        // Publishing the parent page should restore visibility
1453
        Versioned::set_stage(Versioned::DRAFT);
1454
        $about = $this->objFromFixture(SiteTree::class, 'about');
1455
        $about->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
1456
        Versioned::set_stage(Versioned::LIVE);
1457
        $staff = $this->objFromFixture(SiteTree::class, 'staff');
1458
        $this->assertFalse($staff->isOrphaned());
1459
        $this->assertTrue($staff->canView($member));
1460
1461
        // Removing staging page should not prevent live page being visible
1462
        $about->deleteFromStage('Stage');
1463
        $staff->deleteFromStage('Stage');
1464
        $staff = $this->objFromFixture(SiteTree::class, 'staff');
1465
        $this->assertFalse($staff->isOrphaned());
1466
        $this->assertTrue($staff->canView($member));
1467
1468
        // Cleanup
1469
        Versioned::set_reading_mode($origStage);
1470
    }
1471
1472
    /**
1473
     * Test archived page behaviour
1474
     */
1475
    public function testArchivedPages()
1476
    {
1477
        $this->logInWithPermission('ADMIN');
1478
1479
        /** @var SiteTree $page */
1480
        $page = $this->objFromFixture(SiteTree::class, 'home');
1481
        $this->assertTrue($page->canAddChildren());
1482
        $this->assertTrue($page->isOnDraft());
1483
        $this->assertFalse($page->isPublished());
1484
1485
        // Publish
1486
        $page->publishRecursive();
1487
        $this->assertTrue($page->canAddChildren());
1488
        $this->assertTrue($page->isOnDraft());
1489
        $this->assertTrue($page->isPublished());
1490
1491
        // Archive
1492
        $page->doArchive();
1493
        $this->assertFalse($page->canAddChildren());
1494
        $this->assertFalse($page->isOnDraft());
1495
        $this->assertTrue($page->isArchived());
1496
        $this->assertFalse($page->isPublished());
1497
    }
1498
1499
    public function testCanNot()
1500
    {
1501
        // Test that
1502
        $this->logInWithPermission('ADMIN');
1503
        $page = new SiteTreeTest_AdminDenied();
1504
        $this->assertFalse($page->canCreate());
1505
        $this->assertFalse($page->canEdit());
1506
        $this->assertFalse($page->canDelete());
1507
        $this->assertFalse($page->canAddChildren());
1508
        $this->assertFalse($page->canView());
1509
    }
1510
1511
    public function testCanPublish()
1512
    {
1513
        $page = new SiteTreeTest_ClassD();
1514
        $this->logOut();
1515
1516
        // Test that false overrides any can_publish = true
1517
        SiteTreeTest_ExtensionA::$can_publish = true;
1518
        SiteTreeTest_ExtensionB::$can_publish = false;
1519
        $this->assertFalse($page->canPublish());
1520
        SiteTreeTest_ExtensionA::$can_publish = false;
1521
        SiteTreeTest_ExtensionB::$can_publish = true;
1522
        $this->assertFalse($page->canPublish());
1523
1524
        // Test null extensions fall back to canEdit()
1525
        SiteTreeTest_ExtensionA::$can_publish = null;
1526
        SiteTreeTest_ExtensionB::$can_publish = null;
1527
        $page->canEditValue = true;
1528
        $this->assertTrue($page->canPublish());
1529
        $page->canEditValue = false;
1530
        $this->assertFalse($page->canPublish());
1531
    }
1532
1533
    /**
1534
     * Test url rewriting extensions
1535
     */
1536
    public function testLinkExtension()
1537
    {
1538
        Director::config()->set('alternate_base_url', 'http://www.baseurl.com');
1539
        $page = new SiteTreeTest_ClassD();
1540
        $page->URLSegment = 'classd';
1541
        $page->write();
1542
        $this->assertEquals(
1543
            'http://www.updatedhost.com/classd/myaction?extra=1',
1544
            $page->Link('myaction')
1545
        );
1546
        $this->assertEquals(
1547
            'http://www.updatedhost.com/classd/myaction?extra=1',
1548
            $page->AbsoluteLink('myaction')
1549
        );
1550
        $this->assertEquals(
1551
            'classd/myaction',
1552
            $page->RelativeLink('myaction')
1553
        );
1554
    }
1555
1556
    /**
1557
     * Test that the controller name for a SiteTree instance can be gathered by appending "Controller" to the SiteTree
1558
     * class name in a PSR-2 compliant manner.
1559
     */
1560
    public function testGetControllerName()
1561
    {
1562
        $class = Page::create();
1563
        $this->assertSame('PageController', $class->getControllerName());
1564
    }
1565
1566
    /**
1567
     * Test that the controller name for a SiteTree instance can be gathered when set directly via config var
1568
     */
1569
    public function testGetControllerNameFromConfig()
1570
    {
1571
        Config::inst()->update(Page::class, 'controller_name', 'This\\Is\\A\\New\\Controller');
1572
        $class = new Page;
1573
        $this->assertSame('This\\Is\\A\\New\\Controller', $class->getControllerName());
1574
    }
1575
1576
    /**
1577
     * Test that underscored class names (legacy) are still supported (deprecation notice is issued though).
1578
     */
1579
    public function testGetControllerNameWithUnderscoresIsSupported()
1580
    {
1581
        $class = new SiteTreeTest_LegacyControllerName;
1582
        $this->assertEquals(SiteTreeTest_LegacyControllerName_Controller::class, $class->getControllerName());
1583
    }
1584
1585
    public function testTreeTitleCache()
1586
    {
1587
        $siteTree = SiteTree::create();
1588
        $user = $this->objFromFixture(Member::class, 'allsections');
1589
        Security::setCurrentUser($user);
1590
        $pageClass = array_values(SiteTree::page_type_classes())[0];
1591
1592
        $mockPageMissesCache = $this->getMockBuilder($pageClass)
1593
            ->setMethods(['canCreate'])
1594
            ->getMock();
1595
        $mockPageMissesCache
1596
            ->expects($this->exactly(3))
1597
            ->method('canCreate');
1598
1599
        $mockPageHitsCache = $this->getMockBuilder($pageClass)
1600
            ->setMethods(['canCreate'])
1601
            ->getMock();
1602
        $mockPageHitsCache
1603
            ->expects($this->never())
1604
            ->method('canCreate');
1605
1606
        // Initially, cache misses (1)
1607
        Injector::inst()->registerService($mockPageMissesCache, $pageClass);
1608
        $title = $siteTree->getTreeTitle();
1609
        $this->assertNotNull($title);
1610
1611
        // Now it hits
1612
        Injector::inst()->registerService($mockPageHitsCache, $pageClass);
1613
        $title = $siteTree->getTreeTitle();
1614
        $this->assertNotNull($title);
1615
1616
1617
        // Mutating member record invalidates cache. Misses (2)
1618
        $user->FirstName = 'changed';
1619
        $user->write();
1620
        Injector::inst()->registerService($mockPageMissesCache, $pageClass);
1621
        $title = $siteTree->getTreeTitle();
1622
        $this->assertNotNull($title);
1623
1624
        // Now it hits again
1625
        Injector::inst()->registerService($mockPageHitsCache, $pageClass);
1626
        $title = $siteTree->getTreeTitle();
1627
        $this->assertNotNull($title);
1628
1629
        // Different user. Misses. (3)
1630
        $user = $this->objFromFixture(Member::class, 'editor');
1631
        Security::setCurrentUser($user);
1632
        Injector::inst()->registerService($mockPageMissesCache, $pageClass);
1633
        $title = $siteTree->getTreeTitle();
1634
        $this->assertNotNull($title);
1635
    }
1636
}
1637