Passed
Push — master ( 301f96...5c3d68 )
by Robbie
05:48
created

testDefaultResourcesDirHasLeadingUnderscoreRemovedAndResourcesIsUsed()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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

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

862
        $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...
863
            "SELECT \"AuthorID\", \"PublisherID\" FROM \"SiteTree_Versions\"
864
            WHERE \"RecordID\" = ? ORDER BY \"Version\" DESC",
865
            [$about->ID]
866
        )->first();
867
        $this->assertEquals($member->ID, $savedVersion['AuthorID']);
868
        $this->assertEquals(0, $savedVersion['PublisherID']);
869
870
        // Publish the page
871
        $about->publishRecursive();
872
        $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

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