| Total Complexity | 73 | 
| Total Lines | 1585 | 
| Duplicated Lines | 0 % | 
| Changes | 0 | ||
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 | ||
| 32 | class SiteTreeTest extends SapphireTest | ||
| 33 | { | ||
| 34 | protected static $fixture_file = 'SiteTreeTest.yml'; | ||
| 35 | |||
| 36 | protected static $illegal_extensions = [ | ||
| 37 | SiteTree::class => ['SiteTreeSubsites', 'Translatable'], | ||
| 38 | ]; | ||
| 39 | |||
| 40 | protected static $extra_dataobjects = [ | ||
| 41 | SiteTreeTest_ClassA::class, | ||
| 42 | SiteTreeTest_ClassB::class, | ||
| 43 | SiteTreeTest_ClassC::class, | ||
| 44 | SiteTreeTest_ClassD::class, | ||
| 45 | SiteTreeTest_ClassCext::class, | ||
| 46 | SiteTreeTest_NotRoot::class, | ||
| 47 | SiteTreeTest_StageStatusInherit::class, | ||
| 48 | SiteTreeTest_DataObject::class, | ||
| 49 | ]; | ||
| 50 | |||
| 51 | public function reservedSegmentsProvider() | ||
| 52 |     { | ||
| 53 | return [ | ||
| 54 | // segments reserved by rules | ||
| 55 | ['Admin', 'admin-2'], | ||
| 56 | ['Dev', 'dev-2'], | ||
| 57 | ['Robots in disguise', 'robots-in-disguise'], | ||
| 58 | // segments reserved by folder name | ||
| 59 | ['resources', 'resources-2'], | ||
| 60 | ['assets', 'assets-2'], | ||
| 61 | ['notafoldername', 'notafoldername'], | ||
| 62 | ]; | ||
| 63 | } | ||
| 64 | |||
| 65 | public function testCreateDefaultpages() | ||
| 89 | } | ||
| 90 | |||
| 91 | /** | ||
| 92 | * Test generation of the URLSegment values. | ||
| 93 | * - Turns things into lowercase-hyphen-format | ||
| 94 | * - Generates from Title by default, unless URLSegment is explicitly set | ||
| 95 | * - Resolves duplicates by appending a number | ||
| 96 | * - renames classes with a class name conflict | ||
| 97 | */ | ||
| 98 | public function testURLGeneration() | ||
| 99 |     { | ||
| 100 | $expectedURLs = [ | ||
| 101 | 'home' => 'home', | ||
| 102 | 'staff' => 'my-staff', | ||
| 103 | 'about' => 'about-us', | ||
| 104 | 'staffduplicate' => 'my-staff-2', | ||
| 105 | 'product1' => '1-1-test-product', | ||
| 106 | 'product2' => 'another-product', | ||
| 107 | 'product3' => 'another-product-2', | ||
| 108 | 'product4' => 'another-product-3', | ||
| 109 | 'object' => 'object', | ||
| 110 | 'controller' => 'controller', | ||
| 111 | 'numericonly' => '1930', | ||
| 112 | ]; | ||
| 113 | |||
| 114 |         foreach ($expectedURLs as $fixture => $urlSegment) { | ||
| 115 | $obj = $this->objFromFixture(SiteTree::class, $fixture); | ||
| 116 | $this->assertEquals($urlSegment, $obj->URLSegment); | ||
| 117 | } | ||
| 118 | } | ||
| 119 | |||
| 120 | /** | ||
| 121 | * Check if reserved URL's are properly appended with a number at top level | ||
| 122 | * @dataProvider reservedSegmentsProvider | ||
| 123 | */ | ||
| 124 | public function testDisallowedURLGeneration($title, $urlSegment) | ||
| 125 |     { | ||
| 126 | $page = SiteTree::create(['Title' => $title]); | ||
| 127 | $id = $page->write(); | ||
| 128 | $page = SiteTree::get()->byID($id); | ||
| 129 | $this->assertEquals($urlSegment, $page->URLSegment); | ||
| 130 | } | ||
| 131 | |||
| 132 | /** | ||
| 133 | * Check if reserved URL's are not appended with a number on a child page | ||
| 134 | * It's okay to have a URL like domain.com/my-page/admin as it won't interfere with domain.com/admin | ||
| 135 | * @dataProvider reservedSegmentsProvider | ||
| 136 | */ | ||
| 137 | public function testDisallowedChildURLGeneration($title, $urlSegment) | ||
| 145 | } | ||
| 146 | |||
| 147 | /** | ||
| 148 | * Test that publication copies data to SiteTree_Live | ||
| 149 | */ | ||
| 150 | public function testPublishCopiesToLiveTable() | ||
| 151 |     { | ||
| 152 | $obj = $this->objFromFixture(SiteTree::class, 'about'); | ||
| 153 | $obj->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE); | ||
| 154 | |||
| 155 | $createdID = DB::query( | ||
| 156 | "SELECT \"ID\" FROM \"SiteTree_Live\" WHERE \"URLSegment\" = '$obj->URLSegment'" | ||
| 157 | )->value(); | ||
| 158 | $this->assertEquals($obj->ID, $createdID); | ||
| 159 | } | ||
| 160 | |||
| 161 | /** | ||
| 162 | * Test that field which are set and then cleared are also transferred to the published site. | ||
| 163 | */ | ||
| 164 | public function testPublishDeletedFields() | ||
| 165 |     { | ||
| 166 |         $this->logInWithPermission('ADMIN'); | ||
| 167 | |||
| 168 | $obj = $this->objFromFixture(SiteTree::class, 'about'); | ||
| 169 | $obj->Title = "asdfasdf"; | ||
| 170 | $obj->write(); | ||
| 171 | $this->assertTrue($obj->publishRecursive()); | ||
| 172 | |||
| 173 | $this->assertEquals( | ||
| 174 | 'asdfasdf', | ||
| 175 |             DB::query("SELECT \"Title\" FROM \"SiteTree_Live\" WHERE \"ID\" = '$obj->ID'")->value() | ||
| 176 | ); | ||
| 177 | |||
| 178 | $obj->Title = null; | ||
| 179 | $obj->write(); | ||
| 180 | $this->assertTrue($obj->publishRecursive()); | ||
| 181 | |||
| 182 |         $this->assertNull(DB::query("SELECT \"Title\" FROM \"SiteTree_Live\" WHERE \"ID\" = '$obj->ID'")->value()); | ||
| 183 | } | ||
| 184 | |||
| 185 | public function testParentNodeCachedInMemory() | ||
| 186 |     { | ||
| 187 | $parent = SiteTree::create(); | ||
| 188 | $parent->Title = 'Section Title'; | ||
| 189 | $child = SiteTree::create(); | ||
| 190 | $child->Title = 'Page Title'; | ||
| 191 | $child->setParent($parent); | ||
| 192 | |||
| 193 | $this->assertInstanceOf(SiteTree::class, $child->Parent); | ||
| 194 |         $this->assertEquals("Section Title", $child->Parent->Title); | ||
| 195 | } | ||
| 196 | |||
| 197 | public function testParentModelReturnType() | ||
| 198 |     { | ||
| 199 | $parent = new SiteTreeTest_PageNode(); | ||
| 200 | $child = new SiteTreeTest_PageNode(); | ||
| 201 | |||
| 202 | $child->setParent($parent); | ||
| 203 | $this->assertInstanceOf(SiteTreeTest_PageNode::class, $child->Parent); | ||
| 204 | } | ||
| 205 | |||
| 206 | /** | ||
| 207 | * Confirm that DataObject::get_one() gets records from SiteTree_Live | ||
| 208 | */ | ||
| 209 | public function testGetOneFromLive() | ||
| 210 |     { | ||
| 211 | $s = SiteTree::create(); | ||
| 212 | $s->Title = "V1"; | ||
| 213 | $s->URLSegment = "get-one-test-page"; | ||
| 214 | $s->write(); | ||
| 215 | $s->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE); | ||
| 216 | $s->Title = "V2"; | ||
| 217 | $s->write(); | ||
| 218 | |||
| 219 | $oldMode = Versioned::get_reading_mode(); | ||
| 220 | Versioned::set_stage(Versioned::LIVE); | ||
| 221 | |||
| 222 | $checkSiteTree = DataObject::get_one(SiteTree::class, [ | ||
| 223 | '"SiteTree"."URLSegment"' => 'get-one-test-page', | ||
| 224 | ]); | ||
| 225 |         $this->assertEquals("V1", $checkSiteTree->Title); | ||
| 226 | |||
| 227 | Versioned::set_reading_mode($oldMode); | ||
| 228 | } | ||
| 229 | |||
| 230 | public function testChidrenOfRootAreTopLevelPages() | ||
| 231 |     { | ||
| 232 | $pages = SiteTree::get(); | ||
| 233 |         foreach ($pages as $page) { | ||
| 234 | $page->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE); | ||
| 235 | } | ||
| 236 | unset($pages); | ||
| 237 | |||
| 238 | /* If we create a new SiteTree object with ID = 0 */ | ||
| 239 | $obj = SiteTree::create(); | ||
| 240 | /* Then its children should be the top-level pages */ | ||
| 241 |         $stageChildren = $obj->stageChildren()->map('ID', 'Title'); | ||
| 242 |         $liveChildren = $obj->liveChildren()->map('ID', 'Title'); | ||
| 243 |         $allChildren = $obj->AllChildrenIncludingDeleted()->map('ID', 'Title'); | ||
| 244 | |||
| 245 |         $this->assertContains('Home', $stageChildren); | ||
| 246 |         $this->assertContains('Products', $stageChildren); | ||
| 247 |         $this->assertNotContains('Staff', $stageChildren); | ||
| 248 | |||
| 249 |         $this->assertContains('Home', $liveChildren); | ||
| 250 |         $this->assertContains('Products', $liveChildren); | ||
| 251 |         $this->assertNotContains('Staff', $liveChildren); | ||
| 252 | |||
| 253 |         $this->assertContains('Home', $allChildren); | ||
| 254 |         $this->assertContains('Products', $allChildren); | ||
| 255 |         $this->assertNotContains('Staff', $allChildren); | ||
| 256 | } | ||
| 257 | |||
| 258 | public function testCanSaveBlankToHasOneRelations() | ||
| 274 | } | ||
| 275 | |||
| 276 | public function testStageStates() | ||
| 277 |     { | ||
| 278 | // newly created page | ||
| 279 | $createdPage = SiteTree::create(); | ||
| 280 | $createdPage->write(); | ||
| 281 | $this->assertTrue($createdPage->isOnDraft()); | ||
| 282 | $this->assertFalse($createdPage->isPublished()); | ||
| 283 | $this->assertTrue($createdPage->isOnDraftOnly()); | ||
| 284 | $this->assertTrue($createdPage->isModifiedOnDraft()); | ||
| 285 | |||
| 286 | // published page | ||
| 287 | $publishedPage = SiteTree::create(); | ||
| 288 | $publishedPage->write(); | ||
| 289 |         $publishedPage->copyVersionToStage('Stage', 'Live'); | ||
| 290 | $this->assertTrue($publishedPage->isOnDraft()); | ||
| 291 | $this->assertTrue($publishedPage->isPublished()); | ||
| 292 | $this->assertFalse($publishedPage->isOnDraftOnly()); | ||
| 293 | $this->assertFalse($publishedPage->isOnLiveOnly()); | ||
| 294 | $this->assertFalse($publishedPage->isModifiedOnDraft()); | ||
| 295 | |||
| 296 | // published page, deleted from stage | ||
| 297 | $deletedFromDraftPage = SiteTree::create(); | ||
| 298 | $deletedFromDraftPage->write(); | ||
| 299 |         $deletedFromDraftPage->copyVersionToStage('Stage', 'Live'); | ||
| 300 |         $deletedFromDraftPage->deleteFromStage('Stage'); | ||
| 301 | $this->assertFalse($deletedFromDraftPage->isArchived()); | ||
| 302 | $this->assertFalse($deletedFromDraftPage->isOnDraft()); | ||
| 303 | $this->assertTrue($deletedFromDraftPage->isPublished()); | ||
| 304 | $this->assertFalse($deletedFromDraftPage->isOnDraftOnly()); | ||
| 305 | $this->assertTrue($deletedFromDraftPage->isOnLiveOnly()); | ||
| 306 | $this->assertFalse($deletedFromDraftPage->isModifiedOnDraft()); | ||
| 307 | |||
| 308 | // published page, deleted from live | ||
| 309 | $deletedFromLivePage = SiteTree::create(); | ||
| 310 | $deletedFromLivePage->write(); | ||
| 311 |         $deletedFromLivePage->copyVersionToStage('Stage', 'Live'); | ||
| 312 |         $deletedFromLivePage->deleteFromStage('Live'); | ||
| 313 | $this->assertFalse($deletedFromLivePage->isArchived()); | ||
| 314 | $this->assertTrue($deletedFromLivePage->isOnDraft()); | ||
| 315 | $this->assertFalse($deletedFromLivePage->isPublished()); | ||
| 316 | $this->assertTrue($deletedFromLivePage->isOnDraftOnly()); | ||
| 317 | $this->assertFalse($deletedFromLivePage->isOnLiveOnly()); | ||
| 318 | $this->assertTrue($deletedFromLivePage->isModifiedOnDraft()); | ||
| 319 | |||
| 320 | // published page, deleted from both stages | ||
| 321 | $deletedFromAllStagesPage = SiteTree::create(); | ||
| 322 | $deletedFromAllStagesPage->write(); | ||
| 323 |         $deletedFromAllStagesPage->copyVersionToStage('Stage', 'Live'); | ||
| 324 |         $deletedFromAllStagesPage->deleteFromStage('Stage'); | ||
| 325 |         $deletedFromAllStagesPage->deleteFromStage('Live'); | ||
| 326 | $this->assertTrue($deletedFromAllStagesPage->isArchived()); | ||
| 327 | $this->assertFalse($deletedFromAllStagesPage->isOnDraft()); | ||
| 328 | $this->assertFalse($deletedFromAllStagesPage->isPublished()); | ||
| 329 | $this->assertFalse($deletedFromAllStagesPage->isOnDraftOnly()); | ||
| 330 | $this->assertFalse($deletedFromAllStagesPage->isOnLiveOnly()); | ||
| 331 | $this->assertFalse($deletedFromAllStagesPage->isModifiedOnDraft()); | ||
| 332 | |||
| 333 | // published page, modified | ||
| 334 | $modifiedOnDraftPage = SiteTree::create(); | ||
| 335 | $modifiedOnDraftPage->write(); | ||
| 336 |         $modifiedOnDraftPage->copyVersionToStage('Stage', 'Live'); | ||
| 337 | $modifiedOnDraftPage->Content = 'modified'; | ||
| 338 | $modifiedOnDraftPage->write(); | ||
| 339 | $this->assertFalse($modifiedOnDraftPage->isArchived()); | ||
| 340 | $this->assertTrue($modifiedOnDraftPage->isOnDraft()); | ||
| 341 | $this->assertTrue($modifiedOnDraftPage->isPublished()); | ||
| 342 | $this->assertFalse($modifiedOnDraftPage->isOnDraftOnly()); | ||
| 343 | $this->assertFalse($modifiedOnDraftPage->isOnLiveOnly()); | ||
| 344 | $this->assertTrue($modifiedOnDraftPage->isModifiedOnDraft()); | ||
| 345 | } | ||
| 346 | |||
| 347 | /** | ||
| 348 | * Test that a page can be completely deleted and restored to the stage site | ||
| 349 | */ | ||
| 350 | public function testRestoreToStage() | ||
| 351 |     { | ||
| 352 | $page = $this->objFromFixture(SiteTree::class, 'about'); | ||
| 353 | $pageID = $page->ID; | ||
| 354 | $page->delete(); | ||
| 355 | $this->assertTrue(!DataObject::get_by_id(SiteTree::class, $pageID)); | ||
| 356 | |||
| 357 | $deletedPage = Versioned::get_latest_version(SiteTree::class, $pageID); | ||
| 358 | $resultPage = $deletedPage->doRestoreToStage(); | ||
| 359 | |||
| 360 | $requeriedPage = DataObject::get_by_id(SiteTree::class, $pageID); | ||
| 361 | |||
| 362 | $this->assertEquals($pageID, $resultPage->ID); | ||
| 363 | $this->assertEquals($pageID, $requeriedPage->ID); | ||
| 364 |         $this->assertEquals('About Us', $requeriedPage->Title); | ||
| 365 | $this->assertInstanceOf(SiteTree::class, $requeriedPage); | ||
| 366 | |||
| 367 | |||
| 368 | $page2 = $this->objFromFixture(SiteTree::class, 'products'); | ||
| 369 | $page2ID = $page2->ID; | ||
| 370 | $page2->doUnpublish(); | ||
| 371 | $page2->delete(); | ||
| 372 | |||
| 373 | // Check that if we restore while on the live site that the content still gets pushed to | ||
| 374 | // stage | ||
| 375 | Versioned::set_stage(Versioned::LIVE); | ||
| 376 | $deletedPage = Versioned::get_latest_version(SiteTree::class, $page2ID); | ||
| 377 | $deletedPage->doRestoreToStage(); | ||
| 378 | $this->assertFalse( | ||
| 379 | (bool)Versioned::get_one_by_stage(SiteTree::class, Versioned::LIVE, "\"SiteTree\".\"ID\" = " . $page2ID) | ||
| 380 | ); | ||
| 381 | |||
| 382 | Versioned::set_stage(Versioned::DRAFT); | ||
| 383 | $requeriedPage = DataObject::get_by_id(SiteTree::class, $page2ID); | ||
| 384 |         $this->assertEquals('Products', $requeriedPage->Title); | ||
| 385 | $this->assertInstanceOf(SiteTree::class, $requeriedPage); | ||
| 386 | } | ||
| 387 | |||
| 388 | public function testNoCascadingDeleteWithoutID() | ||
| 389 |     { | ||
| 390 |         Config::inst()->update('SiteTree', 'enforce_strict_hierarchy', true); | ||
| 391 | $count = SiteTree::get()->count(); | ||
| 392 | $this->assertNotEmpty($count); | ||
| 393 | $obj = SiteTree::create(); | ||
| 394 | $this->assertFalse($obj->exists()); | ||
| 395 | $fail = true; | ||
| 396 |         try { | ||
| 397 | $obj->delete(); | ||
| 398 |         } catch (LogicException $e) { | ||
| 399 | $fail = false; | ||
| 400 | } | ||
| 401 |         if ($fail) { | ||
| 402 |             $this->fail('Failed to throw delete exception'); | ||
| 403 | } | ||
| 404 | $this->assertCount($count, SiteTree::get()); | ||
| 405 | } | ||
| 406 | |||
| 407 | public function testGetByLink() | ||
| 408 |     { | ||
| 409 | $home = $this->objFromFixture(SiteTree::class, 'home'); | ||
| 410 | $about = $this->objFromFixture(SiteTree::class, 'about'); | ||
| 411 | $staff = $this->objFromFixture(SiteTree::class, 'staff'); | ||
| 412 | $product = $this->objFromFixture(SiteTree::class, 'product1'); | ||
| 413 | |||
| 414 | SiteTree::config()->nested_urls = false; | ||
| 415 | |||
| 416 |         $this->assertEquals($home->ID, SiteTree::get_by_link('/', false)->ID); | ||
| 417 |         $this->assertEquals($home->ID, SiteTree::get_by_link('/home/', false)->ID); | ||
| 418 | $this->assertEquals($about->ID, SiteTree::get_by_link($about->Link(), false)->ID); | ||
| 419 | $this->assertEquals($staff->ID, SiteTree::get_by_link($staff->Link(), false)->ID); | ||
| 420 | $this->assertEquals($product->ID, SiteTree::get_by_link($product->Link(), false)->ID); | ||
| 421 | |||
| 422 | Config::modify()->set(SiteTree::class, 'nested_urls', true); | ||
| 423 | |||
| 424 |         $this->assertEquals($home->ID, SiteTree::get_by_link('/', false)->ID); | ||
| 425 |         $this->assertEquals($home->ID, SiteTree::get_by_link('/home/', false)->ID); | ||
| 426 | $this->assertEquals($about->ID, SiteTree::get_by_link($about->Link(), false)->ID); | ||
| 427 | $this->assertEquals($staff->ID, SiteTree::get_by_link($staff->Link(), false)->ID); | ||
| 428 | $this->assertEquals($product->ID, SiteTree::get_by_link($product->Link(), false)->ID); | ||
| 429 | |||
| 430 | $this->assertEquals( | ||
| 431 | $staff->ID, | ||
| 432 |             SiteTree::get_by_link('/my-staff/', false)->ID, | ||
| 433 | 'Assert a unique URLSegment can be used for b/c.' | ||
| 434 | ); | ||
| 435 | } | ||
| 436 | |||
| 437 | public function testRelativeLink() | ||
| 438 |     { | ||
| 439 | $about = $this->objFromFixture(SiteTree::class, 'about'); | ||
| 440 | $staff = $this->objFromFixture(SiteTree::class, 'staff'); | ||
| 441 | |||
| 442 | Config::modify()->set(SiteTree::class, 'nested_urls', true); | ||
| 443 | |||
| 444 | $this->assertEquals( | ||
| 445 | 'about-us/', | ||
| 446 | $about->RelativeLink(), | ||
| 447 | 'Matches URLSegment on top level without parameters' | ||
| 448 | ); | ||
| 449 | $this->assertEquals( | ||
| 450 | 'about-us/my-staff/', | ||
| 451 | $staff->RelativeLink(), | ||
| 452 | 'Matches URLSegment plus parent on second level without parameters' | ||
| 453 | ); | ||
| 454 | $this->assertEquals( | ||
| 455 | 'about-us/edit', | ||
| 456 |             $about->RelativeLink('edit'), | ||
| 457 | 'Matches URLSegment plus parameter on top level' | ||
| 458 | ); | ||
| 459 | $this->assertEquals( | ||
| 460 | 'about-us/tom&jerry', | ||
| 461 |             $about->RelativeLink('tom&jerry'), | ||
| 462 | 'Doesnt url encode parameter' | ||
| 463 | ); | ||
| 464 | } | ||
| 465 | |||
| 466 | public function testPageLevel() | ||
| 467 |     { | ||
| 468 | $about = $this->objFromFixture(SiteTree::class, 'about'); | ||
| 469 | $staff = $this->objFromFixture(SiteTree::class, 'staff'); | ||
| 470 | $this->assertEquals(1, $about->getPageLevel()); | ||
| 471 | $this->assertEquals(2, $staff->getPageLevel()); | ||
| 472 | } | ||
| 473 | |||
| 474 | public function testAbsoluteLiveLink() | ||
| 475 |     { | ||
| 476 | $parent = $this->objFromFixture(SiteTree::class, 'about'); | ||
| 477 | $child = $this->objFromFixture(SiteTree::class, 'staff'); | ||
| 478 | |||
| 479 | Config::modify()->set(SiteTree::class, 'nested_urls', true); | ||
| 480 | |||
| 481 | $child->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE); | ||
| 482 | $parent->URLSegment = 'changed-on-live'; | ||
| 483 | $parent->write(); | ||
| 484 | $parent->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE); | ||
| 485 | $parent->URLSegment = 'changed-on-draft'; | ||
| 486 | $parent->write(); | ||
| 487 | |||
| 488 |         $this->assertStringEndsWith('changed-on-live/my-staff/', $child->getAbsoluteLiveLink(false)); | ||
| 489 |         $this->assertStringEndsWith('changed-on-live/my-staff/?stage=Live', $child->getAbsoluteLiveLink()); | ||
| 490 | } | ||
| 491 | |||
| 492 | public function testDuplicateChildrenRetainSort() | ||
| 493 |     { | ||
| 494 | $parent = SiteTree::create(); | ||
| 495 | $parent->Title = 'Parent'; | ||
| 496 | $parent->write(); | ||
| 497 | |||
| 498 | $child1 = SiteTree::create(); | ||
| 499 | $child1->ParentID = $parent->ID; | ||
| 500 | $child1->Title = 'Child 1'; | ||
| 501 | $child1->Sort = 2; | ||
| 502 | $child1->write(); | ||
| 503 | |||
| 504 | $child2 = SiteTree::create(); | ||
| 505 | $child2->ParentID = $parent->ID; | ||
| 506 | $child2->Title = 'Child 2'; | ||
| 507 | $child2->Sort = 1; | ||
| 508 | $child2->write(); | ||
| 509 | |||
| 510 | $duplicateParent = $parent->duplicateWithChildren(); | ||
| 511 | $duplicateChildren = $duplicateParent->AllChildren()->toArray(); | ||
| 512 | $this->assertCount(2, $duplicateChildren); | ||
| 513 | |||
| 514 | $duplicateChild2 = array_shift($duplicateChildren); | ||
| 515 | $duplicateChild1 = array_shift($duplicateChildren); | ||
| 516 | |||
| 517 | |||
| 518 |         $this->assertEquals('Child 1', $duplicateChild1->Title); | ||
| 519 |         $this->assertEquals('Child 2', $duplicateChild2->Title); | ||
| 520 | |||
| 521 | // assertGreaterThan works by having the LOWER value first | ||
| 522 | $this->assertGreaterThan($duplicateChild2->Sort, $duplicateChild1->Sort); | ||
| 523 | } | ||
| 524 | |||
| 525 | public function testDeleteFromStageOperatesRecursively() | ||
| 526 |     { | ||
| 527 | Config::modify()->set(SiteTree::class, 'enforce_strict_hierarchy', false); | ||
| 528 | $pageAbout = $this->objFromFixture(SiteTree::class, 'about'); | ||
| 529 | $pageStaff = $this->objFromFixture(SiteTree::class, 'staff'); | ||
| 530 | $pageStaffDuplicate = $this->objFromFixture(SiteTree::class, 'staffduplicate'); | ||
| 531 | |||
| 532 | $pageAbout->delete(); | ||
| 533 | |||
| 534 | $this->assertNull(DataObject::get_by_id(SiteTree::class, $pageAbout->ID)); | ||
| 535 | $this->assertTrue(DataObject::get_by_id(SiteTree::class, $pageStaff->ID) instanceof SiteTree); | ||
| 536 | $this->assertTrue(DataObject::get_by_id(SiteTree::class, $pageStaffDuplicate->ID) instanceof SiteTree); | ||
| 537 | Config::modify()->set(SiteTree::class, 'enforce_strict_hierarchy', true); | ||
| 538 | } | ||
| 539 | |||
| 540 | public function testDeleteFromStageOperatesRecursivelyStrict() | ||
| 541 |     { | ||
| 542 | $pageAbout = $this->objFromFixture(SiteTree::class, 'about'); | ||
| 543 | $pageStaff = $this->objFromFixture(SiteTree::class, 'staff'); | ||
| 544 | $pageStaffDuplicate = $this->objFromFixture(SiteTree::class, 'staffduplicate'); | ||
| 545 | |||
| 546 | $pageAbout->delete(); | ||
| 547 | |||
| 548 | $this->assertNull(DataObject::get_by_id(SiteTree::class, $pageAbout->ID)); | ||
| 549 | $this->assertNull(DataObject::get_by_id(SiteTree::class, $pageStaff->ID)); | ||
| 550 | $this->assertNull(DataObject::get_by_id(SiteTree::class, $pageStaffDuplicate->ID)); | ||
| 551 | } | ||
| 552 | |||
| 553 | public function testDuplicate() | ||
| 554 |     { | ||
| 555 | $pageAbout = $this->objFromFixture(SiteTree::class, 'about'); | ||
| 556 | $dupe = $pageAbout->duplicate(); | ||
| 557 | $this->assertEquals($pageAbout->Title, $dupe->Title); | ||
| 558 | $this->assertNotEquals($pageAbout->URLSegment, $dupe->URLSegment); | ||
| 559 | $this->assertNotEquals($pageAbout->Sort, $dupe->Sort); | ||
| 560 | } | ||
| 561 | |||
| 562 | public function testDeleteFromLiveOperatesRecursively() | ||
| 585 | } | ||
| 586 | |||
| 587 | public function testUnpublishDoesNotDeleteChildrenWithLooseHierachyOn() | ||
| 588 |     { | ||
| 589 | Config::modify()->set(SiteTree::class, 'enforce_strict_hierarchy', false); | ||
| 590 |         $this->logInWithPermission('ADMIN'); | ||
| 591 | |||
| 592 | $pageAbout = $this->objFromFixture(SiteTree::class, 'about'); | ||
| 593 | $pageAbout->publishRecursive(); | ||
| 594 | $pageStaff = $this->objFromFixture(SiteTree::class, 'staff'); | ||
| 595 | $pageStaff->publishRecursive(); | ||
| 596 | $pageStaffDuplicate = $this->objFromFixture(SiteTree::class, 'staffduplicate'); | ||
| 597 | $pageStaffDuplicate->publishRecursive(); | ||
| 598 | |||
| 599 | $parentPage = $this->objFromFixture(SiteTree::class, 'about'); | ||
| 600 | $parentPage->doUnpublish(); | ||
| 601 | |||
| 602 | Versioned::set_stage(Versioned::LIVE); | ||
| 603 | $this->assertNull(DataObject::get_by_id(SiteTree::class, $pageAbout->ID)); | ||
| 604 | $this->assertTrue(DataObject::get_by_id(SiteTree::class, $pageStaff->ID) instanceof SiteTree); | ||
| 605 | $this->assertTrue(DataObject::get_by_id(SiteTree::class, $pageStaffDuplicate->ID) instanceof SiteTree); | ||
| 606 | Versioned::set_stage(Versioned::DRAFT); | ||
| 607 | Config::modify()->set(SiteTree::class, 'enforce_strict_hierarchy', true); | ||
| 608 | } | ||
| 609 | |||
| 610 | public function testDeleteFromLiveOperatesRecursivelyStrict() | ||
| 611 |     { | ||
| 612 |         $this->logInWithPermission('ADMIN'); | ||
| 613 | |||
| 614 | $pageAbout = $this->objFromFixture(SiteTree::class, 'about'); | ||
| 615 | $pageAbout->publishRecursive(); | ||
| 616 | $pageStaff = $this->objFromFixture(SiteTree::class, 'staff'); | ||
| 617 | $pageStaff->publishRecursive(); | ||
| 618 | $pageStaffDuplicate = $this->objFromFixture(SiteTree::class, 'staffduplicate'); | ||
| 619 | $pageStaffDuplicate->publishRecursive(); | ||
| 620 | |||
| 621 | $parentPage = $this->objFromFixture(SiteTree::class, 'about'); | ||
| 622 | $parentPage->doUnpublish(); | ||
| 623 | |||
| 624 | Versioned::set_stage(Versioned::LIVE); | ||
| 625 | $this->assertNull(DataObject::get_by_id(SiteTree::class, $pageAbout->ID)); | ||
| 626 | $this->assertNull(DataObject::get_by_id(SiteTree::class, $pageStaff->ID)); | ||
| 627 | $this->assertNull(DataObject::get_by_id(SiteTree::class, $pageStaffDuplicate->ID)); | ||
| 628 | Versioned::set_stage(Versioned::DRAFT); | ||
| 629 | } | ||
| 630 | |||
| 631 | /** | ||
| 632 | * Simple test to confirm that querying from a particular archive date doesn't throw | ||
| 633 | * an error | ||
| 634 | */ | ||
| 635 | public function testReadArchiveDate() | ||
| 636 |     { | ||
| 637 | $date = '2009-07-02 14:05:07'; | ||
| 638 | Versioned::reading_archived_date($date); | ||
| 639 | SiteTree::get()->where([ | ||
| 640 | '"SiteTree"."ParentID"' => 0, | ||
| 641 | ])->sql($args); | ||
| 642 | $this->assertContains($date, $args); | ||
| 643 | } | ||
| 644 | |||
| 645 | public function testEditPermissions() | ||
| 646 |     { | ||
| 647 | $editor = $this->objFromFixture(Member::class, "editor"); | ||
| 648 | |||
| 649 | $home = $this->objFromFixture(SiteTree::class, "home"); | ||
| 650 | $staff = $this->objFromFixture(SiteTree::class, "staff"); | ||
| 651 | $products = $this->objFromFixture(SiteTree::class, "products"); | ||
| 652 | $product1 = $this->objFromFixture(SiteTree::class, "product1"); | ||
| 653 | $product4 = $this->objFromFixture(SiteTree::class, "product4"); | ||
| 654 | |||
| 655 | // Test logged out users cannot edit | ||
| 656 | $this->logOut(); | ||
| 657 | $this->assertFalse($staff->canEdit()); | ||
| 658 | |||
| 659 | // Can't edit a page that is locked to admins | ||
| 660 | $this->assertFalse($home->canEdit($editor)); | ||
| 661 | |||
| 662 | // Can edit a page that is locked to editors | ||
| 663 | $this->assertTrue($products->canEdit($editor)); | ||
| 664 | |||
| 665 | // Can edit a child of that page that inherits | ||
| 666 | $this->assertTrue($product1->canEdit($editor)); | ||
| 667 | |||
| 668 | // Can't edit a child of that page that has its permissions overridden | ||
| 669 | $this->assertFalse($product4->canEdit($editor)); | ||
| 670 | } | ||
| 671 | |||
| 672 | public function testCanEditWithAccessToAllSections() | ||
| 673 |     { | ||
| 674 | $page = SiteTree::create(); | ||
| 675 | $page->write(); | ||
| 676 | $allSectionMember = $this->objFromFixture(Member::class, 'allsections'); | ||
| 677 | $securityAdminMember = $this->objFromFixture(Member::class, 'securityadmin'); | ||
| 678 | |||
| 679 | $this->assertTrue($page->canEdit($allSectionMember)); | ||
| 680 | $this->assertFalse($page->canEdit($securityAdminMember)); | ||
| 681 | } | ||
| 682 | |||
| 683 | public function testCreatePermissions() | ||
| 684 |     { | ||
| 685 | // Test logged out users cannot create | ||
| 686 | $this->logOut(); | ||
| 687 | $this->assertFalse(singleton(SiteTree::class)->canCreate()); | ||
| 688 | |||
| 689 | // Login with another permission | ||
| 690 |         $this->logInWithPermission('DUMMY'); | ||
| 691 | $this->assertFalse(singleton(SiteTree::class)->canCreate()); | ||
| 692 | |||
| 693 | // Login with basic CMS permission | ||
| 694 | $perms = SiteConfig::config()->required_permission; | ||
| 695 | $this->logInWithPermission(reset($perms)); | ||
| 696 | $this->assertTrue(singleton(SiteTree::class)->canCreate()); | ||
| 697 | |||
| 698 | // Test creation underneath a parent which this user doesn't have access to | ||
| 699 | $parent = $this->objFromFixture(SiteTree::class, 'about'); | ||
| 700 | $this->assertFalse(singleton(SiteTree::class)->canCreate(null, ['Parent' => $parent])); | ||
| 701 | |||
| 702 | // Test creation underneath a parent which doesn't allow a certain child | ||
| 703 | $parentB = new SiteTreeTest_ClassB(); | ||
| 704 | $parentB->Title = 'Only Allows SiteTreeTest_ClassC'; | ||
| 705 | $parentB->write(); | ||
| 706 | $this->assertTrue(singleton(SiteTreeTest_ClassA::class)->canCreate(null)); | ||
| 707 | $this->assertFalse(singleton(SiteTreeTest_ClassA::class)->canCreate(null, ['Parent' => $parentB])); | ||
| 708 | $this->assertTrue(singleton(SiteTreeTest_ClassC::class)->canCreate(null, ['Parent' => $parentB])); | ||
| 709 | |||
| 710 | // Test creation underneath a parent which doesn't exist in the database. This should | ||
| 711 | // fall back to checking whether the user can create pages at the root of the site | ||
| 712 | $this->assertTrue(singleton(SiteTree::class)->canCreate(null, ['Parent' => singleton(SiteTree::class)])); | ||
| 713 | |||
| 714 | //Test we don't check for allowedChildren on parent context if it's not SiteTree instance | ||
| 715 | $this->assertTrue( | ||
| 716 | singleton(SiteTree::class)->canCreate( | ||
| 717 | null, | ||
| 718 | ['Parent' => $this->objFromFixture(SiteTreeTest_DataObject::class, 'relations')] | ||
| 719 | ) | ||
| 720 | ); | ||
| 721 | } | ||
| 722 | |||
| 723 | public function testEditPermissionsOnDraftVsLive() | ||
| 724 |     { | ||
| 725 | // Create an inherit-permission page | ||
| 726 | $page = SiteTree::create(); | ||
| 727 | $page->write(); | ||
| 728 | $page->CanEditType = "Inherit"; | ||
| 729 | $page->publishRecursive(); | ||
| 730 | $pageID = $page->ID; | ||
| 731 | |||
| 732 | // Lock down the site config | ||
| 733 | $sc = $page->SiteConfig; | ||
| 734 | $sc->CanEditType = 'OnlyTheseUsers'; | ||
| 735 | $sc->EditorGroups()->add($this->idFromFixture(Group::class, 'admins')); | ||
| 736 | $sc->write(); | ||
| 737 | |||
| 738 | // Confirm that Member.editor can't edit the page | ||
| 739 | $member = $this->objFromFixture(Member::class, 'editor'); | ||
| 740 | Security::setCurrentUser($member); | ||
| 741 | $this->assertFalse($page->canEdit()); | ||
| 742 | |||
| 743 | // Change the page to be editable by Group.editors, but do not publish | ||
| 744 | $admin = $this->objFromFixture(Member::class, 'admin'); | ||
| 745 | Security::setCurrentUser($admin); | ||
| 746 | $page->CanEditType = 'OnlyTheseUsers'; | ||
| 747 | $page->EditorGroups()->add($this->idFromFixture(Group::class, 'editors')); | ||
| 748 | $page->write(); | ||
| 749 | |||
| 750 | // Clear permission cache | ||
| 751 | /** @var InheritedPermissions $checker */ | ||
| 752 | $checker = SiteTree::getPermissionChecker(); | ||
| 753 | $checker->clearCache(); | ||
| 754 | |||
| 755 | // Confirm that Member.editor can now edit the page | ||
| 756 | $member = $this->objFromFixture(Member::class, 'editor'); | ||
| 757 | Security::setCurrentUser($member); | ||
| 758 | $this->assertTrue($page->canEdit()); | ||
| 759 | |||
| 760 | // Publish the changes to the page | ||
| 761 | $admin = $this->objFromFixture(Member::class, 'admin'); | ||
| 762 | Security::setCurrentUser($admin); | ||
| 763 | $page->publishRecursive(); | ||
| 764 | |||
| 765 | // Confirm that Member.editor can still edit the page | ||
| 766 | $member = $this->objFromFixture(Member::class, 'editor'); | ||
| 767 | Security::setCurrentUser($member); | ||
| 768 | $this->assertTrue($page->canEdit()); | ||
| 769 | } | ||
| 770 | |||
| 771 | public function testCompareVersions() | ||
| 772 |     { | ||
| 773 | // Necessary to avoid | ||
| 774 | $oldCleanerClass = Diff::$html_cleaner_class; | ||
| 775 | Diff::$html_cleaner_class = SiteTreeTest_NullHtmlCleaner::class; | ||
| 776 | |||
| 777 | $page = SiteTree::create(); | ||
| 778 | $page->write(); | ||
| 779 | $this->assertEquals(1, $page->Version); | ||
| 780 | |||
| 781 | // Use inline element to avoid double wrapping applied to | ||
| 782 | // blocklevel elements depending on HTMLCleaner implementation: | ||
| 783 | // <ins><p> gets converted to <ins><p><inst> | ||
| 784 | $page->Content = "<span>This is a test</span>"; | ||
| 785 | $page->write(); | ||
| 786 | $this->assertEquals(2, $page->Version); | ||
| 787 | |||
| 788 | $diff = $page->compareVersions(1, 2); | ||
| 789 | |||
| 790 | $processedContent = trim($diff->Content); | ||
| 791 |         $processedContent = preg_replace('/\s*</', '<', $processedContent); | ||
| 792 |         $processedContent = preg_replace('/>\s*/', '>', $processedContent); | ||
| 793 |         $this->assertEquals("<ins><span>This is a test</span></ins>", $processedContent); | ||
| 794 | |||
| 795 | Diff::$html_cleaner_class = $oldCleanerClass; | ||
| 796 | } | ||
| 797 | |||
| 798 | public function testAuthorIDAndPublisherIDFilledOutOnPublish() | ||
| 799 |     { | ||
| 800 | // Ensure that we have a member ID who is doing all this work | ||
| 801 | $member = $this->objFromFixture(Member::class, "admin"); | ||
| 802 | $this->logInAs($member); | ||
| 803 | |||
| 804 | // Write the page | ||
| 805 | $about = $this->objFromFixture(SiteTree::class, 'about'); | ||
| 806 | $about->Title = "Another title"; | ||
| 807 | $about->write(); | ||
| 808 | |||
| 809 | // Check the version created | ||
| 810 | $savedVersion = DB::prepared_query( | ||
| 811 | "SELECT \"AuthorID\", \"PublisherID\" FROM \"SiteTree_Versions\" | ||
| 812 | WHERE \"RecordID\" = ? ORDER BY \"Version\" DESC", | ||
| 813 | [$about->ID] | ||
| 814 | )->first(); | ||
| 815 | $this->assertEquals($member->ID, $savedVersion['AuthorID']); | ||
| 816 | $this->assertEquals(0, $savedVersion['PublisherID']); | ||
| 817 | |||
| 818 | // Publish the page | ||
| 819 | $about->publishRecursive(); | ||
| 820 | $publishedVersion = DB::prepared_query( | ||
| 821 | "SELECT \"AuthorID\", \"PublisherID\" FROM \"SiteTree_Versions\" | ||
| 822 | WHERE \"RecordID\" = ? ORDER BY \"Version\" DESC", | ||
| 823 | [$about->ID] | ||
| 824 | )->first(); | ||
| 825 | |||
| 826 | // Check the version created | ||
| 827 | $this->assertEquals($member->ID, $publishedVersion['AuthorID']); | ||
| 828 | $this->assertEquals($member->ID, $publishedVersion['PublisherID']); | ||
| 829 | } | ||
| 830 | |||
| 831 | public function testLinkShortcodeHandler() | ||
| 832 |     { | ||
| 833 | $aboutPage = $this->objFromFixture(SiteTree::class, 'about'); | ||
| 834 | $redirectPage = $this->objFromFixture(RedirectorPage::class, 'external'); | ||
| 835 | |||
| 836 | $parser = new ShortcodeParser(); | ||
| 837 |         $parser->register('sitetree_link', [SiteTree::class, 'link_shortcode_handler']); | ||
| 838 | |||
| 839 |         $aboutShortcode = sprintf('[sitetree_link,id=%d]', $aboutPage->ID); | ||
| 840 |         $aboutEnclosed  = sprintf('[sitetree_link,id=%d]Example Content[/sitetree_link]', $aboutPage->ID); | ||
| 841 | |||
| 842 | $aboutShortcodeExpected = $aboutPage->Link(); | ||
| 843 |         $aboutEnclosedExpected  = sprintf('<a href="%s">Example Content</a>', $aboutPage->Link()); | ||
| 844 | |||
| 845 | $this->assertEquals( | ||
| 846 | $aboutShortcodeExpected, | ||
| 847 | $parser->parse($aboutShortcode), | ||
| 848 | 'Test that simple linking works.' | ||
| 849 | ); | ||
| 850 | $this->assertEquals( | ||
| 851 | $aboutEnclosedExpected, | ||
| 852 | $parser->parse($aboutEnclosed), | ||
| 853 | 'Test enclosed content is linked.' | ||
| 854 | ); | ||
| 855 | |||
| 856 | $aboutPage->delete(); | ||
| 857 | |||
| 858 | $this->assertEquals( | ||
| 859 | $aboutShortcodeExpected, | ||
| 860 | $parser->parse($aboutShortcode), | ||
| 861 | 'Test that deleted pages still link.' | ||
| 862 | ); | ||
| 863 | $this->assertEquals($aboutEnclosedExpected, $parser->parse($aboutEnclosed)); | ||
| 864 | |||
| 865 | $aboutShortcode = '[sitetree_link,id="-1"]'; | ||
| 866 | $aboutEnclosed = '[sitetree_link,id="-1"]Example Content[/sitetree_link]'; | ||
| 867 | |||
| 868 |         $this->assertEquals('', $parser->parse($aboutShortcode), 'Test empty result if no suitable matches.'); | ||
| 869 |         $this->assertEquals('', $parser->parse($aboutEnclosed)); | ||
| 870 | |||
| 871 |         $redirectShortcode = sprintf('[sitetree_link,id=%d]', $redirectPage->ID); | ||
| 872 |         $redirectEnclosed  = sprintf('[sitetree_link,id=%d]Example Content[/sitetree_link]', $redirectPage->ID); | ||
| 873 | $redirectExpected = 'http://www.google.com?a&b'; | ||
| 874 | |||
| 875 | $this->assertEquals($redirectExpected, $parser->parse($redirectShortcode)); | ||
| 876 | $this->assertEquals( | ||
| 877 |             sprintf('<a href="%s">Example Content</a>', $redirectExpected), | ||
| 878 | $parser->parse($redirectEnclosed) | ||
| 879 | ); | ||
| 880 | |||
| 881 |         $this->assertEquals('', $parser->parse('[sitetree_link]'), 'Test that invalid ID attributes are not parsed.'); | ||
| 882 |         $this->assertEquals('', $parser->parse('[sitetree_link,id="text"]')); | ||
| 883 |         $this->assertEquals('', $parser->parse('[sitetree_link]Example Content[/sitetree_link]')); | ||
| 884 | } | ||
| 885 | |||
| 886 | public function testIsCurrent() | ||
| 887 |     { | ||
| 888 | $aboutPage = $this->objFromFixture(SiteTree::class, 'about'); | ||
| 889 | $productPage = $this->objFromFixture(SiteTree::class, 'products'); | ||
| 890 | |||
| 891 | Director::set_current_page($aboutPage); | ||
| 892 | $this->assertTrue($aboutPage->isCurrent(), 'Assert that basic isCurrent checks works.'); | ||
| 893 | $this->assertFalse($productPage->isCurrent()); | ||
| 894 | |||
| 895 | $this->assertTrue( | ||
| 896 | DataObject::get_one(SiteTree::class, [ | ||
| 897 | '"SiteTree"."Title"' => 'About Us', | ||
| 898 | ])->isCurrent(), | ||
| 899 | 'Assert that isCurrent works on another instance with the same ID.' | ||
| 900 | ); | ||
| 901 | |||
| 902 | Director::set_current_page($newPage = SiteTree::create()); | ||
| 903 | $this->assertTrue($newPage->isCurrent(), 'Assert that isCurrent works on unsaved pages.'); | ||
| 904 | } | ||
| 905 | |||
| 906 | public function testIsSection() | ||
| 907 |     { | ||
| 908 | $about = $this->objFromFixture(SiteTree::class, 'about'); | ||
| 909 | $staff = $this->objFromFixture(SiteTree::class, 'staff'); | ||
| 910 | $ceo = $this->objFromFixture(SiteTree::class, 'ceo'); | ||
| 911 | |||
| 912 | Director::set_current_page($about); | ||
| 913 | $this->assertTrue($about->isSection()); | ||
| 914 | $this->assertFalse($staff->isSection()); | ||
| 915 | $this->assertFalse($ceo->isSection()); | ||
| 916 | |||
| 917 | Director::set_current_page($staff); | ||
| 918 | $this->assertTrue($about->isSection()); | ||
| 919 | $this->assertTrue($staff->isSection()); | ||
| 920 | $this->assertFalse($ceo->isSection()); | ||
| 921 | |||
| 922 | Director::set_current_page($ceo); | ||
| 923 | $this->assertTrue($about->isSection()); | ||
| 924 | $this->assertTrue($staff->isSection()); | ||
| 925 | $this->assertTrue($ceo->isSection()); | ||
| 926 | } | ||
| 927 | |||
| 928 | public function testURLSegmentReserved() | ||
| 929 |     { | ||
| 930 | $siteTree = SiteTree::create(['URLSegment' => 'admin']); | ||
| 931 | $segment = $siteTree->validURLSegment(); | ||
| 932 | |||
| 933 | $this->assertFalse($segment); | ||
| 934 | } | ||
| 935 | |||
| 936 | public function testURLSegmentAutoUpdate() | ||
| 937 |     { | ||
| 938 | $sitetree = SiteTree::create(); | ||
| 939 | $sitetree->Title = _t( | ||
| 940 | 'SilverStripe\\CMS\\Controllers\\CMSMain.NEWPAGE', | ||
| 941 |             'New {pagetype}', | ||
| 942 | ['pagetype' => $sitetree->i18n_singular_name()] | ||
| 943 | ); | ||
| 944 | $sitetree->write(); | ||
| 945 | $this->assertEquals( | ||
| 946 | 'new-page', | ||
| 947 | $sitetree->URLSegment, | ||
| 948 | 'Sets based on default title on first save' | ||
| 949 | ); | ||
| 950 | |||
| 951 | $sitetree->Title = 'Changed'; | ||
| 952 | $sitetree->write(); | ||
| 953 | $this->assertEquals( | ||
| 954 | 'changed', | ||
| 955 | $sitetree->URLSegment, | ||
| 956 | 'Auto-updates when set to default title' | ||
| 957 | ); | ||
| 958 | |||
| 959 | $sitetree->Title = 'Changed again'; | ||
| 960 | $sitetree->write(); | ||
| 961 | $this->assertEquals( | ||
| 962 | 'changed', | ||
| 963 | $sitetree->URLSegment, | ||
| 964 | 'Does not auto-update once title has been changed' | ||
| 965 | ); | ||
| 966 | } | ||
| 967 | |||
| 968 | public function testURLSegmentAutoUpdateLocalized() | ||
| 969 |     { | ||
| 970 | $oldLocale = i18n::get_locale(); | ||
| 971 |         i18n::set_locale('de_DE'); | ||
| 972 | |||
| 973 | $sitetree = SiteTree::create(); | ||
| 974 | $sitetree->Title = _t( | ||
| 975 | 'SilverStripe\\CMS\\Controllers\\CMSMain.NEWPAGE', | ||
| 976 |             'New {pagetype}', | ||
| 977 | ['pagetype' => $sitetree->i18n_singular_name()] | ||
| 978 | ); | ||
| 979 | $sitetree->write(); | ||
| 980 | $this->assertEquals( | ||
| 981 | 'neue-seite', | ||
| 982 | $sitetree->URLSegment, | ||
| 983 | 'Sets based on default title on first save' | ||
| 984 | ); | ||
| 985 | |||
| 986 | $sitetree->Title = 'Changed'; | ||
| 987 | $sitetree->write(); | ||
| 988 | $this->assertEquals( | ||
| 989 | 'changed', | ||
| 990 | $sitetree->URLSegment, | ||
| 991 | 'Auto-updates when set to default title' | ||
| 992 | ); | ||
| 993 | |||
| 994 | $sitetree->Title = 'Changed again'; | ||
| 995 | $sitetree->write(); | ||
| 996 | $this->assertEquals( | ||
| 997 | 'changed', | ||
| 998 | $sitetree->URLSegment, | ||
| 999 | 'Does not auto-update once title has been changed' | ||
| 1000 | ); | ||
| 1001 | |||
| 1002 | i18n::set_locale($oldLocale); | ||
| 1003 | } | ||
| 1004 | |||
| 1005 | /** | ||
| 1006 | * @covers \SilverStripe\CMS\Model\SiteTree::validURLSegment | ||
| 1007 | */ | ||
| 1008 | public function testValidURLSegmentURLSegmentConflicts() | ||
| 1009 |     { | ||
| 1010 | $sitetree = SiteTree::create(); | ||
| 1011 | SiteTree::config()->nested_urls = false; | ||
| 1012 | |||
| 1013 | $sitetree->URLSegment = 'home'; | ||
| 1014 | $this->assertFalse($sitetree->validURLSegment(), 'URLSegment conflicts are recognised'); | ||
| 1015 | $sitetree->URLSegment = 'home-noconflict'; | ||
| 1016 | $this->assertTrue($sitetree->validURLSegment()); | ||
| 1017 | |||
| 1018 | $sitetree->ParentID = $this->idFromFixture(SiteTree::class, 'about'); | ||
| 1019 | $sitetree->URLSegment = 'home'; | ||
| 1020 | $this->assertFalse($sitetree->validURLSegment(), 'Conflicts are still recognised with a ParentID value'); | ||
| 1021 | |||
| 1022 | Config::modify()->set(SiteTree::class, 'nested_urls', true); | ||
| 1023 | |||
| 1024 | $sitetree->ParentID = 0; | ||
| 1025 | $sitetree->URLSegment = 'home'; | ||
| 1026 | $this->assertFalse($sitetree->validURLSegment(), 'URLSegment conflicts are recognised'); | ||
| 1027 | |||
| 1028 | $sitetree->ParentID = $this->idFromFixture(SiteTree::class, 'about'); | ||
| 1029 | $this->assertTrue($sitetree->validURLSegment(), 'URLSegments can be the same across levels'); | ||
| 1030 | |||
| 1031 | $sitetree->URLSegment = 'my-staff'; | ||
| 1032 | $this->assertFalse($sitetree->validURLSegment(), 'Nested URLSegment conflicts are recognised'); | ||
| 1033 | $sitetree->URLSegment = 'my-staff-noconflict'; | ||
| 1034 | $this->assertTrue($sitetree->validURLSegment()); | ||
| 1035 | } | ||
| 1036 | |||
| 1037 | /** | ||
| 1038 | * @covers \SilverStripe\CMS\Model\SiteTree::validURLSegment | ||
| 1039 | */ | ||
| 1040 | public function testValidURLSegmentClassNameConflicts() | ||
| 1041 |     { | ||
| 1042 | $sitetree = SiteTree::create(); | ||
| 1043 | $sitetree->URLSegment = Controller::class; | ||
| 1044 | |||
| 1045 | $this->assertTrue($sitetree->validURLSegment(), 'Class names are no longer conflicts'); | ||
| 1046 | } | ||
| 1047 | |||
| 1048 | /** | ||
| 1049 | * @covers \SilverStripe\CMS\Model\SiteTree::validURLSegment | ||
| 1050 | */ | ||
| 1051 | public function testValidURLSegmentControllerConflicts() | ||
| 1052 |     { | ||
| 1053 | Config::modify()->set(SiteTree::class, 'nested_urls', true); | ||
| 1054 | |||
| 1055 | $sitetree = SiteTree::create(); | ||
| 1056 | $sitetree->ParentID = $this->idFromFixture(SiteTreeTest_Conflicted::class, 'parent'); | ||
| 1057 | |||
| 1058 | $sitetree->URLSegment = 'index'; | ||
| 1059 | $this->assertFalse($sitetree->validURLSegment(), 'index is not a valid URLSegment'); | ||
| 1060 | |||
| 1061 | $sitetree->URLSegment = 'conflicted-action'; | ||
| 1062 | $this->assertFalse($sitetree->validURLSegment(), 'allowed_actions conflicts are recognised'); | ||
| 1063 | |||
| 1064 | $sitetree->URLSegment = 'conflicted-template'; | ||
| 1065 | $this->assertFalse($sitetree->validURLSegment(), 'Action-specific template conflicts are recognised'); | ||
| 1066 | |||
| 1067 | $sitetree->URLSegment = 'valid'; | ||
| 1068 | $this->assertTrue($sitetree->validURLSegment(), 'Valid URLSegment values are allowed'); | ||
| 1069 | } | ||
| 1070 | |||
| 1071 | public function testURLSegmentPrioritizesExtensionVotes() | ||
| 1072 |     { | ||
| 1073 | $sitetree = SiteTree::create(); | ||
| 1074 | $sitetree->URLSegment = 'unique-segment'; | ||
| 1075 | $this->assertTrue($sitetree->validURLSegment()); | ||
| 1076 | |||
| 1077 | SiteTree::add_extension(SiteTreeTest_Extension::class); | ||
| 1078 | $sitetree = SiteTree::create(); | ||
| 1079 | $sitetree->URLSegment = 'unique-segment'; | ||
| 1080 | $this->assertFalse($sitetree->validURLSegment()); | ||
| 1081 | SiteTree::remove_extension(SiteTreeTest_Extension::class); | ||
| 1082 | } | ||
| 1083 | |||
| 1084 | public function testURLSegmentMultiByte() | ||
| 1085 |     { | ||
| 1086 |         URLSegmentFilter::config()->set('default_allow_multibyte', true); | ||
| 1087 | $sitetree = SiteTree::create(); | ||
| 1088 | $sitetree->write(); | ||
| 1089 | |||
| 1090 | $sitetree->URLSegment = 'brötchen'; | ||
| 1091 | $sitetree->write(); | ||
| 1092 | $sitetree = DataObject::get_by_id(SiteTree::class, $sitetree->ID, false); | ||
| 1093 |         $this->assertEquals($sitetree->URLSegment, rawurlencode('brötchen')); | ||
| 1094 | |||
| 1095 | $sitetree->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE); | ||
| 1096 | $sitetree = DataObject::get_by_id(SiteTree::class, $sitetree->ID, false); | ||
| 1097 |         $this->assertEquals($sitetree->URLSegment, rawurlencode('brötchen')); | ||
| 1098 | $sitetreeLive = Versioned::get_one_by_stage( | ||
| 1099 | SiteTree::class, | ||
| 1100 | Versioned::LIVE, | ||
| 1101 | '"SiteTree"."ID" = ' . $sitetree->ID, | ||
| 1102 | false | ||
| 1103 | ); | ||
| 1104 |         $this->assertEquals($sitetreeLive->URLSegment, rawurlencode('brötchen')); | ||
| 1105 | } | ||
| 1106 | |||
| 1107 | public function testVersionsAreCreated() | ||
| 1108 |     { | ||
| 1109 | $p = SiteTree::create(); | ||
| 1110 | $p->Content = "one"; | ||
| 1111 | $p->write(); | ||
| 1112 | $this->assertEquals(1, $p->Version); | ||
| 1113 | |||
| 1114 | // No changes don't bump version | ||
| 1115 | $p->write(); | ||
| 1116 | $this->assertEquals(1, $p->Version); | ||
| 1117 | |||
| 1118 | $p->Content = "two"; | ||
| 1119 | $p->write(); | ||
| 1120 | $this->assertEquals(2, $p->Version); | ||
| 1121 | |||
| 1122 | // Only change meta-data don't bump version | ||
| 1123 | $p->HasBrokenLink = true; | ||
| 1124 | $p->write(); | ||
| 1125 | $p->HasBrokenLink = false; | ||
| 1126 | $p->write(); | ||
| 1127 | $this->assertEquals(2, $p->Version); | ||
| 1128 | |||
| 1129 | $p->Content = "three"; | ||
| 1130 | $p->write(); | ||
| 1131 | $this->assertEquals(3, $p->Version); | ||
| 1132 | } | ||
| 1133 | |||
| 1134 | public function testPageTypeClasses() | ||
| 1135 |     { | ||
| 1136 | $classes = SiteTree::page_type_classes(); | ||
| 1137 | $this->assertNotContains(SiteTree::class, $classes, 'Page types do not include base class'); | ||
| 1138 |         $this->assertContains('Page', $classes, 'Page types do contain subclasses'); | ||
| 1139 | |||
| 1140 | // Testing what happens in an incorrect config value is set - hide_ancestor should be a string | ||
| 1141 | Config::modify()->set(SiteTreeTest_ClassA::class, 'hide_ancestor', true); | ||
| 1142 | $newClasses = SiteTree::page_type_classes(); | ||
| 1143 | $this->assertEquals( | ||
| 1144 | $classes, | ||
| 1145 | $newClasses, | ||
| 1146 | 'Setting hide_ancestor to a boolean (incorrect) value caused a page class to be hidden' | ||
| 1147 | ); | ||
| 1148 | } | ||
| 1149 | |||
| 1150 | /** | ||
| 1151 | * Tests that core subclasses of SiteTree are included in allowedChildren() by default, but not instances of | ||
| 1152 | * HiddenClass | ||
| 1153 | */ | ||
| 1154 | public function testAllowedChildrenContainsCoreSubclassesButNotHiddenClass() | ||
| 1155 |     { | ||
| 1156 | $page = SiteTree::create(); | ||
| 1157 | $allowedChildren = $page->allowedChildren(); | ||
| 1158 | |||
| 1159 | $this->assertContains( | ||
| 1160 | VirtualPage::class, | ||
| 1161 | $allowedChildren, | ||
| 1162 | 'Includes core subclasses by default' | ||
| 1163 | ); | ||
| 1164 | |||
| 1165 | $this->assertNotContains( | ||
| 1166 | SiteTreeTest_ClassE::class, | ||
| 1167 | $allowedChildren, | ||
| 1168 | 'HiddenClass instances should not be returned' | ||
| 1169 | ); | ||
| 1170 | } | ||
| 1171 | |||
| 1172 | /** | ||
| 1173 | * Tests that various types of SiteTree classes will or will not be returned from the allowedChildren method | ||
| 1174 | * @dataProvider allowedChildrenProvider | ||
| 1175 | * @param string $className | ||
| 1176 | * @param array $expected | ||
| 1177 | * @param string $assertionMessage | ||
| 1178 | */ | ||
| 1179 | public function testAllowedChildren($className, $expected, $assertionMessage) | ||
| 1180 |     { | ||
| 1181 | $class = new $className; | ||
| 1182 | $this->assertEquals($expected, $class->allowedChildren(), $assertionMessage); | ||
| 1183 | } | ||
| 1184 | |||
| 1185 | /** | ||
| 1186 | * @return array | ||
| 1187 | */ | ||
| 1188 | public function allowedChildrenProvider() | ||
| 1189 |     { | ||
| 1190 | return [ | ||
| 1191 | [ | ||
| 1192 | // Class name | ||
| 1193 | SiteTreeTest_ClassA::class, | ||
| 1194 | // Expected | ||
| 1195 | [ SiteTreeTest_ClassB::class ], | ||
| 1196 | // Assertion message | ||
| 1197 | 'Direct setting of allowed children', | ||
| 1198 | ], | ||
| 1199 | [ | ||
| 1200 | SiteTreeTest_ClassB::class, | ||
| 1201 | [ SiteTreeTest_ClassC::class, SiteTreeTest_ClassCext::class ], | ||
| 1202 | 'Includes subclasses', | ||
| 1203 | ], | ||
| 1204 | [ | ||
| 1205 | SiteTreeTest_ClassC::class, | ||
| 1206 | [], | ||
| 1207 | 'Null setting', | ||
| 1208 | ], | ||
| 1209 | [ | ||
| 1210 | SiteTreeTest_ClassD::class, | ||
| 1211 | [SiteTreeTest_ClassC::class], | ||
| 1212 | 'Excludes subclasses if class is prefixed by an asterisk', | ||
| 1213 | ], | ||
| 1214 | ]; | ||
| 1215 | } | ||
| 1216 | |||
| 1217 | public function testAllowedChildrenValidation() | ||
| 1218 |     { | ||
| 1219 | $page = SiteTree::create(); | ||
| 1220 | $page->write(); | ||
| 1221 | $classA = new SiteTreeTest_ClassA(); | ||
| 1222 | $classA->write(); | ||
| 1223 | $classB = new SiteTreeTest_ClassB(); | ||
| 1224 | $classB->write(); | ||
| 1225 | $classC = new SiteTreeTest_ClassC(); | ||
| 1226 | $classC->write(); | ||
| 1227 | $classD = new SiteTreeTest_ClassD(); | ||
| 1228 | $classD->write(); | ||
| 1229 | $classCext = new SiteTreeTest_ClassCext(); | ||
| 1230 | $classCext->write(); | ||
| 1231 | |||
| 1232 | $classB->ParentID = $page->ID; | ||
| 1233 | $valid = $classB->doValidate(); | ||
| 1234 | $this->assertTrue($valid->isValid(), "Does allow children on unrestricted parent"); | ||
| 1235 | |||
| 1236 | $classB->ParentID = $classA->ID; | ||
| 1237 | $valid = $classB->doValidate(); | ||
| 1238 | $this->assertTrue($valid->isValid(), "Does allow child specifically allowed by parent"); | ||
| 1239 | |||
| 1240 | $classC->ParentID = $classA->ID; | ||
| 1241 | $valid = $classC->doValidate(); | ||
| 1242 | $this->assertFalse($valid->isValid(), "Doesnt allow child on parents specifically restricting children"); | ||
| 1243 | |||
| 1244 | $classB->ParentID = $classC->ID; | ||
| 1245 | $valid = $classB->doValidate(); | ||
| 1246 | $this->assertFalse($valid->isValid(), "Doesnt allow child on parents disallowing all children"); | ||
| 1247 | |||
| 1248 | $classB->ParentID = $classCext->ID; | ||
| 1249 | $valid = $classB->doValidate(); | ||
| 1250 | $this->assertTrue($valid->isValid(), "Extensions of allowed classes are incorrectly reported as invalid"); | ||
| 1251 | |||
| 1252 | $classCext->ParentID = $classD->ID; | ||
| 1253 | $valid = $classCext->doValidate(); | ||
| 1254 | $this->assertFalse( | ||
| 1255 | $valid->isValid(), | ||
| 1256 | "Doesnt allow child where only parent class is allowed on parent node, and asterisk prefixing is used" | ||
| 1257 | ); | ||
| 1258 | } | ||
| 1259 | |||
| 1260 | public function testClassDropdown() | ||
| 1261 |     { | ||
| 1262 | $sitetree = SiteTree::create(); | ||
| 1263 | $method = new ReflectionMethod($sitetree, 'getClassDropdown'); | ||
| 1264 | $method->setAccessible(true); | ||
| 1265 | |||
| 1266 | Security::setCurrentUser(null); | ||
| 1267 | $this->assertArrayNotHasKey(SiteTreeTest_ClassA::class, $method->invoke($sitetree)); | ||
| 1268 | |||
| 1269 |         $this->loginWithPermission('ADMIN'); | ||
| 1270 | $this->assertArrayHasKey(SiteTreeTest_ClassA::class, $method->invoke($sitetree)); | ||
| 1271 | |||
| 1272 |         $this->loginWithPermission('CMS_ACCESS_CMSMain'); | ||
| 1273 | $this->assertArrayHasKey(SiteTreeTest_ClassA::class, $method->invoke($sitetree)); | ||
| 1274 | |||
| 1275 | Security::setCurrentUser(null); | ||
| 1276 | } | ||
| 1277 | |||
| 1278 | public function testCanBeRoot() | ||
| 1279 |     { | ||
| 1280 | $page = SiteTree::create(); | ||
| 1281 | $page->ParentID = 0; | ||
| 1282 | $page->write(); | ||
| 1283 | |||
| 1284 | $notRootPage = new SiteTreeTest_NotRoot(); | ||
| 1285 | $notRootPage->ParentID = 0; | ||
| 1286 | $isDetected = false; | ||
| 1287 |         try { | ||
| 1288 | $notRootPage->write(); | ||
| 1289 |         } catch (ValidationException $e) { | ||
| 1290 |             $this->assertContains('is not allowed on the root level', $e->getMessage()); | ||
| 1291 | $isDetected = true; | ||
| 1292 | } | ||
| 1293 | |||
| 1294 |         if (!$isDetected) { | ||
| 1295 |             $this->fail('Fails validation with $can_be_root=false'); | ||
| 1296 | } | ||
| 1297 | } | ||
| 1298 | |||
| 1299 | public function testModifyStatusFlagByInheritance() | ||
| 1300 |     { | ||
| 1301 | $node = new SiteTreeTest_StageStatusInherit(); | ||
| 1302 | $treeTitle = $node->getTreeTitle(); | ||
| 1303 |         $this->assertContains('InheritedTitle', $treeTitle); | ||
| 1304 |         $this->assertContains('inherited-class', $treeTitle); | ||
| 1305 | } | ||
| 1306 | |||
| 1307 | public function testMenuTitleIsUnsetWhenEqualsTitle() | ||
| 1308 |     { | ||
| 1309 | $page = SiteTree::create(); | ||
| 1310 | $page->Title = 'orig'; | ||
| 1311 | $page->MenuTitle = 'orig'; | ||
| 1312 | $page->write(); | ||
| 1313 | |||
| 1314 | // change menu title | ||
| 1315 | $page->MenuTitle = 'changed'; | ||
| 1316 | $page->write(); | ||
| 1317 | $page = SiteTree::get()->byID($page->ID); | ||
| 1318 |         $this->assertEquals('changed', $page->getField('MenuTitle')); | ||
| 1319 | |||
| 1320 | // change menu title back | ||
| 1321 | $page->MenuTitle = 'orig'; | ||
| 1322 | $page->write(); | ||
| 1323 | $page = SiteTree::get()->byID($page->ID); | ||
| 1324 |         $this->assertEquals(null, $page->getField('MenuTitle')); | ||
| 1325 | } | ||
| 1326 | |||
| 1327 | public function testMetaTagGeneratorDisabling() | ||
| 1328 |     { | ||
| 1329 | $generator = Config::inst()->get(SiteTree::class, 'meta_generator'); | ||
| 1330 | |||
| 1331 | $page = new SiteTreeTest_PageNode(); | ||
| 1332 | |||
| 1333 | $meta = $page->MetaTags(); | ||
| 1334 | $this->assertEquals( | ||
| 1335 | 1, | ||
| 1336 |             preg_match('/.*meta name="generator" content="SilverStripe - http:\/\/silverstripe.org".*/', $meta), | ||
| 1337 | 'test default functionality - uses value from Config' | ||
| 1338 | ); | ||
| 1339 | |||
| 1340 | // test proper escaping of quotes in attribute value | ||
| 1341 | Config::modify()->set(SiteTree::class, 'meta_generator', 'Generator with "quotes" in it'); | ||
| 1342 | $meta = $page->MetaTags(); | ||
| 1343 | $this->assertEquals( | ||
| 1344 | 1, | ||
| 1345 |             preg_match('/.*meta name="generator" content="Generator with "quotes" in it".*/', $meta), | ||
| 1346 | 'test proper escaping of values from Config' | ||
| 1347 | ); | ||
| 1348 | |||
| 1349 | // test empty generator - no tag should appear at all | ||
| 1350 | Config::modify()->set(SiteTree::class, 'meta_generator', ''); | ||
| 1351 | $meta = $page->MetaTags(); | ||
| 1352 | $this->assertEquals( | ||
| 1353 | 0, | ||
| 1354 |             preg_match('/.*meta name=.generator..*/', $meta), | ||
| 1355 | 'test blank value means no tag generated' | ||
| 1356 | ); | ||
| 1357 | |||
| 1358 | // reset original value | ||
| 1359 | Config::modify()->set(SiteTree::class, 'meta_generator', $generator); | ||
| 1360 | } | ||
| 1361 | |||
| 1362 | |||
| 1363 | public function testGetBreadcrumbItems() | ||
| 1364 |     { | ||
| 1365 | $page = $this->objFromFixture(SiteTree::class, "breadcrumbs"); | ||
| 1366 | $this->assertEquals(1, $page->getBreadcrumbItems()->count(), "Only display current page."); | ||
| 1367 | |||
| 1368 | // Test breadcrumb order | ||
| 1369 | $page = $this->objFromFixture(SiteTree::class, "breadcrumbs5"); | ||
| 1370 | $breadcrumbs = $page->getBreadcrumbItems(); | ||
| 1371 | $this->assertEquals($breadcrumbs->count(), 5, "Display all breadcrumbs"); | ||
| 1372 | $this->assertEquals($breadcrumbs->first()->Title, "Breadcrumbs", "Breadcrumbs should be the first item."); | ||
| 1373 | $this->assertEquals($breadcrumbs->last()->Title, "Breadcrumbs 5", "Breadcrumbs 5 should be last item."); | ||
| 1374 | |||
| 1375 | // Test breadcrumb max depth | ||
| 1376 | $breadcrumbs = $page->getBreadcrumbItems(2); | ||
| 1377 | $this->assertEquals($breadcrumbs->count(), 2, "Max depth should limit the breadcrumbs to 2 items."); | ||
| 1378 | $this->assertEquals($breadcrumbs->first()->Title, "Breadcrumbs 4", "First item should be Breadrcumbs 4."); | ||
| 1379 | $this->assertEquals($breadcrumbs->last()->Title, "Breadcrumbs 5", "Breadcrumbs 5 should be last."); | ||
| 1380 | } | ||
| 1381 | |||
| 1382 | /** | ||
| 1383 | * Tests SiteTree::MetaTags | ||
| 1384 | * Note that this test makes no assumption on the closing of tags (other than <title></title>) | ||
| 1385 | */ | ||
| 1386 | public function testMetaTags() | ||
| 1387 |     { | ||
| 1388 |         $this->logInWithPermission('ADMIN'); | ||
| 1389 | $page = $this->objFromFixture(SiteTree::class, 'metapage'); | ||
| 1390 | |||
| 1391 | // Test with title | ||
| 1392 | $meta = $page->MetaTags(); | ||
| 1393 | $charset = Config::inst()->get(ContentNegotiator::class, 'encoding'); | ||
| 1394 |         $this->assertContains('<meta http-equiv="Content-Type" content="text/html; charset=' . $charset . '"', $meta); | ||
| 1395 |         $this->assertContains('<meta name="description" content="The <br /> and <br> tags"', $meta); | ||
| 1396 |         $this->assertContains('<link rel="canonical" href="http://www.mysite.com/html-and-xml"', $meta); | ||
| 1397 |         $this->assertContains('<meta name="x-page-id" content="' . $page->ID . '"', $meta); | ||
| 1398 |         $this->assertContains('<meta name="x-cms-edit-link" content="' . $page->CMSEditLink() . '"', $meta); | ||
| 1399 |         $this->assertContains('<title>HTML & XML</title>', $meta); | ||
| 1400 | |||
| 1401 | // Test without title | ||
| 1402 | $meta = $page->MetaTags(false); | ||
| 1403 |         $this->assertNotContains('<title>', $meta); | ||
| 1404 | } | ||
| 1405 | |||
| 1406 | /** | ||
| 1407 | * Test that orphaned pages are handled correctly | ||
| 1408 | */ | ||
| 1409 | public function testOrphanedPages() | ||
| 1410 |     { | ||
| 1411 | $origStage = Versioned::get_reading_mode(); | ||
| 1412 | |||
| 1413 | // Setup user who can view draft content, but lacks cms permission. | ||
| 1414 | // To users such as this, orphaned pages should be inaccessible. canView for these pages is only | ||
| 1415 | // necessary for admin / cms users, who require this permission to edit / rearrange these pages. | ||
| 1416 | $permission = new Permission(); | ||
| 1417 | $permission->Code = 'VIEW_DRAFT_CONTENT'; | ||
| 1418 | $group = new Group(['Title' => 'Staging Users']); | ||
| 1419 | $group->write(); | ||
| 1420 | $group->Permissions()->add($permission); | ||
| 1421 | $member = new Member(); | ||
| 1422 | $member->Email = '[email protected]'; | ||
| 1423 | $member->write(); | ||
| 1424 | $member->Groups()->add($group); | ||
| 1425 | |||
| 1426 | // both pages are viewable in stage | ||
| 1427 | Versioned::set_stage(Versioned::DRAFT); | ||
| 1428 | $about = $this->objFromFixture(SiteTree::class, 'about'); | ||
| 1429 | $staff = $this->objFromFixture(SiteTree::class, 'staff'); | ||
| 1430 | $this->assertFalse($about->isOrphaned()); | ||
| 1431 | $this->assertFalse($staff->isOrphaned()); | ||
| 1432 | $this->assertTrue($about->canView($member)); | ||
| 1433 | $this->assertTrue($staff->canView($member)); | ||
| 1434 | |||
| 1435 | // Publishing only the child page to live should orphan the live record, but not the staging one | ||
| 1436 | $staff->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE); | ||
| 1437 | $this->assertFalse($staff->isOrphaned()); | ||
| 1438 | $this->assertTrue($staff->canView($member)); | ||
| 1439 | Versioned::set_stage(Versioned::LIVE); | ||
| 1440 | $staff = $this->objFromFixture(SiteTree::class, 'staff'); // Live copy of page | ||
| 1441 | $this->assertTrue($staff->isOrphaned()); // because parent isn't published | ||
| 1442 | $this->assertFalse($staff->canView($member)); | ||
| 1443 | |||
| 1444 | // Publishing the parent page should restore visibility | ||
| 1445 | Versioned::set_stage(Versioned::DRAFT); | ||
| 1446 | $about = $this->objFromFixture(SiteTree::class, 'about'); | ||
| 1447 | $about->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE); | ||
| 1448 | Versioned::set_stage(Versioned::LIVE); | ||
| 1449 | $staff = $this->objFromFixture(SiteTree::class, 'staff'); | ||
| 1450 | $this->assertFalse($staff->isOrphaned()); | ||
| 1451 | $this->assertTrue($staff->canView($member)); | ||
| 1452 | |||
| 1453 | // Removing staging page should not prevent live page being visible | ||
| 1454 |         $about->deleteFromStage('Stage'); | ||
| 1455 |         $staff->deleteFromStage('Stage'); | ||
| 1456 | $staff = $this->objFromFixture(SiteTree::class, 'staff'); | ||
| 1457 | $this->assertFalse($staff->isOrphaned()); | ||
| 1458 | $this->assertTrue($staff->canView($member)); | ||
| 1459 | |||
| 1460 | // Cleanup | ||
| 1461 | Versioned::set_reading_mode($origStage); | ||
| 1462 | } | ||
| 1463 | |||
| 1464 | /** | ||
| 1465 | * Test archived page behaviour | ||
| 1466 | */ | ||
| 1467 | public function testArchivedPages() | ||
| 1489 | } | ||
| 1490 | |||
| 1491 | public function testCanNot() | ||
| 1492 |     { | ||
| 1493 | // Test that | ||
| 1494 |         $this->logInWithPermission('ADMIN'); | ||
| 1495 | $page = new SiteTreeTest_AdminDenied(); | ||
| 1496 | $this->assertFalse($page->canCreate()); | ||
| 1497 | $this->assertFalse($page->canEdit()); | ||
| 1498 | $this->assertFalse($page->canDelete()); | ||
| 1499 | $this->assertFalse($page->canAddChildren()); | ||
| 1500 | $this->assertFalse($page->canView()); | ||
| 1501 | } | ||
| 1502 | |||
| 1503 | public function testCanPublish() | ||
| 1523 | } | ||
| 1524 | |||
| 1525 | /** | ||
| 1526 | * Test url rewriting extensions | ||
| 1527 | */ | ||
| 1528 | public function testLinkExtension() | ||
| 1529 |     { | ||
| 1530 |         Director::config()->set('alternate_base_url', 'http://www.baseurl.com'); | ||
| 1531 | $page = new SiteTreeTest_ClassD(); | ||
| 1532 | $page->URLSegment = 'classd'; | ||
| 1533 | $page->write(); | ||
| 1534 | $this->assertEquals( | ||
| 1535 | 'http://www.updatedhost.com/classd/myaction?extra=1', | ||
| 1536 |             $page->Link('myaction') | ||
| 1537 | ); | ||
| 1538 | $this->assertEquals( | ||
| 1539 | 'http://www.updatedhost.com/classd/myaction?extra=1', | ||
| 1540 |             $page->AbsoluteLink('myaction') | ||
| 1541 | ); | ||
| 1542 | $this->assertEquals( | ||
| 1543 | 'classd/myaction', | ||
| 1544 |             $page->RelativeLink('myaction') | ||
| 1545 | ); | ||
| 1546 | } | ||
| 1547 | |||
| 1548 | /** | ||
| 1549 | * Test that the controller name for a SiteTree instance can be gathered by appending "Controller" to the SiteTree | ||
| 1550 | * class name in a PSR-2 compliant manner. | ||
| 1551 | */ | ||
| 1552 | public function testGetControllerName() | ||
| 1553 |     { | ||
| 1554 | $class = Page::create(); | ||
| 1555 |         $this->assertSame('PageController', $class->getControllerName()); | ||
| 1556 | } | ||
| 1557 | |||
| 1558 | /** | ||
| 1559 | * Test that underscored class names (legacy) are still supported (deprecation notice is issued though). | ||
| 1560 | */ | ||
| 1561 | public function testGetControllerNameWithUnderscoresIsSupported() | ||
| 1562 |     { | ||
| 1563 | $class = new SiteTreeTest_LegacyControllerName; | ||
| 1564 | $this->assertEquals(SiteTreeTest_LegacyControllerName_Controller::class, $class->getControllerName()); | ||
| 1565 | } | ||
| 1566 | |||
| 1567 | public function testTreeTitleCache() | ||
| 1617 | } | ||
| 1618 | } | ||
| 1619 | 
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:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths