VirtualPageTest::testVirtualPageAsAnAllowedChild()   A
last analyzed

Complexity

Conditions 5
Paths 16

Size

Total Lines 40
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 27
nc 16
nop 0
dl 0
loc 40
rs 9.1768
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\CMS\Tests\Model;
4
5
use SilverStripe\CMS\Controllers\ModelAsController;
6
use SilverStripe\CMS\Model\RedirectorPage;
7
use SilverStripe\CMS\Model\SiteTree;
8
use SilverStripe\CMS\Model\VirtualPage;
9
use SilverStripe\Core\Config\Config;
10
use SilverStripe\Dev\FunctionalTest;
11
use SilverStripe\ORM\DataObject;
12
use SilverStripe\ORM\ValidationException;
13
use SilverStripe\Security\Member;
14
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...
15
use SilverStripe\Versioned\Versioned;
16
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...
17
18
class VirtualPageTest extends FunctionalTest
19
{
20
    protected static $fixture_file = 'VirtualPageTest.yml';
21
22
    protected $autoFollowRedirection = false;
23
24
    protected static $extra_dataobjects = [
25
        VirtualPageTest_ClassA::class,
26
        VirtualPageTest_ClassB::class,
27
        VirtualPageTest_ClassC::class,
28
        VirtualPageTest_NotRoot::class,
29
        VirtualPageTest_PageExtension::class,
30
        VirtualPageTest_PageWithAllowedChildren::class,
31
        VirtualPageTest_TestDBField::class,
32
        VirtualPageTest_VirtualPageSub::class,
33
    ];
34
35
    protected static $illegal_extensions = [
36
        SiteTree::class => [
37
            SiteTreeSubsites::class,
38
            FluentSiteTreeExtension::class,
39
        ],
40
    ];
41
42
    protected static $required_extensions = [
43
        SiteTree::class => [
44
            VirtualPageTest_PageExtension::class,
45
        ],
46
    ];
47
48
    protected function setUp() : void
49
    {
50
        parent::setUp();
51
52
        // Ensure we always have permission to save/publish
53
        $this->logInWithPermission("ADMIN");
54
55
        // Add extra fields
56
        Config::modify()->merge(VirtualPage::class, 'initially_copied_fields', ['MyInitiallyCopiedField']);
57
        Config::modify()->merge(
58
            VirtualPage::class,
59
            'non_virtual_fields',
60
            ['MyNonVirtualField', 'MySharedNonVirtualField']
61
        );
62
63
        // Ensure all pages are published
64
        /** @var SiteTree $page */
65
        foreach (SiteTree::get() as $page) {
66
            $page->publishSingle();
67
        }
68
    }
69
70
    /**
71
     * Test that, after you update the source page of a virtual page, all the virtual pages
72
     * are updated
73
     */
74
    public function testEditingSourcePageUpdatesVirtualPages()
75
    {
76
        /** @var SiteTree $master */
77
        $master = $this->objFromFixture(SiteTree::class, 'master');
78
        $master->Title = "New title";
79
        $master->MenuTitle = "New menutitle";
80
        $master->Content = "<p>New content</p>";
81
        $master->write();
82
        $master->publishSingle();
83
84
        $vp1 = $this->objFromFixture(VirtualPage::class, 'vp1');
85
        $vp2 = $this->objFromFixture(VirtualPage::class, 'vp2');
86
87
        $this->assertEquals("New title", $vp1->Title);
88
        $this->assertEquals("New title", $vp2->Title);
89
        $this->assertEquals("New menutitle", $vp1->MenuTitle);
90
        $this->assertEquals("New menutitle", $vp2->MenuTitle);
91
        $this->assertEquals("<p>New content</p>", $vp1->Content);
92
        $this->assertEquals("<p>New content</p>", $vp2->Content);
93
    }
94
95
    /**
96
     * Test that, after you publish the source page of a virtual page, all the already published
97
     * virtual pages are published
98
     */
99
    public function testPublishingSourcePagePublishesAlreadyPublishedVirtualPages()
100
    {
101
        $this->logInWithPermission('ADMIN');
102
103
        /** @var SiteTree $master */
104
        $master = $this->objFromFixture(SiteTree::class, 'master');
105
        $master->publishRecursive();
106
107
        $master->Title = "New title";
108
        $master->MenuTitle = "New menutitle";
109
        $master->Content = "<p>New content</p>";
110
        $master->write();
111
112
        /** @var VirtualPage $vp1 */
113
        $vp1 = DataObject::get_by_id(VirtualPage::class, $this->idFromFixture(VirtualPage::class, 'vp1'));
114
        /** @var VirtualPage $vp2 */
115
        $vp2 = DataObject::get_by_id(VirtualPage::class, $this->idFromFixture(VirtualPage::class, 'vp2'));
116
        $this->assertTrue($vp1->publishRecursive());
117
        $this->assertTrue($vp2->publishRecursive());
118
119
        $master->publishRecursive();
120
121
        Versioned::set_stage(Versioned::LIVE);
122
        $vp1 = DataObject::get_by_id(VirtualPage::class, $this->idFromFixture(VirtualPage::class, 'vp1'));
123
        $vp2 = DataObject::get_by_id(VirtualPage::class, $this->idFromFixture(VirtualPage::class, 'vp2'));
124
125
        $this->assertNotNull($vp1);
126
        $this->assertNotNull($vp2);
127
128
        $this->assertEquals("New title", $vp1->Title);
129
        $this->assertEquals("New title", $vp2->Title);
130
        $this->assertEquals("New menutitle", $vp1->MenuTitle);
131
        $this->assertEquals("New menutitle", $vp2->MenuTitle);
132
        $this->assertEquals("<p>New content</p>", $vp1->Content);
133
        $this->assertEquals("<p>New content</p>", $vp2->Content);
134
        Versioned::set_stage(Versioned::DRAFT);
135
    }
136
137
    /**
138
     * Test that virtual pages get the content from the master page when they are created.
139
     */
140
    public function testNewVirtualPagesGrabTheContentFromTheirMaster()
141
    {
142
        $vp = new VirtualPage();
143
        $vp->write();
144
145
        $vp->CopyContentFromID = $this->idFromFixture(SiteTree::class, 'master');
146
        $vp->write();
147
148
        $this->assertEquals("My Page", $vp->Title);
149
        $this->assertEquals("My Page Nav", $vp->MenuTitle);
150
151
        $vp->CopyContentFromID = $this->idFromFixture(SiteTree::class, 'master2');
152
        $vp->write();
153
154
        $this->assertEquals("My Other Page", $vp->Title);
155
        $this->assertEquals("My Other Page Nav", $vp->MenuTitle);
156
    }
157
158
    /**
159
     * Virtual pages are always supposed to chose the same content as the published source page.
160
     * This means that when you publish them, they should show the published content of the source
161
     * page, not the draft content at the time when you clicked 'publish' in the CMS.
162
     */
163
    public function testPublishingAVirtualPageCopiedPublishedContentNotDraftContent()
164
    {
165
        $p = SiteTree::create();
166
        $p->Content = "published content";
167
        $p->write();
168
        $p->publishRecursive();
169
170
        // Virtual page has this content
171
        $vp = new VirtualPage();
172
        $vp->CopyContentFromID = $p->ID;
173
        $vp->write();
174
175
        $vp->publishRecursive();
176
177
        // Don't publish this change - published page will still say 'published content'
178
        $p->Content = "draft content";
179
        $p->write();
180
181
        // The draft content of the virtual page should say 'draft content'
182
        /** @var VirtualPage $vpDraft */
183
        $vpDraft = Versioned::get_by_stage(VirtualPage::class, Versioned::DRAFT)->byID($vp->ID);
184
        $this->assertEquals('draft content', $vpDraft->CopyContentFrom()->Content);
185
        $this->assertEquals('draft content', $vpDraft->Content);
186
187
        // The published content of the virtual page should say 'published content'
188
        /** @var VirtualPage $vpLive */
189
        $vpLive = Versioned::get_by_stage(VirtualPage::class, Versioned::LIVE)->byID($vp->ID);
190
        $this->assertEquals('published content', $vpLive->CopyContentFrom()->Content);
191
        $this->assertEquals('published content', $vpLive->Content);
192
193
        // Publishing the virtualpage should, however, trigger publishing of the live page
194
        $vpDraft->publishRecursive();
195
196
        // Everything is published live
197
        $vpLive = Versioned::get_by_stage(VirtualPage::class, Versioned::LIVE)->byID($vp->ID);
198
        $this->assertEquals('draft content', $vpLive->CopyContentFrom()->Content);
199
        $this->assertEquals('draft content', $vpLive->Content);
200
    }
201
202
    public function testCantPublishVirtualPagesBeforeTheirSource()
203
    {
204
        // An unpublished source page
205
        $p = SiteTree::create();
206
        $p->Content = "test content";
207
        $p->write();
208
209
        // With no source page, we can't publish
210
        $vp = new VirtualPage();
211
        $vp->write();
212
        $this->assertFalse($vp->canPublish());
213
214
        // When the source page isn't published, we can't publish
215
        $vp->CopyContentFromID = $p->ID;
216
        $vp->write();
217
        $this->assertFalse($vp->canPublish());
218
219
        // Once the source page gets published, then we can publish
220
        $p->publishRecursive();
221
        $this->assertTrue($vp->canPublish());
222
    }
223
224
    public function testCanEdit()
225
    {
226
        $parentPage = $this->objFromFixture(SiteTree::class, 'master3');
227
        $virtualPage = $this->objFromFixture(VirtualPage::class, 'vp3');
228
        $bob = $this->objFromFixture(Member::class, 'bob');
229
        $andrew = $this->objFromFixture(Member::class, 'andrew');
230
231
        // Bob can edit the mirrored page, but he shouldn't be able to edit the virtual page.
232
        $this->logInAs($bob);
233
        $this->assertTrue($parentPage->canEdit());
234
        $this->assertFalse($virtualPage->canEdit());
235
236
        //  Andrew can only edit the virtual page, but not the original.
237
        $this->logInAs($andrew);
238
        $this->assertFalse($parentPage->canEdit());
239
        $this->assertTrue($virtualPage->canEdit());
240
    }
241
242
    public function testCanView()
243
    {
244
        /** @var SiteTree $parentPage */
245
        $parentPage = $this->objFromFixture(SiteTree::class, 'master3');
246
        $parentPage->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
247
248
        /** @var VirtualPage $virtualPage */
249
        $virtualPage = $this->objFromFixture(VirtualPage::class, 'vp3');
250
        $virtualPage->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
251
        $cindy = $this->objFromFixture(Member::class, 'cindy');
252
        $alice = $this->objFromFixture(Member::class, 'alice');
253
254
        // Cindy can see both pages
255
        $this->logInAs($cindy);
256
        $this->assertTrue($parentPage->canView());
257
        $this->assertTrue($virtualPage->canView());
258
259
        // Alice can't see the virtual page, since it's restricted to cindy
260
        $this->logInAs($alice);
261
        $this->assertTrue($parentPage->canView());
262
        $this->assertFalse($virtualPage->canView());
263
    }
264
265
    public function testVirtualPagesArentInappropriatelyPublished()
266
    {
267
        // Fixture
268
        $p = SiteTree::create();
269
        $p->Content = "test content";
270
        $p->write();
271
        $vp = new VirtualPage();
272
        $vp->CopyContentFromID = $p->ID;
273
        $vp->write();
274
275
        // VP is oragne
276
        $this->assertTrue($vp->isOnDraftOnly());
277
278
        // VP is still orange after we publish
279
        $p->publishRecursive();
280
        $this->assertTrue($vp->isOnDraftOnly());
281
282
        // A new VP created after P's initial construction
283
        $vp2 = new VirtualPage();
284
        $vp2->CopyContentFromID = $p->ID;
285
        $vp2->write();
286
        $this->assertTrue($vp2->isOnDraftOnly());
287
288
        // Also remains orange after a republish
289
        $p->Content = "new content";
290
        $p->write();
291
        $p->publishRecursive();
292
        $this->assertTrue($vp2->isOnDraftOnly());
293
294
        // VP is now published
295
        $vp->publishRecursive();
296
297
        $this->assertTrue($vp->isPublished());
298
        $this->assertFalse($vp->isModifiedOnDraft());
299
300
        // P edited, P goes green. Change set interface should indicate to the user that the owned page has
301
        // modifications, although the virtual page record itself will not appear as having pending changes.
302
        $p->Content = "third content";
303
        $p->write();
304
305
        $this->assertTrue($p->isModifiedOnDraft());
306
        $this->assertFalse($vp->isModifiedOnDraft());
307
308
        // Publish, VP goes black
309
        $p->publishRecursive();
310
        $this->assertTrue($vp->isPublished());
311
        $this->assertFalse($vp->isModifiedOnDraft());
312
    }
313
314
    public function testUnpublishingSourcePageOfAVirtualPageAlsoUnpublishesVirtualPage()
315
    {
316
        // Create page and virutal page
317
        $p = SiteTree::create();
318
        $p->Title = "source";
319
        $p->write();
320
        $this->assertTrue($p->publishRecursive());
321
        $vp = new VirtualPage();
322
        $vp->CopyContentFromID = $p->ID;
323
        $vp->write();
324
        $vpID = $vp->ID;
325
        $this->assertTrue($vp->publishRecursive());
326
327
        // All is fine, the virtual page doesn't have a broken link
328
        $this->assertFalse($vp->HasBrokenLink);
329
330
        // Unpublish the source page, confirm that the virtual page has also been unpublished
331
        $p->doUnpublish();
332
333
        // The draft VP still has the CopyContentFromID link
334
        $vp->flushCache();
335
        $vp = DataObject::get_by_id(SiteTree::class, $vpID);
336
        $this->assertEquals($p->ID, $vp->CopyContentFromID);
337
        $vpLive = Versioned::get_by_stage(SiteTree::class, Versioned::LIVE)->byID($vpID);
338
        $this->assertNull($vpLive);
339
        // Delete from draft, ensure virtual page deletion cascades
340
        $p->delete();
341
        $vp->flushCache();
342
        $vp = DataObject::get_by_id(SiteTree::class, $vpID);
343
        $this->assertNull($vp);
344
    }
345
346
    public function testDeletingFromLiveSourcePageOfAVirtualPageAlsoUnpublishesVirtualPage()
347
    {
348
        // Create page and virutal page
349
        $p = SiteTree::create();
350
        $p->Title = "source";
351
        $p->write();
352
        $this->assertTrue($p->publishRecursive());
353
        $vp = new VirtualPage();
354
        $vp->CopyContentFromID = $p->ID;
355
        $vp->write();
356
        $vpID = $vp->ID;
357
        $this->assertTrue($vp->publishRecursive());
358
359
        // All is fine, the virtual page doesn't have a broken link
360
        $this->assertFalse($vp->HasBrokenLink);
361
        // Delete the source page from draft, cascades to virtual page
362
        $pID = $p->ID;
363
        $p->delete();
364
        $vp->flushCache();
365
        $vpDraft = Versioned::get_by_stage(SiteTree::class, Versioned::DRAFT)
366
            ->byID($pID);
367
        $this->assertNull($vpDraft);
368
        // Delete the source page form live, confirm that the virtual page has also been unpublished
369
        /** @var SiteTree $pLive */
370
        $pLive = Versioned::get_by_stage(SiteTree::class, Versioned::LIVE)
371
            ->byID($pID);
372
        $this->assertTrue($pLive->doUnpublish());
373
        $vpLive = Versioned::get_by_stage(SiteTree::class, Versioned::LIVE)
374
            ->byID($vpID);
375
        $this->assertNull($vpLive);
376
    }
377
378
    /**
379
     * Base functionality tested in {@link SiteTreeTest->testAllowedChildrenValidation()}.
380
     */
381
    public function testAllowedChildrenLimitedOnVirtualPages()
382
    {
383
        $classA = new SiteTreeTest_ClassA();
384
        $classA->write();
385
        $classB = new SiteTreeTest_ClassB();
386
        $classB->write();
387
        $classBVirtual = new VirtualPage();
388
        $classBVirtual->CopyContentFromID = $classB->ID;
389
        $classBVirtual->write();
390
        $classC = new SiteTreeTest_ClassC();
391
        $classC->write();
392
        $classCVirtual = new VirtualPage();
393
        $classCVirtual->CopyContentFromID = $classC->ID;
394
        $classCVirtual->write();
395
396
        $classBVirtual->ParentID = $classA->ID;
397
        $valid = $classBVirtual->doValidate();
398
        $this->assertTrue($valid->isValid(), "Does allow child linked to virtual page type allowed by parent");
399
400
        $classCVirtual->ParentID = $classA->ID;
401
        $valid = $classCVirtual->doValidate();
402
        $this->assertFalse($valid->isValid(), "Doesn't allow child linked to virtual page type disallowed by parent");
403
    }
404
405
    public function testGetVirtualFields()
406
    {
407
        // Needs association with an original, otherwise will just return the "base" virtual fields
408
        $page = new VirtualPageTest_ClassA();
409
        $page->write();
410
        $virtual = new VirtualPage();
411
        $virtual->CopyContentFromID = $page->ID;
412
        $virtual->write();
413
414
        $this->assertContains('MyVirtualField', $virtual->getVirtualFields());
415
        $this->assertNotContains('MyNonVirtualField', $virtual->getVirtualFields());
416
        $this->assertNotContains('MyInitiallyCopiedField', $virtual->getVirtualFields());
417
    }
418
419
    public function testCopyFrom()
420
    {
421
        $original = new VirtualPageTest_ClassA();
422
        $original->MyInitiallyCopiedField = 'original';
0 ignored issues
show
Bug Best Practice introduced by
The property MyInitiallyCopiedField does not exist on SilverStripe\CMS\Tests\M...\VirtualPageTest_ClassA. Since you implemented __set, consider adding a @property annotation.
Loading history...
423
        $original->MyVirtualField = 'original';
0 ignored issues
show
Bug Best Practice introduced by
The property MyVirtualField does not exist on SilverStripe\CMS\Tests\M...\VirtualPageTest_ClassA. Since you implemented __set, consider adding a @property annotation.
Loading history...
424
        $original->MyNonVirtualField = 'original';
0 ignored issues
show
Bug Best Practice introduced by
The property MyNonVirtualField does not exist on SilverStripe\CMS\Tests\M...\VirtualPageTest_ClassA. Since you implemented __set, consider adding a @property annotation.
Loading history...
425
        $original->write();
426
427
        $virtual = new VirtualPage();
428
        $virtual->CopyContentFromID = $original->ID;
429
        $virtual->write();
430
431
        // Using getField() to avoid side effects from an overloaded __get()
432
        $this->assertEquals(
433
            'original',
434
            $virtual->getField('MyInitiallyCopiedField'),
435
            'Fields listed in $initially_copied_fields are copied on first copyFrom() invocation'
436
        );
437
        $this->assertEquals(
438
            'original',
439
            $virtual->getField('MyVirtualField'),
440
            'Fields not listed in $initially_copied_fields are copied in copyFrom()'
441
        );
442
        $this->assertNull(
443
            $virtual->getField('MyNonVirtualField'),
444
            'Fields listed in $non_virtual_fields are not copied in copyFrom()'
445
        );
446
447
        $original->MyInitiallyCopiedField = 'changed';
448
        $original->write();
449
        $this->assertEquals(
450
            'original',
451
            $virtual->MyInitiallyCopiedField,
452
            'Fields listed in $initially_copied_fields are not copied on subsequent copyFrom() invocations'
453
        );
454
    }
455
456
    public function testCanBeRoot()
457
    {
458
        $page = SiteTree::create();
459
        $page->ParentID = 0;
460
        $page->write();
461
462
        $notRootPage = new VirtualPageTest_NotRoot();
463
        // we don't want the original on root, but rather the VirtualPage pointing to it
464
        $notRootPage->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...
465
        $notRootPage->write();
466
467
        $virtual = new VirtualPage();
468
        $virtual->CopyContentFromID = $page->ID;
469
        $virtual->write();
470
471
        $virtual = DataObject::get_by_id(VirtualPage::class, $virtual->ID, false);
472
        $virtual->CopyContentFromID = $notRootPage->ID;
473
        $virtual->flushCache();
474
475
        $isDetected = false;
476
        try {
477
            $virtual->write();
478
        } catch (ValidationException $e) {
479
            $this->assertStringContainsString('is not allowed on the root level', $e->getMessage());
480
            $isDetected = true;
481
        }
482
483
        if (!$isDetected) {
484
            $this->fail('Fails validation with $can_be_root=false');
485
        }
486
    }
487
488
    public function testPageTypeChangePropagatesToLive()
489
    {
490
        $page = SiteTree::create();
491
        $page->Title = 'published title';
492
        $page->MySharedNonVirtualField = 'original';
493
        $page->write();
494
        $page->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
495
496
        $virtual = new VirtualPageTest_VirtualPageSub();
497
        $virtual->CopyContentFromID = $page->ID;
498
        $virtual->MySharedNonVirtualField = 'virtual published field';
499
        $virtual->write();
500
        $virtual->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
501
502
        $page->Title = 'original'; // 'Title' is a virtual field
503
        // Publication would causes the virtual field to copy through onBeforeWrite(),
504
        // but we want to test that it gets copied on class name change instead
505
        $page->write();
506
507
508
        $nonVirtual = $virtual;
509
        $nonVirtual->ClassName = VirtualPageTest_ClassA::class;
510
        $nonVirtual->MySharedNonVirtualField = 'changed on new type';
511
        $nonVirtual->write(); // not publishing the page type change here
512
513
        // Stage record is changed to the new type and no longer acts as a virtual page
514
        $nonVirtualStage = Versioned::get_one_by_stage(
515
            SiteTree::class,
516
            'Stage',
517
            '"SiteTree"."ID" = ' . $nonVirtual->ID,
0 ignored issues
show
Bug Best Practice introduced by
The property ID does not exist on SilverStripe\CMS\Tests\M...PageTest_VirtualPageSub. Since you implemented __get, consider adding a @property annotation.
Loading history...
518
            false
519
        );
520
        $this->assertNotNull($nonVirtualStage);
521
        $this->assertEquals(VirtualPageTest_ClassA::class, $nonVirtualStage->ClassName);
522
        $this->assertEquals('changed on new type', $nonVirtualStage->MySharedNonVirtualField);
523
        $this->assertEquals(
524
            'original',
525
            $nonVirtualStage->Title,
526
            'Copies virtual fields from original draft into new instance on type change '
527
        );
528
529
        // Virtual page on live keeps working as it should
530
        $virtualLive = Versioned::get_one_by_stage(
531
            SiteTree::class,
532
            Versioned::LIVE,
533
            '"SiteTree_Live"."ID" = ' . $virtual->ID,
534
            false
535
        );
536
        $this->assertNotNull($virtualLive);
537
        $this->assertEquals(VirtualPageTest_VirtualPageSub::class, $virtualLive->ClassName);
538
        $this->assertEquals('virtual published field', $virtualLive->MySharedNonVirtualField);
539
        $this->assertEquals('published title', $virtualLive->Title);
540
541
        // Change live page
542
        $page->Title = 'title changed on original';
543
        $page->MySharedNonVirtualField = 'changed only on original';
544
        $page->write();
545
        $page->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
546
547
        // Virtual page only notices changes to virtualised fields (Title)
548
        $virtualLive = Versioned::get_one_by_stage(
549
            SiteTree::class,
550
            Versioned::LIVE,
551
            '"SiteTree_Live"."ID" = ' . $virtual->ID,
552
            false
553
        );
554
        $this->assertEquals('virtual published field', $virtualLive->MySharedNonVirtualField);
555
        $this->assertEquals('title changed on original', $virtualLive->Title);
556
    }
557
558
    public function testVirtualPageFindsCorrectCasting()
559
    {
560
        $page = new VirtualPageTest_ClassA();
561
        $page->CastingTest = "Some content";
0 ignored issues
show
Bug Best Practice introduced by
The property CastingTest does not exist on SilverStripe\CMS\Tests\M...\VirtualPageTest_ClassA. Since you implemented __set, consider adding a @property annotation.
Loading history...
562
        $page->write();
563
        $virtual = new VirtualPage();
564
        $virtual->CopyContentFromID = $page->ID;
565
        $virtual->write();
566
567
        $this->assertEquals(VirtualPageTest_TestDBField::class, $virtual->castingHelper('CastingTest'));
568
        $this->assertEquals('SOME CONTENT', $virtual->obj('CastingTest')->forTemplate());
569
    }
570
571
    public function testVirtualPageAsAnAllowedChild()
572
    {
573
        $parentPage = new VirtualPageTest_PageWithAllowedChildren();
574
        $parentPage->write();
575
576
        $childPage = new VirtualPageTest_ClassA();
577
        $childPage->ParentID = $parentPage->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...
578
        $childPage->write();
579
580
        // Check we're allowed to create a VirtualPage without linking it to a page yet
581
        $childVirtualPage = new VirtualPage();
582
        $childVirtualPage->ParentID = $parentPage->ID;
583
        try {
584
            $childVirtualPage->write();
585
        } catch (ValidationException $e) {
586
            $this->fail('Failed to write VirtualPage when it is an allowed child');
587
        }
588
589
        // Check that we can link a VirtualPage to a page type that's an allowed child
590
        $childVirtualPage->CopyContentFromID = $childPage->ID;
591
        try {
592
            $childVirtualPage->write();
593
        } catch (ValidationException $e) {
594
            $this->fail('Failed to write VirtualPage when it is linked to an allowed child');
595
        }
596
597
        // Check that we CAN'T link a VirtualPage to a page that is NOT an allowed child
598
        $disallowedChild = new VirtualPageTest_ClassB();
599
        $disallowedChild->write();
600
        $childVirtualPage->CopyContentFromID = $disallowedChild->ID;
601
        $isDetected = false;
602
        try {
603
            $childVirtualPage->write();
604
        } catch (ValidationException $e) {
605
            $this->assertStringContainsString('not allowed as child of this parent page', $e->getMessage());
606
            $isDetected = true;
607
        }
608
609
        if (!$isDetected) {
610
            $this->fail("Shouldn't be allowed to write a VirtualPage that links to a disallowed child");
611
        }
612
    }
613
614
    public function testVirtualPagePointingToRedirectorPage()
615
    {
616
        $rp = new RedirectorPage(['ExternalURL' => 'http://google.com', 'RedirectionType' => 'External']);
617
        $rp->write();
618
        $rp->publishRecursive();
619
620
        $vp = new VirtualPage(['URLSegment' => 'vptest', 'CopyContentFromID' => $rp->ID]);
621
        $vp->write();
622
        $vp->publishRecursive();
623
624
        $response = $this->get($vp->Link());
625
        $this->assertEquals(301, $response->getStatusCode());
626
        $this->assertEquals('http://google.com', $response->getHeader('Location'));
627
    }
628
629
    public function testVirtualPageRendersCorrectTemplate()
630
    {
631
        $this->useTestTheme(dirname(__FILE__), 'virtualpagetest', function () {
632
            $page = new VirtualPageTest_ClassA();
633
            $page->Title = 'Test Page';
634
            $page->Content = 'NotThisContent';
635
            $page->MyInitiallyCopiedField = 'TestContent';
0 ignored issues
show
Bug Best Practice introduced by
The property MyInitiallyCopiedField does not exist on SilverStripe\CMS\Tests\M...\VirtualPageTest_ClassA. Since you implemented __set, consider adding a @property annotation.
Loading history...
636
            $page->write();
637
            $page->publishSingle();
0 ignored issues
show
Bug introduced by
The method publishSingle() does not exist on SilverStripe\CMS\Tests\M...\VirtualPageTest_ClassA. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

637
            $page->/** @scrutinizer ignore-call */ 
638
                   publishSingle();
Loading history...
638
639
            $vp = new VirtualPage();
640
            $vp->CopyContentFromID = $page->ID;
641
            $vp->write();
642
            $vp->publishSingle();
643
644
            $response = $this->get($vp->Link());
645
            $this->assertEquals(200, $response->getStatusCode());
646
            $this->assertStringContainsString('TestContent', $response->getBody());
647
            $this->assertStringNotContainsString('NotThisContent', $response->getBody());
648
649
            // VirtualPageTest_ClassB doesn't have an associated controller for
650
            // ModelAsController::controller_for() to find
651
            $page = new VirtualPageTest_ClassB();
652
            $page->Title = 'Test Page B';
653
            $page->write();
654
            $page->publishSingle();
0 ignored issues
show
Bug introduced by
The method publishSingle() does not exist on SilverStripe\CMS\Tests\M...\VirtualPageTest_ClassB. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

654
            $page->/** @scrutinizer ignore-call */ 
655
                   publishSingle();
Loading history...
655
656
            $vp = new VirtualPage();
657
            $vp->CopyContentFromID = $page->ID;
658
            $vp->write();
659
            $vp->publishSingle();
660
661
            $response = $this->get($vp->Link());
662
            $this->assertEquals(200, $response->getStatusCode());
663
            $this->assertStringContainsString('Test Page B', $response->getBody());
664
        });
665
    }
666
667
    public function testMethod()
668
    {
669
        /** @var VirtualPage $virtualPage */
670
        $virtualPage = $this->objFromFixture(VirtualPage::class, 'vp4');
671
        /** @var VirtualPageTest_ClassAController $controller */
672
        $controller = ModelAsController::controller_for($virtualPage);
673
        $this->assertInstanceOf(VirtualPageTest_ClassAController::class, $controller);
674
        $this->assertTrue($controller->hasMethod('testMethod'));
675
        $this->assertEquals('hello', $controller->testMethod());
676
        $this->assertTrue($controller->hasMethod('modelMethod'));
677
        $this->assertEquals('hi there', $controller->modelMethod());
0 ignored issues
show
Bug introduced by
The method modelMethod() does not exist on SilverStripe\CMS\Tests\M...geTest_ClassAController. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

677
        $this->assertEquals('hi there', $controller->/** @scrutinizer ignore-call */ modelMethod());
Loading history...
678
    }
679
680
    public function testAllowedActions()
681
    {
682
        /** @var VirtualPage $virtualPage */
683
        $virtualPage = $this->objFromFixture(VirtualPage::class, 'vp4');
684
        $controller = ModelAsController::controller_for($virtualPage);
685
        $this->assertContains('testaction', $controller->allowedActions());
686
    }
687
}
688