SiteTreeTest   F
last analyzed

Complexity

Total Complexity 81

Size/Duplication

Total Lines 1707
Duplicated Lines 0 %

Importance

Changes 14
Bugs 0 Features 1
Metric Value
eloc 984
c 14
b 0
f 1
dl 0
loc 1707
rs 1.616
wmc 81

71 Methods

Rating   Name   Duplication   Size   Complexity  
A testCreateDefaultpages() 0 24 3
A testURLGeneration() 0 19 2
A testCanSaveBlankToHasOneRelations() 0 16 1
A testPublishDeletedFields() 0 19 1
A testDeleteFromLiveOperatesRecursively() 0 23 1
B testStageStates() 0 69 1
A testDeleteFromStageOperatesRecursively() 0 13 1
A testGetOneFromLive() 0 19 1
A testChidrenOfRootAreTopLevelPages() 0 26 2
A testAbsoluteLiveLink() 0 16 1
A testDuplicateChildrenRetainSort() 0 31 1
A testUnpublishDoesNotDeleteChildrenWithLooseHierachyOn() 0 21 1
A testParentModelReturnType() 0 7 1
A testPublishCopiesToLiveTable() 0 9 1
A testRelativeLink() 0 26 1
A testDuplicate() 0 7 1
A testPageLevel() 0 6 1
A testRestoreToStage() 0 36 1
A testParentNodeCachedInMemory() 0 10 1
A testGetByLink() 0 27 1
A testArchivedPages() 0 22 1
A testCanEditWithAccessToAllSections() 0 9 1
A allowedChildrenProvider() 0 25 1
A testIsSection() 0 20 1
A testValidURLSegmentURLSegmentConflicts() 0 27 1
A testModifyStatusFlagByInheritance() 0 6 1
A testCompareVersions() 0 25 1
A testEditPermissionsOnDraftVsLive() 0 46 1
A testCanPublish() 0 20 1
A testURLSegmentMultiByte() 0 21 1
A testOrphanedPages() 0 53 1
A testGetControllerName() 0 4 1
A testClassDropdown() 0 16 1
A testURLSegmentAutoUpdateLocalized() 0 35 1
A testCreatePermissions() 0 36 1
A testIsCurrent() 0 18 1
A testGetBreadcrumbItems() 0 17 1
A testURLSegmentPrioritizesExtensionVotes() 0 11 1
A testValidURLSegmentClassNameConflicts() 0 6 1
A testValidURLSegmentControllerConflicts() 0 18 1
A testLinkShortcodeHandler() 0 53 1
A testAllowedChildrenContainsCoreSubclassesButNotHiddenClass() 0 15 1
A testLinkExtension() 0 17 1
A testCanNot() 0 10 1
A testVersionsAreCreated() 0 25 1
A testEditPermissions() 0 25 1
A testAuthorIDAndPublisherIDFilledOutOnPublish() 0 31 1
A testMenuTitleIsUnsetWhenEqualsTitle() 0 18 1
A testGetControllerNameWithUnderscoresIsSupported() 0 4 1
A testURLSegmentAutoUpdate() 0 29 1
A testPageTypeClasses() 0 13 1
A testAllowedChildren() 0 4 1
A testAllowedChildrenValidation() 0 40 1
A testLegacyResourcesDirValuesHaveIncrementedValueAppended() 0 10 2
A testMetaTagGeneratorDisabling() 0 33 1
A testNoCascadingDeleteWithoutID() 0 17 3
A testMetaComponents() 0 46 1
A testDependentPagesOnUnsavedRecord() 0 5 1
A testTreeTitleCache() 0 50 1
A testMetaTags() 0 21 1
A testExplicitlyUsingResourcesDirForURLSegment() 0 6 1
A testDefaultResourcesDirHasLeadingUnderscoreRemovedAndResourcesIsUsed() 0 10 2
A testDisallowedChildURLGeneration() 0 9 1
A testDeleteFromLiveOperatesRecursivelyStrict() 0 20 1
A testReadArchiveDate() 0 14 1
A reservedSegmentsProvider() 0 10 1
A testDeleteFromStageOperatesRecursivelyStrict() 0 12 1
A testGetControllerNameFromConfig() 0 5 1
A testDisallowedURLGeneration() 0 6 1
A testURLSegmentReserved() 0 6 1
A testCanBeRoot() 0 18 3

How to fix   Complexity   

Complex Class

