From 0d7dc49d6b7708a372a01b6694acd253e82eb30d Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Fri, 20 Nov 2015 15:32:52 +1300 Subject: [PATCH] API Rename mysiteconfig to subsiteconfig API make ErrorPageSubsite 4.x compatible BUG Fix incorrect yml BUG Fix incorrect DataExtension::augmentSQL implementation --- _config/config.yml | 6 +- code/extensions/ErrorPageSubsite.php | 35 ++- code/extensions/FileSubsites.php | 2 +- code/extensions/GroupSubsites.php | 2 +- code/extensions/SiteConfigSubsites.php | 2 +- code/extensions/SiteTreeSubsites.php | 2 +- tests/SiteTreeSubsitesTest.php | 21 +- tests/SubsiteTest.yml | 344 ++++++++++++------------- tests/SubsitesVirtualPageTest.php | 49 ++-- tests/SubsitesVirtualPageTest.yml | 14 +- tests/testscript-test-file.pdf | Bin 0 -> 8861 bytes 11 files changed, 242 insertions(+), 235 deletions(-) create mode 100644 tests/testscript-test-file.pdf diff --git a/_config/config.yml b/_config/config.yml index 2e04505..e9b0809 100644 --- a/_config/config.yml +++ b/_config/config.yml @@ -1,6 +1,8 @@ --- -Name: mysiteconfig -After: 'framework/*','cms/*' +Name: subsiteconfig +After: + - 'framework/*' + - 'cms/*' --- AssetAdmin: treats_subsite_0_as_global: true diff --git a/code/extensions/ErrorPageSubsite.php b/code/extensions/ErrorPageSubsite.php index 0e620ca..d82fbf7 100644 --- a/code/extensions/ErrorPageSubsite.php +++ b/code/extensions/ErrorPageSubsite.php @@ -4,14 +4,15 @@ class ErrorPageSubsite extends DataExtension { /** * Alter file path to generated a static (static) error page file to handle error page template on different sub-sites * - * @see Error::get_filepath_for_errorcode() + * {@see Error::get_error_filename()} * * FIXME since {@link Subsite::currentSubsite()} partly relies on Session, viewing other sub-site (including main site) between - * opening ErrorPage in the CMS and publish ErrorPage causes static error page to get generated incorrectly. + * opening ErrorPage in the CMS and publish ErrorPage causes static error page to get generated incorrectly. + * + * @param string $name Filename to write to + * @param int $statusCode Integer error code */ - function alternateFilepathForErrorcode($statusCode, $locale = null) { - $static_filepath = Config::inst()->get($this->owner->ClassName, 'static_filepath'); - $subdomainPart = ""; + public function updateErrorFilename(&$name, $statusCode) { // Try to get current subsite from session $subsite = Subsite::currentSubsite(false); @@ -19,22 +20,18 @@ class ErrorPageSubsite extends DataExtension { // since this function is called from Page class before the controller is created, we have to get subsite from domain instead if(!$subsite) { $subsiteID = Subsite::getSubsiteIDForDomain(); - if($subsiteID != 0) $subsite = DataObject::get_by_id("Subsite", $subsiteID); - else $subsite = null; - } - - if($subsite) { - $subdomain = $subsite->domain(); - $subdomainPart = "-{$subdomain}"; - } - - if(singleton('SiteTree')->hasExtension('Translatable') && $locale && $locale != Translatable::default_locale()) { - $filepath = $static_filepath . "/error-{$statusCode}-{$locale}{$subdomainPart}.html"; - } else { - $filepath = $static_filepath . "/error-{$statusCode}{$subdomainPart}.html"; + if($subsiteID != 0) { + $subsite = DataObject::get_by_id("Subsite", $subsiteID); + } } - return $filepath; + // Without subsite, don't rewrite + if($subsite) { + // Add subdomain to end of filename, just before .html + // This should preserve translatable locale in the filename as well + $subdomain = $subsite->domain(); + $name = substr($name, 0, -5) . "-{$subdomain}.html"; + } } } \ No newline at end of file diff --git a/code/extensions/FileSubsites.php b/code/extensions/FileSubsites.php index 44eb3b0..9504340 100644 --- a/code/extensions/FileSubsites.php +++ b/code/extensions/FileSubsites.php @@ -57,7 +57,7 @@ class FileSubsites extends DataExtension { /** * Update any requests to limit the results to the current site */ - public function augmentSQL(SQLSelect $query) { + public function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null) { if(Subsite::$disable_subsite_filter) return; // If you're querying by ID, ignore the sub-site - this is a bit ugly... (but it was WAYYYYYYYYY worse) diff --git a/code/extensions/GroupSubsites.php b/code/extensions/GroupSubsites.php index dcd3e6e..415b66e 100644 --- a/code/extensions/GroupSubsites.php +++ b/code/extensions/GroupSubsites.php @@ -107,7 +107,7 @@ class GroupSubsites extends DataExtension implements PermissionProvider { /** * Update any requests to limit the results to the current site */ - public function augmentSQL(SQLSelect $query) { + public function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null) { if(Subsite::$disable_subsite_filter) return; if(Cookie::get('noSubsiteFilter') == 'true') return; diff --git a/code/extensions/SiteConfigSubsites.php b/code/extensions/SiteConfigSubsites.php index 21a441d..abc5474 100644 --- a/code/extensions/SiteConfigSubsites.php +++ b/code/extensions/SiteConfigSubsites.php @@ -12,7 +12,7 @@ class SiteConfigSubsites extends DataExtension { /** * Update any requests to limit the results to the current site */ - public function augmentSQL(SQLSelect $query) { + public function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null) { if(Subsite::$disable_subsite_filter) return; // If you're querying by ID, ignore the sub-site - this is a bit ugly... diff --git a/code/extensions/SiteTreeSubsites.php b/code/extensions/SiteTreeSubsites.php index 92b42ac..7d54144 100644 --- a/code/extensions/SiteTreeSubsites.php +++ b/code/extensions/SiteTreeSubsites.php @@ -25,7 +25,7 @@ class SiteTreeSubsites extends DataExtension { /** * Update any requests to limit the results to the current site */ - public function augmentSQL(SQLSelect $query, DataQuery &$dataQuery = null) { + public function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null) { if(Subsite::$disable_subsite_filter) return; if($dataQuery->getQueryParam('Subsite.filter') === false) return; diff --git a/tests/SiteTreeSubsitesTest.php b/tests/SiteTreeSubsitesTest.php index 1ea8202..aab9b2b 100644 --- a/tests/SiteTreeSubsitesTest.php +++ b/tests/SiteTreeSubsitesTest.php @@ -6,7 +6,8 @@ class SiteTreeSubsitesTest extends BaseSubsiteTest { protected $extraDataObjects = array( 'SiteTreeSubsitesTest_ClassA', - 'SiteTreeSubsitesTest_ClassB' + 'SiteTreeSubsitesTest_ClassB', + 'SiteTreeSubsitesTest_ErrorPage' ); protected $illegalExtensions = array( @@ -55,10 +56,9 @@ class SiteTreeSubsitesTest extends BaseSubsiteTest { $subsite1 = $this->objFromFixture('Subsite', 'domaintest1'); Subsite::changeSubsite($subsite1->ID); - $path = ErrorPage::get_filepath_for_errorcode(500); + $path = SiteTreeSubsitesTest_ErrorPage::get_error_filename_spy(500); - $static_path = Config::inst()->get('ErrorPage', 'static_filepath'); - $expected_path = $static_path . '/error-500-'.$subsite1->domain().'.html'; + $expected_path = 'error-500-'.$subsite1->domain().'.html'; $this->assertEquals($expected_path, $path); } @@ -201,3 +201,16 @@ class SiteTreeSubsitesTest extends BaseSubsiteTest { class SiteTreeSubsitesTest_ClassA extends SiteTree implements TestOnly {} class SiteTreeSubsitesTest_ClassB extends SiteTree implements TestOnly {} + +class SiteTreeSubsitesTest_ErrorPage extends ErrorPage implements TestOnly { + + /** + * Helper method to call protected members + * + * @param int $statusCode + * @return string + */ + public static function get_error_filename_spy($statusCode) { + return self::get_error_filename($statusCode); + } +} \ No newline at end of file diff --git a/tests/SubsiteTest.yml b/tests/SubsiteTest.yml index e4b0ef6..96bffb3 100644 --- a/tests/SubsiteTest.yml +++ b/tests/SubsiteTest.yml @@ -1,184 +1,184 @@ Subsite: - main: - Title: Template - subsite1: - Title: Subsite1 Template - subsite2: - Title: Subsite2 Template - domaintest1: - Title: Test 1 - domaintest2: - Title: Test 2 - domaintest3: - Title: Test 3 + main: + Title: 'Template' + subsite1: + Title: 'Subsite1 Template' + subsite2: + Title: 'Subsite2 Template' + domaintest1: + Title: 'Test 1' + domaintest2: + Title: 'Test 2' + domaintest3: + Title: 'Test 3' SubsiteDomain: - subsite1: - SubsiteID: =>Subsite.subsite1 - Domain: subsite1.* - subsite2: - SubsiteID: =>Subsite.subsite2 - Domain: subsite2.* - dt1a: - SubsiteID: =>Subsite.domaintest1 - Domain: one.example.org - IsPrimary: 1 - dt1b: - SubsiteID: =>Subsite.domaintest1 - Domain: one.* - dt2a: - SubsiteID: =>Subsite.domaintest2 - Domain: two.mysite.com - IsPrimary: 1 - dt2b: - SubsiteID: =>Subsite.domaintest2 - Domain: *.mysite.com - dt3: - SubsiteID: =>Subsite.domaintest3 - Domain: three.* - IsPrimary: 1 + subsite1: + SubsiteID: =>Subsite.subsite1 + Domain: 'subsite1.*' + subsite2: + SubsiteID: =>Subsite.subsite2 + Domain: 'subsite2.*' + dt1a: + SubsiteID: =>Subsite.domaintest1 + Domain: 'one.example.org' + IsPrimary: 1 + dt1b: + SubsiteID: =>Subsite.domaintest1 + Domain: 'one.*' + dt2a: + SubsiteID: =>Subsite.domaintest2 + Domain: 'two.mysite.com' + IsPrimary: 1 + dt2b: + SubsiteID: =>Subsite.domaintest2 + Domain: '*.mysite.com' + dt3: + SubsiteID: =>Subsite.domaintest3 + Domain: 'three.*' + IsPrimary: 1 Page: - mainSubsitePage: - Title: MainSubsitePage - SubsiteID: 0 - home: - Title: Home - SubsiteID: =>Subsite.main - about: - Title: About - SubsiteID: =>Subsite.main - linky: - Title: Linky - SubsiteID: =>Subsite.main - staff: - Title: Staff - ParentID: =>Page.about - SubsiteID: =>Subsite.main - contact: - Title: Contact Us - SubsiteID: =>Subsite.main - importantpage: - Title: Important Page - SubsiteID: =>Subsite.main - subsite1_home: - Title: Home (Subsite 1) - SubsiteID: =>Subsite.subsite1 - subsite1_contactus: - Title: Contact Us (Subsite 1) - SubsiteID: =>Subsite.subsite1 - subsite1_staff: - Title: Staff - SubsiteID: =>Subsite.subsite1 - subsite2_home: - Title: Home (Subsite 2) - SubsiteID: =>Subsite.subsite2 - subsite2_contactus: - Title: Contact Us (Subsite 2) - SubsiteID: =>Subsite.subsite2 + mainSubsitePage: + Title: 'MainSubsitePage' + SubsiteID: 0 + home: + Title: 'Home' + SubsiteID: =>Subsite.main + about: + Title: 'About' + SubsiteID: =>Subsite.main + linky: + Title: 'Linky' + SubsiteID: =>Subsite.main + staff: + Title: 'Staff' + ParentID: =>Page.about + SubsiteID: =>Subsite.main + contact: + Title: 'Contact Us' + SubsiteID: =>Subsite.main + importantpage: + Title: 'Important Page' + SubsiteID: =>Subsite.main + subsite1_home: + Title: 'Home (Subsite 1)' + SubsiteID: =>Subsite.subsite1 + subsite1_contactus: + Title: 'Contact Us (Subsite 1)' + SubsiteID: =>Subsite.subsite1 + subsite1_staff: + Title: 'Staff' + SubsiteID: =>Subsite.subsite1 + subsite2_home: + Title: 'Home (Subsite 2)' + SubsiteID: =>Subsite.subsite2 + subsite2_contactus: + Title: 'Contact Us (Subsite 2)' + SubsiteID: =>Subsite.subsite2 PermissionRoleCode: - roleCode1: - Code: CMS_ACCESS_CMSMain + roleCode1: + Code: CMS_ACCESS_CMSMain PermissionRole: - role1: - Title: role1 - Codes: =>PermissionRoleCode.roleCode1 + role1: + Title: role1 + Codes: =>PermissionRoleCode.roleCode1 Group: - admin: - Title: Admin - Code: admin - AccessAllSubsites: 1 - editor: - Title: Editor - Code: editor - AccessAllSubsites: 1 - subsite1_group: - Title: subsite1_group - Code: subsite1_group - AccessAllSubsites: 0 - Subsites: =>Subsite.subsite1 - subsite2_group: - Title: subsite2_group - Code: subsite2_group - AccessAllSubsites: 0 - Subsites: =>Subsite.subsite2 - subsite1admins: - Title: subsite1admins - Code: subsite1admins - AccessAllSubsites: 0 - Subsites: =>Subsite.subsite1 - allsubsitesauthors: - Title: allsubsitesauthors - Code: allsubsitesauthors - AccessAllSubsites: 1 - subsite1_group_via_role: - Title: subsite1_group_via_role - Code: subsite1_group_via_role - AccessAllSubsites: 1 - Roles: =>PermissionRole.role1 + admin: + Title: Admin + Code: admin + AccessAllSubsites: 1 + editor: + Title: Editor + Code: editor + AccessAllSubsites: 1 + subsite1_group: + Title: subsite1_group + Code: subsite1_group + AccessAllSubsites: 0 + Subsites: =>Subsite.subsite1 + subsite2_group: + Title: subsite2_group + Code: subsite2_group + AccessAllSubsites: 0 + Subsites: =>Subsite.subsite2 + subsite1admins: + Title: subsite1admins + Code: subsite1admins + AccessAllSubsites: 0 + Subsites: =>Subsite.subsite1 + allsubsitesauthors: + Title: allsubsitesauthors + Code: allsubsitesauthors + AccessAllSubsites: 1 + subsite1_group_via_role: + Title: subsite1_group_via_role + Code: subsite1_group_via_role + AccessAllSubsites: 1 + Roles: =>PermissionRole.role1 Permission: - admin: - Code: ADMIN - GroupID: =>Group.admin - editor1: - Code: CMS_ACCESS_CMSMain - GroupID: =>Group.editor - editor2: - Code: SITETREE_VIEW_ALL - GroupID: =>Group.editor - editor3: - Code: VIEW_DRAFT_CONTENT - GroupID: =>Group.editor - accesscmsmain1: - Code: CMS_ACCESS_CMSMain - GroupID: =>Group.subsite1_group - accesscmsmain2: - Code: CMS_ACCESS_CMSMain - GroupID: =>Group.subsite2_group - accesscmsmain3: - Code: CMS_ACCESS_CMSMain - GroupID: =>Group.subsite1admins - accesscmsmain4: - Code: CMS_ACCESS_CMSMain - GroupID: =>Group.allsubsitesauthors - securityaccess1: - Code: CMS_ACCESS_SecurityAdmin - GroupID: =>Group.subsite1_group - securityaccess2: - Code: CMS_ACCESS_SecurityAdmin - GroupID: =>Group.subsite2_group - adminsubsite1: - Code: ADMIN - GroupID: =>Group.subsite1admins + admin: + Code: ADMIN + GroupID: =>Group.admin + editor1: + Code: CMS_ACCESS_CMSMain + GroupID: =>Group.editor + editor2: + Code: SITETREE_VIEW_ALL + GroupID: =>Group.editor + editor3: + Code: VIEW_DRAFT_CONTENT + GroupID: =>Group.editor + accesscmsmain1: + Code: CMS_ACCESS_CMSMain + GroupID: =>Group.subsite1_group + accesscmsmain2: + Code: CMS_ACCESS_CMSMain + GroupID: =>Group.subsite2_group + accesscmsmain3: + Code: CMS_ACCESS_CMSMain + GroupID: =>Group.subsite1admins + accesscmsmain4: + Code: CMS_ACCESS_CMSMain + GroupID: =>Group.allsubsitesauthors + securityaccess1: + Code: CMS_ACCESS_SecurityAdmin + GroupID: =>Group.subsite1_group + securityaccess2: + Code: CMS_ACCESS_SecurityAdmin + GroupID: =>Group.subsite2_group + adminsubsite1: + Code: ADMIN + GroupID: =>Group.subsite1admins Member: - admin: - FirstName: Admin - Surname: User - Email: admin@test.com - Password: rangi - Groups: =>Group.admin - editor: - FirstName: Editor - Surname: User - Email: editor@test.com - Password: rangi - Groups: =>Group.editor - subsite1member: - Email: subsite1member@test.com - Groups: =>Group.subsite1_group - subsite2member: - Email: subsite2member@test.com - Groups: =>Group.subsite2_group - subsite1admin: - Email: subsite1admin@test.com - Groups: =>Group.subsite1admins - allsubsitesauthor: - Email: allsubsitesauthor@test.com - Groups: =>Group.allsubsitesauthors - subsite1member2: - Email: subsite1member2@test.com - Groups: =>Group.subsite1_group_via_role + admin: + FirstName: Admin + Surname: User + Email: admin@test.com + Password: rangi + Groups: =>Group.admin + editor: + FirstName: Editor + Surname: User + Email: editor@test.com + Password: rangi + Groups: =>Group.editor + subsite1member: + Email: subsite1member@test.com + Groups: =>Group.subsite1_group + subsite2member: + Email: subsite2member@test.com + Groups: =>Group.subsite2_group + subsite1admin: + Email: subsite1admin@test.com + Groups: =>Group.subsite1admins + allsubsitesauthor: + Email: allsubsitesauthor@test.com + Groups: =>Group.allsubsitesauthors + subsite1member2: + Email: subsite1member2@test.com + Groups: =>Group.subsite1_group_via_role SiteConfig: - config: - CanCreateTopLevelType: LoggedInUsers \ No newline at end of file + config: + CanCreateTopLevelType: LoggedInUsers diff --git a/tests/SubsitesVirtualPageTest.php b/tests/SubsitesVirtualPageTest.php index 1822b51..59eab99 100644 --- a/tests/SubsitesVirtualPageTest.php +++ b/tests/SubsitesVirtualPageTest.php @@ -5,26 +5,29 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest { 'subsites/tests/SubsiteTest.yml', 'subsites/tests/SubsitesVirtualPageTest.yml', ); - - function setUp() { + + public function setUp() { parent::setUp(); - $this->logInWithPermission('ADMIN'); - - $fh = fopen(Director::baseFolder() . '/assets/testscript-test-file.pdf', "w"); - fwrite($fh, str_repeat('x',1000000)); - fclose($fh); + + // Set backend root to /DataDifferencerTest + AssetStoreTest_SpyStore::activate('SubsitesVirtualPageTest'); + + // Create a test files for each of the fixture references + $file = $this->objFromFixture('File', 'file1'); + $page = $this->objFromFixture('SiteTree', 'page1'); + $fromPath = __DIR__ . '/testscript-test-file.pdf'; + $destPath = AssetStoreTest_SpyStore::getLocalPath($file); + Filesystem::makeFolder(dirname($destPath)); + copy($fromPath, $destPath); + + // Hack in site link tracking after the fact + $page->Content = '

'; + $page->write(); } - function tearDown() { + public function tearDown() { + AssetStoreTest_SpyStore::reset(); parent::tearDown(); - $testFiles = array( - '/assets/testscript-test-file.pdf', - '/assets/renamed-test-file.pdf', - '/assets/renamed-test-file-second-time.pdf', - ); - foreach($testFiles as $file) { - if(file_exists(Director::baseFolder().$file)) unlink(Director::baseFolder().$file); - } } // Attempt to bring main:linky to subsite2:linky @@ -52,7 +55,6 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest { function testFileLinkRewritingOnVirtualPages() { // File setup $this->logInWithPermission('ADMIN'); - touch(Director::baseFolder() . '/assets/testscript-test-file.pdf'); // Publish the source page $page = $this->objFromFixture('SiteTree', 'page1'); @@ -70,19 +72,10 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest { $file->write(); // Verify that the draft and publish virtual pages both have the corrected link - $this->assertContains('assertContains('ID")->value()); - $this->assertContains('assertContains('ID")->value()); - - // File teardown - $testFiles = array( - '/assets/testscript-test-file.pdf', - '/assets/renamed-test-file.pdf', - ); - foreach($testFiles as $file) { - if(file_exists(Director::baseFolder().$file)) unlink(Director::baseFolder().$file); - } } function testSubsiteVirtualPagesArentInappropriatelyPublished() { diff --git a/tests/SubsitesVirtualPageTest.yml b/tests/SubsitesVirtualPageTest.yml index 4708448..501d547 100644 --- a/tests/SubsitesVirtualPageTest.yml +++ b/tests/SubsitesVirtualPageTest.yml @@ -1,10 +1,12 @@ # These need to come first so that SiteTree has the link meta-data written. File: - file1: - Filename: assets/testscript-test-file.pdf + file1: + FileFilename: testscript-test-file.pdf + FileHash: 464dedb70af0dc7f8f3360e7f3ae43cbbf1cdf4e + Name: testscript-test-file.pdf SiteTree: - page1: - Title: page1 - URLSegment: page1 - Content:

\ No newline at end of file + page1: + Title: page1 + URLSegment: page1 + Content: '

' diff --git a/tests/testscript-test-file.pdf b/tests/testscript-test-file.pdf new file mode 100644 index 0000000000000000000000000000000000000000..701b6399991a10111a3a5e3d30aa079fa0590e33 GIT binary patch literal 8861 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