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

tests/php/Model/SiteTreeBrokenLinksTest.php (1 issue)

1
<?php
2
3
namespace SilverStripe\CMS\Tests\Model;
4
5
use Silverstripe\Assets\Dev\TestAssetStore;
6
use SilverStripe\CMS\Model\RedirectorPage;
7
use SilverStripe\CMS\Model\SiteTree;
8
use SilverStripe\CMS\Model\SiteTreeLink;
9
use SilverStripe\CMS\Model\VirtualPage;
10
use SilverStripe\CMS\Tests\Model\SiteTreeBrokenLinksTest\NotPageObject;
11
use SilverStripe\Dev\SapphireTest;
12
use SilverStripe\ORM\DataObject;
13
use SilverStripe\ORM\DB;
14
use SilverStripe\Versioned\Versioned;
15
16
/**
17
 * Tests {@see SiteTreeLinkTracking} broken links feature: LinkTracking
18
 */
19
class SiteTreeBrokenLinksTest extends SapphireTest
20
{
21
    protected static $fixture_file = 'SiteTreeBrokenLinksTest.yml';
22
23
    protected static $extra_dataobjects = [
24
        NotPageObject::class,
25
    ];
26
27
    public function setUp()
28
    {
29
        parent::setUp();
30
31
        Versioned::set_stage(Versioned::DRAFT);
32
        TestAssetStore::activate('SiteTreeBrokenLinksTest');
33
        $this->logInWithPermission('ADMIN');
34
    }
35
36
    public function tearDown()
37
    {
38
        TestAssetStore::reset();
39
        parent::tearDown();
40
    }
41
42
    public function testBrokenLinksBetweenPages()
43
    {
44
        /** @var SiteTree $obj */
45
        $obj = $this->objFromFixture(SiteTree::class, 'content');
46
47
        $obj->Content = '<a href="[sitetree_link,id=3423423]">this is a broken link</a>';
48
        $obj->syncLinkTracking();
49
        $this->assertTrue($obj->HasBrokenLink, 'Page has a broken link');
50
51
        $obj->Content = '<a href="[sitetree_link,id=' . $this->idFromFixture(
52
            SiteTree::class,
53
            'about'
54
        ) . ']">this is not a broken link</a>';
55
        $obj->syncLinkTracking();
56
        $this->assertFalse($obj->HasBrokenLink, 'Page does NOT have a broken link');
57
    }
58
59
    /**
60
     * Ensure broken links can be tracked between non-page objects
61
     */
62
    public function testBrokenLinksNonPage()
63
    {
64
        /** @var SiteTree $aboutPage */
65
        $aboutPage = $this->objFromFixture(SiteTree::class, 'about');
66
67
        /** @var NotPageObject $obj */
68
        $obj = $this->objFromFixture(NotPageObject::class, 'object1');
69
        $obj->Content = '<a href="[sitetree_link,id=3423423]">this is a broken link</a>';
70
        $obj->AnotherContent = '<a href="[sitetree_link,id=' . $aboutPage->ID . ']">this is not a broken link</a>';
71
        $obj->write();
72
73
        // Two links created for this record
74
        $this->assertListEquals(
75
            [
76
                ['LinkedID' => 3423423],
77
                ['LinkedID' => $aboutPage->ID],
78
            ],
79
            SiteTreeLink::get()->filter([
80
                'ParentClass' => NotPageObject::class,
81
                'ParentID' => $obj->ID,
82
            ])
83
        );
84
85
        // ManyManyThrough relation only links to unbroken pages
86
        $this->assertListEquals(
87
            [
88
                ['Title' => 'About'],
89
            ],
90
            $obj->LinkTracking()
0 ignored issues
show
The method LinkTracking() does not exist on SilverStripe\CMS\Tests\M...LinksTest\NotPageObject. 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

90
            $obj->/** @scrutinizer ignore-call */ 
91
                  LinkTracking()
Loading history...
91
        );
92
93
        // About-page backlinks contains this object
94
        $this->assertListEquals(
95
            [
96
                ['ID' => $obj->ID],
97
            ],
98
            $aboutPage->BackLinkTracking()
99
        );
100
    }
101
102
    public function testBrokenAnchorBetweenPages()
103
    {
104
        /** @var SiteTree $obj */
105
        $obj = $this->objFromFixture(SiteTree::class, 'content');
106
        $target = $this->objFromFixture(SiteTree::class, 'about');
107
108
        $obj->Content = "<a href=\"[sitetree_link,id={$target->ID}]#no-anchor-here\">this is a broken link</a>";
109
        $obj->syncLinkTracking();
110
        $this->assertTrue($obj->HasBrokenLink, 'Page has a broken link');
111
112
        $obj->Content = "<a href=\"[sitetree_link,id={$target->ID}]#yes-anchor-here\">this is not a broken link</a>";
113
        $obj->syncLinkTracking();
114
        $this->assertFalse($obj->HasBrokenLink, 'Page does NOT have a broken link');
115
    }
116
117
    public function testBrokenVirtualPages()
118
    {
119
        $obj = $this->objFromFixture(SiteTree::class, 'content');
120
        $vp = new VirtualPage();
121
122
        $vp->CopyContentFromID = $obj->ID;
123
        $vp->syncLinkTracking();
124
        $this->assertFalse($vp->HasBrokenLink, 'Working virtual page is NOT marked as broken');
125
126
        $vp->CopyContentFromID = 12345678;
127
        $vp->syncLinkTracking();
128
        $this->assertTrue($vp->HasBrokenLink, 'Broken virtual page IS marked as such');
129
    }
130
131
    public function testBrokenInternalRedirectorPages()
132
    {
133
        $obj = $this->objFromFixture(SiteTree::class, 'content');
134
        $rp = new RedirectorPage();
135
136
        $rp->RedirectionType = 'Internal';
137
138
        $rp->LinkToID = $obj->ID;
139
        $rp->syncLinkTracking();
140
        $this->assertFalse($rp->HasBrokenLink, 'Working redirector page is NOT marked as broken');
141
142
        $rp->LinkToID = 12345678;
143
        $rp->syncLinkTracking();
144
        $this->assertTrue($rp->HasBrokenLink, 'Broken redirector page IS marked as such');
145
    }
146
147
    public function testDeletingMarksBackLinkedPagesAsBroken()
148
    {
149
        // Set up two published pages with a link from content -> about
150
        $linkDest = $this->objFromFixture(SiteTree::class, 'about');
151
152
        /** @var SiteTree $linkSrc */
153
        $linkSrc = $this->objFromFixture(SiteTree::class, 'content');
154
        $linkSrc->Content = "<p><a href=\"[sitetree_link,id=$linkDest->ID]\">about us</a></p>";
155
        $linkSrc->write();
156
157
        // Confirm no broken link
158
        $this->assertEquals(0, (int)$linkSrc->HasBrokenLink);
159
160
        // Delete page from draft
161
        $linkDest->delete();
162
163
        // Confirm draft has broken link
164
        $linkSrc->flushCache();
165
        $linkSrc = $this->objFromFixture(SiteTree::class, 'content');
166
167
        $this->assertEquals(1, (int)$linkSrc->HasBrokenLink);
168
    }
169
170
    public function testPublishingSourceBeforeDestHasBrokenLink()
171
    {
172
        $this->logInWithPermission('ADMIN');
173
174
        // Set up two draft pages with a link from content -> about
175
        /** @var SiteTree $linkDest */
176
        $linkDest = $this->objFromFixture(SiteTree::class, 'about');
177
        // Ensure that it's not on the published site
178
        $linkDest->doUnpublish();
179
180
        /** @var SiteTree $linkSrc */
181
        $linkSrc = $this->objFromFixture(SiteTree::class, 'content');
182
        $linkSrc->Content = "<p><a href=\"[sitetree_link,id=$linkDest->ID]\">about us</a></p>";
183
        $linkSrc->write();
184
185
        // Publish the source of the link, while the dest is still unpublished.
186
        $linkSrc->publishRecursive();
187
188
        // Verify that the link is not marked as broken on draft (source of truth)
189
        $this->assertEquals(0, (int)$linkSrc->HasBrokenLink);
190
191
        // Live doesn't have separate broken link tracking
192
        $this->assertEquals(0, DB::query("SELECT \"HasBrokenLink\" FROM \"SiteTree_Live\"
193
            WHERE \"ID\" = $linkSrc->ID")->value());
194
    }
195
196
    public function testRestoreFixesBrokenLinks()
197
    {
198
        // Create page and virtual page
199
        $p = SiteTree::create();
200
        $p->Title = "source";
201
        $p->write();
202
        $pageID = $p->ID;
203
        $this->assertTrue($p->publishRecursive());
204
205
        // Content links are one kind of link to pages
206
        $p2 = SiteTree::create();
207
        $p2->Title = "regular link";
208
        $p2->Content = "<a href=\"[sitetree_link,id=$p->ID]\">test</a>";
209
        $p2->write();
210
        $this->assertTrue($p2->publishRecursive());
211
212
        // Redirector links are a third
213
        $rp = new RedirectorPage();
214
        $rp->Title = "redirector";
215
        $rp->RedirectionType = 'Internal';
216
        $rp->LinkToID = $p->ID;
217
        $rp->write();
218
        $this->assertTrue($rp->publishRecursive());
219
220
        // Confirm that there are no broken links to begin with
221
        $this->assertFalse($p2->HasBrokenLink);
222
        $this->assertFalse($rp->HasBrokenLink);
223
224
        // Unpublishing doesn't affect broken state on live (draft is source of truth)
225
        /** @var SiteTree $p2Live */
226
        $p2Live = Versioned::get_by_stage(SiteTree::class, Versioned::LIVE)->byID($p2->ID);
227
        /** @var SiteTree $rpLive */
228
        $rpLive = Versioned::get_by_stage(SiteTree::class, Versioned::LIVE)->byID($rp->ID);
229
        $this->assertEquals(0, $p2Live->HasBrokenLink);
230
        $this->assertEquals(0, $rpLive->HasBrokenLink);
231
232
        // Delete the source page, confirm that the VP, RP and page 2 have broken links on draft
233
        $p->delete();
234
        $p2->flushCache();
235
        /** @var SiteTree $p2 */
236
        $p2 = DataObject::get_by_id(SiteTree::class, $p2->ID);
237
        $rp->flushCache();
238
        /** @var RedirectorPage $rp */
239
        $rp = DataObject::get_by_id(SiteTree::class, $rp->ID);
240
        $this->assertEquals(1, $p2->HasBrokenLink);
241
        $this->assertEquals(1, $rp->HasBrokenLink);
242
243
        // Restore the page to stage, confirm that this fixes the links
244
        /** @var SiteTree $p */
245
        $p = Versioned::get_latest_version(SiteTree::class, $pageID);
246
        $p->doRestoreToStage();
247
248
        $p2->flushCache();
249
        $p2 = DataObject::get_by_id(SiteTree::class, $p2->ID);
250
        $rp->flushCache();
251
        $rp = DataObject::get_by_id(SiteTree::class, $rp->ID);
252
        $this->assertFalse((bool)$p2->HasBrokenLink);
253
        $this->assertFalse((bool)$rp->HasBrokenLink);
254
255
        // Publish and confirm that the p2 and RP broken links are fixed on published
256
        $this->assertTrue($p->publishRecursive());
257
        $p2Live = Versioned::get_one_by_stage(SiteTree::class, 'Live', '"SiteTree"."ID" = ' . $p2->ID);
258
        $rpLive = Versioned::get_one_by_stage(SiteTree::class, 'Live', '"SiteTree"."ID" = ' . $rp->ID);
259
        $this->assertFalse((bool)$p2Live->HasBrokenLink);
260
        $this->assertFalse((bool)$rpLive->HasBrokenLink);
261
    }
262
263
    public function testRevertToLiveFixesBrokenLinks()
264
    {
265
        // Create page and virutal page
266
        $page = SiteTree::create();
267
        $page->Title = "source";
268
        $page->write();
269
        $pageID = $page->ID;
270
        $this->assertTrue($page->publishRecursive());
271
272
        // Content links are one kind of link to pages
273
        $page2 = SiteTree::create();
274
        $page2->Title = "regular link";
275
        $page2->Content = "<a href=\"[sitetree_link,id={$pageID}]\">test</a>";
276
        $page2->write();
277
        $this->assertTrue($page2->publishRecursive());
278
279
        // Redirector links are a third
280
        $redirectorPage = new RedirectorPage();
281
        $redirectorPage->Title = "redirector";
282
        $redirectorPage->RedirectionType = 'Internal';
283
        $redirectorPage->LinkToID = $page->ID;
284
        $redirectorPage->write();
285
        $this->assertTrue($redirectorPage->publishRecursive());
286
287
        // Confirm that there are no broken links to begin with
288
        $this->assertFalse($page2->HasBrokenLink);
289
        $this->assertFalse($redirectorPage->HasBrokenLink);
290
291
        // Delete from draft and confirm that broken links are marked
292
        $page->delete();
293
294
        $page2->flushCache();
295
        /** @var SiteTree $page2 */
296
        $page2 = DataObject::get_by_id(SiteTree::class, $page2->ID);
297
        $redirectorPage->flushCache();
298
        /** @var RedirectorPage $redirectorPage */
299
        $redirectorPage = DataObject::get_by_id(SiteTree::class, $redirectorPage->ID);
300
        $this->assertEquals(1, $page2->HasBrokenLink);
301
        $this->assertEquals(1, $redirectorPage->HasBrokenLink);
302
303
        // Call doRevertToLive and confirm that broken links are restored
304
        /** @var SiteTree $pageLive */
305
        $pageLive = Versioned::get_one_by_stage(SiteTree::class, 'Live', '"SiteTree"."ID" = ' . $pageID);
306
        $pageLive->doRevertToLive();
307
308
        $page2->flushCache();
309
        $page2 = DataObject::get_by_id(SiteTree::class, $page2->ID);
310
        $redirectorPage->flushCache();
311
        $redirectorPage = DataObject::get_by_id(SiteTree::class, $redirectorPage->ID);
312
        $this->assertFalse((bool)$page2->HasBrokenLink);
313
        $this->assertFalse((bool)$redirectorPage->HasBrokenLink);
314
    }
315
316
    public function testBrokenAnchorLinksInAPage()
317
    {
318
        /** @var SiteTree $obj */
319
        $obj = $this->objFromFixture(SiteTree::class, 'content');
320
        $origContent = $obj->Content;
321
322
        $obj->Content = $origContent . '<a href="#no-anchor-here">this links to a non-existent in-page anchor or ' .
323
            'skiplink</a>';
324
        $obj->syncLinkTracking();
325
        $this->assertTrue($obj->HasBrokenLink, 'Page has a broken anchor/skiplink');
326
327
        $obj->Content = $origContent . '<a href="#yes-anchor-here">this links to an existent in-page ' .
328
            'anchor/skiplink</a>';
329
        $obj->syncLinkTracking();
330
        $this->assertFalse($obj->HasBrokenLink, 'Page doesn\'t have a broken anchor or skiplink');
331
    }
332
}
333