Complex classes like SiteTreeTest often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SiteTreeTest, and based on these observations, apply Extract Interface, too.

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\FieldType\DBDatetime;
22
use SilverStripe\ORM\ValidationException;
23
use SilverStripe\Security\Group;
24
use SilverStripe\Security\InheritedPermissions;
25
use SilverStripe\Security\Member;
26
use SilverStripe\Security\Permission;
27
use SilverStripe\Security\Security;
28
use SilverStripe\SiteConfig\SiteConfig;
29
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...
30
use SilverStripe\Versioned\Versioned;
31
use SilverStripe\View\Parsers\Diff;
32
use SilverStripe\View\Parsers\ShortcodeParser;
33
use SilverStripe\View\Parsers\URLSegmentFilter;
34
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...
35
use const ASSETS_DIR;
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' => ManifestFileFinder::RESOURCES_DIR]);
0 ignored issues
show
Deprecated Code introduced by
The constant SilverStripe\Core\Manife...leFinder::RESOURCES_DIR has been deprecated: 4.4.0:5.0.0 Use global `RESOURCES_DIR` instead. ( Ignorable by Annotation )

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

160
        $page = SiteTree::create(['URLSegment' => /** @scrutinizer ignore-deprecated */ ManifestFileFinder::RESOURCES_DIR]);

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

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

Loading history...
161
        $id = $page->write();
162
        $page = SiteTree::get()->byID($id);
163
        $this->assertSame(ManifestFileFinder::RESOURCES_DIR . '-2', $page->URLSegment);
0 ignored issues
show
Deprecated Code introduced by
The constant SilverStripe\Core\Manife...leFinder::RESOURCES_DIR has been deprecated: 4.4.0:5.0.0 Use global `RESOURCES_DIR` instead. ( Ignorable by Annotation )

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

163
        $this->assertSame(/** @scrutinizer ignore-deprecated */ ManifestFileFinder::RESOURCES_DIR . '-2', $page->URLSegment);

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

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

Loading history...
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 (ManifestFileFinder::RESOURCES_DIR !== 'resources') {
0 ignored issues
show
Deprecated Code introduced by
The constant SilverStripe\Core\Manife...leFinder::RESOURCES_DIR has been deprecated: 4.4.0:5.0.0 Use global `RESOURCES_DIR` instead. ( Ignorable by Annotation )

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

171
        if (/** @scrutinizer ignore-deprecated */ ManifestFileFinder::RESOURCES_DIR !== 'resources') {

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

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

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 (ManifestFileFinder::RESOURCES_DIR === 'resources') {
0 ignored issues
show
Deprecated Code introduced by
The constant SilverStripe\Core\Manife...leFinder::RESOURCES_DIR has been deprecated: 4.4.0:5.0.0 Use global `RESOURCES_DIR` instead. ( Ignorable by Annotation )

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

187
        if (/** @scrutinizer ignore-deprecated */ ManifestFileFinder::RESOURCES_DIR === 'resources') {

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

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

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->assertNotNull(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
        DBDatetime::set_mock_now('2009-07-02 14:05:07');
690
        $oldPage = SiteTree::create();
691
        $oldPage->Title = 'A really old page';
692
        $oldPage->write();
693
        DBDatetime::clear_mock_now();
694
695
        $date = '2009-07-02 14:05:07';
696
        Versioned::reading_archived_date($date);
697
        $result = SiteTree::get()->where([
698
            '"SiteTree"."ParentID"' => 0
699
        ]);
700
        $this->assertCount(1, $result, '"A really old page" should be returned');
701
    }
702
703
    public function testEditPermissions()
704
    {
705
        $editor = $this->objFromFixture(Member::class, "editor");
706
707
        $home = $this->objFromFixture(SiteTree::class, "home");
708
        $staff = $this->objFromFixture(SiteTree::class, "staff");
709
        $products = $this->objFromFixture(SiteTree::class, "products");
710
        $product1 = $this->objFromFixture(SiteTree::class, "product1");
711
        $product4 = $this->objFromFixture(SiteTree::class, "product4");
712
713
        // Test logged out users cannot edit
714
        $this->logOut();
715
        $this->assertFalse($staff->canEdit());
716
717
        // Can't edit a page that is locked to admins
718
        $this->assertFalse($home->canEdit($editor));
719
720
        // Can edit a page that is locked to editors
721
        $this->assertTrue($products->canEdit($editor));
722
723
        // Can edit a child of that page that inherits
724
        $this->assertTrue($product1->canEdit($editor));
725
726
        // Can't edit a child of that page that has its permissions overridden
727
        $this->assertFalse($product4->canEdit($editor));
728
    }
729
730
    public function testCanEditWithAccessToAllSections()
731
    {
732
        $page = SiteTree::create();
733
        $page->write();
734
        $allSectionMember = $this->objFromFixture(Member::class, 'allsections');
735
        $securityAdminMember = $this->objFromFixture(Member::class, 'securityadmin');
736
737
        $this->assertTrue($page->canEdit($allSectionMember));
738
        $this->assertFalse($page->canEdit($securityAdminMember));
739
    }
740
741
    public function testCreatePermissions()
742
    {
743
        // Test logged out users cannot create
744
        $this->logOut();
745
        $this->assertFalse(singleton(SiteTree::class)->canCreate());
746
747
        // Login with another permission
748
        $this->logInWithPermission('DUMMY');
749
        $this->assertFalse(singleton(SiteTree::class)->canCreate());
750
751
        // Login with basic CMS permission
752
        $perms = SiteConfig::config()->required_permission;
753
        $this->logInWithPermission(reset($perms));
754
        $this->assertTrue(singleton(SiteTree::class)->canCreate());
755
756
        // Test creation underneath a parent which this user doesn't have access to
757
        $parent = $this->objFromFixture(SiteTree::class, 'about');
758
        $this->assertFalse(singleton(SiteTree::class)->canCreate(null, ['Parent' => $parent]));
759
760
        // Test creation underneath a parent which doesn't allow a certain child
761
        $parentB = new SiteTreeTest_ClassB();
762
        $parentB->Title = 'Only Allows SiteTreeTest_ClassC';
763
        $parentB->write();
764
        $this->assertTrue(singleton(SiteTreeTest_ClassA::class)->canCreate(null));
765
        $this->assertFalse(singleton(SiteTreeTest_ClassA::class)->canCreate(null, ['Parent' => $parentB]));
766
        $this->assertTrue(singleton(SiteTreeTest_ClassC::class)->canCreate(null, ['Parent' => $parentB]));
767
768
        // Test creation underneath a parent which doesn't exist in the database. This should
769
        // fall back to checking whether the user can create pages at the root of the site
770
        $this->assertTrue(singleton(SiteTree::class)->canCreate(null, ['Parent' => singleton(SiteTree::class)]));
771
772
        //Test we don't check for allowedChildren on parent context if it's not SiteTree instance
773
        $this->assertTrue(
774
            singleton(SiteTree::class)->canCreate(
775
                null,
776
                ['Parent' => $this->objFromFixture(SiteTreeTest_DataObject::class, 'relations')]
777
            )
778
        );
779
    }
780
781
    public function testEditPermissionsOnDraftVsLive()
782
    {
783
        // Create an inherit-permission page
784
        $page = SiteTree::create();
785
        $page->write();
786
        $page->CanEditType = "Inherit";
787
        $page->publishRecursive();
788
        $pageID = $page->ID;
0 ignored issues
show
Unused Code introduced by
The assignment to $pageID is dead and can be removed.
Loading history...
789
790
        // Lock down the site config
791
        $sc = $page->SiteConfig;
792
        $sc->CanEditType = 'OnlyTheseUsers';
793
        $sc->EditorGroups()->add($this->idFromFixture(Group::class, 'admins'));
794
        $sc->write();
795
796
        // Confirm that Member.editor can't edit the page
797
        $member = $this->objFromFixture(Member::class, 'editor');
798
        Security::setCurrentUser($member);
799
        $this->assertFalse($page->canEdit());
800
801
        // Change the page to be editable by Group.editors, but do not publish
802
        $admin = $this->objFromFixture(Member::class, 'admin');
803
        Security::setCurrentUser($admin);
804
        $page->CanEditType = 'OnlyTheseUsers';
805
        $page->EditorGroups()->add($this->idFromFixture(Group::class, 'editors'));
806
        $page->write();
807
808
        // Clear permission cache
809
        /** @var InheritedPermissions $checker */
810
        $checker = SiteTree::getPermissionChecker();
811
        $checker->clearCache();
812
813
        // Confirm that Member.editor can now edit the page
814
        $member = $this->objFromFixture(Member::class, 'editor');
815
        Security::setCurrentUser($member);
816
        $this->assertTrue($page->canEdit());
817
818
        // Publish the changes to the page
819
        $admin = $this->objFromFixture(Member::class, 'admin');
820
        Security::setCurrentUser($admin);
821
        $page->publishRecursive();
822
823
        // Confirm that Member.editor can still edit the page
824
        $member = $this->objFromFixture(Member::class, 'editor');
825
        Security::setCurrentUser($member);
826
        $this->assertTrue($page->canEdit());
827
    }
828
829
    public function testCompareVersions()
830
    {
831
        // Necessary to avoid
832
        $oldCleanerClass = Diff::$html_cleaner_class;
833
        Diff::$html_cleaner_class = SiteTreeTest_NullHtmlCleaner::class;
834
835
        $page = SiteTree::create();
836
        $page->write();
837
        $this->assertEquals(1, $page->Version);
838
839
        // Use inline element to avoid double wrapping applied to
840
        // blocklevel elements depending on HTMLCleaner implementation:
841
        // <ins><p> gets converted to <ins><p><inst>
842
        $page->Content = "<span>This is a test</span>";
843
        $page->write();
844
        $this->assertEquals(2, $page->Version);
845
846
        $diff = $page->compareVersions(1, 2);
847
848
        $processedContent = trim($diff->Content);
849
        $processedContent = preg_replace('/\s*</', '<', $processedContent);
850
        $processedContent = preg_replace('/>\s*/', '>', $processedContent);
851
        $this->assertEquals("<ins><span>This is a test</span></ins>", $processedContent);
852
853
        Diff::$html_cleaner_class = $oldCleanerClass;
854
    }
855
856
    public function testAuthorIDAndPublisherIDFilledOutOnPublish()
857
    {
858
        // Ensure that we have a member ID who is doing all this work
859
        $member = $this->objFromFixture(Member::class, "admin");
860
        $this->logInAs($member);
861
862
        // Write the page
863
        $about = $this->objFromFixture(SiteTree::class, 'about');
864
        $about->Title = "Another title";
865
        $about->write();
866
867
        // Check the version created
868
        $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

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

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

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

1694
        $mockPageMissesCache = /** @scrutinizer ignore-deprecated */ $this->getMockBuilder($pageClass)

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...
1695
            ->setMethods(['canCreate'])
1696
            ->getMock();
1697
        $mockPageMissesCache
1698
            ->expects($this->exactly(3))
1699
            ->method('canCreate');
1700
1701
        $mockPageHitsCache = $this->getMockBuilder($pageClass)
0 ignored issues
show
Deprecated Code introduced by
The function PHPUnit\Framework\MockOb...ckBuilder::setMethods() has been deprecated: https://github.com/sebastianbergmann/phpunit/pull/3687 ( Ignorable by Annotation )

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

1701
        $mockPageHitsCache = /** @scrutinizer ignore-deprecated */ $this->getMockBuilder($pageClass)

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...
1702
            ->setMethods(['canCreate'])
1703
            ->getMock();
1704
        $mockPageHitsCache
1705
            ->expects($this->never())
1706
            ->method('canCreate');
1707
1708
        // Initially, cache misses (1)
1709
        Injector::inst()->registerService($mockPageMissesCache, $pageClass);
1710
        $title = $siteTree->getTreeTitle();
1711
        $this->assertNotNull($title);
1712
1713
        // Now it hits
1714
        Injector::inst()->registerService($mockPageHitsCache, $pageClass);
1715
        $title = $siteTree->getTreeTitle();
1716
        $this->assertNotNull($title);
1717
1718
1719
        // Mutating member record invalidates cache. Misses (2)
1720
        $user->FirstName = 'changed';
1721
        $user->write();
1722
        Injector::inst()->registerService($mockPageMissesCache, $pageClass);
1723
        $title = $siteTree->getTreeTitle();
1724
        $this->assertNotNull($title);
1725
1726
        // Now it hits again
1727
        Injector::inst()->registerService($mockPageHitsCache, $pageClass);
1728
        $title = $siteTree->getTreeTitle();
1729
        $this->assertNotNull($title);
1730
1731
        // Different user. Misses. (3)
1732
        $user = $this->objFromFixture(Member::class, 'editor');
1733
        Security::setCurrentUser($user);
1734
        Injector::inst()->registerService($mockPageMissesCache, $pageClass);
1735
        $title = $siteTree->getTreeTitle();
1736
        $this->assertNotNull($title);
1737
    }
1738
1739
    public function testDependentPagesOnUnsavedRecord()
1740
    {
1741
        $record = new SiteTree();
1742
        $pages = $record->DependentPages();
1743
        $this->assertCount(0, $pages, 'Unsaved pages should have no dependent pages');
1744
    }
1745
}
1746