Compare commits

..

576 Commits
1.0.6 ... 2

Author SHA1 Message Date
Steve Boyd
0a3056267c Merge branch '2.8' into 2 2024-07-11 16:16:35 +12:00
Guy Sartorelli
92363c4ca8
Merge pull request #583 from creative-commoners/pulls/2.8/composer
DEP Bump minimum version of framework
2024-07-11 10:00:14 +12:00
Steve Boyd
5025c32fc7 DEP Bump minimum version of framework 2024-07-10 10:48:46 +12:00
github-actions
dcc39392cb Merge branch '2.8' into 2 2024-02-07 11:31:02 +00:00
Guy Sartorelli
d66e723a51
TLN Update translations (#557) 2024-02-07 16:13:02 +13:00
github-actions
23b9db798d Merge branch '2.8' into 2 2023-11-08 11:30:53 +00:00
Guy Sartorelli
83600d758a
Merge pull request #550 from creative-commoners/pulls/2.8/tx-1699241375
TLN Update translations
2023-11-07 11:14:25 +13:00
Steve Boyd
dd3073f41a TLN Update translations 2023-11-06 16:29:35 +13:00
Guy Sartorelli
4e292b8e89
Merge pull request #539 from creative-commoners/pulls/2.8/revert
Revert "Switch to listing views of subsite-filtered sections"
2023-09-07 09:44:32 +12:00
Steve Boyd
e020766095 Revert "Switch to listing views of subsite-filtered sections"
This reverts commit 8ba7070b02.
2023-09-06 17:26:00 +12:00
Guy Sartorelli
4049f7a12d
Merge pull request #490 from micschk/patch-1
Switch to listing views of subsite-filtered sections
2023-09-01 14:58:22 +12:00
github-actions
40ee7c76db Merge branch '2.8' into 2 2023-08-23 11:31:18 +00:00
Guy Sartorelli
178b521c54
ENH Update translations (#534) 2023-08-21 13:16:59 +12:00
Guy Sartorelli
7d1431df5e
Merge pull request #533 from lekoala/3-1
don't trigger permissionFailure when it's not needed
2023-08-18 11:15:48 +12:00
Thomas Portelange
39de47167d don't trigger permissionFailure when it's not needed
(cherry picked from commit a19ed3ed54d4927ef96da8ce96f032dc3b0d897e)
2023-08-17 06:44:29 +02:00
github-actions
3ec16bbe56 Merge branch '2.8' into 2 2023-08-16 11:31:00 +00:00
Guy Sartorelli
6ff4f336f7
Merge pull request #527 from michalkleiner/pulls/element-preview
Add extension to correctly support element preview
2023-06-22 10:51:57 +12:00
Michal Kleiner
a052bfd590 Add extension to correctly support element preview 2023-06-22 01:32:07 +10:00
Steve Boyd
d1d1b139d0 Merge branch '2.8' into 2 2023-06-16 12:10:37 +12:00
Guy Sartorelli
b855a555c8
Merge pull request #525 from creative-commoners/pulls/2.8/tx-1686724870
ENH Update translations
2023-06-15 10:06:14 +12:00
Steve Boyd
c847f3e1d8 ENH Update translations 2023-06-14 18:41:10 +12:00
Steve Boyd
86205a6286 Merge branch '2.8' into 2 2023-03-29 09:59:23 +13:00
Guy Sartorelli
413de7014c
MNT Revert erroneous dependency changes (#519) 2023-03-28 16:50:01 +13:00
Maxime Rainville
b4bf58e132
Merge pull request #518 from creative-commoners/pulls/2.8/no-deprecations
FIX Don't use deprecated API
2023-03-28 11:17:03 +13:00
Guy Sartorelli
a249d46dcc
FIX Don't use deprecated API 2023-03-28 10:22:51 +13:00
Maxime Rainville
701c6cd053
Merge pull request #517 from creative-commoners/pulls/2/dispatch-ci
MNT Use gha-dispatch-ci
2023-03-23 14:19:26 +13:00
Steve Boyd
4006884aa7 MNT Use gha-dispatch-ci 2023-03-21 13:41:59 +13:00
Guy Sartorelli
aa9797a5f1
MNT Update development dependencies 2023-03-10 16:38:46 +13:00
Guy Sartorelli
5e823223d7
MNT Update release dependencies 2023-03-10 16:38:42 +13:00
Guy Sartorelli
04fe468f36
MNT Update development dependencies 2023-03-10 12:21:32 +13:00
Guy Sartorelli
21240a7c83
Merge pull request #513 from creative-commoners/pulls/2/tx-1678080159
ENH Update translations
2023-03-08 10:32:23 +13:00
Steve Boyd
339b0c855b ENH Update translations 2023-03-06 18:22:39 +13:00
Guy Sartorelli
d11124f576
Merge pull request #507 from creative-commoners/pulls/2/depr
API Deprecations
2023-02-08 10:43:16 +13:00
Steve Boyd
412b2709d2 API Deprecations 2023-02-07 17:16:51 +13:00
Guy Sartorelli
7d8909ac6d
Merge pull request #504 from creative-commoners/pulls/2/depr
API Deprecate passing multiple IDs
2023-01-26 14:01:22 +13:00
Steve Boyd
30b1f09af4 API Deprecate passing multiple IDs 2023-01-26 10:39:07 +13:00
Guy Sartorelli
6770dedc2a
Merge branch '2.7' into 2 2022-12-19 03:00:28 +00:00
Guy Sartorelli
67a21914eb
Merge branch '2.7-release' into 2.7 2022-12-19 03:00:24 +00:00
Guy Sartorelli
901dbc8848
Merge branch '2.6' into 2.7-release 2022-12-19 13:52:24 +13:00
Steve Boyd
4308ac4316
Merge pull request #498 from creative-commoners/pulls/2.6/file-permissions
Subsite file permissions
2022-12-19 11:34:52 +13:00
Steve Boyd
73f3d15bfb
[CVE-2022-42949] Subsite file permissions 2022-12-19 11:30:59 +13:00
Guy Sartorelli
a7e9e8dcdc
Merge pull request #496 from creative-commoners/pulls/2.7-release/historyviewer
FIX Do not show copy to subsite buttons in history viewer
2022-12-14 10:54:12 +13:00
Steve Boyd
5f489b1df9 FIX Do not show copy to subsite buttons in history viewer 2022-12-14 10:43:53 +13:00
Sabina Talipova
f45ccead3c
Merge pull request #493 from creative-commoners/pulls/2/stop-using-depr
API Stop using deprecated API
2022-12-05 16:36:37 +13:00
Steve Boyd
416f55ad03 API Stop using deprecated API 2022-11-28 17:49:50 +13:00
Guy Sartorelli
ceaa915b77
Merge pull request #492 from creative-commoners/pulls/2/depr-messages
API Update deprecations
2022-11-21 09:56:31 +13:00
Steve Boyd
aba286d8a3 API Update deprecations 2022-11-16 11:55:02 +13:00
Guy Sartorelli
a1ee94ce61
Update translations 2022-11-10 01:56:21 +00:00
Michael van Schaik
8ba7070b02
Switch to listing views of subsite-filtered sections
Fixes #489
2022-09-27 06:12:48 +02:00
Guy Sartorelli
1e311e8668
Merge pull request #487 from creative-commoners/pulls/2/review-behat-tests
ENH Replace ADMIN permissions with less permissions in Behat test
2022-09-13 10:14:54 +12:00
Sabina Talipova
5eb7e8a7a3 ENH Replace ADMIN permissions with less permissions in Behat test 2022-09-09 15:16:40 +12:00
Maxime Rainville
837ab70a8d
Merge pull request #486 from creative-commoners/pulls/2/fix-userdoc-deploy
MNT Fix github action for deploying userdocs
2022-08-24 11:11:02 +12:00
Guy Sartorelli
800e8dc473
MNT Fix github action for deploying userdocs 2022-08-23 13:55:05 +12:00
Guy Sartorelli
d13d73299a
Merge pull request #485 from creative-commoners/pulls/2/userhelp-fix
DOC Correct title for userhelp
2022-08-22 11:01:31 +12:00
Maxime Rainville
945e2bc370 DOC Correct title for userhelp 2022-08-20 22:00:04 +12:00
Steve Boyd
9ad6c8f97e Merge branch '2.6' into 2 2022-08-02 19:00:38 +12:00
Steve Boyd
f566ab2b2a Merge branch '2.5' into 2.6 2022-08-02 19:00:11 +12:00
Guy Sartorelli
0338d41626
Merge pull request #484 from creative-commoners/pulls/2.5/standardise-modules
MNT Standardise modules
2022-08-02 16:11:26 +12:00
Steve Boyd
193866ec62 MNT Standardise modules 2022-08-01 16:23:25 +12:00
Bart van Irsel
74b6cec374
ENH added config setting to ignore subsite language (#481)
added config setting to ignore subsite language; when using subsites in combination with fluent it picked up wrong yml file
2022-07-28 12:19:33 +12:00
Steve Boyd
b16495cf54 Merge branch '2.6' into 2 2022-07-25 11:39:47 +12:00
Steve Boyd
cdc37de076 Merge branch '2.5' into 2.6 2022-07-25 11:39:25 +12:00
Guy Sartorelli
b9d972b7fd
Merge pull request #483 from creative-commoners/pulls/2.5/behat
FIX Move files to client directory
2022-07-20 09:43:37 +12:00
Steve Boyd
6e1b504ff3 FIX Move files to client directory 2022-07-19 18:35:15 +12:00
Guy Sartorelli
2bab73ee35
Merge pull request #482 from creative-commoners/pulls/2.5/module-standards
MNT Use GitHub Actions CI
2022-07-15 17:22:14 +12:00
Steve Boyd
6969fe06d3 MNT Use GitHub Actions CI 2022-07-05 19:07:52 +12:00
Guy Sartorelli
7860a03180
Merge pull request #478 from creative-commoners/pulls/2/php81
ENH PHP 8.1 compatibility
2022-04-26 17:57:39 +12:00
Steve Boyd
ed4663be9b ENH PHP 8.1 compatibility 2022-04-13 13:49:48 +12:00
Maxime Rainville
acf9715c3b
Merge pull request #475 from creative-commoners/pulls/2/php74
DEP Set PHP 7.4 as the minimum version
2022-02-18 22:09:19 +13:00
Michal Kleiner
5c3d000b9b
Merge pull request #476 from wilr/wilr-patch-1
FIX replace `in_array` check with `hasTable` check
2022-02-15 15:39:46 +13:00
Will Rossiter
dfe1ba5c6a
FIX replace in_array check with hasTable check.
in_array is case-sensitive and may not detect that a table exists when using lowercase format
2022-02-15 15:05:43 +13:00
Steve Boyd
07aefb1982 DEP Set PHP 7.4 as the minimum version 2022-02-10 17:39:05 +13:00
Maxime Rainville
2a16e1eefa
Merge pull request #469 from creative-commoners/pulls/2.4/behat
MNT Update behat tests
2021-11-15 14:31:30 +13:00
Steve Boyd
cae9bd51ec MNT Update behat tests 2021-11-12 12:38:44 +13:00
Maxime Rainville
5049a5e13c
Merge pull request #471 from creative-commoners/pulls/2/sapphire-test-nine
API phpunit 9 support
2021-11-01 22:29:34 +13:00
Steve Boyd
13ab072303 API phpunit 9 support 2021-10-27 18:24:03 +13:00
Steve Boyd
0179176b6c Merge branch '2.4' into 2 2021-09-13 16:00:54 +12:00
Maxime Rainville
3a2ef2b3bc
Merge pull request #467 from creative-commoners/pulls/2.4/fix-test
MNT Fix test to work with session-manager module
2021-09-09 22:48:38 +12:00
Steve Boyd
643b8d436a MNT Fix test to work with session-manager module 2021-09-07 15:36:57 +12:00
Steve Boyd
e306264740
Update build status badge 2021-01-21 16:42:37 +13:00
Steve Boyd
f4ec177065 Merge branch '2.3' into 2 2020-11-11 17:01:38 +13:00
Steve Boyd
bd690ae62b
Merge pull request #458 from creative-commoners/pulls/2.3/fix-travis
FIX yml config, use sminnee/phpunit fork
2020-11-11 11:01:17 +13:00
Serge Latyntcev
3fb2e9a1e0 MNT Define phpcs paths 2020-11-09 16:39:15 +13:00
Serge Latyntcev
08e5af4f2b MNT use shared travis config 2020-11-06 15:34:01 +13:00
Serge Latyntcev
b5bd1e69b5 MNT Fix travis matrix 2020-11-06 13:08:31 +13:00
Serge Latyntcev
017804dbd4 DEP use sminnee/phpunit:5.7 fork 2020-11-06 12:17:07 +13:00
Serge Latyntcev
683e7da208 MNT Fix yaml config syntax 2020-11-06 12:17:05 +13:00
Steve Boyd
212790ae29 Merge branch '2.3' into 2 2020-09-12 18:39:03 +12:00
Steve Boyd
5a4d613d8e
Merge pull request #452 from chrometoasters/bugfix/default-subsite-query
FIX Adjusting query used in getSubsiteIDForDomain()
2020-09-12 18:37:35 +12:00
Mohamed Alsharaf
27bae53017 Adjusting query used in getSubsiteIDForDomain
Prevent unknow database field error when a new DB field added
to Subsite data object.
2020-09-11 16:39:33 +12:00
Dylan Wagstaff
fd10b868ec
Merge pull request #451 from cjsewell/2
Fix "Column 'URLSegment' in where clause is ambiguous" when duplicating pages
2020-01-22 09:01:17 +13:00
Corey Sewell
f1fce6f739 Fix "Column 'URLSegment' in where clause is ambiguous" when duplicating pages 2020-01-17 12:35:08 +13:00
Dylan Wagstaff
2175d44755
Merge pull request #448 from open-sausages/pulls/docs-limit-cross-domain
DOCS Limitation around cross-domain usage
2020-01-14 09:33:42 +13:00
Aaron Carlino
831c3c3cbe
META: Add github action to build docs 2019-12-19 13:50:59 +13:00
Ingo Schommer
67af02c3b2 DOCS Limitation around cross-domain usage 2019-12-17 15:33:28 +13:00
Robbie Averill
b32499ef31 Merge branch '2.3' 2019-09-25 15:01:00 -07:00
Robbie Averill
5d015c7a96 Merge branch '2.2' into 2.3 2019-09-25 15:00:47 -07:00
Robbie Averill
38b356c256 Merge branch '2.1' into 2.2
# Conflicts:
 #	tests/php/SiteTreeSubsitesTest.php
2019-09-25 15:00:35 -07:00
Robbie Averill
5cf44c9d02 Merge branch '2.0' into 2.1 2019-09-25 15:00:00 -07:00
Nik Rolls
b1c1931d5d Detect domains correctly in Director sub-calls
Previously it relied on the PHP-level $_SERVER variable; now it will use
the HTTPRequest so it works correctly in more situations.
2019-08-15 10:16:20 +12:00
Guy Marriott
917640699d
FIX Prevent undefined index notice when trying to determine HTTP… (#440)
FIX Prevent undefined index notice when trying to determine HTTP_HOST during dev/build
2019-07-30 10:36:48 +12:00
Robbie Averill
09abe2b2f2 Use Director::host() over direct $_SERVER access 2019-07-29 10:38:14 +02:00
Robbie Averill
9a7cdbbe2d FIX Prevent undefined index notice when trying to determine HTTP_HOST during dev/build 2019-07-26 09:53:54 +02:00
Robbie Averill
ce63a9ed08
Merge pull request #427 from creative-commoners/pulls/2.0/cascading-themes
FIX Improving support for cascading themes
2019-07-15 12:14:30 +02:00
Guy Marriott
58f89801b0
FIX Ensure constant is accessed correctly 2019-07-12 15:24:10 +12:00
Guy Marriott
9feef185dc
Adding documentation about cascading themes 2019-07-12 13:34:42 +12:00
Guy Marriott
2eb04ffa78
FIX Improving support for cascading themes
- Fixes an issue where themes would cascade "up" the list of themes
- Provides configuration for defining custom theme options with their own sets of cascading themes

Fixes #392
2019-07-12 13:34:42 +12:00
Guy Marriott
e73d622bdb
Update README.md (#435)
Update README.md
2019-07-08 12:19:26 +12:00
Greg808
d0054a1294
Update README.md
Subsite::currentSubsiteID() is deprecated use  SubsiteState::singleton()->getSubsiteId()
2019-07-04 16:52:04 +02:00
Robbie Averill
c4adf556cf
Merge pull request #433 from Greg808/patch-3
Update README.md
2019-06-29 13:32:03 +12:00
Greg808
644b9c8b90
Update README.md
Subsite::currentSubsiteID() is deprecated. class_exists needs a fully-qualified class name to work
2019-06-28 10:49:47 +02:00
Robbie Averill
81e6d0fe59
Merge pull request #432 from Greg808/patch-1
Update README.md
2019-06-28 08:00:10 +12:00
Greg808
9abaca6d48
Update README.md
I am not quite sure if this is needed but i'd expect the code snipets to work with Silverstripe 4 if the requirement say Silverstripe 4. I testet it with SS 4.4.1
Subsite::currentSubsiteID() is deprecated.  class_exists needs namespace to work correctly
2019-06-27 17:32:11 +02:00
Robbie Averill
7d27abf2b1 Update expected json content type in unit test 2019-06-25 16:05:38 +12:00
Robbie Averill
001f44d73b Merge branch '2.3' 2019-06-25 15:45:17 +12:00
Robbie Averill
ca01e2680a Merge branch '2.2' into 2.3 2019-06-25 15:45:08 +12:00
Robbie Averill
e41dc8b018 Merge branch '2.1' into 2.2 2019-06-25 15:44:37 +12:00
Robbie Averill
9655371276 Merge branch '2.0' into 2.1 2019-06-25 15:43:59 +12:00
Robbie Averill
67d10ec0cb Remove SilverStripe 4.0-4.2 from Travis builds 2019-06-25 15:43:50 +12:00
Robbie Averill
4fdf2e24e3 FIX LeftAndMainSubsites::canAccess() now accepts a Member argument and falls back to the session member 2019-06-25 15:42:54 +12:00
Robbie Averill
614819a1d3 Reduce Behat builds to SS 4.3 and update postgres version 2019-06-25 11:12:16 +12:00
Robbie Averill
be3bcab715 Merge branch '2.3' 2019-06-25 10:01:52 +12:00
Robbie Averill
135ae961bf Merge branch '2.2' into 2.3 2019-06-25 10:01:46 +12:00
Robbie Averill
eddbc90524 Remove SilverStripe 4.0-4.2 from Travis builds 2019-06-25 10:01:27 +12:00
Robbie Averill
a4e99a2df5 Merge branch '2.3' 2019-06-25 10:01:06 +12:00
Robbie Averill
77fafe6450 Merge branch '2.2' into 2.3 2019-06-25 10:00:47 +12:00
Robbie Averill
4249fffc0f FIX LeftAndMainSubsites::canAccess() now accepts a Member argument and falls back to the session member 2019-06-25 10:00:13 +12:00
Dylan Wagstaff
b3bd51cb6c
Merge pull request #430 from creative-commoners/pulls/2.3/access-passed-member
FIX LeftAndMainSubsites::canAccess() now accepts a Member argument and falls back to the session member
2019-06-24 11:49:50 +12:00
Robbie Averill
0275bb1eca FIX LeftAndMainSubsites::canAccess() now accepts a Member argument and falls back to the session member 2019-06-24 10:19:58 +12:00
Robbie Averill
483a867289
Merge pull request #428 from harmoney-dev/detect-subsite-by-domain-in-mock-requests
Detect domains correctly in Director sub-calls
2019-06-12 15:42:23 +12:00
Nik Rolls
46a863557b
Detect domains correctly in Director sub-calls
Previously it relied on the PHP-level $_SERVER variable; now it will use
the HTTPRequest so it works correctly in more situations.
2019-06-12 15:14:09 +12:00
Garion Herman
19edb78756
Merge pull request #425 from creative-commoners/pulls/2.2/re-save-virtual-page
FIX Subsites virtual pages now allow you to re-save them when used in conjunction with silverstripe-fluent
2019-06-05 06:18:24 +01:00
Garion Herman
5c4a655106
Merge pull request #418 from creative-commoners/pulls/2.3/fluent-domain-docs
Ensure URL segment field type before using its API, and add docs around subsite and fluent domain compatibility
2019-06-05 05:27:06 +01:00
Robbie Averill
f6503822e8
DOCS Fix typos
[ci skip]

Co-Authored-By: Garion Herman <garion@silverstripe.com>
2019-06-05 15:09:57 +12:00
Robbie Averill
2b26876596 Add test for URLSegment prefix set to primary subsite domain for page 2019-05-31 16:41:36 +12:00
Robbie Averill
900d04d94a Add tests and move logic into the if statement 2019-05-31 16:32:28 +12:00
Robbie Averill
1f51fcd909 FIX Subsites virtual pages now allow you to re-save them when used in conjunction with silverstripe-fluent 2019-05-31 14:41:37 +12:00
Robbie Averill
33244fb430
Merge pull request #422 from creative-commoners/pulls/2.3/noice
Tidy output of IsPublic value in Subsites admin
2019-05-31 13:48:51 +12:00
Robbie Averill
b8576744a1
Merge pull request #419 from creative-commoners/pulls/2.3/fix-sitetree-hints-caching
FIX allowed pagetypes displaying incorrectly when switching subsite
2019-05-31 13:34:37 +12:00
Robbie Averill
1595079156
Merge pull request #421 from creative-commoners/pulls/2.3/subsites-virtual-labels
FIX Field labels for subsites virtual pages are no longer repeated
2019-05-31 13:22:32 +12:00
Robbie Averill
9ee451f706
Merge pull request #417 from creative-commoners/pulls/2.3/default-automatic-protocol
FIX Domains now default to "Automatic" protocol, and have the correct help description
2019-05-31 11:59:54 +12:00
Robbie Averill
2a9f3ac0f6 DOCS Fix phpdoc in summary_fields
[ci skip]
2019-05-31 11:44:09 +12:00
Robbie Averill
fadd42910b
Merge pull request #424 from creative-commoners/pulls/2.3/travis-segfault
Remove code coverage, it is segfaulting on SS 4.4
2019-05-31 11:42:29 +12:00
Robbie Averill
c60acb3190 FIX Field labels for subsites virtual pages are no longer repeated 2019-05-31 11:29:30 +12:00
Garion Herman
4d7641e16a FIX allowed pagetypes displaying incorrectly when switching subsite
This patch depends on an update to the CMS module that provides this
extension point. The code is inert when matched with existing CMS
versions.
2019-05-31 11:28:15 +12:00
Robbie Averill
3b8207d70c Ensure URL segment field type before using its API, and add docs around subsite and fluent domain compatibility 2019-05-31 11:27:58 +12:00
Garion Herman
68c763da3e Tidy output of IsPublic value in Subsites admin 2019-05-31 11:27:47 +12:00
Robbie Averill
1e44e1d4ba FIX Domains now default to "Automatic" protocol, and have the correct help description 2019-05-31 11:26:46 +12:00
Robbie Averill
2644083a2d Remove code coverage, it is segfaulting on SS 4.4 2019-05-31 11:17:51 +12:00
Robbie Averill
ee961594bc Merge branch '2.3' 2019-05-10 11:14:12 +12:00
Robbie Averill
e313f2ed5d FIX Update Behat assertion to use correct label for "Search or choose Page" 2019-05-10 10:03:35 +12:00
Robbie Averill
536420ec68 FIX Update Behat assertion to use correct field label for SilverStripe 4.4 2019-05-10 09:46:21 +12:00
Guy Marriott
f8e4804cc1
Update translations 2019-05-09 15:44:59 +12:00
Robbie Averill
46653e2b07 Merge branch '2.3' 2019-04-15 16:27:59 +12:00
Robbie Averill
fe20bc2907 Merge branch '2.2' into 2.3 2019-04-15 16:27:36 +12:00
Robbie Averill
ec327aee7c Update dependencies for SilverStripe ^4.4 2019-04-15 16:26:53 +12:00
Robbie Averill
d1fc84d15c
Merge pull request #408 from creative-commoners/pulls/2.2/insert-a-link
FIX Content editor group users can now insert links into contents while using subsites
2019-02-10 12:52:33 +03:00
Robbie Averill
f003fb5e74 Add PHP 7.3 to build matrix and move Behat builds to SilverStripe 4.3 2019-02-10 12:41:59 +03:00
Robbie Averill
9dbdd992f7 FIX Content editor group users can now insert links into contents while using subsites 2019-02-10 12:18:19 +03:00
Robbie Averill
1294671086 Merge branch '2.2' 2019-01-28 21:22:31 +02:00
Robbie Averill
fbd98ff402 FIX Disable transactions in SubsiteTest to prevent global state bugs in CWP kitchen sink test suite 2019-01-28 21:22:12 +02:00
Dylan Wagstaff
d9fcaa3319
Merge pull request #403 from creative-commoners/pulls/2.3/bootstrap-notice
NEW Use Bootstrap styled alerts in assets notification
2018-11-28 14:40:49 +13:00
Robbie Averill
da2e8fcc8b NEW Use Bootstrap styled alerts in assets notification
This will be the default in SilverStripe 4.4, and has been partially implemented in 4.3
2018-11-27 16:23:58 +01:00
Dylan Wagstaff
ce9dd1a856
Merge pull request #402 from creative-commoners/pulls/2.2/subsites-is-off-the-table-or-maybe-its-on-it-now-idk
FIX Catching situation where database has no tables but it exists
2018-11-08 12:26:59 +13:00
Guy Marriott
59f6685e2a
FIX Catching situation where database has no tables but it exists 2018-11-08 12:05:36 +13:00
Robbie Averill
e510213c3e Merge branch '2.2' 2018-11-07 16:28:55 +02:00
Robbie Averill
a0ecbdf4b6 Remove obsolete branch alias 2018-11-07 16:28:40 +02:00
Robbie Averill
e4fe534f10 Merge branch '2.1' 2018-11-07 16:28:10 +02:00
Guy Marriott
3afdd01d41
Merge pull request #400 from creative-commoners/pulls/2.1/remove-json-methods
FIX Replace Convert JSON methods with json_* methods, deprecated from SilverStripe 4.4
2018-10-29 11:46:16 +13:00
Robbie Averill
2a35a5c70a FIX Replace Convert JSON methods with json_* methods, deprecated from SilverStripe 4.4 2018-10-28 21:41:32 +00:00
Robbie Averill
1fa549886f Define explode limit when removing port 2018-10-20 23:16:05 +02:00
Daniel Hensby
2bf4812947
Merge pull request #399 from creative-commoners/pulls/2.1/separate-test
Update testDomainProtocol to use a dataProvider
2018-10-19 22:50:43 +01:00
Dylan Wagstaff
810ee63ea6
Merge pull request #398 from creative-commoners/pulls/2.1/ignore-domain-port
FIX Ignore ports when matching domain for subsite
2018-10-20 10:31:10 +13:00
Robbie Averill
5e79abdbbc Update testDomainProtocol to use a dataProvider
This might help with test state leakage
2018-10-19 21:51:32 +02:00
Robbie Averill
ff9997e0f2 FIX Ignore ports when matching domain for subsite 2018-10-19 20:51:43 +02:00
Robbie Averill
bf7dd9c37b Merge branch '2.1'
# Conflicts:
  #	src/Extensions/SiteTreeSubsites.php
2018-10-19 16:28:50 +02:00
Robbie Averill
9199d509d6 Merge branch '2.0' into 2.1 2018-10-19 16:27:38 +02:00
Robbie Averill
bbfb93d50d
Merge pull request #397 from silverstripe/revert-388-pulls/2.1/fix-role-permissions
Revert "FIX CMS permission checks for subsite are now handled in the state context"
2018-10-19 11:27:37 +02:00
Guy Marriott
7cc86199e7
Revert "FIX CMS permission checks for subsite are now handled in the state context" 2018-10-19 12:00:50 +13:00
Robbie Averill
1a5666182e
Merge pull request #396 from open-sausages/pulls/2.0/352-fix-migrate-file-task
BUG: Fix `MigrateFileTask` not migrating files for subsites
2018-10-18 11:37:21 +02:00
Robbie Averill
2a6f7b5dfb Automated linting fix 2018-10-18 11:03:16 +02:00
Robbie Averill
6a8f15a194
Merge pull request #395 from DorsetDigital/patch-1
Change source of admin URL in getIsAdmin()
2018-10-18 11:02:46 +02:00
bergice
e52fe41a23 BUG: Fix MigrateFileTask not migrating files for subsites
Fixes #352
2018-10-18 18:41:07 +13:00
DorsetDigital
1e458ef03d
Change source of admin URL in getIsAdmin()
As per #394 
Change direct call to the AdminRootController config setting, using instead the admin_url() method on the class which provides detection via the Director rules, and the fallback to the config setting.
2018-10-17 23:20:20 +01:00
Dylan Wagstaff
4323db52f0
Merge pull request #391 from creative-commoners/pulls/2.1/duplicate-tabs
FIX Remove duplicate Configuration tabs when creating a new subsite
2018-09-14 19:55:33 +12:00
Robbie Averill
bb226a0652 FIX Remove duplicate Configuration tabs when creating a new subsite 2018-09-14 09:42:29 +02:00
Dylan Wagstaff
fa3f1fa767
Merge pull request #389 from creative-commoners/pulls/2.1/dont-catch-exceptions
FIX Only continue delegation when DB exceptions are caused by no database selected
2018-09-08 18:54:51 +12:00
Robbie Averill
313d22ffca FIX Only continue delegation when DB exceptions are caused by no database selected
This prevents the middleware from interrupting legitimate database exceptions from being
propagated.
2018-09-07 11:06:55 +02:00
Guy Marriott
70dc70f494
Merge pull request #388 from creative-commoners/pulls/2.1/fix-role-permissions
FIX CMS permission checks for subsite are now handled in the state context
2018-08-27 14:16:13 +12:00
Robbie Averill
7681634cb2 Remove irrelevant check for subsites list size, use func_num_args() and add break to loop 2018-08-27 10:04:57 +12:00
Robbie Averill
6af985420f FIX CMS permission checks for subsite are now handled in the state context
We now check the subsite state for the context and validate it against the current member's
group permissions using the SilverStripe ORM relationships instead of using SQL queries.

More granular permission checks e.g. canView etc are still up to data models to define and
handle.
2018-08-24 16:58:36 +12:00
Guy Marriott
039a7a8c84
Merge pull request #387 from creative-commoners/pulls/2.1/fix-cross-subsite-duplication
FIX Pages now correctly duplicate children across subsites
2018-08-24 11:03:56 +12:00
Robbie Averill
e8a72e1c33 FIX Duplicate page's parent IDs are now assumed or zeroed after duplication 2018-08-24 10:30:27 +12:00
Guy Marriott
5b8a0dbf13
Merge pull request #386 from creative-commoners/pulls/2.2/deprecate-duplicate
API Deprecate duplicateSubsiteRelations. Use "cascade_duplicates" config API instead.
2018-08-24 10:27:21 +12:00
Robbie Averill
8af796fa7a API Deprecate duplicateSubsiteRelations. Use "cascade_duplicates" config API instead. 2018-08-24 10:13:22 +12:00
Robbie Averill
7f28c32427 FIX Pages now correctly duplicate children across subsites 2018-08-24 10:12:05 +12:00
Robbie Averill
6747b5ffe8
Merge pull request #381 from lekoala/patch-1
allow disabling filter using queryParam
2018-08-24 08:50:47 +12:00
Robbie Averill
9ba1275b49
Merge pull request #382 from lekoala/patch-2
allow using queryParam to disable filter
2018-08-24 08:50:41 +12:00
Guy Marriott
687e013793
Merge branch '2.1' 2018-08-20 09:58:59 +12:00
Guy Marriott
f24fd60f14
Merge pull request #383 from creative-commoners/pulls/2.1/loosen-json-test
Loosen test assertion on content type for application/json
2018-08-20 09:57:26 +12:00
Robbie Averill
87485e39f4 Loosen test assertion on content type for application/json
See https://github.com/silverstripe/silverstripe-framework/issues/5594

This was changed in Silverstripe 4.3
2018-08-20 09:19:54 +12:00
Thomas Portelange
6e35807dc7
allow using queryParam to disable filter
much better than global state (and should potentially replace cookie usage that is user controlled)
2018-08-16 16:20:48 +02:00
Thomas Portelange
c177a9f640
allow disabling filter using queryParam
It is much better than relying on global state
2018-08-16 16:19:25 +02:00
Robbie Averill
d8088edfa9 Merge branch '2.1' 2018-07-26 15:15:29 +12:00
Robbie Averill
bf2c81dce6
Merge pull request #1 from silverstripe-security/pulls/2.1/ss-2018-016
[SS-2018-016] Group table name is escaped to prevent possibility of SQL injection
2018-07-25 09:55:14 +12:00
Robbie Averill
4b6804eaab [SS-2018-016] Group table name is escaped to prevent possibility of SQL injection 2018-07-16 11:22:58 +12:00
Raissa North
7e1c2eb0aa
Merge pull request #378 from creative-commoners/pulls/2.1/initial-publish-bug
Add subsite switching and page publish behat tests
2018-07-16 10:13:20 +12:00
Robbie Averill
191c63bd9b Remove extra subsite fixture definition 2018-07-13 16:31:10 +12:00
Robbie Averill
f635aa9811 Add subsite switching behat tests and a test that shows a UI bug in publishing 2018-07-13 15:59:37 +12:00
Robbie Averill
c732c0c799
Merge pull request #375 from creative-commoners/pulls/2.2/deprecate-old-preview-url-function
Deprecate alternatePreviewLink function as per CMS
2018-07-05 10:48:37 +12:00
Dylan Wagstaff
ffbcb9a0c8 Deprecate alternatePreviewLink function as per CMS
SilverStripe CMS 4.0.0 issues a deprecation notice before calling
alternatePreviewLink on any page that hasMethod (i.e. is applied via an
extension such as SiteTreeSubsites). Instead of double-issuing a notice,
we will just mark this as deprecated in the next minor version via
docblocks.
2018-07-05 09:55:45 +12:00
Dylan Wagstaff
d78e3c4662 Merge branch '2.1' 2018-07-05 09:50:21 +12:00
Dylan Wagstaff
75b851326d Merge branch '2.0' into 2.1 2018-07-05 09:49:11 +12:00
Robbie Averill
19f0162265
Merge pull request #374 from creative-commoners/pulls/2.0/cms-preview-nonbreakage
FIX apply SubsiteID getVar to CMS Preview fetches
2018-07-04 15:57:32 +12:00
Dylan Wagstaff
5370bc8af6 FIX apply SubsiteID getVar to CMS Preview fetches
A preview link must be loaded on the same domain the CMS is loaded
through, which was previously causing issues when a page (identified
via URLSegment) did not exist on the subsite domain. By _always_
prepending the identifier to the preview link, this should never happen.
2018-07-04 15:21:47 +12:00
Guy
c43abb05c2
Merge branch '2.1' 2018-06-20 17:11:18 +12:00
Guy
56bc57a5c5
Removing obsolete branch alias 2018-06-20 17:10:32 +12:00
Dylan Wagstaff
de0bba0df2
Merge pull request #371 from creative-commoners/pulls/2.0/exists
Switch count for exists() check for readability
2018-06-20 09:44:51 +12:00
Robbie Averill
153db12d42 Switch count for exists() check for readability 2018-06-20 09:27:04 +12:00
Dylan Wagstaff
f0a5dcf63c
Merge pull request #370 from creative-commoners/pulls/2.1/hide-single-subsite
NEW Hide subsite selector dropdown if no subsites have been created yet
2018-06-19 17:06:11 +12:00
Dylan Wagstaff
f19b82f029
Merge pull request #369 from creative-commoners/pulls/2.0/fix-edit-check
FIX Do no provide input for canEdit or canPublish if no subsites exist
2018-06-19 17:04:15 +12:00
Robbie Averill
21f4d80ef2 NEW Hide subsite selector dropdown if no subsites have been created yet
Note that "Main site" is always included in the list of subsites, even though it is not an actual record
2018-06-19 15:51:33 +12:00
Robbie Averill
dc9d6de62d FIX Do no provide input for canEdit or canPublish if no subsites exist 2018-06-19 15:43:09 +12:00
Raissa North
9038a166bd
Merge pull request #367 from creative-commoners/pulls/2.0/escaped-menu
FIX Double escaping subsites title in CMS menu
2018-06-19 13:56:02 +12:00
Robbie Averill
315621892d FIX Double escaping subsites title in CMS menu 2018-06-19 13:47:15 +12:00
Robbie Averill
5285a68404
Merge pull request #365 from creative-commoners/pulls/master/add-supported-module-badge
Add supported module badge to readme
2018-06-19 09:23:14 +12:00
Dylan Wagstaff
90bddc7e55 Add supported module badge to readme 2018-06-15 17:49:52 +12:00
Robbie Averill
aae3e655c4 Merge branch '2.0' 2018-06-15 09:44:24 +12:00
Dylan Wagstaff
875f5519ed
Merge pull request #361 from creative-commoners/pulls/2.0/canedit-fix
FIX Do not make subsite based file permission decisions when no subsite is set
2018-06-11 10:41:20 +12:00
Dylan Wagstaff
e8b292ecc5
Merge pull request #362 from creative-commoners/pulls/2.0/fix-group-tablename
FIX Use correct table name for Group model when performing DB upgrades from older versions
2018-06-08 11:30:07 +12:00
Robbie Averill
b6ba567ee5 FIX Do not make subsite based file permission decisions when no subsite is set 2018-06-08 10:05:23 +12:00
Robbie Averill
8222f619f8 FIX Use correct table name for Group model when performing DB upgrades from older versions 2018-06-08 10:04:58 +12:00
Robbie Averill
1e5ee559b0
Merge pull request #363 from creative-commoners/pulls/2.0/re-enable-behat
FIX Re-enable Behat using chromedriver and silverstripe/recipe-testing
2018-06-08 10:03:53 +12:00
Robbie Averill
c30c0d94eb Increase memory limit in Travis configuration 2018-06-07 16:00:52 +12:00
Robbie Averill
af06f803c5 FIX Re-enable Behat using chromedriver and silverstripe/recipe-testing 2018-06-01 15:48:19 +12:00
Robbie Averill
032ea73154 Update branch alias for 2.x-dev 2018-05-25 15:18:34 +12:00
Robbie Averill
631498fd1d Remove obsolete branch alias 2018-05-25 15:17:58 +12:00
Robbie Averill
5c801a4392 Update translations 2018-05-25 15:16:40 +12:00
Robbie Averill
94e88b5be9 Update createDefaultPages to use SubsiteState API 2018-05-04 14:04:50 +12:00
Robbie Averill
adc0395c25 Automated linting 2018-05-04 13:57:25 +12:00
Robbie Averill
cc064c43ce Merge branch '1' 2018-05-04 13:56:26 +12:00
Robbie Averill
531a38e043 Merge branch '1.4' into 1 2018-05-04 13:44:30 +12:00
Dylan Wagstaff
6a8da1caaa
Merge pull request #359 from creative-commoners/pulls/1.4/default-page-on-creation
FIX Automatically create default SiteTree records for new subsites
2018-04-20 21:10:52 +12:00
Robbie Averill
b4943fb77c FIX Automatically create default SiteTree records for new subsites 2018-04-20 17:14:06 +12:00
Dylan
7115d060a6 Update translations 2018-04-04 10:17:01 +12:00
Daniel Hensby
418fa586a3
Merge pull request #353 from creative-commoners/pulls/2.0/count-properly
FIX be consistent with the variable setting
2018-03-27 12:24:21 +01:00
Dylan
fe1c0a58c0
FIX be consistent with the variable setting
So as to avoid fatal errors while attempting to count.
2018-03-27 11:51:30 +01:00
Robbie Averill
c7425ef23e
Merge pull request #354 from dhensby/pulls/live-test
FIX Versioned regressions
2018-03-27 07:50:54 +13:00
Daniel Hensby
67bb7e0028
FIX Versioned regressions 2018-03-26 13:51:20 +01:00
Dylan Wagstaff
0fcde6c0bb
Merge pull request #350 from creative-commoners/pulls/2.0/fix-cmsmain-tests
Use injected CMSMain to prevent unit test error missing dependencies
2018-03-19 11:15:33 +13:00
Robbie Averill
2f7dc7a7a8 Clear hints cache for SS 4.1 only if method exists (SS 4.0 support) 2018-03-19 10:03:32 +13:00
Robbie Averill
30454dc04f FIX Support blacklist for SS 4.0 and 4.1, remove old Translatable reference in test class 2018-03-15 17:15:19 +13:00
Robbie Averill
6feec1de34 Prefer source to ensure that TestAssetStore is installed 2018-03-15 17:05:12 +13:00
Robbie Averill
856496e09d Ensure SiteTree class name hints cache gets cleared between tests 2018-03-15 17:03:41 +13:00
Robbie Averill
7f77bb0c17 Add different installer versions to Travis build matrix 2018-03-15 16:47:00 +13:00
Robbie Averill
afd23be64c FIX Correctly decode page type blacklist from JSON 2018-03-15 16:44:48 +13:00
Robbie Averill
8398a361cd Use injected CMSMain to prevent unit test error missing dependencies 2018-03-15 15:56:10 +13:00
Robbie Averill
f8f39c135f Merge branch '1' 2018-02-22 11:38:07 +13:00
Robbie Averill
24ee5a186e Merge branch '1.4' into 1 2018-02-22 11:37:26 +13:00
Robbie Averill
e04d04dc70 Merge branch '1.3' into 1.4 2018-02-22 11:35:52 +13:00
Robbie Averill
742c4a7fa3 Update branch alias for 1.5.x-dev 2018-02-22 10:22:58 +13:00
Robbie Averill
e4702dc132 Remove obsolete branch-alias 2018-02-22 09:25:11 +13:00
Dylan Wagstaff
299f12765c
Merge pull request #344 from creative-commoners/pulls/2.0/fix-subsite-redirect
FIX Redirect user back to the full current URL after changing subsites
2018-02-09 10:21:24 +13:00
Damian Mooyman
5279748931
Merge pull request #342 from creative-commoners/pulls/2.0/fix-extension-classes
FIX Use correct class names for Settings and Subsites admin menu items
2018-02-08 11:06:58 +13:00
Robbie Averill
ab24aa6d9f FIX Redirect user back to the full current URL after changing subsites 2018-02-08 00:23:00 +13:00
Robbie Averill
510d80683a FIX Use correct class names for Settings and Subsites admin menu items 2018-02-07 19:50:36 +13:00
Daniel Hensby
73943afd5b
Merge pull request #338 from creative-commoners/pulls/2.0/fix-validation-error
Pulls/2.0/fix validation error
2018-02-01 17:02:32 +00:00
Raissa North
34c0c4946c FIX Hide subsite operations when no subsites exist 2018-02-01 15:41:54 +13:00
Raissa North
5404dafac1 FIX Fix unit tests for installations in which the base url is set 2018-02-01 14:26:12 +13:00
Raissa North
e405dff946 FIX Add an empty string to the subsite operations dropdown.
This prevents a subsite from being chosen as the default option, assuming this is undesired
2018-02-01 14:24:04 +13:00
Raissa North
809b1d9b85 Fix switch statement syntax and replace deprecated doPublish() method. 2018-02-01 13:24:48 +13:00
Raissa North
098660e27d Refactor GridFieldSubsiteDetailForm_ItemRequest and $_cache_accessible_sites
Remove underscores to comply with PSR-2
2018-02-01 13:22:48 +13:00
Raissa North
8b5f593999 Fix line length 2018-02-01 13:19:02 +13:00
Raissa North
db69c486a0 FIX: Turn SilverStripe Map into an array to give an accurate falsey value 2018-01-31 17:25:32 +13:00
Raissa North
bb5f2c07e1 Merge branch 'master' of https://github.com/silverstripe/silverstripe-subsites into pulls/2.0/fix-validation-error 2018-01-31 11:02:19 +13:00
Raissa North
aa370f8df6 FIX Replace deprecated doPublish() method with publishRecursive() 2018-01-31 10:10:09 +13:00
Raissa North
4f15894f86 Add codesniffer ruleset 2018-01-31 10:08:21 +13:00
Daniel Hensby
cde41c4409
Merge pull request #337 from creative-commoners/pulls/2.0/fix-subsites-treedropdown
FIX Subsites VirtualPage link to edit main page and TreeDropdownField API implementations
2018-01-30 20:38:48 +00:00
Robbie Averill
34b3fbf04d FIX Subsites VirtualPage link to edit main page and TreeDropdownField API implementations 2018-01-30 16:21:45 +13:00
Robbie Averill
6fb0af0f93 FIX Typo in requirements call to TreeDropdownField javascript resource 2018-01-26 16:09:42 +13:00
Dylan Wagstaff
8eee411d5a
Merge pull request #335 from creative-commoners/pulls/2.0/more-configging
API Convert most of Subsite public statics to config properties
2018-01-23 12:49:22 +13:00
Robbie Averill
17427fd251 Ignore host-map.php in phpcs, and ensure it is disabled in unit tests 2018-01-23 12:09:48 +13:00
Robbie Averill
6bbf988fda API Convert most of Subsite public statics to config properties 2018-01-23 12:09:48 +13:00
Robbie Averill
73f5bcaecc
Merge pull request #336 from mikenz/patch-2
Fix alignment of menu toggle icons
2018-01-23 10:39:10 +13:00
Dylan Wagstaff
17e0db47a1
Merge pull request #334 from creative-commoners/pulls/2.0/fix-file-subsites
API Move subsite dropdown logic for folders into FolderFormFactoryExtension
2018-01-23 10:37:38 +13:00
Mike Cochrane
d02b668885
Fix alignment of menu toggle icons 2018-01-22 23:01:59 +13:00
Robbie Averill
a88ac5d05d Switch from precise to trusty in Travis 2018-01-18 16:27:00 +13:00
Robbie Averill
ace068b93d FIX Move extensions to correct folder location for PSR-4 compatibility 2018-01-18 16:23:11 +13:00
Robbie Averill
4f174ffd6e NEW Add test for FolderFormFactoryExtension with a separate fixture 2018-01-18 16:22:35 +13:00
Robbie Averill
a0836bed79 NEW Add PHP 7.2 to Travis build matrix 2018-01-18 16:04:09 +13:00
Robbie Averill
1dd1f2ce6c API Move subsite dropdown logic for folders into FolderFormFactoryExtension 2018-01-18 16:03:58 +13:00
Robbie Averill
a201e7e953
Merge pull request #332 from mikenz/css-did-nothing
Remove or update CSS that wasn't doing anything
2018-01-08 10:08:14 +13:00
Mike Cochrane
4e297d920b Remove or update CSS that wasn't doing anything 2018-01-05 04:29:54 +00:00
Damian Mooyman
1cdae5024a
Merge pull request #329 from mikenz/patch-1
Bugfix: Don't assume all forms have a URLSegment field
2017-12-14 14:55:45 +13:00
Mike Cochrane
157db89dbf
Bugfix: Don't assume all forms have a URLSegment field 2017-12-14 14:17:30 +13:00
Robbie Averill
727efb0ea0
Merge pull request #328 from jyrkij/parent-calls
Add parent call to onAfterWrite
2017-12-14 09:44:41 +13:00
Jyrki Lilja
01d09695a9 Add parent call to onAfterWrite 2017-12-13 14:53:09 +02:00
Damian Mooyman
33622c440b
Merge pull request #326 from creative-commoners/pulls/2.0/vendorise
NEW Convert to vendor module
2017-12-06 08:55:05 +13:00
Robbie Averill
b529947362 Update constraint for silverstripe/serve dependency 2017-12-04 15:33:29 +13:00
Robbie Averill
bcab2af5af FIX Update Scrutinizer configuration to run new build system 2017-12-04 15:29:15 +13:00
Robbie Averill
95a71566f1 NEW Convert to vendor module 2017-12-04 15:26:17 +13:00
Damian Mooyman
0ebf9509bf
Merge pull request #325 from jsirish/refactor/canEditExtend
Subsite - allow canEdit to be extended
2017-11-23 15:38:05 +13:00
Jason Irish
8fcfe55ba8 Subsite - allow canEdit to be extended 2017-11-22 12:22:57 -06:00
Daniel Hensby
7bf430ba83
Merge branch 'pull/322' 2017-10-11 16:17:20 +01:00
Mike Cochrane
1dcba49e5c SubsitesTreeDropdownField: Get the tree in the context of the correct subsite 2017-10-06 11:09:58 +13:00
Daniel Hensby
0157bd0c05 Merge pull request #321 from mikenz/resetable-state
Implement Resettable interface on SubsiteState so it gets reset between unit tests
2017-10-05 17:22:56 +01:00
Robbie Averill
4a1f571f1c Merge pull request #319 from mikenz/lost-namespaces
Add missing use lines, fix some class names, remove unused use lines
2017-10-05 19:06:33 +13:00
Mike Cochrane
a2a86c6eb1 Implement Resettable interface on SubsiteState so it gets reset between unit tests 2017-10-05 04:52:53 +00:00
Mike Cochrane
7301099241 Add missing use lines, fix some class names, remove unused use lines 2017-10-05 03:09:47 +00:00
Will Rossiter
c2a81f76ae Merge pull request #320 from creative-commoners/pulls/2.0/vendorise-ci
FIX Use vendor file paths for CI references to core
2017-10-05 10:15:52 +13:00
Robbie Averill
fa6b80624c FIX Use vendor file paths for CI references to core 2017-10-05 09:57:46 +13:00
Will Rossiter
3996810fa9 Merge pull request #318 from creative-commoners/pulls/2.0/subsite-management-ux-fixes
NEW Remove Subsite Configuration header, rename Main tab and remove Domains GridField title
2017-10-02 20:24:36 +13:00
Robbie Averill
62df0b45a5 NEW Remove Subsite Configuration header, rename Main tab and remove Domains GridField title 2017-10-02 16:34:21 +13:00
Will Rossiter
4328f8aa35 Merge pull request #316 from creative-commoners/pulls/2.0/text-collection-fixes
FIX Add localisation for missing SITECONTENTLEFT and run text collector
2017-09-27 09:00:08 +13:00
Robbie Averill
9822807a58 FIX Add localisation for missing SITECONTENTLEFT and run text collector 2017-09-26 14:49:05 +13:00
Franco Springveldt
c343542106 Merge pull request #315 from creative-commoners/pulls/2.0/minor-ux-improvements
FIX Update XHR controller hook selector and refactor getCMSFields to use scaffolding and separate tabs
2017-09-21 11:44:41 +12:00
Robbie Averill
961185a6fd FIX Make subsite configuration CMS fields header label translatable 2017-09-21 10:17:35 +12:00
Robbie Averill
95e437d828 FIX Update XHR controller hook selector and refactor getCMSFields to use scaffolding and separate tabs 2017-09-19 14:51:14 +12:00
Robbie Averill
d9fb455d3d Merge pull request #313 from wernerkrauss/fix-309
update test for handling subsite specific themes
2017-09-13 16:47:45 +12:00
Werner M. Krauß
e83435b2b6 update test for handling subsite specific themes
fixes #309
2017-09-13 06:13:55 +02:00
Will Rossiter
ea00ac3520 Merge pull request #312 from creative-commoners/pulls/2.0/add-readme-badges
DOCS Add Scrutinizer and codecov badges to readme
2017-09-13 13:42:35 +12:00
Will Rossiter
ed8efc2144 Merge pull request #311 from creative-commoners/pulls/2.0/fix-dataprovider
FIX Update visibility of test dataprovider and update doc block
2017-09-13 13:28:18 +12:00
Robbie Averill
05953f1758 DOCS Add Scrutinizer and codecov badges to readme 2017-09-13 11:57:12 +12:00
Robbie Averill
02a0895f4a FIX Update visibility of test dataprovider and update doc block 2017-09-13 11:51:32 +12:00
Robbie Averill
0f5c6008e1 FIX PHPCS violation in Subsite 2017-09-13 09:06:47 +12:00
Will Rossiter
20e1c016f1 Merge pull request #310 from wilr/pulls/4.0/fix-blacklist-toggle
FIX Page type blacklist not matching classes correctly (Fixes 297)
2017-09-13 08:28:15 +12:00
Will Rossiter
188b02df6b FIX Page type blacklist not matching classes correctly (Fixes 297)
Tidied up the UI, removed custom javascript in favour of core Toggle field.
2017-09-12 09:50:26 +12:00
prij
32385e580d Excluded colon from domain field validation 2017-09-12 07:20:42 +12:00
Will Rossiter
784c3fca5e Merge pull request #305 from creative-commoners/pulls/2.0/namespace-translations
NEW Namespace translations, implement into class localisation calls
2017-09-11 12:06:07 +12:00
Robbie Averill
1704d1d033 FIX Update class name in upgrader map 2017-09-11 11:50:49 +12:00
Robbie Averill
9bccfffe4e NEW Namespace translations, implement into class localisation calls 2017-09-11 11:50:49 +12:00
Will Rossiter
b8ab1eda7e Merge pull request #301 from creative-commoners/pulls/2.0/move-code-to-src
Rename "code" to "src" for PSR-4 consistency
2017-09-11 11:46:09 +12:00
Robbie Averill
94755775ba Rename "code" to "src" for PSR-4 consistency 2017-09-11 11:44:52 +12:00
Will Rossiter
83077ff78f Merge pull request #303 from creative-commoners/pulls/2.0/buildtask-segments
NEW Add segment to SubsiteCopyPageTask
2017-09-11 11:31:57 +12:00
Daniel Hensby
60b259ebb5 Merge pull request #308 from wernerkrauss/feature-alternateabsolutelink-action
alternateAbsoluteLink() respects action parameter
2017-09-08 12:13:33 +01:00
Daniel Hensby
1452e3600b
Travis fixes 2017-09-08 12:06:38 +01:00
Werner M. Krauß
774dee91d6
alternateAbsoluteLink() respects action parameter
fixes #275
2017-09-08 12:06:37 +01:00
Robbie Averill
40b8e102d0 Merge branch '1' 2017-09-08 15:01:26 +12:00
Robbie Averill
bbf4b67e23 Merge branch '1.3' into 1 2017-09-08 14:58:21 +12:00
Robbie Averill
827f4f2fe9 Merge branch '1.2' into 1.3 2017-09-08 14:58:01 +12:00
Robbie Averill
f0b762cd0d Merge branch '1.1' into 1.2 2017-09-08 14:46:50 +12:00
Robbie Averill
c1bf1bcc21 Merge branch '1.0' into 1.1 2017-09-08 14:41:11 +12:00
Robbie Averill
2cbdeba69a BUG Remove Behat tests from Travis matrix for SS3 2017-09-08 14:31:02 +12:00
Robbie Averill
58b8476ede Merge branch '1' 2017-09-08 13:57:10 +12:00
Robbie Averill
e84c4f5d7d Merge branch '1.3' into 1 2017-09-08 13:55:16 +12:00
Robbie Averill
4884bdde05 Merge branch '1.2' into 1.3 2017-09-08 13:54:28 +12:00
Robbie Averill
4328c20b45 Merge branch '1.1' into 1.2 2017-09-08 13:54:05 +12:00
Robbie Averill
669995d544 Use precise distro on Travis 2017-09-08 13:52:59 +12:00
Robbie Averill
20b81fc4b0 Merge branch '1.0' into 1.1 2017-09-08 13:52:05 +12:00
Damian Mooyman
9d186d6e7a Merge pull request #307 from creative-commoners/pulls/2.0/upgrade-exclusions
Add upgrade exclusion rules
2017-09-08 13:09:40 +12:00
Damian Mooyman
bd26907abf Merge pull request #306 from creative-commoners/pulls/1.0/remove-transifex
SS3 translations are frozen. Please contribute translation fixes via pull requests to lang files.
2017-09-08 13:09:23 +12:00
Daniel Hensby
f7bdc570d1 Merge pull request #300 from wernerkrauss/sanitise-error-filename
Sanitise filenames for error pages
2017-09-07 14:23:00 +01:00
Werner M. Krauß
2da5828e90 Sanitise filenames for error pages
fixes #299
2017-09-07 11:19:07 +02:00
Robbie Averill
de2220754c Add upgrade exclusion rules 2017-09-07 17:01:49 +12:00
Robbie Averill
df4f68be08 SS3 translations are frozen. Please contribute translation fixes via pull requests to lang files. 2017-09-07 16:47:42 +12:00
Damian Mooyman
aab69e0baa Merge pull request #304 from creative-commoners/pulls/2.0/namespace-templates
API Move templates to namespaced location, remove unused templates
2017-09-07 16:43:46 +12:00
Robbie Averill
8c4a4e743c API Move templates to namespaced location, remove unused templates
templates/LeftAndMain_Menu.ss looks to be a copy of the fully namespaced version, which is no longer used - removed. GridFieldAddFromTemplateButton also looks to be unused, so was also removed.
2017-09-07 16:20:01 +12:00
Robbie Averill
e331c51abb NEW Add segment to SubsiteCopyPageTask 2017-09-07 16:00:40 +12:00
Franco Springveldt
a7fee1729e Merge pull request #302 from creative-commoners/pulls/2.0/fix-db-not-ready-delegation
FIX Ensure delegated requests are continued when DB builds fail in middleware
2017-09-07 15:58:53 +12:00
Robbie Averill
56accd0856 FIX Ensure delegated requests are continued when DB builds fail in middleware 2017-09-07 15:54:53 +12:00
Damian Mooyman
adfa7257d7 Merge pull request #294 from creative-commoners/pulls/2.0/fix-tests
Fix up test suite, re-enable Behat, update further SS4 API changes
2017-09-06 10:26:05 +12:00
Robbie Averill
19aeb8fd64 API Add getSubsiteIdWasChanged calculated dynamically 2017-09-05 13:48:28 +12:00
Robbie Averill
65f85faff6 FIX Catch database exceptions in middleware when DB is not ready, set session ID after delegation 2017-09-05 12:07:49 +12:00
Robbie Averill
1ac6e78bb3 FIX Remove session coupling, leave it to middleware. Use state instead. 2017-09-04 11:45:21 +12:00
Robbie Averill
793b46ede3 FIX Update i18n::validate_locale for SS4 2017-09-01 12:33:16 +12:00
Robbie Averill
314f218306 Merge branch '1' 2017-09-01 12:30:43 +12:00
Robbie Averill
ffe0d61c4f Merge branch '1.3' into 1 2017-09-01 12:29:48 +12:00
Robbie Averill
c3471ece1c Merge pull request #288 from gregsmirnov/pulls/1.3/fix-subsite-page-language
Fix page rendering with proper subsite locale
2017-09-01 12:22:34 +12:00
Robbie Averill
b9582167c7 Mark SubsitesVirtualPage tests as incomplete, need to be fixed later 2017-08-31 14:17:47 +12:00
Robbie Averill
d934fbe08c FIX Update behat tests and add configuration 2017-08-31 10:21:46 +12:00
Robbie Averill
1a9797c185 FIX Remove last use of static session methods, update some namespaces and assertion fixes 2017-08-31 09:44:09 +12:00
Damian Mooyman
46bcffaa86 Merge pull request #293 from creative-commoners/pulls/2.0/add-subsite-state
NEW Add SubsiteState and initialisation middleware
2017-08-30 17:42:41 +12:00
Robbie Averill
b0087b9035 FIX Allow persisted subsite IDs to session from state, fix remaining unit tests 2017-08-30 15:29:13 +12:00
Robbie Averill
c155855100 FIX Update API changes in ErrorPage and typo in extension config class name 2017-08-30 12:14:11 +12:00
Robbie Averill
c620ff02f4 FIX LeftAndMain references to owner class, replace Member::currentUser usage 2017-08-30 12:04:43 +12:00
Robbie Averill
38031887a9 FIX Update alternateTreeTitle to updateTreeTitle 2017-08-30 11:54:42 +12:00
Robbie Averill
e129cafa94 NEW Add SubsiteState and initialisation middleware, replace Subsite::currentSubsiteID use 2017-08-30 11:47:11 +12:00
Damian Mooyman
5cf2d87e59 Merge pull request #291 from creative-commoners/pulls/2.0/update-travis-config
Update Travis configuration for PHP 5.6-7.1, add composer dependencies and codecov.io config
2017-08-29 18:18:04 +12:00
Robbie Averill
ce360aa383 Comment out Behat Travis runs, needs some love 2017-08-29 17:49:01 +12:00
Robbie Averill
c081de1202 FIX Replace static assertions with instance calls 2017-08-29 17:43:29 +12:00
Robbie Averill
7ffaf61aeb FIX Update tests API implementations, add missing fixture namespaces 2017-08-29 17:43:29 +12:00
Robbie Averill
8d8ee14cc2 FIX Run SS standard PHP linter, separate SiteTree test mock classes 2017-08-29 17:43:28 +12:00
Robbie Averill
9fdc1d6607 Update Travis configuration for PHP 5.6-7.1, add composer dependencies and codecov.io config 2017-08-29 17:43:14 +12:00
Damian Mooyman
60725e7b5c Merge pull request #289 from wernerkrauss/fix-ss-4-master
SilverStripe 4 Compatibility
2017-08-29 16:47:29 +12:00
Garion Herman
8be959f747 Migrate excluded report to YAML config. 2017-08-28 22:23:39 +12:00
Garion Herman
ddefef11d6 Fix Subsite picker UI, reformat CSS. 2017-08-28 22:10:40 +12:00
Garion Herman
af6f499cac Add non-blank default to ThemeFieldEmptyString translation. 2017-08-28 22:05:01 +12:00
Garion Herman
9a289a2e17 Merge branch 'master' into fix-ss-4-master 2017-08-28 21:52:32 +12:00
Franco Springveldt
428a5f958e Update translations 2017-08-25 16:44:47 +12:00
Gregory Smirnov
ab81117c5e Fix page rendering with proper subsite locale 2017-08-10 20:14:59 +02:00
Robbie Averill
a13bf10184 Merge remote-tracking branch 'origin/1' 2017-08-02 16:39:32 +12:00
Robbie Averill
03e52101bb Revert "SS4 namespaces compatibility"
This reverts commit e8f5f58bb0.
2017-08-02 15:51:37 +12:00
Robbie Averill
9b09eb94e0 Merge remote-tracking branch 'origin/1.3' into 1 2017-08-02 15:10:09 +12:00
Robbie Averill
4d38452435 Update branch alias for 1.4.x-dev 2017-08-02 14:55:15 +12:00
Robbie Averill
9b97f8af89 Use dist:precise for Travis builds to ensure PHP 5.3 works 2017-08-02 14:52:30 +12:00
Robbie Averill
a4f647c9ed Merge remote-tracking branch 'origin/1.3' into 1 2017-08-02 14:49:52 +12:00
Robbie Averill
43d128d520 Merge remote-tracking branch 'origin/1.2' into 1.3 2017-08-02 14:48:26 +12:00
Robbie Averill
6eed988317 Merge remote-tracking branch 'origin/1.1' into 1.2 2017-08-02 14:47:53 +12:00
Garion Herman
5ec1c94410 Bring session / validation calls in line with SS4b1 API, replace icon. 2017-07-25 14:25:58 +12:00
Garion Herman
438836bfcc Shift LeftAndMain_Menu template to force override. 2017-07-21 12:30:59 +12:00
Garion Herman
32c45d0856 Merge branch 'fix-ss-4-alpha-7' into fix-ss-4-master 2017-07-21 12:27:57 +12:00
Robbie Averill
8bc9728104 Merge pull request #287 from timkung/hotfix/default-subsite-query
Adjusting query used in getSubsiteIDForDomain
2017-07-19 18:22:16 +12:00
Tim Kung
24ebd1c9f9 adjusting query used in getSubsiteIDForDomain to prevent new DB fields being added to the SQL call if they are not yet added to the DB 2017-07-19 17:57:23 +12:00
Daniel Hensby
83b0afe4d4 Merge pull request #282 from creative-commoners/pulls/1.3/travis-php7
Add PHP7 + SS3.6 build to Travis configuration
2017-06-15 17:00:11 +01:00
Robbie Averill
5be5f1c3b3 Add PHP7 + SS3.6 build to Travis configuration 2017-06-15 11:41:17 +12:00
Werner M. Krauß
2b7c3d1f85 Updating README 2017-06-08 08:35:06 +02:00
Werner M. Krauß
877f4f5f9d Add Subsite theme as main theme; allow cascading of themes 2017-06-07 12:18:35 +02:00
Garion Herman
1975861aec Fix Duplicate to Subsite functionality. 2017-06-04 15:05:59 +12:00
Garion Herman
c2484365cf Fix misc. Subsite docblocks, remove DataObject::get_by_id call. 2017-06-04 14:34:38 +12:00
Garion Herman
49fbfcb459 Adapt to Permission::reset, DataObject::duplicate API changes. 2017-06-04 14:33:04 +12:00
Garion Herman
21a8c56217 Adapt SubsiteXHRController to LeftAndMain API changes. 2017-06-04 14:30:51 +12:00
Werner M. Krauß
a4a1ab6a78 formatting code 2017-06-01 15:57:53 +02:00
Werner M. Krauß
55c7240425 fixing code analysis: simplify if statements 2017-06-01 15:56:28 +02:00
Werner M. Krauß
9673c881c1 fixing code analysis: simplify if-return statements 2017-06-01 15:48:01 +02:00
Werner M. Krauß
fea1684f5c fixing code analysis: use static::assertContains 2017-06-01 15:44:32 +02:00
Werner M. Krauß
9d3c4506af fixing code analysis: fix parameter in method call 2017-06-01 15:43:30 +02:00
Werner M. Krauß
849c0061e8 fixing code analysis: phpunit: use assertInstanceOf 2017-06-01 15:32:46 +02:00
Werner M. Krauß
25754e1158 fixing code analysis: updating phpdoc 2017-06-01 15:32:45 +02:00
Werner M. Krauß
17010f39a3 fixing code analysis: removing unneeded else statements 2017-06-01 15:32:44 +02:00
Werner M. Krauß
abe1ac9fe6 fixing code analysis: replacing deprecated methods 2017-06-01 15:32:38 +02:00
Werner M. Krauß
4fc13b19f7 fixing code analysis: removing unnecessary parenthesis 2017-06-01 15:32:36 +02:00
Werner M. Krauß
e7ad086641 fixing code analysis: safely use single quotes 2017-06-01 15:32:30 +02:00
Werner M. Krauß
76852594a8 fixing code analysis: public method and case mismatch 2017-06-01 15:31:30 +02:00
Werner M. Krauß
e86cc55ba6 fixing a PHP7 only statement to be 5.6 compatible 2017-06-01 14:43:25 +02:00
Werner M. Krauß
e03e7d9ce9 fixing a namespaced ORM call 2017-06-01 14:42:56 +02:00
Werner M. Krauß
212e4797b0 marking some tests as skipped that need more refactoring 2017-05-31 06:41:45 +02:00
Werner M. Krauß
6f1e1ab953 SubsiteVirtualPage: SubsiteID should not be a virtual field 2017-05-30 21:43:37 +02:00
Werner M. Krauß
709cbfa2c8 fix SubsiteAdminTest 2017-05-30 21:21:15 +02:00
Werner M. Krauß
f348f5fa97 fix SiteConfigSubsites AugmentSQL: get the right table name to filter 2017-05-30 20:50:52 +02:00
Werner M. Krauß
997459caf3 Get some more tests working
Updating config and i18n calls
2017-05-30 20:50:22 +02:00
Werner M. Krauß
492f437589 fixing FileSubsitesTest
call extension directly in test, as Versioned now is also applied to File
and has this method
2017-05-30 15:15:54 +02:00
Werner M. Krauß
e2bdd5ca41 fixing tests: adjusting yml file, update Config::modify usage 2017-05-30 15:14:28 +02:00
Tim Kung
5d3af16aaf - namespacing all classes
- moving all phpunit tests into tests/php
- moving all extensions from _config.php into config.yml and removing obsolete _config.php
- moving GridFieldSubsiteDetailForm_ItemRequest into own file

(cherry picked from commit ee02828)
2017-05-30 15:13:40 +02:00
Tim Kung
d8e72f3ac9 updating spacing and adding autoload key
(cherry picked from commit e201462)
2017-05-30 14:04:42 +02:00
Tim Kung
8586749318 Updating editorconfig
(cherry picked from commit 2f99e51)
2017-05-30 14:03:24 +02:00
Werner M. Krauß
b8f98323ae merge #2 2017-05-30 11:14:51 +02:00
Werner M. Krauß
9862cf5ea6 merge ss4 fixes by cheddam 2017-05-29 13:42:42 +02:00
Robbie Averill
4126a0a1fa Merge pull request #273 from danaenz/1.3
Conditionally add Theme dropdown (if there are any themes available)…
2017-05-25 11:49:36 +12:00
Werner M. Krauß
d1e829697f adjusting test fixtures yml file 2017-05-24 15:40:58 +02:00
Werner M. Krauß
c5f507b3f9 reformat code and tests 2017-05-24 15:26:28 +02:00
Werner M. Krauß
2295501587 upgrade tests 2017-05-24 15:25:34 +02:00
Werner M. Krauß
ef602abe47 replace config::inst()->update() with config::modify->set() 2017-05-24 15:20:51 +02:00
Werner M. Krauß
a49189ef58 fix removed i18n::get_common_locales() 2017-05-24 15:09:13 +02:00
Werner M. Krauß
a7ef6472ee db query fixes 2017-05-24 14:55:03 +02:00
Werner M. Krauß
f814534d93 config: fix excluded report 2017-05-24 14:51:02 +02:00
Werner M. Krauß
b71e544820 adding table names config 2017-05-24 14:31:56 +02:00
Werner M. Krauß
fe6d93eaac fixing double use declarations of Subsite class 2017-05-24 14:31:36 +02:00
Werner M. Krauß
8aa6512a49 adding legacy.yml for remapping DataObjects 2017-05-24 14:05:44 +02:00
Werner M. Krauß
80885a75d7 adding .upgrade.yml 2017-05-24 14:03:51 +02:00
Werner M. Krauß
ff6d28b067 move config to yml 2017-05-24 13:56:10 +02:00
Werner M. Krauß
2c3b5bf5af remove VirtualPageController usage 2017-05-24 13:42:53 +02:00
Werner M. Krauß
e33a5b4cae upgrader: upgrade code 2017-05-24 13:36:04 +02:00
Werner M. Krauß
2c84e627db upgrader: namespacing classes 2017-05-24 12:32:05 +02:00
Werner M. Krauß
5bb718224c fix circular dependency error 2017-05-24 12:03:30 +02:00
Damian Mooyman
6225b2d680 Merge pull request #278 from DarrenInwood/sql-update
Update SQL to remove ambiguity
2017-05-23 13:57:01 +12:00
Darren Inwood
0fc3490219 Update SQL to remove ambiguity 2017-05-22 12:29:03 +12:00
Danae Miller-Clendon
8705a46b98 Conditionally add Theme dropdown (if there are any themese available) for #261. Replace field instantiatiors in getCMSFields() with ::create() to support injection.
Add empty string / lang entry to Theme dropdown
2017-04-24 15:41:31 +12:00
Daniel Hensby
77fb5c8d77 Merge pull request #269 from robbieaverill/patch-2
Update PHPUnit version to 4.8
2017-01-26 20:38:46 +00:00
Robbie Averill
6fb0aab811 Update PHPUnit version to 4.8
As in the framework
2017-01-26 10:17:24 +13:00
Damian Mooyman
10302932a9 Update translations 2016-11-17 12:39:49 +13:00
Daniel Hensby
2cce5e1606 Merge pull request #266 from SomarDesignStudios/pulls/fix-copy-child-pages
Fix copying child pages to subsite
2016-11-11 10:55:07 +00:00
David Craig
ae6badf5c0
Fix copying child pages to subsite 2016-11-11 16:18:41 +13:00
Daniel Hensby
84551163a6 Merge pull request #264 from kinglozzer/ss4-compat
SS4 namespaces compatibility
2016-09-23 15:20:57 +01:00
Loz Calver
e8f5f58bb0 SS4 namespaces compatibility 2016-09-23 09:34:23 +01:00
Daniel Hensby
81c95c9f3e Merge pull request #259 from tractorcow/pulls/test-state
BUG Prevent translatable / subdirs interfering with test state
2016-08-31 17:38:00 +01:00
Ed
9f498616fd add license to composer.json (#260) 2016-08-31 11:09:47 +12:00
Damian Mooyman
0e61dfc3f6 BUG Prevent translatable / subdirs interfering with test state 2016-08-23 12:36:12 +12:00
Damian Mooyman
7c97a8ae74 Update translations 2016-08-17 11:08:01 +12:00
Damian Mooyman
6afac5f9af Merge pull request #196 from dnadesign/duplication_improvements
made duplication between subsites more robust
2016-08-10 18:30:14 +12:00
Daniel Hensby
65bf5c732b Merge pull request #255 from dnadesign/copy_children_subsites
NEW: Add IncludeChildren option for copying pages between subsites.
2016-07-18 11:40:23 +01:00
Daniel Hensby
b8667fb933 Merge pull request #256 from madmatt/pulls/fix-null-dataquery
Fix error when dataQuery is a null object
2016-07-18 11:39:47 +01:00
madmatt
3962df53cd Fix error when dataQuery is a null object 2016-07-18 17:10:11 +12:00
Will Rossiter
c8f6f4a588 NEW: Add IncludeChildren option for copying pages between subsites. 2016-07-18 16:41:38 +12:00
Ingo Schommer
871e2bcefd Merge pull request #250 from open-sausages/pulls/fix-copy
BUG Fix copy to subsite breaking on sub-pages
2016-06-13 10:24:13 +12:00
Damian Mooyman
ff28ac1b1e BUG Fix copy to subsite breaking on sub-pages
Fixes #192

Signed-off-by: Damian Mooyman <damian@silverstripe.com>
Signed-off-by: Ingo Schommer <ingo@silverstripe.com>
2016-06-13 10:23:33 +12:00
Daniel Hensby
f7bab259b7 Merge pull request #249 from open-sausages/pulls/fix-xhr-canaccess
BUG Prevent SubsiteXHRController failing if there are no subsites available
2016-06-06 12:37:31 +01:00
Damian Mooyman
98636f8f58
BUG Prevent SubsiteXHRController failing if there are no subsites available
Fixes #200
2016-05-27 15:55:27 +12:00
Damian Mooyman
bdecbe9097 Update changelog for 1.2.3 2016-05-24 16:58:07 +12:00
Daniel Hensby
9e7142d229 Merge pull request #247 from tractorcow/pulls/1.2/fix-subsites-urlsegment
BUG Fix issue with urlsegment being renamed in subsites
2016-05-23 10:06:34 +01:00
Damian Mooyman
a98958fdf9
BUG Fix issue with urlsegment being renamed in subsites 2016-05-23 15:29:01 +12:00
Damian Mooyman
e556a57250 Update changelog for 1.2.2 2016-05-19 11:28:13 +12:00
Damian Mooyman
8572263cb6 Update translations 2016-05-18 16:58:44 +12:00
Daniel Hensby
50cc4f857a Merge pull request #246 from tractorcow/pulls/1.1/sudo-false
Set sudo:false to use new containerised structure
2016-05-16 10:07:25 +01:00
Damian Mooyman
6e91600507 Set sudo:false to use new containerised structure 2016-05-16 17:18:22 +12:00
Daniel Hensby
c7dbf44f11 DOCS Fixing broken link 2016-04-06 12:09:06 +01:00
John Milmine
3587bc666f made duplication between subsites more robust
and added some extra methods so it's easier to overwrite or extend
2016-03-22 21:55:11 +13:00
Damian Mooyman
01f976aa28 Merge pull request #234 from gorriecoe/CMSMenu-attributes-fix
Added attributes to menu link
2016-02-10 09:28:34 +13:00
Gorrie
07d449fa2d Removed target attribute to match main template 2016-02-09 16:09:21 +13:00
Gorrie
cf642aae2f Added attributes to menu link
Fix for missing attributes defined in CMSMenu::add_link($id, $title,
$link, $priority, $attributes);
2016-02-05 11:20:51 +13:00
Damian Mooyman
504342b45e Add changelog for 1.2.1 release 2016-02-04 17:34:48 +13:00
Damian Mooyman
7ee8bdbf72 Merge pull request #176 from jason-zz/patch-1
Update SubsiteDomain.php
2016-02-04 17:26:56 +13:00
Damian Mooyman
cef0781c92 BUG: The move to subsite folder dropdown in files is gone
Rebase of https://github.com/silverstripe/silverstripe-subsites/pull/152
2016-02-04 17:22:03 +13:00
Damian Mooyman
20c6cbc851 Merge pull request #178 from dnadesign/ImageTrackingFix
redoing bugfix
2016-02-04 17:05:29 +13:00
Damian Mooyman
0a93a42c2c Merge pull request #210 from helpfulrobot/add-standard-scrutinizer-config
Added standard Scrutinizer config
2016-02-04 17:04:16 +13:00
Damian Mooyman
6ea8dc7ca8 Update translations 2016-02-04 11:03:42 +13:00
Scott Hutchinson
8a0afcf225 Merge pull request #232 from tractorcow/pulls/33compat
Update templates for 3.3 compatibility
2016-02-03 16:47:45 +13:00
Damian Mooyman
a1aba25708 Update templates for 3.3 compatibility 2016-02-03 13:27:03 +13:00
Daniel Hensby
69693e77ca Merge pull request #230 from ianherbert/patch-1
DOCS Update README.md
2016-01-27 18:17:00 +00:00
Ian Herbert
8b049fd46c Update README.md
Fixing broken links to userguide documentation.
2016-01-27 15:58:00 +13:00
Damian Mooyman
c15491c8c5 Merge pull request #229 from ctx2002/patch-1
BUG Fix Subsite module does not picks up themes
2016-01-25 15:49:11 +13:00
ctx2002
598d45aca6 Subsite module does not picks up themes
This PR fixed unable to create subsite problm.
 I was unable to create a subsite, because of no themes been shown in Theme dropdown menu.

How to test:

1> Use composer to install a fresh copy of Silverstripe 3.2
2> Use composer to install subsite module.
3> Try to create a subsite. 
4> No themes loaded into Theme dropdown menu.

5> Apply this PR.
6> fresh page / or dev/build,  now, themes loaded into The dropdown menu.
2016-01-25 15:36:20 +13:00
Damian Mooyman
fb670d30d7 Merge pull request #224 from mandrew/1.1
Moved user docs into userguide folder to display on userhelp site
2016-01-05 14:00:15 +13:00
Damian Mooyman
540071c449 Merge pull request #227 from mandrew/1.2
DOCS: Moved user docs into userguide folder to display on userhelp site
2016-01-05 14:00:04 +13:00
Mike Andrewartha
3f3fa95e2c moved user content into userhelp folder, removed old user manual pdf, added index file, updated readme links 2016-01-05 13:44:15 +13:00
Damian Mooyman
686bf3bdda Merge pull request #226 from helpfulrobot/update-license-year
Updated license year
2016-01-05 11:20:13 +13:00
helpfulrobot
d803de205f Updated license year 2016-01-01 06:45:57 +13:00
Damian Mooyman
18f248fadd Merge pull request #225 from mandrew/1.0
Moved user docs into userguide folder to display on userhelp site
2015-12-21 14:52:59 +13:00
Mike Andrewartha
257d71d946 moved user content into userhelp folder, removed user manual, added index file, updated readme links 2015-12-21 11:16:45 +13:00
Mike Andrewartha
280562fb7e moved user content into userhelp folder, removed old user manual pdf, added index file, updated readme links 2015-12-21 11:08:11 +13:00
Scott Hutchinson
8458c52429 Merge pull request #222 from tractorcow/pulls/master-template
BUG Fix menu compatibility with framework 4.x
2015-11-26 13:36:52 +13:00
Damian Mooyman
8afbb6842f BUG Fix menu compatibility with framework 4.x 2015-11-26 13:24:29 +13:00
Damian Mooyman
145b8e6633 Remove alias from 1.1 branch 2015-11-25 11:49:10 +13:00
Damian Mooyman
b4024c94bb Add changelog for 1.2.0 2015-11-25 11:48:01 +13:00
Damian Mooyman
2803573a9c Update master branch to 2.0.0 for 4.x compatibility 2015-11-25 11:45:34 +13:00
Daniel Hensby
1876b78204 Merge pull request #221 from tractorcow/pulls/1.1/fix-https
API Add option to specify http / https on subsite domains
2015-11-24 09:50:39 +00:00
Daniel Hensby
a4c925b625 Merge pull request #220 from tractorcow/psr-2
Reformat for psr-2
2015-11-24 09:50:28 +00:00
Damian Mooyman
ce90c2124b API Add option to specify http / https on subsite domains 2015-11-24 18:03:49 +13:00
Daniel Hensby
218382b984 Merge pull request #218 from tractorcow/pulls/fix-config
API Fix compatibility with framework 4.x
2015-11-23 09:36:50 +00:00
Daniel Hensby
4c17537d1d Merge pull request #219 from helpfulrobot/add-standard-code-of-conduct
Added standard code of conduct
2015-11-23 09:33:14 +00:00
Damian Mooyman
a0ede56c0e Reformat for psr-2 2015-11-23 16:53:45 +13:00
helpfulrobot
e241e70fbf Added standard code of conduct 2015-11-21 20:17:10 +13:00
helpfulrobot
a38c3a6ff3 Added standard Scrutinizer config 2015-11-21 19:32:46 +13:00
Damian Mooyman
0d7dc49d6b API Rename mysiteconfig to subsiteconfig
API make ErrorPageSubsite 4.x compatible
BUG Fix incorrect yml
BUG Fix incorrect DataExtension::augmentSQL implementation
2015-11-20 17:04:23 +13:00
Daniel Hensby
d45ff44edb Merge pull request #213 from helpfulrobot/add-standard-editor-config
Added standard editor config
2015-11-19 17:30:39 +00:00
Daniel Hensby
2fc67a1e5d Merge pull request #215 from helpfulrobot/add-standard-license
Added standard license
2015-11-19 17:21:16 +00:00
Daniel Hensby
7ba4a20b75 Merge pull request #216 from helpfulrobot/add-license-to-composer
Added license to composer.json
2015-11-19 11:59:31 +00:00
Daniel Hensby
933cfc8cfa Merge pull request #217 from helpfulrobot/add-standard-git-attributes
Added standard git attributes
2015-11-19 10:41:36 +00:00
helpfulrobot
9000bc47c5 Added standard git attributes 2015-11-19 19:13:41 +13:00
helpfulrobot
415206e70b Added license to composer.json 2015-11-19 18:53:20 +13:00
helpfulrobot
4b0440b15b Added standard license 2015-11-19 18:32:20 +13:00
Scott Hutchinson
5f02e0c406 Merge pull request #214 from tractorcow/pulls/release-110
Add changelog for 1.1.0 release
2015-11-19 17:13:38 +13:00
Damian Mooyman
1fc2fe5440 Add changelog for 1.1.0 release 2015-11-19 16:48:57 +13:00
helpfulrobot
a9ee70c5dd Added standard editor config 2015-11-19 13:26:50 +13:00
Damian Mooyman
984d5d9a59 Update translations 2015-11-17 16:07:38 +13:00
Damian Mooyman
5e18e8dca3 Merge remote-tracking branch 'origin/1.1'
Conflicts:
	.travis.yml
2015-11-13 19:06:09 +13:00
Damian Mooyman
c22c8dc810 Merge remote-tracking branch 'origin/1.0' into 1.1
Conflicts:
	code/extensions/LeftAndMainSubsites.php
	tests/SiteTreeSubsitesTest.php
2015-11-13 19:02:12 +13:00
Damian Mooyman
e9678221fc Merge pull request #209 from chillu/pulls/subsites-dropdown
BUG Subsites selection on SubsitesVirtualPage (fixes #45 and #47)
2015-11-13 18:52:23 +13:00
Ingo Schommer
cf534aad31 BUG Subsites selection on SubsitesVirtualPage (fixes #45 and #47) 2015-11-13 17:31:44 +13:00
Scott Hutchinson
63aa46aa36 Merge pull request #207 from tractorcow/pulls/tests
Test 3 / 3.2 branches and php 5.6
2015-10-30 18:45:54 +13:00
Damian Mooyman
c7077ae749 Test 3 / 3.2 branches and php 5.6 2015-10-30 17:51:53 +13:00
Damian Mooyman
79625583bd Merge pull request #206 from scott1702/1.1
Add sticky nav toggle button
2015-10-30 15:57:13 +13:00
scott1702
8c9c62f08c Add sticky nav toggle button 2015-10-30 15:46:16 +13:00
Daniel Hensby
0d9d697286 Merge pull request #204 from bummzack/issue-203-missing-route
Added missing route to `SubsiteXHRController`
2015-10-29 14:55:37 +00:00
Roman Schmid
798a1158d6 Added missing route to SubsiteXHRController for SilverStripe 3.2 compatibility. 2015-10-28 10:39:21 +01:00
Will Rossiter
b979b38694 FIX: #138 allow subsite summary fields to be customized 2015-10-21 09:09:10 +13:00
Damian Mooyman
3bcaa48d67 Update translations 2015-08-20 12:47:37 +12:00
Will Rossiter
9c9e0bfa94 Correct link to editing a subsite virtual page. 2015-07-27 10:57:10 +12:00
Daniel Hensby
ce3f8c9060 Merge pull request #191 from wernerkrauss/patch-1
Wrong edit link in SubsitesVirtualPage
2015-07-22 13:39:44 +01:00
Daniel Hensby
0274b9effd Move to new travis containerised infrastructure 2015-07-20 15:58:43 +01:00
Damian Mooyman
80daa54dfc Merge pull request #188 from torleif/patch-2
SubsitesVirtualPage icon disapears in CMS
2015-07-02 12:13:06 +12:00
wernerkrauss
996abfcc58 Wrong edit link in SubsitesVirtualPage
In SS3.1 we have /admin/pages for cms, was still old link.
2015-06-17 16:30:56 +02:00
Daniel Hensby
6f06dd565b Updating travis provisioner
Travis will now be more resilient to `composer self-update` failures
2015-06-15 10:02:06 +01:00
Damian Mooyman
601e174b34 Merge pull request #190 from assertchris/3.2-compat
3.2 compat changes
2015-06-09 11:12:45 +12:00
Christopher Pitt
750cdbcff9 3.2 compat changes 2015-06-09 10:50:43 +12:00
torleif
c97b1140bf SubsitesVirtualPage icon disapears in CMS
The SubsitesVirtualPage icon is hidden in the CMS when using the subsite module and CWP
2015-05-27 15:46:39 +12:00
Ingo Schommer
5604879146 4.x core compat 2015-04-30 22:39:22 +12:00
Damian Mooyman
bfc70f9b06 Fix subsites to use correct permissions
See http://www.silverstripe.org/software/download/security-releases/ss-2015-008-sitetree-creation-permission-vulnerability

Conflicts:
	tests/SiteTreeSubsitesTest.php
2015-04-30 22:34:28 +12:00
Ingo Schommer
c18a0a266f Adjusted tests to new SiteTree->canCreate() logic in 3.1.11+
Checks SiteConfig permissions by default now
2015-04-30 22:34:28 +12:00
Ingo Schommer
999c115961 Merge pull request #173 from dhensby/master
Adding .editorconfig
2015-04-30 19:51:48 +12:00
Damian Mooyman
c389f36eda Merge pull request #184 from unclecheese/patch-2
MINOR: Update documentation to have more clarity around risks/benefits
2015-03-25 11:43:03 +13:00
unclecheese
2d4d35b1a7 MINOR: Update documentation to have more clarity around risks/benefits 2015-03-25 10:07:12 +13:00
Sean Harvey
d701afcb61 Merge pull request #183 from tractorcow/pulls/1.0/update-test-permissions
Fix subsites to use correct permissions
2015-03-23 14:58:11 +13:00
Damian Mooyman
2595d655cb Fix subsites to use correct permissions
See http://www.silverstripe.org/software/download/security-releases/ss-2015-008-sitetree-creation-permission-vulnerability
2015-03-23 14:35:52 +13:00
Daniel Hensby
831a918f8d Merge pull request #181 from tractorcow/pulls/3.2-compat
Use 3.2 compatible API
2015-02-24 16:52:56 +00:00
Damian Mooyman
5b9af35566 Use 3.2 compatible API 2015-02-24 09:34:34 +13:00
Damian Mooyman
e6c8dff7be Merge remote-tracking branch 'origin/1.0'
Conflicts:
	.travis.yml
	code/extensions/LeftAndMainSubsites.php
	composer.json
2015-02-24 09:12:38 +13:00
John Milmine
94edb694b7 redoing bugfix 7b11e979fa removed by cac77703 2015-02-12 08:14:49 +13:00
Jason
d99785d773 Update SubsiteDomain.php
Add $default_sort.
2014-12-17 10:44:43 +11:00
Daniel Hensby
6d6667aa08 Adding .editorconfig 2014-12-04 15:48:23 +00:00
Will Rossiter
0520b57f84 Merge pull request #145 from purplespider/patch-2
Fixes #135: LeftAndMain switching between subsites
2014-11-24 15:11:12 +13:00
Damian Mooyman
b6f59741d6 Update master-alias to follow stable release versioning 2014-11-19 14:12:34 +13:00
Damian Mooyman
533c7fae8d Merge pull request #163 from unclecheese/patch-1
Update subsites caveat to align more with other references.
2014-10-06 11:05:45 +13:00
unclecheese
8acc4a230a Update subsites caveat to align more with other references.
e.g. here https://www.cwp.govt.nz/about/how-does-cwp-work/

Rewording of subsites caveat
2014-10-06 10:59:58 +13:00
Sean Harvey
ba6cb193b7 Merge pull request #157 from tractorcow/pulls/0.6/branch
BUG Fix incompatibility with framework 3.2
2014-08-26 18:06:40 +12:00
Damian Mooyman
a97b0d33eb BUG Fix incompatibility with framework 3.2 2014-08-26 11:42:50 +12:00
James Cocker
47df87f62c Fixes #135: LeftAndMain switching between subsites
When trying to switch to a different subsite from a page's editing view, it wouldn't switch. This was partly due to a $record always existing due to the homepage fallback on currentPageID : https://github.com/silverstripe/silverstripe-cms/blob/3.1/code/controllers/CMSMain.php#L816

So as currentPage() couldn't actually be used to test for the existance of a current page, I've added in a check for isset($this->owner->urlParams['ID']).

I've also moved the check for $_GET['SubsiteID’] which indicated a forced subsite switch (eg. via the dropdown switcher) above the check for a current page, as it should take precedence, and it wasn't being run when both conditions matched causing the subsite not to change.

Tested changing subsites from /admin/pages, from page edit view, from a page edit URL, and from other CMS sections such as Files and Security, and all seems to be working perfectly now.
2014-06-04 16:41:28 +01:00
166 changed files with 9023 additions and 4771 deletions

23
.editorconfig Normal file
View File

@ -0,0 +1,23 @@
# For more information about the properties used in
# this file, please see the EditorConfig documentation:
# http://editorconfig.org/
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,feature}]
indent_size = 2
[{.travis.yml,package.json,composer.json}]
# The indent size used in the `package.json` file cannot be changed
# https://github.com/npm/npm/pull/3180#issuecomment-16336516
indent_size = 2
indent_style = space

7
.gitattributes vendored Normal file
View File

@ -0,0 +1,7 @@
/tests export-ignore
/docs export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/.travis.yml export-ignore
/.scrutinizer.yml export-ignore
/codecov.yml export-ignore

11
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,11 @@
name: CI
on:
push:
pull_request:
workflow_dispatch:
jobs:
ci:
name: CI
uses: silverstripe/gha-ci/.github/workflows/ci.yml@v1

View File

@ -0,0 +1,16 @@
name: Deploy Userhelp Docs
on:
push:
branches:
- '3'
- '2'
- '1.1'
paths:
- 'docs/en/userguide/**'
jobs:
deploy:
name: deploy-userhelp-docs
runs-on: ubuntu-latest
steps:
- name: Run build hook
run: curl -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_BUILD_HOOK }}

16
.github/workflows/dispatch-ci.yml vendored Normal file
View File

@ -0,0 +1,16 @@
name: Dispatch CI
on:
# At 11:30 AM UTC, only on Saturday and Sunday
schedule:
- cron: '30 11 * * 6,0'
jobs:
dispatch-ci:
name: Dispatch CI
# Only run cron on the silverstripe account
if: (github.event_name == 'schedule' && github.repository_owner == 'silverstripe') || (github.event_name != 'schedule')
runs-on: ubuntu-latest
steps:
- name: Dispatch CI
uses: silverstripe/gha-dispatch-ci@v1

17
.github/workflows/keepalive.yml vendored Normal file
View File

@ -0,0 +1,17 @@
name: Keepalive
on:
workflow_dispatch:
# The 4th of every month at 10:50am UTC
schedule:
- cron: '50 10 4 * *'
jobs:
keepalive:
name: Keepalive
# Only run cron on the silverstripe account
if: (github.event_name == 'schedule' && github.repository_owner == 'silverstripe') || (github.event_name != 'schedule')
runs-on: ubuntu-latest
steps:
- name: Keepalive
uses: silverstripe/gha-keepalive@v1

1
.gitignore vendored
View File

@ -1 +1,2 @@
.DS_Store
host-map.php

View File

@ -1,34 +0,0 @@
# See https://github.com/silverstripe-labs/silverstripe-travis-support for setup details
language: php
php:
- 5.4
env:
global:
- CORE_RELEASE=3.1
matrix:
- DB=MYSQL
- DB=PGSQL
matrix:
include:
- php: 5.3
env: DB=MYSQL
- php: 5.4
env: DB=MYSQL BEHAT_TEST=1
before_script:
- composer self-update
- phpenv rehash
- git clone git://github.com/silverstripe-labs/silverstripe-travis-support.git ~/travis-support
- "if [ \"$BEHAT_TEST\" = \"\" ]; then php ~/travis-support/travis_setup.php --source `pwd` --target ~/builds/ss; fi"
- "if [ \"$BEHAT_TEST\" = \"1\" ]; then php ~/travis-support/travis_setup.php --source `pwd` --target ~/builds/ss --require silverstripe/behat-extension; fi"
- cd ~/builds/ss
- php ~/travis-support/travis_setup_selenium.php --if-env BEHAT_TEST
- php ~/travis-support/travis_setup_php54_webserver.php --if-env BEHAT_TEST
script:
- "if [ \"$BEHAT_TEST\" = \"\" ]; then phpunit subsites/tests; fi"
- "if [ \"$BEHAT_TEST\" = \"1\" ]; then vendor/bin/behat @subsites; fi"

View File

@ -1,8 +1,9 @@
[main]
host = https://www.transifex.com
[silverstripe-subsites.master]
[o:silverstripe:p:silverstripe-subsites:r:master]
file_filter = lang/<lang>.yml
source_file = lang/en.yml
source_lang = en
type = YML
type = YML

20
.upgrade.yml Normal file
View File

@ -0,0 +1,20 @@
mappings:
SubsiteAdmin: SilverStripe\Subsites\Admin\SubsiteAdmin
SubsiteXHRController: SilverStripe\Subsites\Controller\SubsiteXHRController
CMSPageAddControllerExtension: SilverStripe\Subsites\Extensions\CMSPageAddControllerExtension
ControllerSubsites: SilverStripe\Subsites\Extensions\ControllerSubsites
ErrorPageSubsite: SilverStripe\Subsites\Extensions\ErrorPageSubsite
FileSubsites: SilverStripe\Subsites\Extensions\FileSubsites
GroupSubsites: SilverStripe\Subsites\Extensions\GroupSubsites
LeftAndMainSubsites: SilverStripe\Subsites\Extensions\LeftAndMainSubsites
SiteConfigSubsites: SilverStripe\Subsites\Extensions\SiteConfigSubsites
SiteTreeSubsites: SilverStripe\Subsites\Extensions\SiteTreeSubsites
SubsiteMenuExtension: SilverStripe\Subsites\Extensions\SubsiteMenuExtension
GridFieldSubsiteDetailForm: SilverStripe\Subsites\Forms\GridFieldSubsiteDetailForm
GridFieldSubsiteDetailForm_ItemRequest: SilverStripe\Subsites\Forms\GridFieldSubsiteDetailFormItemRequest
SubsitesTreeDropdownField: SilverStripe\Subsites\Forms\SubsitesTreeDropdownField
Subsite: SilverStripe\Subsites\Model\Subsite
SubsiteDomain: SilverStripe\Subsites\Model\SubsiteDomain
SubsitesVirtualPage: SilverStripe\Subsites\Pages\SubsitesVirtualPage
SubsiteReportWrapper: SilverStripe\Subsites\Reports\SubsiteReportWrapper
SubsiteCopyPagesTask: SilverStripe\Subsites\Tasks\SubsiteCopyPagesTask

51
CHANGELOG.md Normal file
View File

@ -0,0 +1,51 @@
# Changelog
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## [2.0.0 (unreleased)]
* Updating to be compatible with SilverStripe 4
* Subsite specific theme is now added to default theme, as themes are now cascadable
* Global subsite information moved to injectable `SubsiteState` singleton service
* `FileExtension:::default_root_folders_global` converted to a configuration property
* `Subsite::$check_is_public` converted to a configuration property
* `Subsite::$strict_subdomain_matching` converted to a configuration property
* `Subsite::$force_subsite` deprecated and will be removed in future - use `SubsiteState::singleton()->withState()` instead
* `Subsite::$write_hostmap` converted to a configuration property
* `Subsite::$allowed_themes` made protected
## [1.2.3]
* BUG Fix issue with urlsegment being renamed in subsites
## [1.2.2]
* Update translations.
* Added attributes to menu link
## [1.2.1]
* BUG: The move to subsite folder dropdown in files is gone
* Update templates for 3.3 compatibility
* Update userhelp documentation
* Fix Subsite module does not picks up themes
* Update translations
## [1.2.0]
* API Add option to specify http / https on subsite domains
## [1.1.0]
* Changelog added.
* Fixes #135: LeftAndMain switching between subsites
* BUG Fix incompatibility with framework 3.2
* Adjusted tests to new SiteTree->canCreate() logic in 3.1.11+
* Fix subsites to use correct permissions
* Wrong edit link in SubsitesVirtualPage
* Added missing route to `SubsiteXHRController` for SilverStripe 3.2 compatibility.
* Add sticky nav toggle button
* BUG Subsites selection on SubsitesVirtualPage (fixes #45 and #47)
* Update translations

24
LICENSE
View File

@ -1,24 +0,0 @@
* Copyright (c) 2008, Silverstripe Ltd.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the <organization> nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY Silverstripe Ltd. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL Silverstripe Ltd. BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,6 +1,7 @@
# Subsites Module
[![Build Status](https://secure.travis-ci.org/silverstripe/silverstripe-subsites.png?branch=master)](http://travis-ci.org/silverstripe/silverstripe-subsites)
[![CI](https://github.com/silverstripe/silverstripe-subsites/actions/workflows/ci.yml/badge.svg)](https://github.com/silverstripe/silverstripe-subsites/actions/workflows/ci.yml)
[![Silverstripe supported module](https://img.shields.io/badge/silverstripe-supported-0071C4.svg)](https://www.silverstripe.org/software/addons/silverstripe-commercially-supported-module-list/)
## Introduction
@ -8,7 +9,7 @@ The subsites module provides a convenient way of running multiple websites from
sharing users, content, and assets between them - the sites will be managed from a single CMS.
A useful way to think of its use is where you have a business with a global headquarters and four branches in various
countries. The subsites module allows the five offices to use a single SilverStripe installation, and have information
countries. The subsites module allows the five offices to use a single Silverstripe installation, and have information
from the headquarters flow down into the branches. The branches can hold information that is individual and the website
templates can also be different.
@ -21,8 +22,8 @@ permissions" will imply that the person will likely be able to escalate his/her
For user documentation please see:
1. [Setting up subsites](docs/en/set_up.md)
1. [Working with subsites](docs/en/working_with.md)
1. [Setting up subsites](docs/en/userguide/set_up.md)
1. [Working with subsites](docs/en/userguide/working_with.md)
## Features & limitations
@ -39,21 +40,26 @@ For user documentation please see:
### Limitations:
* Subsites are usually accessed via their own separate domains.
In order to allow efficient cross-subsite CMS editing,
they can also be accessed via URL parameters rather than domain maps.
This can weaken domain-specific security controls in your environment
such as domain-specific IP whitelists, firewall rules or business logic.
* Each subsite domain name has to be set up on the server first, and DNS records need to be updated as appropriate.
* A subsite cannot use a different codebase as the main site, they are intrinsically tied
* However, you can remove page types from a subsite when creating the subsite - [see the setup documentation for further details](set_up.md)
* However, you can remove page types from a subsite when creating the subsite - [see the setup documentation for further details](docs/en/userguide/set_up.md)
* The only code a developer can edit between subsites is the theme
* The separation between subsites in the CMS needs to be seen as cosmetic, and mostly applicable to the "Pages" and "Files" sections of the CMS.
* All subsites run in the same process space and data set. Therefore if an outage affects one subsite it will affect all subsites, and if bad code or hardware corrupts one subsite's data, it's very likely that it has corrupted all subsite data.
* All subsites run in the same process space and data set. Therefore if an outage affects one subsite it will affect all subsites, and if bad code or hardware corrupts one subsite's data, it's very likely that it has corrupted all subsite data.
* This principle applies to application error, security vulnerabilities and high levels of traffic
* It is not currently possible to backup or restore the data from a single subsite.
* It is not currently possible to backup or restore the data from a single subsite.
* It is awkward (but not impossible) to have separate teams of developers working on different subsites - primarily because of the level of collaboration needed. It is more suited to the same group of developers being responsible for all of the subsites.
If more isolation of code, security, or performance is needed, then consider running multiple separate installations (e.g. on separate servers).
## Requirements
* SilverStripe 3.1
* Silverstripe 4.x
## Installation
@ -63,7 +69,7 @@ If more isolation of code, security, or performance is needed, then consider run
* Once you've created a subsite, you'll see a "Create Subsite Domain" button, hit that button to enter a domain or subdomain for your subsite. This will determine the URL of your website. For example, if your site is running on `http://localhost/mysite`, and you set the subdomain to "subsite", then your subsite will be accessible on `http://subsite.localhost/mysite`
* Go to the "Pages" section of the CMS. In the top-left above the menu, you'll see a dropdown listing the two subsites - "Main site" is the original site that you had before you installed the subsites module. Select your new subsite, and the site content tree will be changed. It should be empty at this stage.
* Add a page - change its title to "Home", and its URL Segment will be changed to "home". Save the page.
* Update your DNS and, if necessary, your webserver configuration, so that your subdomain will point to the SilverStripe installation on your webserver. Visit this new subdomain. You should see the new subsite homepage.
* Update your DNS and, if necessary, your webserver configuration, so that your subdomain will point to the Silverstripe installation on your webserver. Visit this new subdomain. You should see the new subsite homepage.
## Usage
@ -95,7 +101,55 @@ In some Browsers the SubsiteID is visible if you hover over the "Edit" link in t
### Subsite-specific themes
Download a second theme from http://www.silverstripe.com/themes/ and put it in your themes folder. Open admin/subsites?flush=1 and select one of your subsites from the menu on the bottom-left. You should see a Theme dropdown in the subsite details, and it should list both your original theme and the new theme. Select the new theme in the dropdown. Now, this subsite will use a different theme from the main site.
Download a second theme from http://www.silverstripe.com/themes/ and put it in your themes folder. Open
admin/subsites?flush=1 and select one of your subsites from the menu on the bottom-left. You should see a
Theme dropdown in the subsite details, and it should list both your original theme and the new theme. Select the new
theme in the dropdown. Now, this subsite will use a different theme from the main site.
#### Cascading themes
In Silverstripe 4 themes will resolve theme files by looking through a list of themes (see the documentation on
[creating your own theme](https://docs.silverstripe.org/en/4/developer_guides/templates/themes/#developing-your-own-theme)).
Subsites will inherit this configuration for the order of themes. Choosing a theme for a Subsite will set the list of
themes to that chosen theme, and all themes that are defined below the chosen theme in priority. For example, with a
theme configuration as follows:
```yaml
SilverStripe\View\SSViewer:
themes:
- '$public'
- 'my-theme'
- 'watea'
- 'starter'
- '$default'
```
Choosing `watea` in your Subsite will create a cascading config as follows:
```yaml
themes:
- 'watea'
- '$public'
- 'starter'
- '$default'
```
You may also completely define your own cascading theme lists for CMS users to choose as theme options for their
subsite:
```yaml
SilverStripe\Subsites\Service\ThemeResolver:
theme_options:
normal:
- '$public'
- 'watea'
- 'starter'
- '$default'
special:
- 'my-theme'
- 'starter'
- '$default'
```
### Limit available themes for a subsite
@ -123,8 +177,8 @@ Include the current SubsiteID as a hidden field on getCMSFields, or updateCMSFie
:::php
public function getCMSFields() {
$fields = parent::getCMSFields();
if(class_exists('Subsite')){
$fields->push(new HiddenField('SubsiteID','SubsiteID', Subsite::currentSubsiteID()));
if(class_exists(Subsite::class)){
$fields->push(new HiddenField('SubsiteID','SubsiteID', SubsiteState::singleton()->getSubsiteId()));
}
return $fields;
}
@ -136,10 +190,10 @@ To limit your admin gridfields to the current Subsite records, you can do someth
:::php
public function getEditForm($id = null, $fields = null){
$form = parent::getEditForm($id, $fields);
$gridField = $form->Fields()->fieldByName($this->sanitiseClassName($this->modelClass));
if(class_exists('Subsite')){
$list = $gridField->getList()->filter(array('SubsiteID'=>Subsite::currentSubsiteID()));
if(class_exists(Subsite::class)){
$list = $gridField->getList()->filter(['SubsiteID'=>SubsiteState::singleton()->getSubsiteId()]);
$gridField->setList($list);
}
@ -164,6 +218,14 @@ or by defining the subsiteCMSShowInMenu function in your admin:
return true;
}
### Using Subsites in combination with Fluent
When using Subsites in combination with Fluent module, the Subsites module sets the i18n locale to the language defined in the current Subsite. When this behaviour is not desired and you need to use the locale in FluentState use the following setting in your yml config file:
```yaml
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
ignore_subsite_locale: true
```
### Public display of a subsite
@ -189,4 +251,3 @@ for all subdomains:
## Screenshots
![](docs/en/_images/subsites-module-adminscreenshot.png)

View File

@ -1,28 +0,0 @@
<?php
/**
* The subsites module modifies the behaviour of the CMS - in the SiteTree and Group databases - to store information
* about a number of sub-sites, rather than a single site.
*/
SiteTree::add_extension('SiteTreeSubsites');
ContentController::add_extension('ControllerSubsites');
CMSPageAddController::add_extension('CMSPageAddControllerExtension');
LeftAndMain::add_extension('LeftAndMainSubsites');
LeftAndMain::add_extension('ControllerSubsites');
Group::add_extension('GroupSubsites');
File::add_extension('FileSubsites');
ErrorPage::add_extension('ErrorPageSubsite');
SiteConfig::add_extension('SiteConfigSubsites');
SS_Report::add_excluded_reports('SubsiteReportWrapper');
//Display in cms menu
AssetAdmin::add_extension('SubsiteMenuExtension');
SecurityAdmin::add_extension('SubsiteMenuExtension');
CMSMain::add_extension('SubsiteMenuExtension');
CMSPagesController::add_extension('SubsiteMenuExtension');
SubsiteAdmin::add_extension('SubsiteMenuExtension');
CMSSettingsController::add_extension('SubsiteMenuExtension');

View File

@ -1,6 +1,11 @@
---
Name: mysiteconfig
After: 'framework/*','cms/*'
Name: subsiteconfig
After:
- 'framework/*'
---
AssetAdmin:
SilverStripe\AssetAdmin\Controller\AssetAdmin:
treats_subsite_0_as_global: true
SilverStripe\Reports\Report:
excluded_reports:
- SilverStripe\Subsites\Reports\SubsiteReportWrapper

75
_config/extensions.yml Normal file
View File

@ -0,0 +1,75 @@
---
Name: subsiteextensions
After:
- 'framework/*'
---
SilverStripe\CMS\Model\SiteTree:
extensions:
- SilverStripe\Subsites\Extensions\SiteTreeSubsites
SilverStripe\CMS\Controllers\ContentController:
extensions:
- SilverStripe\Subsites\Extensions\ControllerSubsites
SilverStripe\CMS\Controllers\CMSPageAddController:
extensions:
- SilverStripe\Subsites\Extensions\CMSPageAddControllerExtension
SilverStripe\Admin\LeftAndMain:
extensions:
- SilverStripe\Subsites\Extensions\LeftAndMainSubsites
- SilverStripe\Subsites\Extensions\ControllerSubsites
SilverStripe\Security\Group:
extensions:
- SilverStripe\Subsites\Extensions\GroupSubsites
SilverStripe\Assets\File:
extensions:
- SilverStripe\Subsites\Extensions\FileSubsites
SilverStripe\AssetAdmin\Forms\FolderFormFactory:
extensions:
- SilverStripe\Subsites\Extensions\FolderFormFactoryExtension
SilverStripe\ErrorPage\ErrorPage:
extensions:
- SilverStripe\Subsites\Extensions\ErrorPageSubsite
SilverStripe\SiteConfig\SiteConfig:
extensions:
- SilverStripe\Subsites\Extensions\SiteConfigSubsites
SilverStripe\AssetAdmin\Controller\AssetAdmin:
extensions:
- SilverStripe\Subsites\Extensions\SubsiteMenuExtension
SilverStripe\Admin\SecurityAdmin:
extensions:
- SilverStripe\Subsites\Extensions\SubsiteMenuExtension
SilverStripe\CMS\Controllers\CMSMain:
extensions:
- SilverStripe\Subsites\Extensions\HintsCacheKeyExtension
- SilverStripe\Subsites\Extensions\SubsiteMenuExtension
SilverStripe\CMS\Controllers\CMSPagesController:
extensions:
- SilverStripe\Subsites\Extensions\SubsiteMenuExtension
SilverStripe\Subsites\Admin\SubsiteAdmin:
extensions:
- SilverStripe\Subsites\Extensions\SubsiteMenuExtension
SilverStripe\SiteConfig\SiteConfigLeftAndMain:
extensions:
- SilverStripe\Subsites\Extensions\SubsiteMenuExtension
---
Name: subsite-preview-elemental
Only:
classexists: DNADesign\Elemental\Models\BaseElement
---
DNADesign\Elemental\Models\BaseElement:
extensions:
- SilverStripe\Subsites\Extensions\BaseElementSubsites

9
_config/legacy.yml Normal file
View File

@ -0,0 +1,9 @@
---
Name: subsites-legacy
---
SilverStripe\ORM\DatabaseAdmin:
classname_value_remapping:
Subsite: SilverStripe\Subsites\Model\Subsite
SubsiteDomain: SilverStripe\Subsites\Model\SubsiteDomain
SubsitesVirtualPage: SilverStripe\Subsites\Pages\SubsitesVirtualPage

12
_config/middleware.yml Normal file
View File

@ -0,0 +1,12 @@
---
Name: subsitesmiddleware
After:
- requestprocessors
---
SilverStripe\Core\Injector\Injector:
SilverStripe\Control\Director:
properties:
Middlewares:
SubsitesStateMiddleware: '%$SilverStripe\Subsites\Middleware\InitStateMiddleware'
SilverStripe\Dev\Tasks\MigrateFileTask:
class: SilverStripe\Subsites\Tasks\SubsiteMigrateFileTask

30
behat.yml Normal file
View File

@ -0,0 +1,30 @@
default:
suites:
subsites:
paths:
- '%paths.modules.subsites%/tests/behat/features'
contexts:
- SilverStripe\Framework\Tests\Behaviour\FeatureContext
- SilverStripe\Framework\Tests\Behaviour\CmsFormsContext
- SilverStripe\Framework\Tests\Behaviour\CmsUiContext
- SilverStripe\BehatExtension\Context\BasicContext
- SilverStripe\BehatExtension\Context\EmailContext
- SilverStripe\CMS\Tests\Behaviour\LoginContext
- SilverStripe\CMS\Tests\Behaviour\ThemeContext
# Using asset-admin for fixture context to get iAttachTheFileToDropzone()
- SilverStripe\AssetAdmin\Tests\Behat\Context\FixtureContext:
# Note: double indent for args is intentional
- '%paths.modules.subsites%/tests/behat/files/'
extensions:
SilverStripe\BehatExtension\MinkExtension:
default_session: facebook_web_driver
javascript_session: facebook_web_driver
facebook_web_driver:
browser: chrome
wd_host: "http://127.0.0.1:9515" #chromedriver port
browser_name: chrome
SilverStripe\BehatExtension\Extension:
screenshot_path: '%paths.base%/artifacts/screenshots'
bootstrap_file: "vendor/silverstripe/cms/tests/behat/serve-bootstrap.php"

View File

@ -0,0 +1,82 @@
/**
* Styling for the subsite actions section in the CMS
*/
#SubsiteActions {
position: absolute;
padding: 0;
margin: 0;
right: 0;
top: 0;
width: 350px;
text-align: right;
margin-right: 130px;
height: 51px;
border-bottom: 3px solid #d4d0c8;
color: #fff;
}
#SubsiteActions fieldset {
padding: 3px;
border-style: none;
margin-top: 1px;
background: none;
}
#SubsiteActions fieldset span {
padding: 3px;
}
.cms-menu .cms-subsites {
padding: 3px 0 15px;
}
.cms-menu .cms-subsites .field.dropdown {
margin: 0 10px;
padding-bottom: 0;
}
.cms-menu.collapsed .cms-subsites {
display: none;
}
.cms-menu .cms-subsites .dropdown span {
padding-left: 5px;
color: black;
}
.cms-subsites .chosen-container-single .chosen-single div b {
margin: 0 5px;
}
#AddSubsiteLink {
display: block;
font-size: 80%;
margin-left: 3px;
}
#Form_AddSubsiteForm .field {
margin-left: 100px;
}
#Form_AddSubsiteForm label.left {
float: left;
width: 100px;
margin-left: -100px;
}
body.SubsiteAdmin .right form #URL .fieldgroup * {
font-size: 11px;
}
.cms-add-form #PageType li .class-SubsitesVirtualPage {
background-position: 0 -32px !important;
}
.subsites-move-dropdown {
display: none;
}
.editor__details .subsites-move-dropdown,
#Form_fileEditorForm.subsites-move-dropdown {
display: block;
}

View File

@ -1,6 +1,5 @@
/*jslint browser: true, nomen: true*/
/*global $, window, jQuery*/
(function($) {
'use strict';
$.entwine('ss', function($) {
@ -13,39 +12,36 @@
}
});
/*
* Reload subsites dropdown when links are processed
/*
* Reload subsites dropdown when links are processed
*/
$('.cms-container .cms-menu-list li a').entwine({
onclick: function(e) {
$('.cms-container').loadFragment('SubsiteXHRController', 'SubsiteList');
$('.cms-container').loadFragment('admin/subsite_xhr', 'SubsiteList');
this._super(e);
}
});
/*
* Reload subsites dropdown when the admin area reloads (for deleting sites)
/*
* Reload subsites dropdown when the admin area reloads (for deleting sites)
*/
$('.cms-container .SubsiteAdmin .cms-edit-form fieldset.ss-gridfield').entwine({
onreload: function(e) {
$('.cms-container').loadFragment('SubsiteXHRController', 'SubsiteList');
$('.cms-container').loadFragment('admin/subsite_xhr', 'SubsiteList');
this._super(e);
}
});
/*
/*
* Reload subsites dropdown when subsites are added or names are modified
*/
$('.cms-container .cms-content-fields .subsite-model').entwine({
$('.cms-container .tab.subsite-model').entwine({
onadd: function(e) {
$('.cms-container').loadFragment('SubsiteXHRController', 'SubsiteList');
$('.cms-container').loadFragment('admin/subsite_xhr', 'SubsiteList');
this._super(e);
}
});
// Subsite tab of Group editor
$('#Form_ItemEditForm_AccessAllSubsites').entwine({
/**
@ -53,18 +49,18 @@
*/
onmatch: function () {
this.showHideSubsiteList();
var ref=this;
$('#Form_ItemEditForm_AccessAllSubsites input').change(function() {
ref.showHideSubsiteList();
});
},
showHideSubsiteList: function () {
$('#Form_ItemEditForm_Subsites').parent().parent().css('display', ($('#Form_ItemEditForm_AccessAllSubsites_1').is(':checked') ? 'none':''));
}
});
$('.cms-edit-form').entwine({
/**
* TODO: Fix with Entwine API extension. See https://github.com/silverstripe/silverstripe-subsites/pull/125
@ -89,26 +85,6 @@
return opts;
}
});
/**
* Binding a visibility toggle anchor to a longer list of checkboxes.
* Hidden by default, unless either the toggle checkbox, or any of the
* actual value checkboxes are selected.
*/
$('#PageTypeBlacklist').entwine({
onmatch: function() {
var hasLimits=Boolean($('#PageTypeBlacklist').find('input:checked').length);
jQuery('#PageTypeBlacklist').toggle(hasLimits);
//Bind listener
$('a#PageTypeBlacklistToggle').click(function(e) {
jQuery('#PageTypeBlacklist').toggle();
e.stopPropagation();
return false;
});
}
});
$('.cms-edit-form input[name=action_copytosubsite]').entwine({
onclick: function(e) {
@ -125,7 +101,7 @@
/**
* Update links and forms with GET/POST SubsiteID param, so we remaing on the current subsite.
* The initial link for the iframe comes from SiteTreeSubsites::alternatePreviewLink.
* The initial link for the iframe comes from SiteTreeSubsites::updatePreviewLink.
*
* This is done so we can use the CMS domain for displaying previews so we prevent single-origin
* violations and SSL cert problems that come up when iframing content from a different URL.

View File

@ -0,0 +1,34 @@
(function($) {
$.entwine('ss', function($) {
/**
* Choose a subsite from which to select pages.
* Needs to clear tree dropdowns in case selection is changed.
*/
$('select.subsitestreedropdownfield-chooser').entwine({
onchange: function() {
// TODO Data binding between two fields
const name = this.attr('name').replace('_SubsiteID', '');
let field = $('#Form_EditForm_' + name).first();
field.setValue(0);
field.refresh();
field.trigger('change');
}
});
/**
* Add selected subsite from separate dropdown to the request parameters
* before asking for the tree.
*/
$('.TreeDropdownField.SubsitesTreeDropdownField').entwine({
getAttributes() {
const fieldName = this.attr('id').replace('Form_EditForm_', '');
const subsiteID = $('#Form_EditForm_' + fieldName + '_SubsiteID option:selected').val();
let attributes = this._super();
attributes.data.urlTree += "?" + fieldName + "_SubsiteID=" + subsiteID;
attributes.data.cacheKey = attributes.data.cacheKey.substring(0, 19) + "_" + subsiteID;
return attributes;
}
});
});
})(jQuery);

1
code-of-conduct.md Normal file
View File

@ -0,0 +1 @@
When having discussions about this module in issues or pull request please adhere to the [SilverStripe Community Code of Conduct](https://docs.silverstripe.org/en/contributing/code_of_conduct).

View File

@ -1,33 +0,0 @@
<?php
/**
* Admin interface to manage and create {@link Subsite} instances.
*
* @package subsites
*/
class SubsiteAdmin extends ModelAdmin {
private static $managed_models = array('Subsite');
private static $url_segment = 'subsites';
private static $menu_title = "Subsites";
private static $menu_icon = "subsites/images/subsites.png";
public $showImportForm=false;
private static $tree_class = 'Subsite';
public function getEditForm($id = null, $fields = null) {
$form = parent::getEditForm($id, $fields);
$grid=$form->Fields()->dataFieldByName('Subsite');
if($grid) {
$grid->getConfig()->removeComponentsByType('GridFieldDetailForm');
$grid->getConfig()->addComponent(new GridFieldSubsiteDetailForm());
}
return $form;
}
}

View File

@ -1,65 +0,0 @@
<?php
/**
* Creates a subsite-aware version of another report.
* Pass another report (or its classname) into the constructor.
*/
class SubsiteReportWrapper extends SS_ReportWrapper {
///////////////////////////////////////////////////////////////////////////////////////////
// Filtering
function parameterFields() {
$subsites = Subsite::accessible_sites('CMS_ACCESS_CMSMain', true);
$options = $subsites->toDropdownMap('ID', 'Title');
$subsiteField = new TreeMultiselectField(
'Subsites',
_t('SubsiteReportWrapper.ReportDropdown', 'Sites'),
$options
);
$subsiteField->setValue(array_keys($options));
// We don't need to make the field editable if only one subsite is available
if(sizeof($options) <= 1) {
$subsiteField = $subsiteField->performReadonlyTransformation();
}
$fields = parent::parameterFields();
if($fields) {
$fields->insertBefore($subsiteField, $fields->First()->Name());
} else {
$fields = new FieldList($subsiteField);
}
return $fields;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Columns
function columns() {
$columns = parent::columns();
$columns['Subsite.Title'] = "Subsite";
return $columns;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Querying
function beforeQuery($params) {
// The user has select a few specific sites
if(!empty($params['Subsites'])) {
Subsite::$force_subsite = $params['Subsites'];
// Default: restrict to all accessible sites
} else {
$subsites = Subsite::accessible_sites('CMS_ACCESS_CMSMain');
$options = $subsites->toDropdownMap('ID', 'Title');
Subsite::$force_subsite = join(',', array_keys($options));
}
}
function afterQuery() {
// Manually manage the subsite filtering
Subsite::$force_subsite = null;
}
}

View File

@ -1,45 +0,0 @@
<?php
/**
* Section-agnostic PJAX controller.
*/
class SubsiteXHRController extends LeftAndMain {
/**
* Relax the access permissions, so anyone who has access to any CMS subsite can access this controller.
*/
public function canView($member = null) {
if (parent::canView()) return true;
if (Subsite::all_accessible_sites()->count()>0) return true;
return false;
}
/**
* Similar as above, but for the LeftAndMainSubsites - allow access if user allowed into the CMS at all.
*/
public function canAccess() {
if (Subsite::all_accessible_sites()->count()>0) return true;
}
public function getResponseNegotiator() {
$negotiator = parent::getResponseNegotiator();
$self = $this;
// Register a new callback
$negotiator->setCallback('SubsiteList', function() use(&$self) {
return $self->SubsiteList();
});
return $negotiator;
}
/**
* Provide the list of available subsites as a cms-section-agnostic PJAX handler.
*/
public function SubsiteList() {
return $this->renderWith('SubsiteList');
}
}

View File

@ -1,210 +0,0 @@
<?php
class SubsitesVirtualPage extends VirtualPage {
private static $description = 'Displays the content of a page on another subsite';
private static $db = array(
'CustomMetaTitle' => 'Varchar(255)',
'CustomMetaKeywords' => 'Varchar(255)',
'CustomMetaDescription' => 'Text',
'CustomExtraMeta' => 'HTMLText'
);
public function getCMSFields() {
$fields = parent::getCMSFields();
$subsites = DataObject::get('Subsite');
if(!$subsites) {
$subsites = new ArrayList();
}else {
$subsites=ArrayList::create($subsites->toArray());
}
$subsites->push(new ArrayData(array('Title' => 'Main site', 'ID' => 0)));
$subsiteSelectionField = new DropdownField(
"CopyContentFromID_SubsiteID",
_t('SubsitesVirtualPage.SubsiteField',"Subsite"),
$subsites->map('ID', 'Title'),
($this->CopyContentFromID) ? $this->CopyContentFrom()->SubsiteID : Session::get('SubsiteID')
);
$fields->addFieldToTab(
'Root.Main',
$subsiteSelectionField,
'CopyContentFromID'
);
// Setup the linking to the original page.
$pageSelectionField = new SubsitesTreeDropdownField(
"CopyContentFromID",
_t('VirtualPage.CHOOSE', "Choose a page to link to"),
"SiteTree",
"ID",
"MenuTitle"
);
if(Controller::has_curr() && Controller::curr()->getRequest()) {
$subsiteID = Controller::curr()->getRequest()->postVar('CopyContentFromID_SubsiteID');
$pageSelectionField->setSubsiteID($subsiteID);
}
$fields->replaceField('CopyContentFromID', $pageSelectionField);
// Create links back to the original object in the CMS
if($this->CopyContentFromID) {
$editLink = "admin/page/edit/show/$this->CopyContentFromID/?SubsiteID=" . $this->CopyContentFrom()->SubsiteID;
$linkToContent = "
<a class=\"cmsEditlink\" href=\"$editLink\">" .
_t('VirtualPage.EDITCONTENT', 'Click here to edit the content') .
"</a>";
$fields->removeByName("VirtualPageContentLinkLabel");
$fields->addFieldToTab(
"Root.Main",
$linkToContentLabelField = new LabelField('VirtualPageContentLinkLabel', $linkToContent),
'Title'
);
$linkToContentLabelField->setAllowHTML(true);
}
$fields->addFieldToTab(
'Root.Main',
TextField::create(
'CustomMetaTitle',
$this->fieldLabel('CustomMetaTitle')
)->setDescription(_t('SubsitesVirtualPage.OverrideNote', 'Overrides inherited value from the source')),
'MetaTitle'
);
$fields->addFieldToTab(
'Root.Main',
TextareaField::create(
'CustomMetaKeywords',
$this->fieldLabel('CustomMetaTitle')
)->setDescription(_t('SubsitesVirtualPage.OverrideNote')),
'MetaKeywords'
);
$fields->addFieldToTab(
'Root.Main',
TextareaField::create(
'CustomMetaDescription',
$this->fieldLabel('CustomMetaTitle')
)->setDescription(_t('SubsitesVirtualPage.OverrideNote')),
'MetaDescription'
);
$fields->addFieldToTab(
'Root.Main',
TextField::create(
'CustomExtraMeta',
$this->fieldLabel('CustomMetaTitle')
)->setDescription(_t('SubsitesVirtualPage.OverrideNote')),
'ExtraMeta'
);
return $fields;
}
public function fieldLabels($includerelations = true) {
$labels = parent::fieldLabels($includerelations);
$labels['CustomMetaTitle'] = _t('Subsite.CustomMetaTitle','Title');
$labels['CustomMetaKeywords'] = _t('Subsite.CustomMetaKeywords','Keywords');
$labels['CustomMetaDescription'] = _t('Subsite.CustomMetaDescription','Description');
$labels['CustomExtraMeta'] = _t('Subsite.CustomExtraMeta','Custom Meta Tags');
return $labels;
}
public function getVirtualFields() {
$fields = parent::getVirtualFields();
foreach($fields as $k => $v) {
if($v == 'SubsiteID') unset($fields[$k]);
}
foreach(self::$db as $field => $type) if (in_array($field, $fields)) unset($fields[array_search($field, $fields)]);
return $fields;
}
public function syncLinkTracking() {
$oldState = Subsite::$disable_subsite_filter;
Subsite::$disable_subsite_filter = true;
if ($this->CopyContentFromID) $this->HasBrokenLink = DataObject::get_by_id('SiteTree', $this->CopyContentFromID) ? false : true;
Subsite::$disable_subsite_filter = $oldState;
}
public function onBeforeWrite() {
parent::onBeforeWrite();
if($this->CustomMetaTitle) $this->MetaTitle = $this->CustomMetaTitle;
else {
$this->MetaTitle = $this->ContentSource()->MetaTitle ? $this->ContentSource()->MetaTitle : $this->MetaTitle;
}
if($this->CustomMetaKeywords) $this->MetaKeywords = $this->CustomMetaKeywords;
else {
$this->MetaKeywords = $this->ContentSource()->MetaKeywords ? $this->ContentSource()->MetaKeywords : $this->MetaKeywords;
}
if($this->CustomMetaDescription) $this->MetaDescription = $this->CustomMetaDescription;
else {
$this->MetaDescription = $this->ContentSource()->MetaDescription ? $this->ContentSource()->MetaDescription : $this->MetaDescription;
}
if($this->CustomExtraMeta) $this->ExtraMeta = $this->CustomExtraMeta;
else {
$this->ExtraMeta = $this->ContentSource()->ExtraMeta ? $this->ContentSource()->ExtraMeta : $this->ExtraMeta;
}
}
public function validURLSegment() {
$isValid = parent::validURLSegment();
// Veto the validation rules if its false. In this case, some logic
// needs to be duplicated from parent to find out the exact reason the validation failed.
if(!$isValid) {
$IDFilter = ($this->ID) ? "AND \"SiteTree\".\"ID\" <> $this->ID" : null;
$parentFilter = null;
if(Config::inst()->get('SiteTree', 'nested_urls')) {
if($this->ParentID) {
$parentFilter = " AND \"SiteTree\".\"ParentID\" = $this->ParentID";
} else {
$parentFilter = ' AND "SiteTree"."ParentID" = 0';
}
}
$origDisableSubsiteFilter = Subsite::$disable_subsite_filter;
Subsite::$disable_subsite_filter = true;
$existingPage = DataObject::get_one(
'SiteTree',
"\"URLSegment\" = '$this->URLSegment' $IDFilter $parentFilter",
false // disable cache, it doesn't include subsite status in the key
);
Subsite::$disable_subsite_filter = $origDisableSubsiteFilter;
$existingPageInSubsite = DataObject::get_one(
'SiteTree',
"\"URLSegment\" = '$this->URLSegment' $IDFilter $parentFilter",
false // disable cache, it doesn't include subsite status in the key
);
// If URL has been vetoed because of an existing page,
// be more specific and allow same URLSegments in different subsites
$isValid = !($existingPage && $existingPageInSubsite);
}
return $isValid;
}
}
class SubsitesVirtualPage_Controller extends VirtualPage_Controller {
public function reloadContent() {
$this->failover->copyFrom($this->failover->CopyContentFrom());
$this->failover->write();
return;
}
public function init(){
$origDisableSubsiteFilter = Subsite::$disable_subsite_filter;
Subsite::$disable_subsite_filter = true;
parent::init();
Subsite::$disable_subsite_filter = $origDisableSubsiteFilter;
}
}

View File

@ -1,8 +0,0 @@
<?php
class CMSPageAddControllerExtension extends Extension {
function updatePageOptions(&$fields) {
$fields->push(new HiddenField('SubsiteID', 'SubsiteID', Subsite::currentSubsiteID()));
}
}

View File

@ -1,20 +0,0 @@
<?php
/**
* @package subsites
*/
class ControllerSubsites extends Extension {
function controllerAugmentInit(){
if($subsite = Subsite::currentSubsite()){
if($theme = $subsite->Theme)
SSViewer::set_theme($theme);
}
}
function CurrentSubsite(){
if($subsite = Subsite::currentSubsite()){
return $subsite;
}
}
}
?>

View File

@ -1,40 +0,0 @@
<?php
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()
*
* 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.
*/
function alternateFilepathForErrorcode($statusCode, $locale = null) {
$static_filepath = Config::inst()->get($this->owner->ClassName, 'static_filepath');
$subdomainPart = "";
// Try to get current subsite from session
$subsite = Subsite::currentSubsite(false);
// 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";
}
return $filepath;
}
}

View File

@ -1,131 +0,0 @@
<?php
/**
* Extension for the File object to add subsites support
*
* @package subsites
*/
class FileSubsites extends DataExtension {
// If this is set to true, all folders created will be default be
// considered 'global', unless set otherwise
static $default_root_folders_global = false;
private static $has_one=array(
'Subsite' => 'Subsite',
);
/**
* Amends the CMS tree title for folders in the Files & Images section.
* Prefixes a '* ' to the folders that are accessible from all subsites.
*/
function alternateTreeTitle() {
if($this->owner->SubsiteID == 0) return " * " . $this->owner->Title;
else return $this->owner->Title;
}
/**
* Add subsites-specific fields to the folder editor.
*/
function updateCMSFields(FieldList $fields) {
if($this->owner instanceof Folder) {
$sites = Subsite::accessible_sites('CMS_ACCESS_AssetAdmin');
$values = array();
$values[0] = _t('FileSubsites.AllSitesDropdownOpt','All sites');
foreach ($sites as $site) {
$values[$site->ID] = $site->Title;
}
ksort($values);
if($sites){
//Dropdown needed to move folders between subsites
$dropdown = new DropdownField(
'SubsiteID',
_t('FileSubsites.SubsiteFieldLabel','Subsite'),
$values
);
$dropdown->addExtraClass('subsites-move-dropdown');
$fields->push($dropdown);
$fields->push(new LiteralField(
'Message',
'<p class="message notice">'.
_t('ASSETADMIN.SUBSITENOTICE', 'Folders and files created in the main site are accessible by all subsites.')
.'</p>'
));
}
}
}
/**
* Update any requests to limit the results to the current site
*/
function augmentSQL(SQLQuery &$query) {
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)
//@TODO I don't think excluding if SiteTree_ImageTracking is a good idea however because of the SS 3.0 api and ManyManyList::removeAll() changing the from table after this function is called there isn't much of a choice
$from = $query->getFrom();
$where = $query->getWhere();
if(!isset($from['SiteTree_ImageTracking']) && !($where && preg_match('/\.(\'|"|`|)ID(\'|"|`|)/', $where[0]))) {
$subsiteID = (int) Subsite::currentSubsiteID();
// The foreach is an ugly way of getting the first key :-)
foreach($query->getFrom() as $tableName => $info) {
$where = "\"$tableName\".\"SubsiteID\" IN (0, $subsiteID)";
$query->addWhere($where);
break;
}
$sect=array_values($query->getSelect());
$isCounting = strpos($sect[0], 'COUNT') !== false;
// Ordering when deleting or counting doesn't apply
if(!$query->getDelete() && !$isCounting) {
$query->addOrderBy("\"SubsiteID\"");
}
}
}
function onBeforeWrite() {
if (!$this->owner->ID && !$this->owner->SubsiteID) {
if (self::$default_root_folders_global) {
$this->owner->SubsiteID = 0;
} else {
$this->owner->SubsiteID = Subsite::currentSubsiteID();
}
}
}
function onAfterUpload() {
// If we have a parent, use it's subsite as our subsite
if ($this->owner->Parent()) {
$this->owner->SubsiteID = $this->owner->Parent()->SubsiteID;
} else {
$this->owner->SubsiteID = Subsite::currentSubsiteID();
}
$this->owner->write();
}
function canEdit($member = null) {
// Check the CMS_ACCESS_SecurityAdmin privileges on the subsite that owns this group
$subsiteID = Session::get('SubsiteID');
if($subsiteID&&$subsiteID == $this->owner->SubsiteID) {
return true;
} else {
Session::set('SubsiteID', $this->owner->SubsiteID);
$access = Permission::check(array('CMS_ACCESS_AssetAdmin', 'CMS_ACCESS_LeftAndMain'));
Session::set('SubsiteID', $subsiteID);
return $access;
}
}
/**
* Return a piece of text to keep DataObject cache keys appropriately specific
*/
function cacheKeyComponent() {
return 'subsite-'.Subsite::currentSubsiteID();
}
}

View File

@ -1,188 +0,0 @@
<?php
/**
* Extension for the Group object to add subsites support
*
* @package subsites
*/
class GroupSubsites extends DataExtension implements PermissionProvider {
private static $db = array(
'AccessAllSubsites' => 'Boolean'
);
private static $many_many = array(
'Subsites' => 'Subsite'
);
private static $defaults = array(
'AccessAllSubsites' => true
);
/**
* Migrations for GroupSubsites data.
*/
function requireDefaultRecords() {
// Migration for Group.SubsiteID data from when Groups only had a single subsite
$groupFields = DB::getConn()->fieldList('Group');
// Detection of SubsiteID field is the trigger for old-style-subsiteID migration
if(isset($groupFields['SubsiteID'])) {
// Migrate subsite-specific data
DB::query('INSERT INTO "Group_Subsites" ("GroupID", "SubsiteID")
SELECT "ID", "SubsiteID" FROM "Group" WHERE "SubsiteID" > 0');
// Migrate global-access data
DB::query('UPDATE "Group" SET "AccessAllSubsites" = 1 WHERE "SubsiteID" = 0');
// Move the field out of the way so that this migration doesn't get executed again
DB::getConn()->renameField('Group', 'SubsiteID', '_obsolete_SubsiteID');
// No subsite access on anything means that we've just installed the subsites module.
// Make all previous groups global-access groups
} else if(!DB::query('SELECT "Group"."ID" FROM "Group"
LEFT JOIN "Group_Subsites" ON "Group_Subsites"."GroupID" = "Group"."ID" AND "Group_Subsites"."SubsiteID" > 0
WHERE "AccessAllSubsites" = 1
OR "Group_Subsites"."GroupID" IS NOT NULL ')->value()) {
DB::query('UPDATE "Group" SET "AccessAllSubsites" = 1');
}
}
function updateCMSFields(FieldList $fields) {
if($this->owner->canEdit() ){
// i18n tab
$fields->findOrMakeTab('Root.Subsites',_t('GroupSubsites.SECURITYTABTITLE','Subsites'));
$subsites = Subsite::accessible_sites(array('ADMIN', 'SECURITY_SUBSITE_GROUP'), true);
$subsiteMap = $subsites->map();
// Prevent XSS injection
$subsiteMap = Convert::raw2xml($subsiteMap);
// Interface is different if you have the rights to modify subsite group values on
// all subsites
if(isset($subsiteMap[0])) {
$fields->addFieldToTab("Root.Subsites", new OptionsetField("AccessAllSubsites",
_t('GroupSubsites.ACCESSRADIOTITLE', 'Give this group access to'),
array(
1 => _t('GroupSubsites.ACCESSALL', "All subsites"),
0 => _t('GroupSubsites.ACCESSONLY', "Only these subsites"),
)
));
unset($subsiteMap[0]);
$fields->addFieldToTab("Root.Subsites", new CheckboxSetField("Subsites", "",
$subsiteMap));
} else {
if (sizeof($subsiteMap) <= 1) {
$fields->addFieldToTab("Root.Subsites", new ReadonlyField("SubsitesHuman",
_t('GroupSubsites.ACCESSRADIOTITLE', 'Give this group access to'),
reset($subsiteMap)));
} else {
$fields->addFieldToTab("Root.Subsites", new CheckboxSetField("Subsites",
_t('GroupSubsites.ACCESSRADIOTITLE', 'Give this group access to'),
$subsiteMap));
}
}
}
}
/**
* If this group belongs to a subsite,
* append the subsites title to the group title
* to make it easy to distinguish in the tree-view
* of the security admin interface.
*/
function alternateTreeTitle() {
if($this->owner->AccessAllSubsites) {
$title = _t('GroupSubsites.GlobalGroup', 'global group');
return htmlspecialchars($this->owner->Title, ENT_QUOTES) . ' <i>(' . $title . ')</i>';
} else {
$subsites = Convert::raw2xml(implode(", ", $this->owner->Subsites()->column('Title')));
return htmlspecialchars($this->owner->Title) . " <i>($subsites)</i>";
}
}
/**
* Update any requests to limit the results to the current site
*/
function augmentSQL(SQLQuery &$query) {
if(Subsite::$disable_subsite_filter) return;
if(Cookie::get('noSubsiteFilter') == 'true') return;
// If you're querying by ID, ignore the sub-site - this is a bit ugly...
if(!$query->filtersOnID()) {
/*if($context = DataObject::context_obj()) $subsiteID = (int)$context->SubsiteID;
else */$subsiteID = (int)Subsite::currentSubsiteID();
// Don't filter by Group_Subsites if we've already done that
$hasGroupSubsites = false;
foreach($query->getFrom() as $item) {
if((is_array($item) && strpos($item['table'], 'Group_Subsites')!==false) || (!is_array($item) && strpos($item, 'Group_Subsites')!==false)) {
$hasGroupSubsites = true;
break;
}
}
if(!$hasGroupSubsites) {
if($subsiteID) {
$query->addLeftJoin("Group_Subsites", "\"Group_Subsites\".\"GroupID\"
= \"Group\".\"ID\" AND \"Group_Subsites\".\"SubsiteID\" = $subsiteID");
$query->addWhere("(\"Group_Subsites\".\"SubsiteID\" IS NOT NULL OR
\"Group\".\"AccessAllSubsites\" = 1)");
} else {
$query->addWhere("\"Group\".\"AccessAllSubsites\" = 1");
}
}
// WORKAROUND for databases that complain about an ORDER BY when the column wasn't selected (e.g. SQL Server)
$select=$query->getSelect();
if(isset($select[0]) && !$select[0] == 'COUNT(*)') {
$query->orderby = "\"AccessAllSubsites\" DESC" . ($query->orderby ? ', ' : '') . $query->orderby;
}
}
}
function onBeforeWrite() {
// New record test approximated by checking whether the ID has changed.
// Note also that the after write test is only used when we're *not* on a subsite
if($this->owner->isChanged('ID') && !Subsite::currentSubsiteID()) {
$this->owner->AccessAllSubsites = 1;
}
}
function onAfterWrite() {
// New record test approximated by checking whether the ID has changed.
// Note also that the after write test is only used when we're on a subsite
if($this->owner->isChanged('ID') && $currentSubsiteID = Subsite::currentSubsiteID()) {
$subsites = $this->owner->Subsites();
$subsites->add($currentSubsiteID);
}
}
function alternateCanEdit() {
// Find the sites that this group belongs to and the sites where we have appropriate perm.
$accessibleSites = Subsite::accessible_sites('CMS_ACCESS_SecurityAdmin')->column('ID');
$linkedSites = $this->owner->Subsites()->column('ID');
// We are allowed to access this site if at we have CMS_ACCESS_SecurityAdmin permission on
// at least one of the sites
return (bool)array_intersect($accessibleSites, $linkedSites);
}
function providePermissions() {
return array(
'SECURITY_SUBSITE_GROUP' => array(
'name' => _t('GroupSubsites.MANAGE_SUBSITES', 'Manage subsites for groups'),
'category' => _t('Permissions.PERMISSIONS_CATEGORY', 'Roles and access permissions'),
'help' => _t('GroupSubsites.MANAGE_SUBSITES_HELP', 'Ability to limit the permissions for a group to one or more subsites.'),
'sort' => 200
)
);
}
}
?>

View File

@ -1,301 +0,0 @@
<?php
/**
* Decorator designed to add subsites support to LeftAndMain
*
* @package subsites
*/
class LeftAndMainSubsites extends Extension {
private static $allowed_actions = array('CopyToSubsite');
/**
* Normally SubsiteID=0 on a DataObject means it is only accessible from the special "main site".
* However in some situations SubsiteID=0 will be understood as a "globally accessible" object in which
* case this property is set to true (i.e. in AssetAdmin).
*/
private static $treats_subsite_0_as_global = false;
function init() {
Requirements::css('subsites/css/LeftAndMain_Subsites.css');
Requirements::javascript('subsites/javascript/LeftAndMain_Subsites.js');
Requirements::javascript('subsites/javascript/VirtualPage_Subsites.js');
}
/**
* Set the title of the CMS tree
*/
function getCMSTreeTitle() {
$subsite = Subsite::currentSubSite();
return $subsite ? Convert::raw2xml($subsite->Title) : _t('LeftAndMain.SITECONTENTLEFT');
}
function updatePageOptions(&$fields) {
$fields->push(new HiddenField('SubsiteID', 'SubsiteID', Subsite::currentSubsiteID()));
}
/**
* Find all subsites accessible for current user on this controller.
*
* @return ArrayList of {@link Subsite} instances.
*/
function sectionSites($includeMainSite = true, $mainSiteTitle = "Main site", $member = null) {
if($mainSiteTitle == 'Main site') {
$mainSiteTitle = _t('Subsites.MainSiteTitle', 'Main site');
}
// Rationalise member arguments
if(!$member) $member = Member::currentUser();
if(!$member) return new ArrayList();
if(!is_object($member)) $member = DataObject::get_by_id('Member', $member);
// Collect permissions - honour the LeftAndMain::required_permission_codes, current model requires
// us to check if the user satisfies ALL permissions. Code partly copied from LeftAndMain::canView.
$codes = array();
$extraCodes = Config::inst()->get($this->owner->class, 'required_permission_codes');
if($extraCodes !== false) {
if($extraCodes) $codes = array_merge($codes, (array)$extraCodes);
else $codes[] = "CMS_ACCESS_{$this->owner->class}";
} else {
// Check overriden - all subsites accessible.
return Subsite::all_sites();
}
// Find subsites satisfying all permissions for the Member.
$codesPerSite = array();
$sitesArray = array();
foreach ($codes as $code) {
$sites = Subsite::accessible_sites($code, $includeMainSite, $mainSiteTitle, $member);
foreach ($sites as $site) {
// Build the structure for checking how many codes match.
$codesPerSite[$site->ID][$code] = true;
// Retain Subsite objects for later.
$sitesArray[$site->ID] = $site;
}
}
// Find sites that satisfy all codes conjuncitvely.
$accessibleSites = new ArrayList();
foreach ($codesPerSite as $siteID => $siteCodes) {
if (count($siteCodes)==count($codes)) {
$accessibleSites->push($sitesArray[$siteID]);
}
}
return $accessibleSites;
}
/*
* Returns a list of the subsites accessible to the current user.
* It's enough for any section to be accessible for the section to be included.
*/
public function Subsites() {
return Subsite::all_accessible_sites();
}
/*
* Generates a list of subsites with the data needed to
* produce a dropdown site switcher
* @return ArrayList
*/
public function ListSubsites(){
$list = $this->Subsites();
$currentSubsiteID = Subsite::currentSubsiteID();
if($list == null || $list->Count() == 1 && $list->First()->DefaultSite == true){
return false;
}
Requirements::javascript('subsites/javascript/LeftAndMain_Subsites.js');
$output = new ArrayList();
foreach($list as $subsite) {
$CurrentState = $subsite->ID == $currentSubsiteID ? 'selected' : '';
$output->push(new ArrayData(array(
'CurrentState' => $CurrentState,
'ID' => $subsite->ID,
'Title' => Convert::raw2xml($subsite->Title)
)));
}
return $output;
}
public function alternateMenuDisplayCheck($controllerName) {
if(!class_exists($controllerName)){
return false;
}
// Check subsite support.
if(Subsite::currentSubsiteID() == 0){
// Main site always supports everything.
return true;
} else {
$controller = singleton($controllerName);
if($controller->hasMethod('subsiteCMSShowInMenu') && $controller->subsiteCMSShowInMenu()){
return true;
}
}
// It's not necessary to check access permissions here. Framework calls canView on the controller,
// which in turn uses the Permission API which is augmented by our GroupSubsites.
return false;
}
public function CanAddSubsites() {
return Permission::check("ADMIN", "any", null, "all");
}
/**
* Helper for testing if the subsite should be adjusted.
*/
public function shouldChangeSubsite($adminClass, $recordSubsiteID, $currentSubsiteID) {
if (Config::inst()->get($adminClass, 'treats_subsite_0_as_global') && $recordSubsiteID==0) return false;
if ($recordSubsiteID!=$currentSubsiteID) return true;
return false;
}
/**
* Check if the current controller is accessible for this user on this subsite.
*/
function canAccess() {
// Admin can access everything, no point in checking.
$member = Member::currentUser();
if($member &&
(
Permission::checkMember($member, 'ADMIN') || // 'Full administrative rights' in SecurityAdmin
Permission::checkMember($member, 'CMS_ACCESS_LeftAndMain') // 'Access to all CMS sections' in SecurityAdmin
)) {
return true;
}
// Check if we have access to current section on the current subsite.
$accessibleSites = $this->owner->sectionSites(true, "Main site", $member);
if ($accessibleSites->count() && $accessibleSites->find('ID', Subsite::currentSubsiteID())) {
// Current section can be accessed on the current site, all good.
return true;
}
return false;
}
/**
* Prevent accessing disallowed resources. This happens after onBeforeInit has executed,
* so all redirections should've already taken place.
*/
public function alternateAccessCheck() {
return $this->owner->canAccess();
}
/**
* Redirect the user to something accessible if the current section/subsite is forbidden.
*
* This is done via onBeforeInit as it needs to be done before the LeftAndMain::init has a
* chance to forbids access via alternateAccessCheck.
*
* If we need to change the subsite we force the redirection to /admin/ so the frontend is
* fully re-synchronised with the internal session. This is better than risking some panels
* showing data from another subsite.
*/
public function onBeforeInit() {
// We are accessing the CMS, so we need to let Subsites know we will be using the session.
Subsite::$use_session_subsiteid = true;
// FIRST, check if we need to change subsites due to the URL.
// Catch forced subsite changes that need to cause CMS reloads.
if(isset($_GET['SubsiteID'])) {
// Clear current page when subsite changes (or is set for the first time)
if(!Session::get('SubsiteID') || $_GET['SubsiteID'] != Session::get('SubsiteID')) {
Session::clear("{$this->owner->class}.currentPage");
}
// Update current subsite in session
Subsite::changeSubsite($_GET['SubsiteID']);
if (!$this->owner->canView(Member::currentUser())) {
//Redirect to the default CMS section
return $this->owner->redirect('admin/');
}
}
// Automatically redirect the session to appropriate subsite when requesting a record.
// This is needed to properly initialise the session in situations where someone opens the CMS via a link.
$record = $this->owner->currentPage();
if($record && isset($record->SubsiteID) && is_numeric($record->SubsiteID) && isset($this->owner->urlParams['ID'])) {
if ($this->shouldChangeSubsite($this->owner->class, $record->SubsiteID, Subsite::currentSubsiteID())) {
// Update current subsite in session
Subsite::changeSubsite($record->SubsiteID);
if (!$this->owner->canView(Member::currentUser())) {
//Redirect to the default CMS section
return $this->owner->redirect('admin/');
}
}
}
// SECOND, check if we need to change subsites due to lack of permissions.
if (!$this->owner->canAccess()) {
$member = Member::currentUser();
// Current section is not accessible, try at least to stick to the same subsite.
$menu = CMSMenu::get_menu_items();
foreach($menu as $candidate) {
if($candidate->controller && $candidate->controller!=$this->owner->class) {
$accessibleSites = singleton($candidate->controller)->sectionSites(true, 'Main site', $member);
if ($accessibleSites->count() && $accessibleSites->find('ID', Subsite::currentSubsiteID())) {
// Section is accessible, redirect there.
return $this->owner->redirect(singleton($candidate->controller)->Link());
}
}
}
// If no section is available, look for other accessible subsites.
foreach($menu as $candidate) {
if($candidate->controller) {
$accessibleSites = singleton($candidate->controller)->sectionSites(true, 'Main site', $member);
if ($accessibleSites->count()) {
Subsite::changeSubsite($accessibleSites->First()->ID);
return $this->owner->redirect(singleton($candidate->controller)->Link());
}
}
}
// We have not found any accessible section or subsite. User should be denied access.
return Security::permissionFailure($this->owner);
}
// Current site is accessible. Allow through.
return;
}
function augmentNewSiteTreeItem(&$item) {
$item->SubsiteID = isset($_POST['SubsiteID']) ? $_POST['SubsiteID'] : Subsite::currentSubsiteID();
}
function onAfterSave($record) {
if($record->hasMethod('NormalRelated') && ($record->NormalRelated() || $record->ReverseRelated())) {
$this->owner->response->addHeader('X-Status', rawurlencode(_t('LeftAndMainSubsites.Saved', 'Saved, please update related pages.')));
}
}
function copytosubsite($data, $form) {
$page = DataObject::get_by_id('SiteTree', $data['ID']);
$subsite = DataObject::get_by_id('Subsite', $data['CopyToSubsiteID']);
$newPage = $page->duplicateToSubsite($subsite->ID, true);
$response = $this->owner->getResponse();
$response->addHeader('X-Reload', true);
return $this->owner->redirect(Controller::join_links($this->owner->Link('show'), $newPage->ID));
}
}

View File

@ -1,45 +0,0 @@
<?php
/**
* Extension for the SiteConfig object to add subsites support
*/
class SiteConfigSubsites extends DataExtension {
private static $has_one = array(
'Subsite' => 'Subsite', // The subsite that this page belongs to
);
/**
* Update any requests to limit the results to the current site
*/
function augmentSQL(SQLQuery &$query) {
if(Subsite::$disable_subsite_filter) return;
// If you're querying by ID, ignore the sub-site - this is a bit ugly...
if (!$query->where || (!preg_match('/\.(\'|"|`|)ID(\'|"|`|)( ?)=/', $query->where[0]) && !preg_match('/\.?(\'|"|`|)SubsiteID(\'|"|`|)( ?)=/', $query->where[0]))) {
/*if($context = DataObject::context_obj()) $subsiteID = (int)$context->SubsiteID;
else */$subsiteID = (int)Subsite::currentSubsiteID();
$froms=$query->getFrom();
$froms=array_keys($froms);
$tableName = array_shift($froms);
if($tableName != 'SiteConfig') return;
$query->addWhere("\"$tableName\".\"SubsiteID\" IN ($subsiteID)");
}
}
function onBeforeWrite() {
if((!is_numeric($this->owner->ID) || !$this->owner->ID) && !$this->owner->SubsiteID) $this->owner->SubsiteID = Subsite::currentSubsiteID();
}
/**
* Return a piece of text to keep DataObject cache keys appropriately specific
*/
function cacheKeyComponent() {
return 'subsite-'.Subsite::currentSubsiteID();
}
function updateCMSFields(FieldList $fields) {
$fields->push(new HiddenField('SubsiteID','SubsiteID', Subsite::currentSubsiteID()));
}
}

View File

@ -1,301 +0,0 @@
<?php
/**
* Extension for the SiteTree object to add subsites support
*/
class SiteTreeSubsites extends DataExtension {
private static $has_one = array(
'Subsite' => 'Subsite', // The subsite that this page belongs to
);
private static $many_many = array(
'CrossSubsiteLinkTracking' => 'SiteTree' // Stored separately, as the logic for URL rewriting is different
);
private static $many_many_extraFields = array(
"CrossSubsiteLinkTracking" => array("FieldName" => "Varchar")
);
function isMainSite() {
if($this->owner->SubsiteID == 0) return true;
return false;
}
/**
* Update any requests to limit the results to the current site
*/
function augmentSQL(SQLQuery &$query, DataQuery &$dataQuery = null) {
if(Subsite::$disable_subsite_filter) return;
if($dataQuery->getQueryParam('Subsite.filter') === false) return;
// Don't run on delete queries, since they are always tied to
// a specific ID.
if ($query->getDelete()) return;
// If you're querying by ID, ignore the sub-site - this is a bit ugly...
// if(!$query->where || (strpos($query->where[0], ".\"ID\" = ") === false && strpos($query->where[0], ".`ID` = ") === false && strpos($query->where[0], ".ID = ") === false && strpos($query->where[0], "ID = ") !== 0)) {
if (!$query->where || (!preg_match('/\.(\'|"|`|)ID(\'|"|`|)( ?)=/', $query->where[0]))) {
if (Subsite::$force_subsite) $subsiteID = Subsite::$force_subsite;
else {
/*if($context = DataObject::context_obj()) $subsiteID = (int)$context->SubsiteID;
else */$subsiteID = (int)Subsite::currentSubsiteID();
}
// The foreach is an ugly way of getting the first key :-)
foreach($query->getFrom() as $tableName => $info) {
// The tableName should be SiteTree or SiteTree_Live...
if(strpos($tableName,'SiteTree') === false) break;
$query->addWhere("\"$tableName\".\"SubsiteID\" IN ($subsiteID)");
break;
}
}
}
function onBeforeWrite() {
if(!$this->owner->ID && !$this->owner->SubsiteID) $this->owner->SubsiteID = Subsite::currentSubsiteID();
parent::onBeforeWrite();
}
function updateCMSFields(FieldList $fields) {
$subsites = Subsite::accessible_sites("CMS_ACCESS_CMSMain");
$subsitesMap = array();
if($subsites && $subsites->Count()) {
$subsitesMap = $subsites->map('ID', 'Title');
unset($subsitesMap[$this->owner->SubsiteID]);
}
// Master page edit field (only allowed from default subsite to avoid inconsistent relationships)
$isDefaultSubsite = $this->owner->SubsiteID == 0 || $this->owner->Subsite()->DefaultSite;
if($isDefaultSubsite && $subsitesMap) {
$fields->addFieldToTab(
'Root.Main',
new DropdownField(
"CopyToSubsiteID",
_t('SiteTreeSubsites.CopyToSubsite', "Copy page to subsite"),
$subsitesMap,
''
)
);
$fields->addFieldToTab(
'Root.Main',
$copyAction = new InlineFormAction(
"copytosubsite",
_t('SiteTreeSubsites.CopyAction', "Copy")
)
);
$copyAction->includeDefaultJS(false);
}
// replace readonly link prefix
$subsite = $this->owner->Subsite();
$nested_urls_enabled = Config::inst()->get('SiteTree', 'nested_urls');
if($subsite && $subsite->ID) {
$baseUrl = Director::protocol() . $subsite->domain() . '/';
$baseLink = Controller::join_links (
$baseUrl,
($nested_urls_enabled && $this->owner->ParentID ? $this->owner->Parent()->RelativeLink(true) : null)
);
$urlsegment = $fields->dataFieldByName('URLSegment');
$urlsegment->setURLPrefix($baseLink);
}
}
function alternateSiteConfig() {
if(!$this->owner->SubsiteID) return false;
$sc = DataObject::get_one('SiteConfig', '"SubsiteID" = ' . $this->owner->SubsiteID);
if(!$sc) {
$sc = new SiteConfig();
$sc->SubsiteID = $this->owner->SubsiteID;
$sc->Title = _t('Subsite.SiteConfigTitle','Your Site Name');
$sc->Tagline = _t('Subsite.SiteConfigSubtitle','Your tagline here');
$sc->write();
}
return $sc;
}
/**
* Only allow editing of a page if the member satisfies one of the following conditions:
* - Is in a group which has access to the subsite this page belongs to
* - Is in a group with edit permissions on the "main site"
*
* @return boolean
*/
function canEdit($member = null) {
if(!$member) $member = Member::currentUser();
// Find the sites that this user has access to
$goodSites = Subsite::accessible_sites('CMS_ACCESS_CMSMain',true,'all',$member)->column('ID');
if (!is_null($this->owner->SubsiteID)) {
$subsiteID = $this->owner->SubsiteID;
} else {
// The relationships might not be available during the record creation when using a GridField.
// In this case the related objects will have empty fields, and SubsiteID will not be available.
//
// We do the second best: fetch the likely SubsiteID from the session. The drawback is this might
// make it possible to force relations to point to other (forbidden) subsites.
$subsiteID = Subsite::currentSubsiteID();
}
// Return true if they have access to this object's site
if(!(in_array(0, $goodSites) || in_array($subsiteID, $goodSites))) return false;
}
/**
* @return boolean
*/
function canDelete($member = null) {
if(!$member && $member !== FALSE) $member = Member::currentUser();
return $this->canEdit($member);
}
/**
* @return boolean
*/
function canAddChildren($member = null) {
if(!$member && $member !== FALSE) $member = Member::currentUser();
return $this->canEdit($member);
}
/**
* @return boolean
*/
function canPublish($member = null) {
if(!$member && $member !== FALSE) $member = Member::currentUser();
return $this->canEdit($member);
}
/**
* Create a duplicate of this page and save it to another subsite
* @param $subsiteID int|Subsite The Subsite to copy to, or its ID
*/
public function duplicateToSubsite($subsiteID = null) {
if(is_object($subsiteID)) {
$subsite = $subsiteID;
$subsiteID = $subsite->ID;
} else $subsite = DataObject::get_by_id('Subsite', $subsiteID);
$oldSubsite=Subsite::currentSubsiteID();
if($subsiteID) {
Subsite::changeSubsite($subsiteID);
}else {
$subsiteID=$oldSubsite;
}
$page = $this->owner->duplicate(false);
$page->CheckedPublicationDifferences = $page->AddedToStage = true;
$subsiteID = ($subsiteID ? $subsiteID : $oldSubsite);
$page->SubsiteID = $subsiteID;
// MasterPageID is here for legacy purposes, to satisfy the subsites_relatedpages module
$page->MasterPageID = $this->owner->ID;
$page->write();
Subsite::changeSubsite($oldSubsite);
return $page;
}
/**
* Called by ContentController::init();
*/
static function contentcontrollerInit($controller) {
$subsite = Subsite::currentSubsite();
if($subsite && $subsite->Theme) SSViewer::set_theme(Subsite::currentSubsite()->Theme);
}
function alternateAbsoluteLink() {
// Generate the existing absolute URL and replace the domain with the subsite domain.
// This helps deal with Link() returning an absolute URL.
$url = Director::absoluteURL($this->owner->Link());
if($this->owner->SubsiteID) {
$url = preg_replace('/\/\/[^\/]+\//', '//' . $this->owner->Subsite()->domain() . '/', $url);
}
return $url;
}
/**
* Use the CMS domain for iframed CMS previews to prevent single-origin violations
* and SSL cert problems.
*/
function alternatePreviewLink($action = null) {
$url = Director::absoluteURL($this->owner->Link());
if($this->owner->SubsiteID) {
$url = HTTP::setGetVar('SubsiteID', $this->owner->SubsiteID, $url);
}
return $url;
}
/**
* Inject the subsite ID into the content so it can be used by frontend scripts.
*/
function MetaTags(&$tags) {
if($this->owner->SubsiteID) {
$tags .= "<meta name=\"x-subsite-id\" content=\"" . $this->owner->SubsiteID . "\" />\n";
}
return $tags;
}
function augmentSyncLinkTracking() {
// Set LinkTracking appropriately
$links = HTTP::getLinksIn($this->owner->Content);
$linkedPages = array();
if($links) foreach($links as $link) {
if(substr($link, 0, strlen('http://')) == 'http://') {
$withoutHttp = substr($link, strlen('http://'));
if(strpos($withoutHttp, '/') && strpos($withoutHttp, '/') < strlen($withoutHttp)) {
$domain = substr($withoutHttp, 0, strpos($withoutHttp, '/'));
$rest = substr($withoutHttp, strpos($withoutHttp, '/') + 1);
$subsiteID = Subsite::getSubsiteIDForDomain($domain);
if($subsiteID == 0) continue; // We have no idea what the domain for the main site is, so cant track links to it
$origDisableSubsiteFilter = Subsite::$disable_subsite_filter;
Subsite::disable_subsite_filter(true);
$candidatePage = DataObject::get_one("SiteTree", "\"URLSegment\" = '" . Convert::raw2sql(urldecode( $rest)) . "' AND \"SubsiteID\" = " . $subsiteID, false);
Subsite::disable_subsite_filter($origDisableSubsiteFilter);
if($candidatePage) {
$linkedPages[] = $candidatePage->ID;
} else {
$this->owner->HasBrokenLink = true;
}
}
}
}
$this->owner->CrossSubsiteLinkTracking()->setByIDList($linkedPages);
}
/**
* Return a piece of text to keep DataObject cache keys appropriately specific
*/
function cacheKeyComponent() {
return 'subsite-'.Subsite::currentSubsiteID();
}
/**
* @param Member
* @return boolean|null
*/
function canCreate($member = null) {
// Typically called on a singleton, so we're not using the Subsite() relation
$subsite = Subsite::currentSubsite();
if($subsite && $subsite->exists() && $subsite->PageTypeBlacklist) {
$blacklisted = explode(',', $subsite->PageTypeBlacklist);
// All subclasses need to be listed explicitly
if(in_array($this->owner->class, $blacklisted)) return false;
}
}
}

View File

@ -1,52 +0,0 @@
<?php
class GridFieldSubsiteDetailForm extends GridFieldDetailForm {
protected $itemRequestClass='GridFieldSubsiteDetailForm_ItemRequest';
}
class GridFieldSubsiteDetailForm_ItemRequest extends GridFieldDetailForm_ItemRequest {
private static $allowed_actions = array(
'ItemEditForm',
);
/**
* Builds an item edit form. The arguments to getCMSFields() are the popupController and
* popupFormName, however this is an experimental API and may change.
*
* @todo In the future, we will probably need to come up with a tigher object representing a partially
* complete controller with gaps for extra functionality. This, for example, would be a better way
* of letting Security/login put its log-in form inside a UI specified elsewhere.
*
* @return Form
* @see GridFieldDetailForm_ItemRequest::ItemEditForm()
*/
function ItemEditForm() {
$form=parent::ItemEditForm();
if($this->record->ID == 0) {
$templates = Subsite::get()->sort('Title');
$templateArray = array();
if($templates) {
$templateArray = $templates->map('ID', 'Title');
}
$templateDropdown = new DropdownField('TemplateID', _t('Subsite.COPYSTRUCTURE', 'Copy structure from:'), $templateArray);
$templateDropdown->setEmptyString('(' . _t('Subsite.NOTEMPLATE', 'No template') . ')');
$form->Fields()->addFieldToTab('Root.Configuration', $templateDropdown);
}
return $form;
}
function doSave($data, $form) {
$new_record = $this->record->ID == 0;
if($new_record && isset($data['TemplateID']) && !empty($data['TemplateID'])) {
$template = Subsite::get()->byID(intval($data['TemplateID']));
if($template) {
$this->record = $template->duplicate();
}
}
return parent::doSave($data, $form);
}
}

View File

@ -1,44 +0,0 @@
<?php
/**
* Wraps around a TreedropdownField to add ability for temporary
* switching of subsite sessions.
*
* @package subsites
*/
class SubsitesTreeDropdownField extends TreeDropdownField {
private static $allowed_actions = array(
'tree'
);
protected $subsiteID = 0;
protected $extraClasses = array('SubsitesTreeDropdownField');
function Field($properties = array()) {
$html = parent::Field($properties);
Requirements::javascript('subsites/javascript/SubsitesTreeDropdownField.js');
return $html;
}
function setSubsiteID($id) {
$this->subsiteID = $id;
}
function getSubsiteID() {
return $this->subsiteID;
}
function tree(SS_HTTPRequest $request) {
$oldSubsiteID = Session::get('SubsiteID');
Session::set('SubsiteID', $this->subsiteID);
$results = parent::tree($request);
Session::set('SubsiteID', $oldSubsiteID);
return $results;
}
}

View File

@ -1,820 +0,0 @@
<?php
/**
* A dynamically created subsite. SiteTree objects can now belong to a subsite.
* You can simulate subsite access without setting up virtual hosts by appending ?SubsiteID=<ID> to the request.
*
* @package subsites
*/
class Subsite extends DataObject {
/**
* @var $use_session_subsiteid Boolean Set to TRUE when using the CMS and FALSE
* when browsing the frontend of a website.
*
* @todo Remove flag once the Subsite CMS works without session state,
* similarly to the Translatable module.
*/
public static $use_session_subsiteid = false;
/**
* @var boolean $disable_subsite_filter If enabled, bypasses the query decoration
* to limit DataObject::get*() calls to a specific subsite. Useful for debugging.
*/
public static $disable_subsite_filter = false;
/**
* Allows you to force a specific subsite ID, or comma separated list of IDs.
* Only works for reading. An object cannot be written to more than 1 subsite.
*/
public static $force_subsite = null;
/**
*
* @var boolean
*/
public static $write_hostmap = true;
/**
* Memory cache of accessible sites
*
* @array
*/
private static $_cache_accessible_sites = array();
/**
* Memory cache of subsite id for domains
*
* @var array
*/
private static $_cache_subsite_for_domain = array();
/**
* @var array $allowed_themes Numeric array of all themes which are allowed to be selected for all subsites.
* Corresponds to subfolder names within the /themes folder. By default, all themes contained in this folder
* are listed.
*/
private static $allowed_themes = array();
/**
* @var Boolean If set to TRUE, don't assume 'www.example.com' and 'example.com' are the same.
* Doesn't affect wildcard matching, so '*.example.com' will match 'www.example.com' (but not 'example.com')
* in both TRUE or FALSE setting.
*/
public static $strict_subdomain_matching = false;
/**
* @var boolean Respects the IsPublic flag when retrieving subsites
*/
public static $check_is_public = true;
/**
* Set allowed themes
*
* @param array $themes - Numeric array of all themes which are allowed to be selected for all subsites.
*/
public static function set_allowed_themes($themes) {
self::$allowed_themes = $themes;
}
/**
* Gets the subsite currently set in the session.
*
* @uses ControllerSubsites->controllerAugmentInit()
* @return Subsite
*/
public static function currentSubsite() {
// get_by_id handles caching so we don't have to
return DataObject::get_by_id('Subsite', self::currentSubsiteID());
}
/**
* This function gets the current subsite ID from the session. It used in the backend so Ajax requests
* use the correct subsite. The frontend handles subsites differently. It calls getSubsiteIDForDomain
* directly from ModelAsController::getNestedController. Only gets Subsite instances which have their
* {@link IsPublic} flag set to TRUE.
*
* You can simulate subsite access without creating virtual hosts by appending ?SubsiteID=<ID> to the request.
*
* @todo Pass $request object from controller so we don't have to rely on $_GET
*
* @param boolean $cache
* @return int ID of the current subsite instance
*/
public static function currentSubsiteID() {
$id = NULL;
if(isset($_GET['SubsiteID'])) {
$id = (int)$_GET['SubsiteID'];
} else if (Subsite::$use_session_subsiteid) {
$id = Session::get('SubsiteID');
}
if($id === NULL) {
$id = self::getSubsiteIDForDomain();
}
return (int)$id;
}
/**
* Switch to another subsite through storing the subsite identifier in the current PHP session.
* Only takes effect when {@link Subsite::$use_session_subsiteid} is set to TRUE.
*
* @param int|Subsite $subsite Either the ID of the subsite, or the subsite object itself
*/
public static function changeSubsite($subsite) {
// Session subsite change only meaningful if the session is active.
// Otherwise we risk setting it to wrong value, e.g. if we rely on currentSubsiteID.
if (!Subsite::$use_session_subsiteid) return;
if(is_object($subsite)) $subsiteID = $subsite->ID;
else $subsiteID = $subsite;
Session::set('SubsiteID', (int)$subsiteID);
// Set locale
if (is_object($subsite) && $subsite->Language != '') {
$locale = i18n::get_locale_from_lang($subsite->Language);
if($locale) {
i18n::set_locale($locale);
}
}
Permission::flush_permission_cache();
}
/**
* Get a matching subsite for the given host, or for the current HTTP_HOST.
* Supports "fuzzy" matching of domains by placing an asterisk at the start of end of the string,
* for example matching all subdomains on *.example.com with one subsite,
* and all subdomains on *.example.org on another.
*
* @param $host The host to find the subsite for. If not specified, $_SERVER['HTTP_HOST'] is used.
* @return int Subsite ID
*/
public static function getSubsiteIDForDomain($host = null, $checkPermissions = true) {
if($host == null && isset($_SERVER['HTTP_HOST'])) {
$host = $_SERVER['HTTP_HOST'];
}
$matchingDomains = null;
$cacheKey = null;
if ($host) {
if(!self::$strict_subdomain_matching) $host = preg_replace('/^www\./', '', $host);
$cacheKey = implode('_', array($host, Member::currentUserID(), self::$check_is_public));
if(isset(self::$_cache_subsite_for_domain[$cacheKey])) return self::$_cache_subsite_for_domain[$cacheKey];
$SQL_host = Convert::raw2sql($host);
$matchingDomains = DataObject::get(
"SubsiteDomain",
"'$SQL_host' LIKE replace(\"SubsiteDomain\".\"Domain\",'*','%')",
"\"IsPrimary\" DESC"
)->innerJoin('Subsite', "\"Subsite\".\"ID\" = \"SubsiteDomain\".\"SubsiteID\" AND \"Subsite\".\"IsPublic\"=1");
}
if($matchingDomains && $matchingDomains->Count()) {
$subsiteIDs = array_unique($matchingDomains->column('SubsiteID'));
$subsiteDomains = array_unique($matchingDomains->column('Domain'));
if(sizeof($subsiteIDs) > 1) {
throw new UnexpectedValueException(sprintf(
"Multiple subsites match on '%s': %s",
$host,
implode(',', $subsiteDomains)
));
}
$subsiteID = $subsiteIDs[0];
} else if($default = DataObject::get_one('Subsite', "\"DefaultSite\" = 1")) {
// Check for a 'default' subsite
$subsiteID = $default->ID;
} else {
// Default subsite id = 0, the main site
$subsiteID = 0;
}
if ($cacheKey) {
self::$_cache_subsite_for_domain[$cacheKey] = $subsiteID;
}
return $subsiteID;
}
/**
*
* @param string $className
* @param string $filter
* @param string $sort
* @param string $join
* @param string $limit
* @return DataList
*/
public static function get_from_all_subsites($className, $filter = "", $sort = "", $join = "", $limit = "") {
$result = DataObject::get($className, $filter, $sort, $join, $limit);
$result = $result->setDataQueryParam('Subsite.filter', false);
return $result;
}
/**
* Disable the sub-site filtering; queries will select from all subsites
*/
public static function disable_subsite_filter($disabled = true) {
self::$disable_subsite_filter = $disabled;
}
/**
* Flush caches on database reset
*/
public static function on_db_reset() {
self::$_cache_accessible_sites = array();
self::$_cache_subsite_for_domain = array();
}
/**
* Return all subsites, regardless of permissions (augmented with main site).
*
* @return SS_List List of {@link Subsite} objects (DataList or ArrayList).
*/
public static function all_sites($includeMainSite = true, $mainSiteTitle = "Main site") {
$subsites = Subsite::get();
if($includeMainSite) {
$subsites = $subsites->toArray();
$mainSite = new Subsite();
$mainSite->Title = $mainSiteTitle;
array_unshift($subsites, $mainSite);
$subsites = ArrayList::create($subsites);
}
return $subsites;
}
/*
* Returns an ArrayList of the subsites accessible to the current user.
* It's enough for any section to be accessible for the site to be included.
*
* @return ArrayList of {@link Subsite} instances.
*/
public static function all_accessible_sites($includeMainSite = true, $mainSiteTitle = "Main site", $member = null) {
// Rationalise member arguments
if(!$member) $member = Member::currentUser();
if(!$member) return new ArrayList();
if(!is_object($member)) $member = DataObject::get_by_id('Member', $member);
$subsites = new ArrayList();
// Collect subsites for all sections.
$menu = CMSMenu::get_viewable_menu_items();
foreach($menu as $candidate) {
if ($candidate->controller) {
$accessibleSites = singleton($candidate->controller)->sectionSites(
$includeMainSite,
$mainSiteTitle,
$member
);
// Replace existing keys so no one site appears twice.
$subsites->merge($accessibleSites);
}
}
$subsites->removeDuplicates();
return $subsites;
}
/**
* Return the subsites that the current user can access by given permission.
* Sites will only be included if they have a Title.
*
* @param $permCode array|string Either a single permission code or an array of permission codes.
* @param $includeMainSite If true, the main site will be included if appropriate.
* @param $mainSiteTitle The label to give to the main site
* @param $member
* @return DataList of {@link Subsite} instances
*/
public static function accessible_sites($permCode, $includeMainSite = true, $mainSiteTitle = "Main site", $member = null) {
// Rationalise member arguments
if(!$member) $member = Member::currentUser();
if(!$member) return new ArrayList();
if(!is_object($member)) $member = DataObject::get_by_id('Member', $member);
// Rationalise permCode argument
if(is_array($permCode)) $SQL_codes = "'" . implode("', '", Convert::raw2sql($permCode)) . "'";
else $SQL_codes = "'" . Convert::raw2sql($permCode) . "'";
// Cache handling
$cacheKey = $SQL_codes . '-' . $member->ID . '-' . $includeMainSite . '-' . $mainSiteTitle;
if(isset(self::$_cache_accessible_sites[$cacheKey])) {
return self::$_cache_accessible_sites[$cacheKey];
}
$subsites = DataList::create('Subsite')
->where("\"Subsite\".\"Title\" != ''")
->leftJoin('Group_Subsites', "\"Group_Subsites\".\"SubsiteID\" = \"Subsite\".\"ID\"")
->innerJoin('Group', "\"Group\".\"ID\" = \"Group_Subsites\".\"GroupID\" OR \"Group\".\"AccessAllSubsites\" = 1")
->innerJoin('Group_Members', "\"Group_Members\".\"GroupID\"=\"Group\".\"ID\" AND \"Group_Members\".\"MemberID\" = $member->ID")
->innerJoin('Permission', "\"Group\".\"ID\"=\"Permission\".\"GroupID\" AND \"Permission\".\"Code\" IN ($SQL_codes, 'CMS_ACCESS_LeftAndMain', 'ADMIN')");
if(!$subsites) $subsites = new ArrayList();
$rolesSubsites = DataList::create('Subsite')
->where("\"Subsite\".\"Title\" != ''")
->leftJoin('Group_Subsites', "\"Group_Subsites\".\"SubsiteID\" = \"Subsite\".\"ID\"")
->innerJoin('Group', "\"Group\".\"ID\" = \"Group_Subsites\".\"GroupID\" OR \"Group\".\"AccessAllSubsites\" = 1")
->innerJoin('Group_Members', "\"Group_Members\".\"GroupID\"=\"Group\".\"ID\" AND \"Group_Members\".\"MemberID\" = $member->ID")
->innerJoin('Group_Roles', "\"Group_Roles\".\"GroupID\"=\"Group\".\"ID\"")
->innerJoin('PermissionRole', "\"Group_Roles\".\"PermissionRoleID\"=\"PermissionRole\".\"ID\"")
->innerJoin('PermissionRoleCode', "\"PermissionRole\".\"ID\"=\"PermissionRoleCode\".\"RoleID\" AND \"PermissionRoleCode\".\"Code\" IN ($SQL_codes, 'CMS_ACCESS_LeftAndMain', 'ADMIN')");
if(!$subsites && $rolesSubsites) return $rolesSubsites;
$subsites = new ArrayList($subsites->toArray());
if($rolesSubsites) foreach($rolesSubsites as $subsite) {
if(!$subsites->find('ID', $subsite->ID)) {
$subsites->push($subsite);
}
}
if($includeMainSite) {
if(!is_array($permCode)) $permCode = array($permCode);
if(self::hasMainSitePermission($member, $permCode)) {
$subsites=$subsites->toArray();
$mainSite = new Subsite();
$mainSite->Title = $mainSiteTitle;
array_unshift($subsites, $mainSite);
$subsites=ArrayList::create($subsites);
}
}
self::$_cache_accessible_sites[$cacheKey] = $subsites;
return $subsites;
}
/**
* Write a host->domain map to subsites/host-map.php
*
* This is used primarily when using subsites in conjunction with StaticPublisher
*
* @param string $file - filepath of the host map to be written
* @return void
*/
public static function writeHostMap($file = null) {
if (!self::$write_hostmap) return;
if (!$file) $file = Director::baseFolder().'/subsites/host-map.php';
$hostmap = array();
$subsites = DataObject::get('Subsite');
if ($subsites) foreach($subsites as $subsite) {
$domains = $subsite->Domains();
if ($domains) foreach($domains as $domain) {
$domainStr = $domain->Domain;
if(!self::$strict_subdomain_matching) $domainStr = preg_replace('/^www\./', '', $domainStr);
$hostmap[$domainStr] = $subsite->domain();
}
if ($subsite->DefaultSite) $hostmap['default'] = $subsite->domain();
}
$data = "<?php \n";
$data .= "// Generated by Subsite::writeHostMap() on " . date('d/M/y') . "\n";
$data .= '$subsiteHostmap = ' . var_export($hostmap, true) . ';';
if (is_writable(dirname($file)) || is_writable($file)) {
file_put_contents($file, $data);
}
}
/**
* Checks if a member can be granted certain permissions, regardless of the subsite context.
* Similar logic to {@link Permission::checkMember()}, but only returns TRUE
* if the member is part of a group with the "AccessAllSubsites" flag set.
* If more than one permission is passed to the method, at least one of them must
* be granted for if to return TRUE.
*
* @todo Allow permission inheritance through group hierarchy.
*
* @param Member Member to check against. Defaults to currently logged in member
* @param Array Permission code strings. Defaults to "ADMIN".
* @return boolean
*/
public static function hasMainSitePermission($member = null, $permissionCodes = array('ADMIN')) {
if(!is_array($permissionCodes))
user_error('Permissions must be passed to Subsite::hasMainSitePermission as an array', E_USER_ERROR);
if(!$member && $member !== FALSE) $member = Member::currentUser();
if(!$member) return false;
if(!in_array("ADMIN", $permissionCodes)) $permissionCodes[] = "ADMIN";
$SQLa_perm = Convert::raw2sql($permissionCodes);
$SQL_perms = join("','", $SQLa_perm);
$memberID = (int)$member->ID;
// Count this user's groups which can access the main site
$groupCount = DB::query("
SELECT COUNT(\"Permission\".\"ID\")
FROM \"Permission\"
INNER JOIN \"Group\" ON \"Group\".\"ID\" = \"Permission\".\"GroupID\" AND \"Group\".\"AccessAllSubsites\" = 1
INNER JOIN \"Group_Members\" ON \"Group_Members\".\"GroupID\" = \"Permission\".\"GroupID\"
WHERE \"Permission\".\"Code\" IN ('$SQL_perms')
AND \"MemberID\" = {$memberID}
")->value();
// Count this user's groups which have a role that can access the main site
$roleCount = DB::query("
SELECT COUNT(\"PermissionRoleCode\".\"ID\")
FROM \"Group\"
INNER JOIN \"Group_Members\" ON \"Group_Members\".\"GroupID\" = \"Group\".\"ID\"
INNER JOIN \"Group_Roles\" ON \"Group_Roles\".\"GroupID\"=\"Group\".\"ID\"
INNER JOIN \"PermissionRole\" ON \"Group_Roles\".\"PermissionRoleID\"=\"PermissionRole\".\"ID\"
INNER JOIN \"PermissionRoleCode\" ON \"PermissionRole\".\"ID\"=\"PermissionRoleCode\".\"RoleID\"
WHERE \"PermissionRoleCode\".\"Code\" IN ('$SQL_perms')
AND \"Group\".\"AccessAllSubsites\" = 1
AND \"MemberID\" = {$memberID}
")->value();
// There has to be at least one that allows access.
return ($groupCount + $roleCount > 0);
}
/**
*
* @var array
*/
private static $db = array(
'Title' => 'Varchar(255)',
'RedirectURL' => 'Varchar(255)',
'DefaultSite' => 'Boolean',
'Theme' => 'Varchar',
'Language' => 'Varchar(6)',
// Used to hide unfinished/private subsites from public view.
// If unset, will default to true
'IsPublic' => 'Boolean',
// Comma-separated list of disallowed page types
'PageTypeBlacklist' => 'Text',
);
/**
*
* @var array
*/
private static $has_many = array(
'Domains' => 'SubsiteDomain',
);
/**
*
* @var array
*/
private static $belongs_many_many = array(
"Groups" => "Group",
);
/**
*
* @var array
*/
private static $defaults = array(
'IsPublic' => 1
);
/**
*
* @var array
*/
private static $searchable_fields = array(
'Title',
'Domains.Domain',
'IsPublic',
);
/**
*
* @var string
*/
private static $default_sort = "\"Title\" ASC";
/**
* @todo Possible security issue, don't grant edit permissions to everybody.
* @return boolean
*/
public function canEdit($member = false) {
return true;
}
/**
* Show the configuration fields for each subsite
*
* @return FieldList
*/
public function getCMSFields() {
if($this->ID!=0) {
$domainTable = new GridField(
"Domains",
_t('Subsite.DomainsListTitle',"Domains"),
$this->Domains(),
GridFieldConfig_RecordEditor::create(10)
);
}else {
$domainTable = new LiteralField(
'Domains',
'<p>'._t('Subsite.DOMAINSAVEFIRST', 'You can only add domains after saving for the first time').'</p>'
);
}
$languageSelector = new DropdownField(
'Language',
$this->fieldLabel('Language'),
i18n::get_common_locales()
);
$pageTypeMap = array();
$pageTypes = SiteTree::page_type_classes();
foreach($pageTypes as $pageType) {
$pageTypeMap[$pageType] = singleton($pageType)->i18n_singular_name();
}
asort($pageTypeMap);
$fields = new FieldList(
$subsiteTabs = new TabSet('Root',
new Tab(
'Configuration',
_t('Subsite.TabTitleConfig', 'Configuration'),
new HeaderField($this->getClassName() . ' configuration', 2),
new TextField('Title', $this->fieldLabel('Title'), $this->Title),
new HeaderField(
_t('Subsite.DomainsHeadline',"Domains for this subsite")
),
$domainTable,
$languageSelector,
// new TextField('RedirectURL', 'Redirect to URL', $this->RedirectURL),
new CheckboxField('DefaultSite', $this->fieldLabel('DefaultSite'), $this->DefaultSite),
new CheckboxField('IsPublic', $this->fieldLabel('IsPublic'), $this->IsPublic),
new DropdownField('Theme',$this->fieldLabel('Theme'), $this->allowedThemes(), $this->Theme),
new LiteralField(
'PageTypeBlacklistToggle',
sprintf(
'<div class="field"><a href="#" id="PageTypeBlacklistToggle">%s</a></div>',
_t('Subsite.PageTypeBlacklistField', 'Disallow page types?')
)
),
new CheckboxSetField(
'PageTypeBlacklist',
false,
$pageTypeMap
)
)
),
new HiddenField('ID', '', $this->ID),
new HiddenField('IsSubsite', '', 1)
);
$subsiteTabs->addExtraClass('subsite-model');
$this->extend('updateCMSFields', $fields);
return $fields;
}
/**
*
* @param boolean $includerelations
* @return array
*/
public function fieldLabels($includerelations = true) {
$labels = parent::fieldLabels($includerelations);
$labels['Title'] = _t('Subsites.TitleFieldLabel', 'Subsite Name');
$labels['RedirectURL'] = _t('Subsites.RedirectURLFieldLabel', 'Redirect URL');
$labels['DefaultSite'] = _t('Subsites.DefaultSiteFieldLabel', 'Default site');
$labels['Theme'] = _t('Subsites.ThemeFieldLabel', 'Theme');
$labels['Language'] = _t('Subsites.LanguageFieldLabel', 'Language');
$labels['IsPublic'] = _t('Subsites.IsPublicFieldLabel', 'Enable public access');
$labels['PageTypeBlacklist'] = _t('Subsites.PageTypeBlacklistFieldLabel', 'Page Type Blacklist');
$labels['Domains.Domain'] = _t('Subsites.DomainFieldLabel', 'Domain');
$labels['PrimaryDomain'] = _t('Subsites.PrimaryDomainFieldLabel', 'Primary Domain');
return $labels;
}
/**
*
* @return array
*/
public function summaryFields() {
return array(
'Title' => $this->fieldLabel('Title'),
'PrimaryDomain' => $this->fieldLabel('PrimaryDomain'),
'IsPublic' => _t('Subsite.IsPublicHeaderField','Active subsite'),
);
}
/**
* Return the themes that can be used with this subsite, as an array of themecode => description
*
* @return array
*/
public function allowedThemes() {
if($themes = $this->stat('allowed_themes')) {
return ArrayLib::valuekey($themes);
} else {
$themes = array();
if(is_dir('../themes/')) {
foreach(scandir('../themes/') as $theme) {
if($theme[0] == '.') continue;
$theme = strtok($theme,'_');
$themes[$theme] = $theme;
}
ksort($themes);
}
return $themes;
}
}
/**
* @return string Current locale of the subsite
*/
public function getLanguage() {
if($this->getField('Language')) {
return $this->getField('Language');
} else {
return i18n::get_locale();
}
}
/**
*
* @return ValidationResult
*/
public function validate() {
$result = parent::validate();
if(!$this->Title) {
$result->error(_t('Subsite.ValidateTitle', 'Please add a "Title"'));
}
return $result;
}
/**
* Whenever a Subsite is written, rewrite the hostmap
*
* @return void
*/
public function onAfterWrite() {
Subsite::writeHostMap();
parent::onAfterWrite();
}
/**
* Return the primary domain of this site. Tries to "normalize" the domain name,
* by replacing potential wildcards.
*
* @return string The full domain name of this subsite (without protocol prefix)
*/
public function domain() {
if($this->ID) {
$domains = DataObject::get("SubsiteDomain", "\"SubsiteID\" = $this->ID", "\"IsPrimary\" DESC","", 1);
if($domains && $domains->Count()>0) {
$domain = $domains->First()->Domain;
// If there are wildcards in the primary domain (not recommended), make some
// educated guesses about what to replace them with:
$domain = preg_replace('/\.\*$/',".$_SERVER[HTTP_HOST]", $domain);
// Default to "subsite." prefix for first wildcard
// TODO Whats the significance of "subsite" in this context?!
$domain = preg_replace('/^\*\./',"subsite.", $domain);
// *Only* removes "intermediate" subdomains, so 'subdomain.www.domain.com' becomes 'subdomain.domain.com'
$domain = str_replace('.www.','.', $domain);
return $domain;
}
// SubsiteID = 0 is often used to refer to the main site, just return $_SERVER['HTTP_HOST']
} else {
return $_SERVER['HTTP_HOST'];
}
}
/**
*
* @return string - The full domain name of this subsite (without protocol prefix)
*/
public function getPrimaryDomain() {
return $this->domain();
}
/**
*
* @return string
*/
public function absoluteBaseURL() {
return "http://" . $this->domain() . Director::baseURL();
}
/**
* @todo getClassName is redundant, already stored as a database field?
*/
public function getClassName() {
return $this->class;
}
/**
* Javascript admin action to duplicate this subsite
*
* @return string - javascript
*/
public function adminDuplicate() {
$newItem = $this->duplicate();
$message = _t(
'Subsite.CopyMessage',
'Created a copy of {title}',
array('title' => Convert::raw2js($this->Title))
);
return <<<JS
statusMessage($message, 'good');
$('Form_EditForm').loadURLFromServer('admin/subsites/show/$newItem->ID');
JS;
}
/**
* Make this subsite the current one
*/
public function activate() {
Subsite::changeSubsite($this);
}
/**
*
* @param array $permissionCodes
* @return DataList
*/
public function getMembersByPermission($permissionCodes = array('ADMIN')){
if(!is_array($permissionCodes))
user_error('Permissions must be passed to Subsite::getMembersByPermission as an array', E_USER_ERROR);
$SQL_permissionCodes = Convert::raw2sql($permissionCodes);
$SQL_permissionCodes = join("','", $SQL_permissionCodes);
return DataObject::get(
'Member',
"\"Group\".\"SubsiteID\" = $this->ID AND \"Permission\".\"Code\" IN ('$SQL_permissionCodes')",
'',
"LEFT JOIN \"Group_Members\" ON \"Member\".\"ID\" = \"Group_Members\".\"MemberID\"
LEFT JOIN \"Group\" ON \"Group\".\"ID\" = \"Group_Members\".\"GroupID\"
LEFT JOIN \"Permission\" ON \"Permission\".\"GroupID\" = \"Group\".\"ID\""
);
}
/**
* Duplicate this subsite
*/
public function duplicate($doWrite = true) {
$duplicate = parent::duplicate($doWrite);
$oldSubsiteID = Session::get('SubsiteID');
self::changeSubsite($this->ID);
/*
* Copy data from this object to the given subsite. Does this using an iterative depth-first search.
* This will make sure that the new parents on the new subsite are correct, and there are no funny
* issues with having to check whether or not the new parents have been added to the site tree
* when a page, etc, is duplicated
*/
$stack = array(array(0,0));
while(count($stack) > 0) {
list($sourceParentID, $destParentID) = array_pop($stack);
$children = Versioned::get_by_stage('Page', 'Live', "\"ParentID\" = $sourceParentID", '');
if($children) {
foreach($children as $child) {
self::changeSubsite($duplicate->ID); //Change to destination subsite
$childClone = $child->duplicateToSubsite($duplicate, false);
$childClone->ParentID = $destParentID;
$childClone->writeToStage('Stage');
$childClone->publish('Stage', 'Live');
self::changeSubsite($this->ID); //Change Back to this subsite
array_push($stack, array($child->ID, $childClone->ID));
}
}
}
self::changeSubsite($oldSubsiteID);
return $duplicate;
}
}

View File

@ -1,81 +0,0 @@
<?php
/**
* @property text Domain domain name of this subsite. Do not include the URL scheme here
* @property bool IsPrimary Is this the primary subdomain?
*/
class SubsiteDomain extends DataObject {
/**
*
* @var array
*/
private static $db = array(
"Domain" => "Varchar(255)",
"IsPrimary" => "Boolean",
);
/**
*
* @var array
*/
private static $has_one = array(
"Subsite" => "Subsite",
);
/**
*
* @var array
*/
private static $summary_fields=array(
'Domain',
'IsPrimary',
);
/**
* Whenever a Subsite Domain is written, rewrite the hostmap
*
* @return void
*/
public function onAfterWrite() {
Subsite::writeHostMap();
}
/**
*
* @return \FieldList
*/
public function getCMSFields() {
$fields = new FieldList(
new TextField('Domain', $this->fieldLabel('Domain'), null, 255),
new CheckboxField('IsPrimary', $this->fieldLabel('IsPrimary'))
);
$this->extend('updateCMSFields', $fields);
return $fields;
}
/**
*
* @param bool $includerelations
* @return array
*/
public function fieldLabels($includerelations = true) {
$labels = parent::fieldLabels($includerelations);
$labels['Domain'] = _t('SubsiteDomain.DOMAIN', 'Domain');
$labels['IsPrimary'] = _t('SubsiteDomain.IS_PRIMARY', 'Is Primary Domain');
return $labels;
}
/**
* Before writing the Subsite Domain, strip out any HTML the user has entered.
* @return void
*/
public function onBeforeWrite() {
parent::onBeforeWrite();
//strip out any HTML to avoid XSS attacks
$this->Domain = Convert::html2raw($this->Domain);
}
}

View File

@ -1,67 +0,0 @@
<?php
/**
* Handy alternative to copying pages when creating a subsite through the UI.
* Can be used to batch-add new pages after subsite creation,
* or simply to process a large site outside of the UI.
*
* Example: sake dev/tasks/SubsiteCopyPagesTask from=<subsite-source> to=<subsite-target>
*/
class SubsiteCopyPagesTask extends BuildTask {
protected $title = 'Copy pages to different subsite';
protected $description = '';
function run($request) {
$subsiteFromId = $request->getVar('from');
if(!is_numeric($subsiteFromId)) throw new InvalidArgumentException('Missing "from" parameter');
$subsiteFrom = DataObject::get_by_id('Subsite', $subsiteFromId);
if(!$subsiteFrom) throw new InvalidArgumentException('Subsite not found');
$subsiteToId = $request->getVar('to');
if(!is_numeric($subsiteToId)) throw new InvalidArgumentException('Missing "to" parameter');
$subsiteTo = DataObject::get_by_id('Subsite', $subsiteToId);
if(!$subsiteTo) throw new InvalidArgumentException('Subsite not found');
$useVirtualPages = (bool)$request->getVar('virtual');
Subsite::changeSubsite($subsiteFrom);
// Copy data from this template to the given subsite. Does this using an iterative depth-first search.
// This will make sure that the new parents on the new subsite are correct, and there are no funny
// issues with having to check whether or not the new parents have been added to the site tree
// when a page, etc, is duplicated
$stack = array(array(0,0));
while(count($stack) > 0) {
list($sourceParentID, $destParentID) = array_pop($stack);
$children = Versioned::get_by_stage('SiteTree', 'Live', "\"ParentID\" = $sourceParentID", '');
if($children) {
foreach($children as $child) {
if($useVirtualPages) {
$childClone = new SubsitesVirtualPage();
$childClone->writeToStage('Stage');
$childClone->CopyContentFromID = $child->ID;
$childClone->SubsiteID = $subsiteTo->ID;
} else {
$childClone = $child->duplicateToSubsite($subsiteTo->ID, true);
}
$childClone->ParentID = $destParentID;
$childClone->writeToStage('Stage');
$childClone->publish('Stage', 'Live');
array_push($stack, array($child->ID, $childClone->ID));
$this->log(sprintf('Copied "%s" (#%d, %s)', $child->Title, $child->ID, $child->Link()));
}
}
unset($children);
}
}
function log($msg) {
echo $msg . "\n";
}
}

1
codecov.yml Normal file
View File

@ -0,0 +1 @@
comment: false

View File

@ -1,17 +1,44 @@
{
"name": "silverstripe/subsites",
"description": "Run multiple sites from a single SilverStripe install.",
"type": "silverstripe-module",
"keywords": ["silverstripe", "subsites", "multisite"],
"authors": [
{
"name": "Sam Minnee",
"email": "sam@silverstripe.com"
}
],
"require":
{
"silverstripe/framework": "~3.1.0",
"silverstripe/cms": "~3.1.0"
}
}
"name": "silverstripe/subsites",
"description": "Run multiple sites from a single SilverStripe install.",
"license": "BSD-3-Clause",
"type": "silverstripe-vendormodule",
"keywords": [
"silverstripe",
"subsites",
"multisite"
],
"authors": [
{
"name": "Sam Minnee",
"email": "sam@silverstripe.com"
}
],
"require": {
"php": "^7.4 || ^8.0",
"silverstripe/framework": "^4.12",
"silverstripe/cms": "^4.4@dev",
"silverstripe/admin": "^1.4@dev",
"silverstripe/asset-admin": "^1.4@dev",
"silverstripe/errorpage": "^1.4@dev",
"silverstripe/versioned": "^1.4@dev"
},
"require-dev": {
"phpunit/phpunit": "^9.5",
"squizlabs/php_codesniffer": "^3.0"
},
"autoload": {
"psr-4": {
"SilverStripe\\Subsites\\": "src/",
"SilverStripe\\Subsites\\Tests\\": "tests/php/"
}
},
"extra": {
"expose": [
"client/javascript",
"client/css"
]
},
"minimum-stability": "dev",
"prefer-stable": true
}

View File

@ -1,95 +0,0 @@
/**
* Styling for the subsite actions section in the CMS
*/
#SubsiteActions {
position: absolute;
padding: 0;
margin: 0;
right: 0;
top: 0;
width: 350px;
text-align: right;
margin-right: 130px;
height: 51px;
border-bottom: 3px solid #d4d0c8;
color: #fff;
}
#SubsiteActions fieldset {
padding: 3px;
border-style: none;
margin-top: 1px;
background: none;
}
#SubsiteActions fieldset span {
padding: 3px;
}
.cms-menu .cms-subsites{
padding:3px 0px 15px;
}
.cms-menu .cms-subsites .field.dropdown{
padding-bottom:0;
margin-bottom:0;
}
.cms-menu.collapsed .cms-subsites {
display: none;
}
/* Custom chzn styles for dark blue background */
.cms-subsites .chzn-container-single .chzn-single,
.cms-subsites .chzn-container-active .chzn-single {
filter: none; /* Fix for IE9 */
border: 1px solid #152338;
background:#213557;
-webkit-box-shadow: inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
box-shadow: inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
}
.cms-menu .cms-subsites .dropdown span{
padding-left:5px;
}
.cms-subsites .chzn-container-single .chzn-single div b{
background: url(../images/chosen-sprite-light.png) 3px 0 no-repeat;
}
.cms-subsites .chzn-container .chzn-drop{
padding-left:5px;
background:#213557;
border: 1px solid #152338;
border-top:0;
color:#fff;
-webkit-box-shadow: inset 1px 0 0 rgba(255,255,255,.125);
box-shadow: inset 1px 0 0 rgba(255,255,255,.125);
}
#AddSubsiteLink {
display: block;
font-size: 80%;
margin-left: 3px;
}
#Form_AddSubsiteForm .field {
margin-left: 100px;
}
#Form_AddSubsiteForm label.left {
float: left;
width: 100px;
margin-left: -100px;
}
body.SubsiteAdmin .right form #URL .fieldgroup * {
font-size: 11px;
}
.cms-add-form #PageType li .class-SubsitesVirtualPage, .class-SubsitesVirtualPage a .jstree-pageicon {
background-position: 0 -32px !important;
}
.subsites-move-dropdown{
display:none;
}
#Root_DetailsView .subsites-move-dropdown{
display:block;
}

View File

@ -1,87 +1,9 @@
## Introduction
# Subsites
Subsites is a module to allow you manage multiple related sites from a single CMS interface.
When Subsites is installed your existing site is defined as the main site, you will be then be able to create related
subsites under the main site.
So for example you may have an international presence and you want to create a subsite for a country where you do
business which is geared just for that market.
You could create a subsite for this and have all information related to that country kept under this subsite, you can
also set up a subdomain for this site.
One of the benefits of subsites is that it is easy to copy pages between the subsites and you have access to all of
the assets across all of the subsites.
Subsites is not for running unrelated websites on a single SilverStripe instance so if 2 sites have different vhosts
you will not be able to run them with Subsites on a single SilverStripe instance.
With Subsites you can set up users to have access to all subsites or just a selection of subsites.
## Common subsite uses
Subsites can be used for various different reasons here are some of the common ones
* Setting up a subsite for a small campaign so for example a clothing company may set up a summer or winter subsite to
market just that season of clothing.
* Locking down a particular subsite you may create a particular department like recruitment who would have access to
create and edit pages for their particular subsite but they would not be able to modify the main website.
* Running sub-domains on a single SilverStripe instance, with subsites if a sub-domain is pointing to the same instance
and has been setup correctly you can manage this via a single CMS instance.
* Subsites can not be used to run multiple websites on a single instance.
Subsites does not allow you to run multiple domains/vhosts on a single instance.
## Access
Access to certain subsites can be limited to administrators based on the groups they are in.
So for example if you had a couple of subsites you could create a group for each subsite and then specify that the
group had access to all subsites or just a specific subsites.
To access this functionality go to
Security -> Groups
![alt text](_images/subsite-admin-security-group.png "Groups")
Select the group you want to modify and then go to the Subsites tab
You can also limit the page types that are available for a subsite (but not the main site).
This can be done via accessing the particular subsite you want to amend via the Subsite admin section, underneath the
Subsite theme will be a link called 'Disallow page types?' clicking on that link will display a list of checkboxes for
all of the page types which can be selected to disable that page type for the subsite you are editing.
This is useful when you create a content editor and you do not want them to be able to add certain page types.
## Theme
A theme is group of templates, images and CSS for the look of a website.
When you are using Subsites you may have different themes installed for your site so you could apply different
themes for each subsite.
## Page types
Page types refer to the type of pages that can be set up on a site.
A page type will have certain features and functionality some examples on SilverStripe would be 'Page', 'HomePage'
and 'ErrorPage' these all differ to each other in what they would be used for so you would use Page for any pages
underneath the HomePage.
You would only have one HomePage for your site and you may have some logic to only allow you to create one of these
pages, ErrorPage would only be used for error pages and would be designed to be very minimal to work in situations
where the site is experiencing difficulties like no DB access.
You can set up a Subsite to only work with certain page types so you may have a page type with a contact form for a
particular department so you may set up a new subsite and not allow that page type to be used on a particular subsite.
You will not be able to filter Page Types for the main site.
## Assets
Assets are files that have been uploaded via the CMS.
It is suggested to use a naming convention for files designated to be used on a particular subsite or to create folders
for each subsite to help organise them.
[User guide](userguide/index.md)
## FAQ
### How can I restrict a content author to a particular subsite?
@ -126,3 +48,14 @@ to speak to your website administrator or hosting provider to facilitate this.
You can simulate subsite access without setting up virtual hosts by appending ?SubsiteID=<ID> to the request.
### How do Subsite domains work with Fluent domains?
The Subsites module and Fluent translation module both provide the concept of defining "domains" and let you
configure the host name for it. This functionality is essentially performing the same duty in both modules.
In the "URL segment" field for CMS pages, both Subsites and Fluent will add their context to the value. If you
have a Subsite domain configured but no Fluent domain, Fluent will respect the existing domain and add its
locale context to the value. If you have a Subsite domain configured and a Fluent domain configured, Fluent will
use its own domain host name value, and the Subsite domain value will be lost. For this reason, you will need
to ensure that you use the same host name in both Subsite and Fluent domain entries.

View File

@ -28,5 +28,5 @@ This method is called when a pages are being copied between the main site or ano
### alternateAbsoluteLink
This method modifies the absolute link to contain the valid subsite domain
### alternatePreviewLink
### updatePreviewLink
This method modifies the preview link for the CMS.

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 93 KiB

View File

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

View File

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View File

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View File

Before

Width:  |  Height:  |  Size: 153 KiB

After

Width:  |  Height:  |  Size: 153 KiB

View File

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 176 KiB

View File

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 128 KiB

View File

@ -0,0 +1,50 @@
---
title: Working with multiple websites
summary: Setting up and editing multiple websites using SilverStripe
---
# Working with multiple sites
## In this section:
* Understand subsites
* Learn how to create and delete subsites
* Learn how to manage subsite permissions
* Enable/Disable public access to subsites
* Learn how to create and use subsite templates
* Learn how to edit existing subsites
* Sharing content between the main site and subsites
## Before we begin:
* Make sure you have the SilverStripe [Subsites](http://addons.silverstripe.org/add-ons/silverstripe/subsites) module installed.
* Make sure you are in the "Subsites" section on the Navigation Tabs.
* Make sure you have full administrative rights on your site.
## Understanding subsites
Subsites is a module to allow you manage multiple related sites from a single CMS interface. Because all sites run on a single installation of SilverStripe, they can share users, content and assets. They can all use the same templates, or each use different ones.
When Subsites is installed your existing site is defined as the main site, you will be then be able to create related subsites under the main site.
So for example you may have an international presence and you want to create a subsite for a country where you do business which is geared just for that market. You could create a subsite for this and have all information related to that country kept under this subsite, you can also set up a subdomain for this site.
One of the benefits of subsites is that it is easy to copy pages between the subsites and you have access to all of the assets across all of the subsites.
Subsites is not for running unrelated websites on a single SilverStripe instance so if two sites have different vhosts you will not be able to run them with Subsites on a single SilverStripe instance.
With Subsites you can set up users to have access to all subsites or just a selection of subsites.
## Common subsite uses
Subsites can be used for various different reasons here are some of the common ones:
* Setting up a subsite for a small campaign so for example a clothing company may set up a summer or winter subsite to market just that season of clothing.
* Locking down a particular subsite you may create a particular department like recruitment who would have access to create and edit pages for their particular subsite but they would not be able to modify the main website.
* Running sub-domains on a single SilverStripe instance, with subsites if a sub-domain is pointing to the same instance and has been setup correctly you can manage this via a single CMS instance.
* Subsites can not be used to run multiple websites on a single instance. Subsites does not allow you to run multiple domains/vhosts on a single instance.
## Documentation
* [Set up](set_up.md)
* [Working with subsites](working_with.md)

View File

@ -1,45 +1,46 @@
---
title: Setting up
---
# Setting up
## Creating subsites
To view, edit and create subsites, go to the *Subsite* section of the CMS. Click *Search* to view a list of all
existing subsites.
To view, edit and create subsites, go to the *Subsite* section of the CMS. Click *Search* to view a list of all existing subsites.
![View subsites](_images/view-subsites.jpg)
Click on a subsite name to edit its details. Click the red X to delete a subsite (you will be asked for confirmation.)
To create a new subsite, click *Add Subsite*. This opens the *Subsite configuration* subsection.
Click on a subsite name to edit its details. To create a new subsite, click *Add Subsite*. This opens the *Subsite configuration* subsection.
## Deleting a subsite
Click the red X to delete a subsite (you will be asked for confirmation.)
## Subsite configuration
![Subsite configuration](_images/subsite-configuration.jpg)
You can configure the following details for a subsite:
* *Name of subsite:* This is the display name for the site in the CMS (not public-facing)
* *Domains for this subsite:* Lets you add one or more domains for this subsite, e.g., subsite.co.nz, subsite.org.nz,
subsite.com
* *Domains for this subsite:* Lets you add one or more domains for this subsite, e.g., subsite.co.nz, subsite.org.nz, subsite.com
* *Language:* Sets the language for the subsite. This affects the spellchecker (not the CMS interface language)
* *Default site:* If your site is accessed by a domain not listed in any subsites, this subsite is shown as default.
* *Enable public access:* Enables/disables the subsite. Corresponds to the *Active Subsite* column.
* *Theme:* Shows the list of available themes that exist in the themes directory. The subsite will use the templates
and styles from the selected theme.
* *Disallow page types:* Lets you mark some page types to prevent being used within this subsite.
* *Copy structure from:* Gives you the option to select an existing subsite from which to copy pages and files (see
"Copying subsites" for more information about this)
* *Theme:* Shows the list of available themes that exist in the themes directory. The subsite will use the templates and styles from the selected theme.
* *Disallow page types:* Lets you mark some page types to prevent them being used within this subsite (but not the main site). Clicking on that link will display a list of checkboxes for all of the page types which can be selected to disable that page type for the subsite you are editing. This is useful when you create a content editor and you do not want them to be able to add certain page types.
* *Copy structure from:* Gives you the option to select an existing subsite from which to copy pages and files (see "Copying subsites" for more information about this)
## Roles
When creating roles, you can assign the following subsite-specific permissions:
* *Access to 'Subsites' section:* Shows the *Subsite* section in the CMS, allowing you to manage subsites for your
site (ie, create, edit, view.)
* *Access to 'Subsites' section:* Shows the *Subsite* section in the CMS, allowing you to manage subsites for your site (ie, create, edit, view.)
* *Manage subsites for groups:* Ability to limit the permissions for a group to one or more subsites.
* *Manage assets for subsites:* Ability to select the subsite to which an asset folder belongs (also requires access to
*Files* section)
* *Manage assets for subsites:* Ability to select the subsite to which an asset folder belongs (also requires access to *Files* section)
## Groups
Groups can have access to all sites, or to one or more specific subsites. If you have different staff in charge of each
subsite, you probably want to create a separate group for each subsite.
Groups can have access to all sites, or to one or more specific subsites. If you have different staff in charge of each subsite, you probably want to [create](#creating-groups) a separate group for each subsite.
The dropdown in the upper left corner of the CMS indicates which subsite you are currently on.
@ -50,7 +51,19 @@ these subsites* to reveal a checklist of all available sites.
![Group subsites access](_images/group-subsites-access.png)
### Creating groups
Access to certain subsites can be limited to administrators based on the groups they are in.
So for example if you had a couple of subsites you could create a group for each subsite and then specify that the group had access to all subsites or just a specific subsites.
To access this functionality go to Security -> Groups
![Creating Groups](_images/subsite-admin-security-group.png "Groups")
Select the group you want to modify and then go to the Subsites tab
## Copying subsites
Duplicating subsites can be useful if you want to create several subsites based on the same general site structure. You can set up a collection of pages, files, and images and use it as a template. When you create a new subsite, instead of starting from scratch you can copy it all from your existing subsite. This will copy all pages, files and images from that subsite into your new subsite.
To create a new subsite template, create a new subsite described above under "Creating subsites" but don't add any domains. Add a name that will make it easy to see that it is a template. Select the new template from the subsites dropdown in the upper right and create the pages and add the files and images you'd like to become part of the
@ -60,7 +73,14 @@ When you create a new subsite, you can now choose to Copy structure from your te
![Copy subsite structure](_images/copy-structure.jpg)
## Disable particular page types from a subsite
## Page types
Page types refer to the type of pages that can be set up on a site. A page type will have certain features and functionality some examples on SilverStripe would be 'Page', 'HomePage' and 'ErrorPage' these all differ to each other in what they would be used for so you would use Page for any pages
underneath the HomePage.
You would only have one HomePage for your site and you may have some logic to only allow you to create one of these pages, ErrorPage would only be used for error pages and would be designed to be very minimal to work in situations where the site is experiencing difficulties (like no database access).
### Disable particular page types from a subsite
Sometimes, you will have two or more websites that are very similar, but have some small differences. For example, a head office and its 3 satellite offices may have 4 subsites, but only the head office site will have a "Company News" section on the site. In this instance, you can still use the subsites module, and use the 'Disallow page types' ability to remove certain page types from being created on subsites.
1. Create a new subsite as you normally would via the Subsites section in the CMS
@ -69,7 +89,7 @@ Sometimes, you will have two or more websites that are very similar, but have so
4. Select the page types that you wish to forbid from this subsite.
5. Click the Save button at the bottom of the section.
Note that this process is exactly the same when editing an existing subsite, you just select the subsite you want to remove page types from instead of creating a new subsite.
*Note:* This process is exactly the same when editing an existing subsite, you just select the subsite you want to remove page types from instead of creating a new subsite. Also you cannot not filter Page Types for the main site.
![Disallow page types screenshot](_images/disallow-page-types.png)
@ -79,4 +99,10 @@ Now, whenever someone wants to create a new page on the subsite (the 'London Bra
The page type blacklist applies only to future pages, not existing ones. If an administrator blacklists a defined page type on a subsite, existing pages of that type will remain on the subsite. The administrator (or another user) can later change it to a type that is not blacklisted after the page is published; however, once a user does that, that "grandfathered" pagetype will no longer be available to the published page.
For example, say a subsite user publishes a new Company Page before it was forbidden by an administrator. Later, a user with full administrative rights decides to disallow Company Pages from being created on the subsite. This restriction will only apply to all future pages that are published, but not to existing Company Pages. The full administrator (or any user with sufficient publishing rights) will have to manually convert any published Company Pages on the subsite to that of another allowed type.
For example, say a subsite user publishes a new Company Page before it was forbidden by an administrator. Later, a user with full administrative rights decides to disallow Company Pages from being created on the subsite. This restriction will only apply to all future pages that are published, but not to existing Company Pages. The full administrator (or any user with sufficient publishing rights) will have to manually convert any published Company Pages on the subsite to that of another allowed type.
## Themes
A theme is group of templates, images and CSS for the look of a website. When you are using Subsites you may have different themes installed for your site so you could apply different themes for each subsite.
## Assets
Assets are files that have been uploaded via the CMS. It is suggested to use a naming convention for files designated to be used on a particular subsite or to create folders for each subsite to help organise them.

View File

@ -1,12 +1,15 @@
---
title: Working with subsites
---
# Working with subsites
## Managing content across subsites
Select a subsite from the dropdown in the upper left to display the content for that site in the site tree.
To edit a particular subsite, choose the subsite from the dropdown menu in the left-hand menu.
![Subsites dropdown](_images/subsites-dropdown.png)
## Subsites virtual pages
### Subsites virtual pages
You can pull in the content from a page that resides on another subsite by creating a page of the type **Subsites
Virtual Page**. Pick the subsite from which you want to pull the content, then select the page. As with regular virtual
@ -15,7 +18,7 @@ the original content changes.
![Subsites virtual page](_images/subsites-virtual-page.jpg)
## Duplicating pages from the main site
### Duplicating pages from the main site
If you have an existing page on the main site that you would like to copy to a subsite, all you need to do is:
@ -25,4 +28,4 @@ If you have an existing page on the main site that you would like to copy to a s
![Subsites copy page from main site](_images/copy-page-to-subsite.jpg)
You will now be directed to the chosen subsite where the page will now be duplicated in the site tree
You will now be directed to the chosen subsite where the page will now be duplicated in the site tree

View File

@ -1,25 +0,0 @@
(function($) {
$.entwine('ss', function($) {
$('.TreeDropdownField').entwine({
subsiteID: function() {
var subsiteSel = $('#CopyContentFromID_SubsiteID select')[0];
if(!subsiteSel) return;
subsiteSel.onchange = (function() {
this.createTreeNode(true);
this.ajaxGetTree((function(response) {
this.newTreeReady(response, true);
this.updateTreeLabel();
}).bind(this));
}).bind(this);
return subsiteSel.options[subsiteSel.selectedIndex].value;
},
getRequestParams: function() {
var name = this.find(':input:hidden').attr('name'), obj = {};
obj[name + '_SubsiteID'] = parseInt(this.subsiteID());
return obj;
}
});
});
})(jQuery);

View File

@ -1,54 +1,68 @@
ar:
SubsiteAdmin:
MENUTITLE: مواقع فرعية
SubsiteXHRController:
MENUTITLE: المراقب XHR للموقع الفرعي
ASSETADMIN:
SUBSITENOTICE: 'يمكن الوصول إلى المجلدات والملفات التي تم إنشاؤها في الموقع الرئيسي من طرف كل المواقع الفرعية.'
FileSubsites:
SilverStripe\CMS\Model\VirtualPage:
EDITCONTENT: 'انقر هنا لتحرير المحتوى'
SilverStripe\Subsites\Admin\SubsiteAdmin:
MENUTITLE: 'المواقع الفرعية'
SilverStripe\Subsites\Extensions\FileSubsites:
has_one_Subsite: 'الموقع الفرعي'
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
AllSitesDropdownOpt: 'كافة المواقع'
SubsiteFieldLabel: الموقع الفرعي
GridFieldAddFromTemplateButton:
AddFromTemplate: 'اضف جديد من القالب'
GroupSubsites:
SUBSITENOTICE: 'يمكن الوصول إلى المجلدات والملفات التي تم إنشاؤها في الموقع الرئيسي من طرف كل المواقع الفرعية.'
SubsiteFieldLabel: 'الموقع الفرعي'
SilverStripe\Subsites\Extensions\GroupSubsites:
ACCESSALL: 'كل المواقع الفرعية'
ACCESSONLY: 'فقط هذه المواقع الفرعية'
ACCESSRADIOTITLE: 'امنح هذه المجموعة تصريح الولوج إلى'
GlobalGroup: 'المجموعة العامة'
MANAGE_SUBSITES: 'إدارة مواقع فرعية للمجموعات'
MANAGE_SUBSITES_HELP: 'القدرة على الحد من أذونات مجموعة ما على موقع فرعي واحدة أو أكثر.'
SECURITYTABTITLE: مواقع فرعية
LeftAndMainSubsites:
SECURITYTABTITLE: 'المواقع الفرعية'
many_many_Subsites: 'المواقع الفرعية'
SilverStripe\Subsites\Extensions\LeftAndMainSubsites:
Saved: 'تمّ الحفظ، يرجى تحديث الصفحات ذات الصلة.'
SiteTreeSubsites:
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
has_one_Subsite: 'الموقع الفرعي'
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
CopyAction: نسخ
CopyToSubsite: 'نسخ الصفحة في موقع فرعي'
Subsite:
COPYSTRUCTURE: 'نسخ الهيكل من:'
has_one_Subsite: 'الموقع الفرعي'
SilverStripe\Subsites\Model\Subsite:
ConfigurationTab: المواصفات
CopyMessage: 'إنشاء نسخة من {title}'
CustomExtraMeta: 'العلامات الوصفية المخصصة'
CustomExtraMeta: 'تخصيص Meta Tags'
CustomMetaDescription: الوصف
CustomMetaKeywords: كلمات البحث
CustomMetaKeywords: 'كلمات البحث'
CustomMetaTitle: عنوان
DOMAINSAVEFIRST: 'لا يمكنك إضافة النطاقات إلا بعد القيام بالحفظ لأول مرة'
DomainsHeadline: 'نطاقات هذا الموقع الفرعي'
DomainsListTitle: النطاقات
IsPublicHeaderField: 'موقع فرعي نشط'
NOTEMPLATE: 'بدون قالب'
PLURALNAME: المواقع الفرعية
PLURALNAME: 'المواقع الفرعية'
PageTypeBlacklistField: 'عدم السماح بأصناف الصفحات؟'
SINGULARNAME: الموقع الفرعي
SINGULARNAME: 'الموقع الفرعي'
SiteConfigSubtitle: 'هنا سطر الوصف الخاص بك'
SiteConfigTitle: 'اسم موقعك'
TabTitleConfig: المواصفات
ValidateTitle: 'الرجاء إضافة "عنوان"'
SubsiteDomain:
DOMAIN: نطاق
IS_PRIMARY: 'هو نطاق أساسي'
belongs_many_many_Groups: المجموعات
db_DefaultSite: 'الموقع الافتراضي'
db_Language: لغة
db_RedirectURL: 'إعادة توجيه عنوان موقع الويب'
db_Theme: المحور
db_Title: عنوان
has_many_Domains: النطاقات
SilverStripe\Subsites\Model\SubsiteDomain:
DOMAIN: النطاق
PLURALNAME: 'نطاقات موقع فرعي'
SINGULARNAME: 'نطاق موقع فرعي'
SubsiteReportWrapper:
db_Domain: النطاق
has_one_Subsite: 'الموقع الفرعي'
SilverStripe\Subsites\Pages\SubsitesVirtualPage:
DESCRIPTION: 'يعرض محتوى صفحة على موقع فرعي آخر'
PLURALNAME: 'قاعدة الصفحات'
SINGULARNAME: 'الصفحة الإفتراضية للمواقع الفرعية'
SubsiteField: 'الموقع الفرعي'
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
ReportDropdown: المواقع
ReportDropdownSubsite: 'الموقع الفرعي'
Subsite:
COPYSTRUCTURE: 'نسخ الهيكل من:'
NOTEMPLATE: 'بدون قالب'
Subsites:
DefaultSiteFieldLabel: 'الموقع الافتراضي'
DomainFieldLabel: النطاق
@ -60,8 +74,3 @@ ar:
RedirectURLFieldLabel: 'إعادة توجيه عنوان موقع الويب'
ThemeFieldLabel: المحور
TitleFieldLabel: 'اسم الموقع الفرعي'
SubsitesVirtualPage:
DESCRIPTION: 'يعرض محتوى صفحة على موقع فرعي آخر'
PLURALNAME: 'قاعدة الصفحات'
SINGULARNAME: 'الصفحة الإفتراضية للمواقع الفرعية'
SubsiteField: الموقع الفرعي

64
lang/cs.yml Normal file
View File

@ -0,0 +1,64 @@
cs:
LeftAndMain_Menu:
Hello: Ahoj
LOGOUT: 'Odhlásit se'
SilverStripe\Subsites\Admin\SubsiteAdmin:
MENUTITLE: Subsites
SilverStripe\Subsites\Extensions\GroupSubsites:
SECURITYTABTITLE: Subsites
many_many_Subsites: Subsites
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
CopyAction: Kopírovat
SilverStripe\Subsites\Model\Subsite:
ConfigurationTab: Konfigurace
CustomExtraMeta: 'Vlastní meta tagy'
CustomMetaDescription: Popis
CustomMetaKeywords: 'Klíčová slova'
CustomMetaTitle: Název
PLURALNAME: Subsites
SiteConfigSubtitle: 'Slogan Vašeho webu'
SiteConfigTitle: 'Název Vašeho webu'
ValidateTitle: 'Prosím vložte "Název"'
belongs_many_many_Groups: Skupiny
db_DefaultSite: 'Výchozí web'
db_Language: Jazyk
db_Theme: Téma
db_Title: Název
has_many_Domains: Domény
SilverStripe\Subsites\Model\SubsiteDomain:
DOMAIN: Doména
PLURALNAME: 'Domény webů'
SINGULARNAME: 'Doména webu'
db_Domain: Doména
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
ReportDropdown: Weby
SiteTreeSubsites:
CopyAction: Kopírovat
Subsite:
CustomExtraMeta: 'Vlastní meta tagy'
CustomMetaDescription: Popis
CustomMetaKeywords: 'Klíčová slova'
CustomMetaTitle: Název
DOMAINSAVEFIRST: 'Domény můžete přidávat až po uložení'
DomainsHeadline: 'Domény pro tento web'
DomainsListTitle: Domény
SiteConfigSubtitle: 'Slogan Vašeho webu'
SiteConfigTitle: 'Název Vašeho webu'
TabTitleConfig: Konfigurace
ValidateTitle: 'Prosím vložte "Název"'
SubsiteAdmin:
MENUTITLE: Subsites
SubsiteDomain:
DOMAIN: Doména
PLURALNAME: 'Domény webů'
SINGULARNAME: 'Doména webu'
SubsiteReportWrapper:
ReportDropdown: Weby
Subsites:
DefaultSiteFieldLabel: 'Výchozí web'
DomainFieldLabel: Doména
LanguageFieldLabel: Jazyk
ThemeFieldLabel: Téma
TitleFieldLabel: 'Název subsite'
SubsitesVirtualPage:
PLURALNAME: 'Základní stránky'

View File

@ -1,16 +1,17 @@
de:
SubsiteAdmin:
DomainNameField:
INVALID_DOMAIN: 'Ungültige Domain'
SilverStripe\CMS\Model\VirtualPage:
EDITCONTENT: 'Klicken Sie hier, um den Inhalt zu bearbeiten'
SilverStripe\Subsites\Admin\SubsiteAdmin:
MENUTITLE: Subsites
SubsiteXHRController:
MENUTITLE: SubsiteXHRController
ASSETADMIN:
SilverStripe\Subsites\Extensions\FileSubsites:
has_one_Subsite: Subseite
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
AllSitesDropdownOpt: 'Alle Seiten'
SUBSITENOTICE: 'Auf Ordner und Dateien der Hauptseite kann von allen Subsites zugegriffen werden.'
FileSubsites:
AllSitesDropdownOpt: 'Alle Subseiten'
SubsiteFieldLabel: Subseite
GridFieldAddFromTemplateButton:
AddFromTemplate: 'Neu hinzufügen von Template'
GroupSubsites:
SilverStripe\Subsites\Extensions\GroupSubsites:
ACCESSALL: 'Alle Subsites'
ACCESSONLY: 'Nur diese Subsites'
ACCESSRADIOTITLE: 'Dieser Gruppe Zugriff geben auf'
@ -18,50 +19,82 @@ de:
MANAGE_SUBSITES: 'Subseiten für jede Gruppe bearbeiten'
MANAGE_SUBSITES_HELP: 'Möglichkeit, die Berechtigungen einer Gruppe auf bestimmte Subsites zu beschränken.'
SECURITYTABTITLE: Subsites
LeftAndMainSubsites:
Saved: 'Gespeichert.'
SiteTreeSubsites:
many_many_Subsites: Subsites
SilverStripe\Subsites\Extensions\LeftAndMainSubsites:
SITECONTENTLEFT: 'Seiten Inhalt'
Saved: 'Gespeichert, bitte aktualisieren Sie verknüpfte Seiten'
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
has_one_Subsite: Subseite
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
CopyAction: Kopieren
CopyToSubsite: 'Seite auf Subseite Kopieren'
Subsite:
COPYSTRUCTURE: 'Struktur kopieren von:'
CopyToSubsiteWithChildren: 'Samt Unterseiten?'
SubsiteOperations: 'Subseiten Operationen'
has_one_Subsite: Subseite
SilverStripe\Subsites\Model\Subsite:
ConfigurationTab: Einstellungen
CopyMessage: 'Kopie von {title} erstellt'
CustomExtraMeta: 'Benutzerdefinierte Meta-Tags'
CustomMetaDescription: Beschreibung
CustomMetaKeywords: Schlüsselwörter
CustomMetaTitle: Titel
DOMAINSAVEFIRST: 'Domains können erst nach dem ersten Speichern hinzugefügt werden'
DomainsHeadline: 'Domains für diese Subsite'
DomainsListTitle: Domains
IsPublicHeaderField: 'Aktive Subsite'
NOTEMPLATE: 'Kein Template'
PLURALNAME: Subsites
PageTypeBlacklistField: 'Seitentyp verbieten?'
SINGULARNAME: Subsite
PLURALS:
one: 'Eine Subseite'
other: '{count} Subsites'
PageTypeBlacklistField: 'Seitentypen verbieten?'
SINGULARNAME: Subseite
SiteConfigSubtitle: 'Ihr Websiteslogan'
SiteConfigTitle: 'Name Ihrer Website'
TabTitleConfig: Einstellungen
ThemeFieldEmptyString: '-'
ValidateTitle: 'Bitte geben Sie einen Titel an'
SubsiteDomain:
DOMAIN: Domain
IS_PRIMARY: 'Ist primäre Domain'
PLURALNAME: 'Subsite Domains'
SINGULARNAME: 'Subsite Domain'
SubsiteReportWrapper:
belongs_many_many_Groups: Gruppe
db_DefaultSite: 'Standard Seite'
db_Language: Sprache
db_RedirectURL: Weierleitungs-URL
db_Theme: Theme
db_Title: Titel
has_many_Domains: Domains
SilverStripe\Subsites\Model\SubsiteDomain:
DOMAIN: Domäne
DOMAIN_DESCRIPTION: 'Hostname dieser Subsite (ohne Protokol). Joker (*) ist erlaubt.'
IS_PRIMARY: 'Ist Hauptdomäne?'
PLURALNAME: 'Domänen der Subseite'
PLURALS:
one: 'Eine Domäne der Subseite'
other: '{count} Domainen der Subseite'
PROTOCOL_AUTOMATIC: Automatisch
PROTOCOL_DESCRIPTION: 'DIes ist die Standarddomäne für diese Subseite'
PROTOCOL_HTTP: 'http://'
PROTOCOL_HTTPS: 'https://'
Protocol: Protokoll
SINGULARNAME: 'Domäne der Subseite'
db_Domain: Domäne
db_Protocol: Protokoll
has_one_Subsite: Subseite
SilverStripe\Subsites\Pages\SubsitesVirtualPage:
DESCRIPTION: 'Zeigt den Inhalt einer anderen Seite von einer anderen Subseite an'
OverrideNote: 'Überschreibt den geerbten Wert der Quelle'
PLURALNAME: 'Subsites Virtuelle Seiten'
PLURALS:
one: 'Eine Subsites Virtuelle Seite'
other: '{count} Subsites Virtuelle Seiten'
SINGULARNAME: 'Subsites Virtuelle Seite'
SubsiteField: Subseite
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
ReportDropdown: Seiten
ReportDropdownSubsite: Subseite
Subsite:
COPYSTRUCTURE: 'Struktur kopieren von:'
NOTEMPLATE: 'Kein Template'
Subsites:
DefaultSiteFieldLabel: 'Standard Seite'
DomainFieldLabel: Domäne
IsPublicFieldLabel: 'Öffentlich zugänglich'
LanguageFieldLabel: Sprache
MainSiteTitle: 'Hauptsite'
MainSiteTitle: Hauptsite
PageTypeBlacklistFieldLabel: 'Ausgeschlossene Seitentypen'
PrimaryDomainFieldLabel: 'Primäre Domain'
RedirectURLFieldLabel: 'Weierleitungs-URL'
RedirectURLFieldLabel: Weierleitungs-URL
ThemeFieldLabel: Theme
TitleFieldLabel: 'Name der Subsite'
SubsitesVirtualPage:
DESCRIPTION: 'Zeigt den Inhalt einer anderen Seite von einer anderen Subsite an'
PLURALNAME: 'Subsites Virtuelle Seiten'
SINGULARNAME: 'Subsites Virtuelle Seite'
SubsiteField: Subsite

View File

@ -1,16 +1,19 @@
en:
SubsiteAdmin:
DomainNameField:
INVALID_DOMAIN: 'Invalid domain name'
SilverStripe\CMS\Model\VirtualPage:
EDITCONTENT: 'Click here to edit the content'
SilverStripe\Subsites\Admin\SubsiteAdmin:
MENUTITLE: Subsites
SubsiteXHRController:
MENUTITLE: SubsiteXHRController
ASSETADMIN:
SUBSITENOTICE: 'Folders and files created in the main site are accessible by all subsites.'
FileSubsites:
SilverStripe\Subsites\Controller\SubsiteXHRController:
MENUTITLE: SilverStripe\Subsites\Controller\SubsiteXHRController
SilverStripe\Subsites\Extensions\FileSubsites:
has_one_Subsite: Subsite
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
AllSitesDropdownOpt: 'All sites'
SUBSITENOTICE: 'Folders and files created in the main site are accessible by all subsites.'
SubsiteFieldLabel: Subsite
GridFieldAddFromTemplateButton:
AddFromTemplate: 'Add New from Template'
GroupSubsites:
SilverStripe\Subsites\Extensions\GroupSubsites:
ACCESSALL: 'All subsites'
ACCESSONLY: 'Only these subsites'
ACCESSRADIOTITLE: 'Give this group access to'
@ -18,37 +21,85 @@ en:
MANAGE_SUBSITES: 'Manage subsites for groups'
MANAGE_SUBSITES_HELP: 'Ability to limit the permissions for a group to one or more subsites.'
SECURITYTABTITLE: Subsites
LeftAndMainSubsites:
db_AccessAllSubsites: 'Access all subsites'
many_many_Subsites: Subsites
SilverStripe\Subsites\Extensions\LeftAndMainSubsites:
SITECONTENTLEFT: 'Site Content'
Saved: 'Saved, please update related pages.'
SiteTreeSubsites:
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
has_one_Subsite: Subsite
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
CopyAction: Copy
CopyToSubsite: 'Copy page to subsite'
Subsite:
COPYSTRUCTURE: 'Copy structure from:'
CopyToSubsiteWithChildren: 'Include children pages?'
SubsiteOperations: 'Subsite Operations'
has_one_Subsite: Subsite
many_many_CrossSubsiteLinkTracking: 'Cross subsite link tracking'
SilverStripe\Subsites\Model\Subsite:
ConfigurationTab: Configuration
CopyMessage: 'Created a copy of {title}'
CustomExtraMeta: 'Custom Meta Tags'
CustomMetaDescription: Description
CustomMetaKeywords: Keywords
CustomMetaTitle: Title
DOMAINSAVEFIRST: 'You can only add domains after saving for the first time'
DomainsHeadline: 'Domains for this subsite'
DomainsListTitle: Domains
IsPublicHeaderField: 'Active subsite'
NOTEMPLATE: 'No template'
PLURALNAME: Subsites
PLURALS:
one: 'A Subsite'
other: '{count} Subsites'
PageTypeBlacklistField: 'Disallow page types?'
SINGULARNAME: Subsite
SiteConfigSubtitle: 'Your tagline here'
SiteConfigTitle: 'Your Site Name'
TabTitleConfig: Configuration
ThemeFieldEmptyString: '-'
ValidateTitle: 'Please add a "Title"'
SubsiteDomain:
belongs_many_many_Groups: Groups
db_DefaultSite: 'Default site'
db_IsPublic: 'Is public'
db_Language: Language
db_PageTypeBlacklist: 'Page type blacklist'
db_RedirectURL: 'Redirect URL'
db_Theme: Theme
db_Title: Title
has_many_Domains: Domains
SilverStripe\Subsites\Model\SubsiteDomain:
DOMAIN: Domain
IS_PRIMARY: 'Is Primary Domain'
DOMAIN_DESCRIPTION: 'Hostname of this subsite (exclude protocol). Allows wildcards (*).'
ISPRIMARY_DESCRIPTION: 'Mark this as the default domain for this subsite'
IS_PRIMARY: 'Is Primary Domain?'
PLURALNAME: 'Subsite Domains'
PLURALS:
one: 'A Subsite Domain'
other: '{count} Subsite Domains'
PROTOCOL_AUTOMATIC: Automatic
PROTOCOL_DESCRIPTION: 'When generating links to this subsite, use the selected protocol. <br />Selecting ''Automatic'' means subsite links will default to the current protocol.'
PROTOCOL_HTTP: 'http://'
PROTOCOL_HTTPS: 'https://'
Protocol: Protocol
SINGULARNAME: 'Subsite Domain'
SubsiteReportWrapper:
db_Domain: Domain
db_IsPrimary: 'Is primary'
db_Protocol: Protocol
has_one_Subsite: Subsite
SilverStripe\Subsites\Pages\SubsitesVirtualPage:
DESCRIPTION: 'Displays the content of a page on another subsite'
OverrideNote: 'Overrides inherited value from the source'
PLURALNAME: 'Subsites Virtual Pages'
PLURALS:
one: 'A Subsites Virtual Page'
other: '{count} Subsites Virtual Pages'
SINGULARNAME: 'Subsites Virtual Page'
SubsiteField: Subsite
db_CustomExtraMeta: 'Custom extra meta'
db_CustomMetaDescription: 'Custom meta description'
db_CustomMetaKeywords: 'Custom meta keywords'
db_CustomMetaTitle: 'Custom meta title'
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
ReportDropdown: Sites
ReportDropdownAll: All
ReportDropdownSubsite: Subsite
Subsite:
COPYSTRUCTURE: 'Copy structure from:'
NOTEMPLATE: 'No template'
Subsites:
DefaultSiteFieldLabel: 'Default site'
DomainFieldLabel: Domain
@ -60,8 +111,3 @@ en:
RedirectURLFieldLabel: 'Redirect URL'
ThemeFieldLabel: Theme
TitleFieldLabel: 'Subsite Name'
SubsitesVirtualPage:
DESCRIPTION: 'Displays the content of a page on another subsite'
PLURALNAME: 'Subsites Virtual Pages'
SINGULARNAME: 'Subsites Virtual Page'
SubsiteField: Subsite

View File

@ -1,54 +1,105 @@
eo:
SubsiteAdmin:
DomainNameField:
INVALID_DOMAIN: 'Nevalida domajna nomo'
SilverStripe\CMS\Model\VirtualPage:
EDITCONTENT: 'Alklaku ĉi tie por redakti la enhavon'
SilverStripe\Subsites\Admin\SubsiteAdmin:
MENUTITLE: Subretejoj
SubsiteXHRController:
MENUTITLE: SubsiteXHRController
ASSETADMIN:
SUBSITENOTICE: 'Dosierujoj kaj dosieroj kreitaj en la ĉefa retejo estas alireblaj de ĉiuj retejoj'
FileSubsites:
SilverStripe\Subsites\Controller\SubsiteXHRController:
MENUTITLE: SilverStripe\Subretejoj\Reganto\SubretejaXHRReganto
SilverStripe\Subsites\Extensions\FileSubsites:
has_one_Subsite: Subretejo
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
AllSitesDropdownOpt: 'Ĉiuj retejoj'
SUBSITENOTICE: 'Dosierujoj kaj dosieroj kreitaj en la ĉefa retejo estas alireblaj de ĉiuj retejoj'
SubsiteFieldLabel: Subretejo
GridFieldAddFromTemplateButton:
AddFromTemplate: 'Aldoni novan el ŝablono'
GroupSubsites:
SilverStripe\Subsites\Extensions\GroupSubsites:
ACCESSALL: 'Ĉiuj subretejoj'
ACCESSONLY: 'Nur ĉi tiuj subretejoj'
ACCESSRADIOTITLE: 'Doni ĉi tiun grupon aliron al'
ACCESSRADIOTITLE: 'Doni al ĉi tiu grupo aliron al'
GlobalGroup: 'ĉiea grupo'
MANAGE_SUBSITES: 'Administri subretejojn por grupoj'
MANAGE_SUBSITES_HELP: 'Eblo limigi la permesojn por grupo al unu aŭ pluaj subretejoj.'
SECURITYTABTITLE: Subsites
LeftAndMainSubsites:
SECURITYTABTITLE: Subretejoj
db_AccessAllSubsites: 'Aliri ĉiujn subretejojn'
many_many_Subsites: Subretejoj
SilverStripe\Subsites\Extensions\LeftAndMainSubsites:
SITECONTENTLEFT: 'Enhavo de retejo'
Saved: 'Konservita, bonvole ĝisdatigi rilatajn paĝojn.'
SiteTreeSubsites:
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
has_one_Subsite: Subretejo
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
CopyAction: Kopio
CopyToSubsite: 'Kopii paĝon al subretejo'
Subsite:
COPYSTRUCTURE: 'Kopii strukturon de:'
CopyToSubsiteWithChildren: 'Ĉu inkluzivi paĝidojn?'
SubsiteOperations: 'Subretejaj operacioj'
has_one_Subsite: Subretejo
many_many_CrossSubsiteLinkTracking: 'Trans-subreteja ligspurado'
SilverStripe\Subsites\Model\Subsite:
ConfigurationTab: Agordaro
CopyMessage: 'Kreis kopion de {title}'
CustomExtraMeta: 'Propraj meta-etikedoj'
CustomExtraMeta: 'Propraj meta-etikedoj '
CustomMetaDescription: Priskribo
CustomMetaKeywords: Ŝlosilvortoj
CustomMetaTitle: Titolo
DOMAINSAVEFIRST: 'Vi povas aldoni domajnojn post konservante unuafoje'
DomainsHeadline: 'Domajnoj por ĉi tiu subretejo'
DomainsListTitle: Domajnoj
IsPublicHeaderField: 'Aktiva subretejo'
NOTEMPLATE: 'Mankas ŝablono'
PLURALNAME: Subretejoj
PageTypeBlacklistField: 'Ĉu malpermesu paĝajn tipojn?'
PLURALS:
one: 'Unu subretejo'
other: '{count} subretejoj'
PageTypeBlacklistField: 'Ĉu malpermesi paĝajn tipojn?'
SINGULARNAME: Subretejo
SiteConfigSubtitle: 'Jen via slogano'
SiteConfigTitle: 'Nomo de via retejo'
TabTitleConfig: Agordaro
ValidateTitle: 'Bonvole aldonu "Titolon"'
SubsiteDomain:
ThemeFieldEmptyString: '-'
ValidateTitle: 'Bonvole aldoni "Titolon"'
belongs_many_many_Groups: Grupoj
db_DefaultSite: 'Apriora retejo'
db_IsPublic: 'Estas publika'
db_Language: Lingvo
db_PageTypeBlacklist: 'Paĝtipa nigra listo'
db_RedirectURL: 'Redirekti je URL'
db_Theme: Etoso
db_Title: Titolo
has_many_Domains: Domajnoj
SilverStripe\Subsites\Model\SubsiteDomain:
DOMAIN: Domajno
IS_PRIMARY: 'Estas unuaranga domajno'
DOMAIN_DESCRIPTION: 'Gastiga nomo de ĉi tiu subretejo (ellasu protokolon). Permesas ĵokerojn (*).'
ISPRIMARY_DESCRIPTION: 'Marki ĉi tion kiel la aprioran domajnon por ĉi tiu subretejo'
IS_PRIMARY: 'Ĉu unuaranga domajno?'
PLURALNAME: 'Subretejaj domajnoj'
PLURALS:
one: 'Unu subreteja domajno'
other: '{count} subretejaj domajnoj'
PROTOCOL_AUTOMATIC: Aŭtomata
PROTOCOL_DESCRIPTION: 'Kiam generante ligilojn al ĉi tiu subretejo, uzu la elektitan protokolon.'
PROTOCOL_HTTP: 'http://'
PROTOCOL_HTTPS: 'https://'
Protocol: Protokolo
SINGULARNAME: 'Subreteja domajno'
SubsiteReportWrapper:
db_Domain: Domajno
db_IsPrimary: 'Estas unuaranga'
db_Protocol: Protokolo
has_one_Subsite: Subretejo
SilverStripe\Subsites\Pages\SubsitesVirtualPage:
DESCRIPTION: 'Vidigas la enhavon de paĝo en alia subretejo'
OverrideNote: 'Anstataŭigas hereditan valoron el la fonto'
PLURALNAME: 'Virtualaj paĝoj de subretejoj'
PLURALS:
one: 'Unu virtuala paĝo de subretejoj'
other: '{count} virtualaj paĝoj de subretejoj'
SINGULARNAME: 'Virtuala paĝo de subretejoj'
SubsiteField: Subretejo
db_CustomExtraMeta: 'Propra kroma meta'
db_CustomMetaDescription: 'Propra metapriskribo'
db_CustomMetaKeywords: 'Propraj meta-ŝlosilvortoj '
db_CustomMetaTitle: 'Propraj meta-titolo'
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
ReportDropdown: Retejoj
ReportDropdownAll: Ĉiuj
ReportDropdownSubsite: Subretejo
Subsite:
COPYSTRUCTURE: 'Kopii strukturon de:'
NOTEMPLATE: 'Mankas ŝablono'
Subsites:
DefaultSiteFieldLabel: 'Apriora retejo'
DomainFieldLabel: Domajno
@ -60,8 +111,3 @@ eo:
RedirectURLFieldLabel: 'Redirekti je URL'
ThemeFieldLabel: Etoso
TitleFieldLabel: 'Nomo de subretejo'
SubsitesVirtualPage:
DESCRIPTION: 'Vidigas la enhavon de paĝo en alia subretejo'
PLURALNAME: 'Virtualaj paĝoj de subretejoj'
SINGULARNAME: 'Virtuala paĝo de subretejoj'
SubsiteField: Subretejo

13
lang/es.yml Normal file
View File

@ -0,0 +1,13 @@
es:
SilverStripe\Subsites\Model\Subsite:
CustomExtraMeta: 'Meta Tags Personalizadas'
CustomMetaDescription: Descripción
CustomMetaTitle: Título
SiteConfigTitle: 'Nombre de tu Sitio'
belongs_many_many_Groups: Grupos
db_Language: Lenguaje
db_Theme: Tema
db_Title: Título
Subsites:
LanguageFieldLabel: Lenguaje
ThemeFieldLabel: Tema

13
lang/et_EE.yml Normal file
View File

@ -0,0 +1,13 @@
et_EE:
SilverStripe\Subsites\Model\Subsite:
CustomExtraMeta: 'Kohandatud metamärgendid'
CustomMetaDescription: Kirjeldus
CustomMetaTitle: Pealkiri
SiteConfigTitle: 'Teie saidi nimi'
belongs_many_many_Groups: Grupid
db_Language: Keel
db_Theme: Kujundus
db_Title: Pealkiri
Subsites:
LanguageFieldLabel: Keel
ThemeFieldLabel: Kujundus

59
lang/fa_IR.yml Normal file
View File

@ -0,0 +1,59 @@
fa_IR:
DomainNameField:
INVALID_DOMAIN: 'نام دامنه نامعتبر'
SilverStripe\CMS\Model\VirtualPage:
EDITCONTENT: 'برای ویرایش محتوا اینجا را کلیک کنید'
SilverStripe\Subsites\Admin\SubsiteAdmin:
MENUTITLE: 'زیر سایت ها'
SilverStripe\Subsites\Extensions\FileSubsites:
has_one_Subsite: 'زیر سایت'
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
AllSitesDropdownOpt: 'تمامی سایت ها'
SubsiteFieldLabel: 'زیر سایت'
SilverStripe\Subsites\Extensions\GroupSubsites:
ACCESSALL: 'تمامی زیر سایت ها'
ACCESSONLY: 'فقط این زیر سایت ها'
SECURITYTABTITLE: 'زیر سایت ها'
many_many_Subsites: 'زیر سایت ها'
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
has_one_Subsite: 'زیر سایت'
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
has_one_Subsite: 'زیر سایت'
SilverStripe\Subsites\Model\Subsite:
ConfigurationTab: پیکربندی
CustomExtraMeta: 'متا تگ‌های اختصاصی'
CustomMetaDescription: توضحیات
CustomMetaKeywords: 'کلید واژه ها'
CustomMetaTitle: عنوان
PLURALNAME: 'زیر سایت ها'
SINGULARNAME: 'زیر سایت'
SiteConfigTitle: 'نام سایت شما'
belongs_many_many_Groups: گروه‌ها
db_DefaultSite: 'سایت پیش فرض'
db_Language: زبان
db_Theme: پوسته
db_Title: عنوان
has_many_Domains: 'دامنه ها'
SilverStripe\Subsites\Model\SubsiteDomain:
DOMAIN: دامنه
PLURALNAME: 'دامنه های زیر سایت'
PROTOCOL_AUTOMATIC: 'به صورت خودکار'
Protocol: پروتکل
SINGULARNAME: 'دمنه زیرسایت'
db_Domain: دامنه
db_Protocol: پروتکل
has_one_Subsite: 'زیر سایت'
SilverStripe\Subsites\Pages\SubsitesVirtualPage:
SubsiteField: 'زیر سایت'
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
ReportDropdown: 'سایت ها'
ReportDropdownSubsite: 'زیر سایت'
Subsite:
NOTEMPLATE: 'بدون قالب'
Subsites:
DefaultSiteFieldLabel: 'سایت پیش فرض'
DomainFieldLabel: دامنه
LanguageFieldLabel: زبان
PrimaryDomainFieldLabel: 'دامنه اولیه'
ThemeFieldLabel: پوسته
TitleFieldLabel: 'نام زیر سایت'

View File

@ -1,67 +1,92 @@
fi:
SubsiteAdmin:
DomainNameField:
INVALID_DOMAIN: 'Virheellinen domain-nimi'
SilverStripe\CMS\Model\VirtualPage:
EDITCONTENT: 'Napsauta tässä muokataksesi sisältöä'
SilverStripe\Subsites\Admin\SubsiteAdmin:
MENUTITLE: Alasivustot
SubsiteXHRController:
MENUTITLE: SubsiteXHRController
ASSETADMIN:
SUBSITENOTICE: 'Kansiot ja tiedostot, jotka on luotu pääsivustolla, ovat käytettävissä kaikissa alisivustoissa.'
FileSubsites:
SilverStripe\Subsites\Extensions\FileSubsites:
has_one_Subsite: Alasivusto
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
AllSitesDropdownOpt: 'Kaikki sivustot'
SubsiteFieldLabel: Alisivusto
GridFieldAddFromTemplateButton:
AddFromTemplate: 'Lisää uusi sivupohjasta'
GroupSubsites:
SUBSITENOTICE: 'Kansiot ja tiedostot, jotka on luotu pääsivustolla, ovat käytettävissä kaikissa alisivustoissa.'
SubsiteFieldLabel: Alasivusto
SilverStripe\Subsites\Extensions\GroupSubsites:
ACCESSALL: 'Kaikki alasivustot'
ACCESSONLY: 'Vain nämä alasivustot'
ACCESSRADIOTITLE: 'Anna tälle ryhmälle pääsy kohteeseen'
GlobalGroup: 'Globaaliryhmä'
MANAGE_SUBSITES: 'Hallinnoi ryhmien alisivustoja'
ACCESSRADIOTITLE: 'Anna ryhmän oikeudet »'
GlobalGroup: Globaaliryhmä
MANAGE_SUBSITES: 'Hallinnoi ryhmien alasivustoja'
MANAGE_SUBSITES_HELP: 'Mahdollisuus rajoittaa ryhmän oikeuksia yhdelle tai useammalle alisivustolle.'
SECURITYTABTITLE: Alisivustot
LeftAndMainSubsites:
SECURITYTABTITLE: Alasivustot
many_many_Subsites: Alasivut
SilverStripe\Subsites\Extensions\LeftAndMainSubsites:
SITECONTENTLEFT: 'Sivuston sisältö'
Saved: 'Tallennettu, ole hyvä ja päivitä liittyvät sivut.'
SiteTreeSubsites:
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
has_one_Subsite: Alasivusto
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
CopyAction: Kopioi
CopyToSubsite: 'Kopioi sivu alisivustolle'
Subsite:
COPYSTRUCTURE: 'Kopioi rakenne kohteesta:'
CopyToSubsite: 'Kopioi sivu alasivustolle'
CopyToSubsiteWithChildren: 'Sisällytä alasivut?'
SubsiteOperations: 'Alasivuston toiminnot'
has_one_Subsite: Alasivusto
SilverStripe\Subsites\Model\Subsite:
ConfigurationTab: Asetukset
CopyMessage: 'Luotiin kopio lähteestä {title}'
CustomExtraMeta: 'Omat Meta-tagit'
CustomExtraMeta: 'Omat meta-tagit'
CustomMetaDescription: Kuvaus
CustomMetaKeywords: Avainsanat
CustomMetaTitle: Otsikko
DOMAINSAVEFIRST: 'Voit lisätä domain-osoitteita vasta tallentamisen jälkeen'
DomainsHeadline: 'Tämän alisivuston domainit'
DomainsListTitle: Domainit
IsPublicHeaderField: 'Aktiivinen alisivusto'
NOTEMPLATE: 'Ei sivupohjaa'
PLURALNAME: Alasivustot
PLURALNAME: Alasivut
PLURALS:
one: Alasivu
other: '{count} alasivua'
PageTypeBlacklistField: 'Kiellä sivutyyppien käyttö?'
SINGULARNAME: Alisivusto
SiteConfigSubtitle: 'Iskulauseesi'
SINGULARNAME: Alasivusto
SiteConfigSubtitle: 'Tähän sloganisi'
SiteConfigTitle: 'Sivuston nimi'
TabTitleConfig: Asetukset
ValidateTitle: 'Lisää "Otsikko"'
SubsiteDomain:
DOMAIN: Domain
IS_PRIMARY: 'On päädomain'
belongs_many_many_Groups: Ryhmät
db_DefaultSite: Oletussivusto
db_Language: Kieli
db_RedirectURL: 'Edelleenohjaus URL'
db_Theme: Teema
db_Title: Otsikko
has_many_Domains: Domainit
SilverStripe\Subsites\Model\SubsiteDomain:
DOMAIN: Verkkotunnus
DOMAIN_DESCRIPTION: 'Tämä alisivuston isäntänimi (ilman protokollaa). Sallii wildcard-merkit (*).'
ISPRIMARY_DESCRIPTION: 'Merkitse tämä oletusdomainiksi tälle alisivustolle'
IS_PRIMARY: 'On päädomain?'
PLURALNAME: 'Alisivuston domain-osoitteet'
PROTOCOL_AUTOMATIC: Automaattinen
PROTOCOL_HTTP: 'http://'
PROTOCOL_HTTPS: 'https://'
Protocol: Protokolla
SINGULARNAME: 'Alisivuston domain-osoite'
SubsiteReportWrapper:
ReportDropdown: Sivustot
Subsites:
DefaultSiteFieldLabel: 'Oletussivusto'
DomainFieldLabel: Domain
IsPublicFieldLabel: 'Aktivoi julkinen pääsy'
LanguageFieldLabel: Kieli
MainSiteTitle: 'Pääsivusto'
PageTypeBlacklistFieldLabel: 'Sivutyyppien mustalista'
PrimaryDomainFieldLabel: 'Oletusdomain'
RedirectURLFieldLabel: 'Edelleenohjaus URL'
ThemeFieldLabel: Teema
TitleFieldLabel: 'Alisivuston nimi'
SubsitesVirtualPage:
db_Domain: Verkkotunnus
db_Protocol: Protokolla
has_one_Subsite: Alasivusto
SilverStripe\Subsites\Pages\SubsitesVirtualPage:
DESCRIPTION: 'Näyttää sisällön toisen alisivuston sivulta'
PLURALNAME: 'Alisivustojen virtuaaliset sivut'
SINGULARNAME: 'Alisivuston Virtuaalisivu'
SubsiteField: Alisivu
SubsiteField: Alasivusto
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
ReportDropdown: Sivustot
ReportDropdownSubsite: Alasivusto
Subsite:
COPYSTRUCTURE: 'Kopioi rakenne kohteesta:'
NOTEMPLATE: 'Ei sivupohjaa'
Subsites:
DefaultSiteFieldLabel: Oletussivusto
DomainFieldLabel: Domain
IsPublicFieldLabel: 'Aktivoi julkinen pääsy'
LanguageFieldLabel: Kieli
MainSiteTitle: Pääsivusto
PageTypeBlacklistFieldLabel: 'Sivutyyppien mustalista'
PrimaryDomainFieldLabel: Oletusdomain
RedirectURLFieldLabel: 'Edelleenohjaus URL'
ThemeFieldLabel: Teema
TitleFieldLabel: 'Alisivuston nimi'

14
lang/fr.yml Normal file
View File

@ -0,0 +1,14 @@
fr:
SilverStripe\Subsites\Model\Subsite:
ConfigurationTab: Configuration
CustomExtraMeta: 'Balises Méta personnalisées'
CustomMetaDescription: Description
CustomMetaTitle: Titre
SiteConfigTitle: 'Nom du site'
belongs_many_many_Groups: Groupes
db_Language: Langue
db_Theme: Thème
db_Title: Titre
Subsites:
LanguageFieldLabel: Langue
ThemeFieldLabel: Thème

87
lang/hr.yml Normal file
View File

@ -0,0 +1,87 @@
hr:
DomainNameField:
INVALID_DOMAIN: 'Netočan naziv domene'
SilverStripe\CMS\Model\VirtualPage:
EDITCONTENT: 'Klikni ovdje za uređivanje sadržaja'
SilverStripe\Subsites\Admin\SubsiteAdmin:
MENUTITLE: Podsajtovi
SilverStripe\Subsites\Extensions\FileSubsites:
has_one_Subsite: Podsajt
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
AllSitesDropdownOpt: 'Svi sajtovi'
SUBSITENOTICE: 'Direktoriji i datoteke kreirane u glavnom sajtu su dostupne svim podsajtovima.'
SubsiteFieldLabel: Podsajt
SilverStripe\Subsites\Extensions\GroupSubsites:
ACCESSALL: 'Svi podsajtovi'
ACCESSONLY: 'Samo ovi podsajtovi'
ACCESSRADIOTITLE: 'Dodijeli ovoj grupi pristup za'
GlobalGroup: 'globalna grupa'
MANAGE_SUBSITES: 'Upravljaj podsajtove za grupe'
MANAGE_SUBSITES_HELP: 'Mogućnost limitiranja prava za grupu za jedan ili više podsajtova.'
SECURITYTABTITLE: Podsajtovi
many_many_Subsites: Podsajtovi
SilverStripe\Subsites\Extensions\LeftAndMainSubsites:
Saved: 'Spremljeno, molimo osvježite povezane stranice.'
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
has_one_Subsite: Podsajt
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
CopyAction: Kopiraj
CopyToSubsite: 'Kopiraj stranicu u podsajt'
CopyToSubsiteWithChildren: 'Uključi podstranice?'
SubsiteOperations: 'Operacije podređenog prostora'
has_one_Subsite: Podsajt
SilverStripe\Subsites\Model\Subsite:
ConfigurationTab: Konfiguracija
CopyMessage: 'Kreirana kopija od {title}'
CustomExtraMeta: 'Prilagođeni Meta tagovi'
CustomMetaDescription: Opis
CustomMetaKeywords: 'Ključne riječi'
CustomMetaTitle: Naslov
PLURALNAME: Podsajtovi
PageTypeBlacklistField: 'Ne dopuštaj tipove stranica?'
SINGULARNAME: Podsajt
SiteConfigSubtitle: 'vaš slogan ovdje'
SiteConfigTitle: 'Naziv vašeg weba'
ValidateTitle: 'Molimo dodajte "Naslov"'
belongs_many_many_Groups: Grupe
db_DefaultSite: 'Zadani sajt'
db_Language: Jezik
db_RedirectURL: 'Link preusmjeravanja'
db_Theme: Tema
db_Title: Naslov
has_many_Domains: Domene
SilverStripe\Subsites\Model\SubsiteDomain:
DOMAIN: Domena
DOMAIN_DESCRIPTION: 'Hostname ovog podsajta (bez protokola). Omogućava wildcard (*).'
ISPRIMARY_DESCRIPTION: 'Označi kao zadanu domenu za ovu podstranicu'
IS_PRIMARY: 'Da li je glavna domena?'
PLURALNAME: 'Domene podsajtova'
PROTOCOL_AUTOMATIC: Automatsko
PROTOCOL_HTTP: 'http://'
PROTOCOL_HTTPS: 'https://'
Protocol: Protokol
SINGULARNAME: 'Domena podsajta'
db_Domain: Domena
db_Protocol: Protokol
has_one_Subsite: Podsajt
SilverStripe\Subsites\Pages\SubsitesVirtualPage:
DESCRIPTION: 'Prikazuje sadržaj stranice na drugom podsajtu'
SINGULARNAME: 'Virtualna stranica podsajta'
SubsiteField: Podsajt
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
ReportDropdown: Sajtovi
ReportDropdownSubsite: Podsajt
Subsite:
COPYSTRUCTURE: 'Kopiraj strukturu od:'
NOTEMPLATE: 'Nema predloška'
Subsites:
DefaultSiteFieldLabel: 'Zadani sajt'
DomainFieldLabel: Domena
IsPublicFieldLabel: 'Omogućni javni pristup'
LanguageFieldLabel: Jezik
MainSiteTitle: 'Glavni sajt'
PageTypeBlacklistFieldLabel: 'Crna lista tipova stranica'
PrimaryDomainFieldLabel: 'Glavna domena'
RedirectURLFieldLabel: 'Link preusmjeravanja'
ThemeFieldLabel: Tema
TitleFieldLabel: 'Naziv podsajta'

52
lang/id.yml Normal file
View File

@ -0,0 +1,52 @@
id:
FileSubsites:
SubsiteFieldLabel: Subsitus
GroupSubsites:
SECURITYTABTITLE: Subsitus
SilverStripe\Subsites\Admin\SubsiteAdmin:
MENUTITLE: Subsitus
SilverStripe\Subsites\Extensions\FileSubsites:
has_one_Subsite: Subsitus
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
SubsiteFieldLabel: Subsitus
SilverStripe\Subsites\Extensions\GroupSubsites:
SECURITYTABTITLE: Subsitus
many_many_Subsites: Subsitus
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
has_one_Subsite: Subsitus
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
has_one_Subsite: Subsitus
SilverStripe\Subsites\Model\Subsite:
CustomExtraMeta: 'Penanda Meta'
CustomMetaDescription: Deskripsi
CustomMetaKeywords: 'Kata kunci'
CustomMetaTitle: Judul
PLURALNAME: Subsitus
SINGULARNAME: Subsitus
SiteConfigTitle: 'Nama Situs'
belongs_many_many_Groups: Kelompok
db_Language: Bahasa
db_Theme: Tema
db_Title: Judul
SilverStripe\Subsites\Model\SubsiteDomain:
has_one_Subsite: Subsitus
SilverStripe\Subsites\Pages\SubsitesVirtualPage:
SubsiteField: Subsitus
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
ReportDropdown: Situs
ReportDropdownSubsite: Subsitus
Subsite:
CustomMetaDescription: Deskripsi
CustomMetaKeywords: 'Kata kunci'
CustomMetaTitle: Judul
PLURALNAME: Subsitus
SINGULARNAME: Subsitus
SubsiteAdmin:
MENUTITLE: Subsitus
SubsiteReportWrapper:
ReportDropdown: Situs
Subsites:
LanguageFieldLabel: Bahasa
ThemeFieldLabel: Tema
SubsitesVirtualPage:
SubsiteField: Subsitus

87
lang/it.yml Normal file
View File

@ -0,0 +1,87 @@
it:
DomainNameField:
INVALID_DOMAIN: 'Nome a dominio non valido'
SilverStripe\CMS\Model\VirtualPage:
EDITCONTENT: 'Clicca qui per editare il contenuto'
SilverStripe\Subsites\Admin\SubsiteAdmin:
MENUTITLE: Sottositi
SilverStripe\Subsites\Extensions\FileSubsites:
has_one_Subsite: Sottosito
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
AllSitesDropdownOpt: 'Tutti i siti'
SUBSITENOTICE: 'Le cartelle e i files creati nel sito principale sono accessibili da tutti i sottositi.'
SubsiteFieldLabel: Sottosito
SilverStripe\Subsites\Extensions\GroupSubsites:
ACCESSALL: 'Tutti i sottositi'
ACCESSONLY: 'Solo questi sottositi'
ACCESSRADIOTITLE: 'Dai a questo gruppo accesso a'
GlobalGroup: 'Gruppo globale'
MANAGE_SUBSITES: 'Gestisci sottositi per gruppi'
MANAGE_SUBSITES_HELP: 'Abilità di limitare i permessi per un gruppo di uno o più sottositi.'
SECURITYTABTITLE: Sottositi
many_many_Subsites: Sottositi
SilverStripe\Subsites\Extensions\LeftAndMainSubsites:
Saved: 'Salvato, si prega di aggiornare le pagine relative.'
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
has_one_Subsite: Sottosito
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
CopyAction: Copia
CopyToSubsite: 'Copia pagina in sottosito'
CopyToSubsiteWithChildren: 'Includi pagine figlie?'
SubsiteOperations: 'Operazioni sui sottositi'
has_one_Subsite: Sottosito
SilverStripe\Subsites\Model\Subsite:
ConfigurationTab: Configurazione
CopyMessage: 'Creata una copia di {title}'
CustomExtraMeta: 'Meta Tags personalizzati'
CustomMetaDescription: Descrizione
CustomMetaKeywords: Keywords
CustomMetaTitle: Titolo
PLURALNAME: Sottositi
PageTypeBlacklistField: 'Disabilita tipi di pagina?'
SINGULARNAME: Sottosito
SiteConfigSubtitle: 'Lo slogan qui'
SiteConfigTitle: 'Nome del sito'
ValidateTitle: 'Prego aggiungere il "Titolo"'
belongs_many_many_Groups: Gruppi
db_DefaultSite: 'Sito di default'
db_Language: Lingua
db_RedirectURL: 'URL di reindirizzamento'
db_Theme: Tema
db_Title: Titolo
has_many_Domains: Domini
SilverStripe\Subsites\Model\SubsiteDomain:
DOMAIN: Dominio
DOMAIN_DESCRIPTION: 'Nome host di questo sottosito (escluso il protocollo). Permette caratteri wildcard (*).'
ISPRIMARY_DESCRIPTION: 'Segna questo come il dominio di default per questo sottosito'
IS_PRIMARY: 'È il dominio primario?'
PLURALNAME: 'Domini del sottosito'
PROTOCOL_AUTOMATIC: Automatico
PROTOCOL_HTTP: 'http://'
PROTOCOL_HTTPS: 'https://'
Protocol: Protocollo
SINGULARNAME: 'Dominio del sottosito'
db_Domain: Dominio
db_Protocol: Protocollo
has_one_Subsite: Sottosito
SilverStripe\Subsites\Pages\SubsitesVirtualPage:
DESCRIPTION: 'Mostra il contenuto di una pagina appartenente ad un altro sottosito'
SINGULARNAME: 'Pagina virtuale dei sottositi'
SubsiteField: Sottosito
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
ReportDropdown: Siti
ReportDropdownSubsite: Sottosito
Subsite:
COPYSTRUCTURE: 'Copia struttura da:'
NOTEMPLATE: 'Nessuno schema'
Subsites:
DefaultSiteFieldLabel: 'Sito di default'
DomainFieldLabel: Dominio
IsPublicFieldLabel: 'Permetti accesso pubblico'
LanguageFieldLabel: Lingua
MainSiteTitle: 'Sito principale'
PageTypeBlacklistFieldLabel: 'Blacklist dei tipi di pagina'
PrimaryDomainFieldLabel: 'Dominio primario'
RedirectURLFieldLabel: 'URL di reindirizzamento'
ThemeFieldLabel: Tema
TitleFieldLabel: 'Nome sottosito'

View File

@ -1,17 +1,60 @@
ja:
GridFieldAddFromTemplateButton:
AddFromTemplate: テンプレートから新しく追加
GroupSubsites:
ACCESSALL: 全てのサブサイト
ACCESSONLY: これらのサブサイトのみ
ACCESSRADIOTITLE: このグループに選択先へのアクセス権を与える
SECURITYTABTITLE: サブサイト
SilverStripe\CMS\Model\VirtualPage:
EDITCONTENT: ここをクリックしてコンテンツを編集
SilverStripe\Subsites\Admin\SubsiteAdmin:
MENUTITLE: サブサイト
SilverStripe\Subsites\Extensions\FileSubsites:
has_one_Subsite: サブサイト
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
SubsiteFieldLabel: サブサイト
SilverStripe\Subsites\Extensions\GroupSubsites:
ACCESSALL: 全てのサブサイト
ACCESSONLY: これらのサブサイトのみ
ACCESSRADIOTITLE: このグループに選択先へのアクセス権を与える
SECURITYTABTITLE: サブサイト
many_many_Subsites: サブサイト
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
has_one_Subsite: サブサイト
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
has_one_Subsite: サブサイト
SilverStripe\Subsites\Model\Subsite:
CustomExtraMeta: メタタグをカスタム
CustomMetaDescription: 説明文
CustomMetaTitle: タイトル
PLURALNAME: サブサイト
SINGULARNAME: サブサイト
SiteConfigTitle: サイト名
belongs_many_many_Groups: グループ
db_Language: 言語
db_Theme: テーマ
db_Title: タイトル
SilverStripe\Subsites\Model\SubsiteDomain:
DOMAIN: ドメイン
PLURALNAME: サブサイトのドメイン
SINGULARNAME: サブサイトのドメイン
db_Domain: ドメイン
has_one_Subsite: サブサイト
SilverStripe\Subsites\Pages\SubsitesVirtualPage:
SINGULARNAME: サブサイトの仮想ページ
SubsiteField: サブサイト
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
ReportDropdownSubsite: サブサイト
SubsiteAdmin:
MENUTITLE: サブサイト
GridFieldAddFromTemplateButton:
AddFromTemplate: 'テンプレートから新しく追加'
GroupSubsites:
ACCESSALL: '全てのサブサイト'
ACCESSONLY: 'これらのサブサイトのみ'
ACCESSRADIOTITLE: 'このグループに選択先へのアクセス権を与える'
SECURITYTABTITLE: サブサイト
SubsiteDomain:
DOMAIN: ドメイン
IS_PRIMARY: 'プライマリドメイン'
PLURALNAME: 'サブサイトのドメイン'
SINGULARNAME: 'サブサイトのドメイン'
PLURALNAME: サブサイトのドメイン
SINGULARNAME: サブサイトのドメイン
Subsites:
DomainFieldLabel: ドメイン
LanguageFieldLabel: 言語
ThemeFieldLabel: テーマ
SubsitesVirtualPage:
SINGULARNAME: 'サブサイトの仮想ページ'
SINGULARNAME: サブサイトの仮想ページ

43
lang/lt.yml Normal file
View File

@ -0,0 +1,43 @@
lt:
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
AllSitesDropdownOpt: 'Visos svetainės'
SilverStripe\Subsites\Extensions\LeftAndMainSubsites:
Saved: 'Išsaugota, prašome atnaujinti susijusius puslapius'
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
CopyAction: Kopijuoti
CopyToSubsite: 'Kopijuoti puslapį į kitą svetainę'
SilverStripe\Subsites\Model\Subsite:
ConfigurationTab: Nustatymai
CopyMessage: 'Sukurta {title} kopija'
CustomExtraMeta: 'Kitos meta žymės'
CustomMetaDescription: Aprašymas
CustomMetaKeywords: Raktažodžiai
CustomMetaTitle: Pavadinimas
PageTypeBlacklistField: 'Neleidžiami puslapių tipai'
SiteConfigSubtitle: 'Jūsų svetainės šūkis'
SiteConfigTitle: 'Jūsų svetainės pavadinimas'
ValidateTitle: 'Prašome įvesti "Pavadinimą"'
belongs_many_many_Groups: Grupės
db_DefaultSite: 'Pagrindinė svetainė'
db_Language: Kalba
db_RedirectURL: 'Nukreipimo nuoroda'
db_Theme: Tema
db_Title: Pavadinimas
has_many_Domains: Domenai
SilverStripe\Subsites\Model\SubsiteDomain:
DOMAIN: Domenas
db_Domain: Domenas
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
ReportDropdown: Svetainės
Subsite:
COPYSTRUCTURE: 'Kopijuoti struktūrą iš:'
NOTEMPLATE: 'Nėra šablono'
Subsites:
DefaultSiteFieldLabel: 'Pagrindinė svetainė'
DomainFieldLabel: Domenas
IsPublicFieldLabel: 'Leisti pasiekti visiems'
LanguageFieldLabel: Kalba
MainSiteTitle: 'Pagrindinis puslapis'
PrimaryDomainFieldLabel: 'Pagrindinis domenas'
RedirectURLFieldLabel: 'Nukreipimo nuoroda'
ThemeFieldLabel: Tema

View File

@ -1,54 +1,68 @@
mi:
SubsiteAdmin:
MENUTITLE: Ngā pae iti
SubsiteXHRController:
MENUTITLE: ManaXHRPaeiti
ASSETADMIN:
SUBSITENOTICE: 'Ka taea ngā kōpaki me ngā kōnae kua hangaia i te pae matua te uru mā ngā pae iti katoa.'
FileSubsites:
SilverStripe\CMS\Model\VirtualPage:
EDITCONTENT: 'Pāwhiri ki konei hei whakatika i ngā ihirangi'
SilverStripe\Subsites\Admin\SubsiteAdmin:
MENUTITLE: 'Ngā pae iti'
SilverStripe\Subsites\Extensions\FileSubsites:
has_one_Subsite: 'Pae iti'
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
AllSitesDropdownOpt: 'Ngā pae katoa'
SubsiteFieldLabel: Pae iti
GridFieldAddFromTemplateButton:
AddFromTemplate: 'Tāpiri Hōu mai i te Tātauira'
GroupSubsites:
SUBSITENOTICE: 'Ka taea ngā kōpaki me ngā kōnae kua hangaia i te pae matua te uru mā ngā pae iti katoa.'
SubsiteFieldLabel: 'Pae iti'
SilverStripe\Subsites\Extensions\GroupSubsites:
ACCESSALL: 'Ngā pae iti katoa'
ACCESSONLY: 'Ko ēnei pae iti anake'
ACCESSRADIOTITLE: 'Tukuna tēnei rōpū kia uru ki'
GlobalGroup: 'Rōpū Hurinoa'
MANAGE_SUBSITES: 'Whakahaere pae iti mō ngā rōpū'
MANAGE_SUBSITES_HELP: 'Te āheinga ki te whakawhāiti whakaaetanga mō tētahi rōpū ki tētahi neke atu rānei o ngā pae iti.'
SECURITYTABTITLE: Ngā pae iti
LeftAndMainSubsites:
SECURITYTABTITLE: 'Ngā pae iti'
many_many_Subsites: 'Ngā pae iti'
SilverStripe\Subsites\Extensions\LeftAndMainSubsites:
Saved: 'Kua tiakina, whakahoutia ngā whārangi pāhono.'
SiteTreeSubsites:
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
has_one_Subsite: 'Pae iti'
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
CopyAction: Tārua
CopyToSubsite: 'Tāruatia te whārangi ki te pae iti'
Subsite:
COPYSTRUCTURE: 'Tāurutia te hanganga mai i:'
has_one_Subsite: 'Pae iti'
SilverStripe\Subsites\Model\Subsite:
ConfigurationTab: Whirihoranga
CopyMessage: 'I hangaia he tārua o {title}'
CustomExtraMeta: 'Ngā Tūtohu Meta Ritenga'
CustomMetaDescription: Whakaahuatanga
CustomMetaKeywords: Ngā kupumatua
CustomMetaKeywords: 'Ngā kupumatua'
CustomMetaTitle: Taitara
DOMAINSAVEFIRST: 'Ka taea noa iho te tāpiri rohe i muri i te tiakinga tuatahitanga'
DomainsHeadline: 'Ngā rohe mō tēnei pae iti'
DomainsListTitle: Ngā Rohe
IsPublicHeaderField: 'Pae iti hohe'
NOTEMPLATE: 'Kāore he tātauira'
PLURALNAME: Ngā pae iti
PLURALNAME: 'Ngā pae iti'
PageTypeBlacklistField: 'Me whakakāhore ngā momo whārangi?'
SINGULARNAME: Pae iti
SINGULARNAME: 'Pae iti'
SiteConfigSubtitle: 'Tō rārangi tautuhinga ki konei'
SiteConfigTitle: 'Tō Ingoa Pae'
TabTitleConfig: Whirihoranga
ValidateTitle: 'Tāurua he "Taitara"'
SubsiteDomain:
belongs_many_many_Groups: 'Ngā Rōpū'
db_DefaultSite: 'Pae taunoa'
db_Language: Reo
db_RedirectURL: 'Tukua anō te URL'
db_Theme: Kaupapa
db_Title: Taitara
has_many_Domains: 'Ngā Rohe'
SilverStripe\Subsites\Model\SubsiteDomain:
DOMAIN: Rohe
IS_PRIMARY: 'Ko Te Rohe Matua'
PLURALNAME: 'Ngā Rohe Pae Iti'
SINGULARNAME: 'Rohe Pae Iti'
SubsiteReportWrapper:
ReportDropdown: Ngā Pae
db_Domain: Rohe
has_one_Subsite: 'Pae iti'
SilverStripe\Subsites\Pages\SubsitesVirtualPage:
DESCRIPTION: 'Ka whakaatu i ngā ihirangi o tētahi whārangi ki tētahi atu pae iti'
PLURALNAME: 'Ngā Whārangi Taketake'
SINGULARNAME: 'Whārangi Mariko Pae Iti'
SubsiteField: 'Pae iti'
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
ReportDropdown: 'Ngā Pae'
ReportDropdownSubsite: 'Pae iti'
Subsite:
COPYSTRUCTURE: 'Tāurutia te hanganga mai i:'
NOTEMPLATE: 'Kāore he tātauira'
Subsites:
DefaultSiteFieldLabel: 'Pae taunoa'
DomainFieldLabel: Rohe
@ -60,8 +74,3 @@ mi:
RedirectURLFieldLabel: 'Tukua anō te URL'
ThemeFieldLabel: Kaupapa
TitleFieldLabel: 'Ingoa Pae Iti'
SubsitesVirtualPage:
DESCRIPTION: 'Ka whakaatu i ngā ihirangi o tētahi whārangi ki tētahi atu pae iti'
PLURALNAME: 'Ngā Whārangi Taketake'
SINGULARNAME: 'Whārangi Mariko Pae Iti'
SubsiteField: Pae iti

View File

@ -1,6 +1,4 @@
nb_NO:
SubsiteAdmin:
MENUTITLE: Underdomener
GridFieldAddFromTemplateButton:
AddFromTemplate: 'Add New from Template'
GroupSubsites:
@ -8,10 +6,46 @@ nb_NO:
ACCESSONLY: 'Only these subsites'
ACCESSRADIOTITLE: 'Give this group access to'
SECURITYTABTITLE: subdomener
SubsiteDomain:
SilverStripe\CMS\Model\VirtualPage:
EDITCONTENT: 'klikk her for å endre dette innholdet'
SilverStripe\Subsites\Admin\SubsiteAdmin:
MENUTITLE: subdomener
SilverStripe\Subsites\Extensions\FileSubsites:
has_one_Subsite: Subdomene
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
SubsiteFieldLabel: Subdomene
SilverStripe\Subsites\Extensions\GroupSubsites:
ACCESSALL: 'All subsites'
ACCESSONLY: 'Only these subsites'
ACCESSRADIOTITLE: 'Give this group access to'
SECURITYTABTITLE: subdomener
many_many_Subsites: subdomener
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
has_one_Subsite: Subdomene
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
has_one_Subsite: Subdomene
SilverStripe\Subsites\Model\Subsite:
ConfigurationTab: Konfigurasjon
PLURALNAME: subdomener
SINGULARNAME: Subdomene
SilverStripe\Subsites\Model\SubsiteDomain:
DOMAIN: Domain
IS_PRIMARY: 'Is Primary Domain'
PLURALNAME: 'Subsite Domains'
SINGULARNAME: 'Subsite Domain'
db_Domain: Domain
has_one_Subsite: Subdomene
SilverStripe\Subsites\Pages\SubsitesVirtualPage:
SINGULARNAME: 'Subdomeners Virtuelle Side'
SubsiteField: Subdomene
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
ReportDropdownSubsite: Subdomene
SubsiteAdmin:
MENUTITLE: Underdomener
SubsiteDomain:
DOMAIN: Domain
PLURALNAME: 'Subsite Domains'
SINGULARNAME: 'Subsite Domain'
Subsites:
DomainFieldLabel: Domain
SubsitesVirtualPage:
SINGULARNAME: 'Subdomeners Virtuelle Side'

View File

@ -1,34 +1,103 @@
nl:
SubsiteAdmin:
DomainNameField:
INVALID_DOMAIN: 'Ongeldige domein naam'
SilverStripe\CMS\Model\VirtualPage:
EDITCONTENT: 'Klik hier om inhoud aan te passen'
SilverStripe\Subsites\Admin\SubsiteAdmin:
MENUTITLE: Subsites
FileSubsites:
SilverStripe\Subsites\Controller\SubsiteXHRController:
MENUTITLE: Subsites
SilverStripe\Subsites\Extensions\FileSubsites:
has_one_Subsite: Subsite
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
AllSitesDropdownOpt: 'Alle sites'
SUBSITENOTICE: 'Mappen en bestanden van de Hoofdsite zijn toegankelijk voor alle subsites.'
SubsiteFieldLabel: Subsite
GridFieldAddFromTemplateButton:
AddFromTemplate: 'Nieuwe toevegen vanaf template'
GroupSubsites:
SilverStripe\Subsites\Extensions\GroupSubsites:
ACCESSALL: 'Alle subsites'
ACCESSONLY: 'Alleen deze subsites'
ACCESSRADIOTITLE: 'Geef deze groep rechten aan'
GlobalGroup: 'globale groep'
MANAGE_SUBSITES: 'Beheer subsites voor groepen'
MANAGE_SUBSITES_HELP: 'Bepaal de toegangsrechten voor groepen per subsite'
SECURITYTABTITLE: Subsites
Subsite:
db_AccessAllSubsites: 'Toegang tot alle subsites'
many_many_Subsites: Subsites
SilverStripe\Subsites\Extensions\LeftAndMainSubsites:
SITECONTENTLEFT: 'Site inhoud'
Saved: 'Opgeslagen, pas onderliggende pagina''s aan.'
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
has_one_Subsite: Subsite
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
CopyAction: Kopieer
CopyToSubsite: 'Kopieer pagina''s naar subsite'
CopyToSubsiteWithChildren: 'Inclusief onderliggende pagina''s?'
SubsiteOperations: 'Subsite Acties'
has_one_Subsite: Subsite
SilverStripe\Subsites\Model\Subsite:
ConfigurationTab: Configuratie
CopyMessage: 'Kopie gemaakt van {title}'
CustomExtraMeta: 'Andere meta tags'
CustomMetaDescription: Omschrijving
CustomMetaKeywords: Sleutelwoorden
CustomMetaTitle: Titel
PLURALNAME: Subsites
PLURALS:
one: 'Een Subsite'
other: '{count} Subsites'
PageTypeBlacklistField: 'Niet toegestane paginatypes?'
SINGULARNAME: Subsite
SiteConfigSubtitle: 'Jouw slagzin hier'
SiteConfigTitle: 'Jouw Site Naam'
TabTitleConfig: Configuratie
SubsiteDomain:
ThemeFieldEmptyString: '-'
ValidateTitle: 'Voeg een "Titel" toe'
belongs_many_many_Groups: Groepen
db_DefaultSite: 'Standaard Site'
db_Language: Taal
db_RedirectURL: 'Redirect URL'
db_Theme: Thema
db_Title: Titel
SilverStripe\Subsites\Model\SubsiteDomain:
DOMAIN: Domein
DOMAIN_DESCRIPTION: 'Domeinnaam van deze subsite (zonder http), wildcards zijn toegestaan (*).'
ISPRIMARY_DESCRIPTION: 'Dit is de standaard domeinnaam voor deze subsite'
IS_PRIMARY: 'Is Primaire domein'
PLURALNAME: 'Subsite Domeinen'
PLURALS:
one: 'Een subsite domein'
other: '{count} subsite domeinen'
PROTOCOL_AUTOMATIC: Automatisch
PROTOCOL_DESCRIPTION: 'Wordt gebruikt bij het genereren van links naar deze subsite. <br />''Automatisch'' houdt in dat het huidige protocol gebruikt zal worden.'
PROTOCOL_HTTP: 'http://'
PROTOCOL_HTTPS: 'https://'
Protocol: Protocol
SINGULARNAME: 'Subsite Domein'
Subsites:
DefaultSiteFieldLabel: 'Standaard Site'
LanguageFieldLabel: Taal
PrimaryDomainFieldLabel: 'Primaire domein'
ThemeFieldLabel: Thema
TitleFieldLabel: 'Subsite naam'
SubsitesVirtualPage:
PLURALNAME: 'Subsites Virtuele pagina'
db_Domain: Domein
db_Protocol: Protocol
has_one_Subsite: Subsite
SilverStripe\Subsites\Pages\SubsitesVirtualPage:
DESCRIPTION: 'Toon de inhoud van een pagina op een andere subsite'
OverrideNote: 'Overschrijft de overgenomen tekst van de gelinkte pagina'
PLURALNAME: 'Subsites Virtuele paginas'
PLURALS:
one: 'Subsites Virtuele pagina'
other: '{count} Subsites Virtuele paginas'
SINGULARNAME: 'Subsites Virtuele pagina'
SubsiteField: Subsite
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
ReportDropdown: Sites
ReportDropdownSubsite: Subsite
Subsite:
COPYSTRUCTURE: 'Kopieer structuur vanaf:'
NOTEMPLATE: 'Geen template'
Subsites:
DefaultSiteFieldLabel: 'Standaard Site'
DomainFieldLabel: Domein
IsPublicFieldLabel: 'Publiek toegankelijk'
LanguageFieldLabel: Taal
MainSiteTitle: 'Hoofd site'
PageTypeBlacklistFieldLabel: 'Paginatypes blacklist'
PrimaryDomainFieldLabel: 'Primaire domein'
RedirectURLFieldLabel: 'Redirect URL'
ThemeFieldLabel: Thema
TitleFieldLabel: 'Subsite naam'

13
lang/pl.yml Normal file
View File

@ -0,0 +1,13 @@
pl:
SilverStripe\Subsites\Model\Subsite:
CustomExtraMeta: 'Własne meta tagi'
CustomMetaDescription: Opis
CustomMetaTitle: Tytuł
SiteConfigTitle: 'Nazwa twojego serwisu'
belongs_many_many_Groups: Grupy
db_Language: Język
db_Theme: Szablon
db_Title: Tytuł
Subsites:
LanguageFieldLabel: Język
ThemeFieldLabel: Szablon

View File

@ -1,6 +1,4 @@
pl_PL:
SubsiteAdmin:
MENUTITLE: Podwitryny
GridFieldAddFromTemplateButton:
AddFromTemplate: 'Dodaj nową na podstawie szablonu'
GroupSubsites:
@ -8,10 +6,51 @@ pl_PL:
ACCESSONLY: 'Tylko te podwitryny'
ACCESSRADIOTITLE: 'Daj tej grupie dostęp do'
SECURITYTABTITLE: Podwitryny
SubsiteDomain:
SilverStripe\CMS\Model\VirtualPage:
EDITCONTENT: 'Kliknij tutaj aby edytować zawartość'
SilverStripe\Subsites\Admin\SubsiteAdmin:
MENUTITLE: Podwitryny
SilverStripe\Subsites\Extensions\FileSubsites:
has_one_Subsite: Podwitryna
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
SubsiteFieldLabel: Podwitryna
SilverStripe\Subsites\Extensions\GroupSubsites:
ACCESSALL: 'Wszystkie podwitryny'
ACCESSONLY: 'Tylko te podwitryny'
ACCESSRADIOTITLE: 'Daj tej grupie dostęp do'
SECURITYTABTITLE: Podwitryny
many_many_Subsites: Podwitryny
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
has_one_Subsite: Podwitryna
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
has_one_Subsite: Podwitryna
SilverStripe\Subsites\Model\Subsite:
ConfigurationTab: Konfiguracja
CustomExtraMeta: 'Własne meta tagi'
CustomMetaTitle: Tytuł
PLURALNAME: Podwitryny
SINGULARNAME: Podwitryna
db_Language: Język
db_Title: Tytuł
SilverStripe\Subsites\Model\SubsiteDomain:
DOMAIN: Domena
IS_PRIMARY: 'Ma priorytet?'
PLURALNAME: 'Domeny podwitryny'
SINGULARNAME: 'Domena podwitryny'
db_Domain: Domena
has_one_Subsite: Podwitryna
SilverStripe\Subsites\Pages\SubsitesVirtualPage:
SINGULARNAME: 'Wirtualna strona dla podwitryn'
SubsiteField: Podwitryna
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
ReportDropdownSubsite: Podwitryna
SubsiteAdmin:
MENUTITLE: Podwitryny
SubsiteDomain:
DOMAIN: Domena
PLURALNAME: 'Domeny podwitryny'
SINGULARNAME: 'Domena podwitryny'
Subsites:
DomainFieldLabel: Domena
LanguageFieldLabel: Język
SubsitesVirtualPage:
SINGULARNAME: 'Wirtualna strona dla podwitryn'

87
lang/ru.yml Normal file
View File

@ -0,0 +1,87 @@
ru:
DomainNameField:
INVALID_DOMAIN: 'Неверный домен'
SilverStripe\CMS\Model\VirtualPage:
EDITCONTENT: 'Нажмите для изменения содержимого'
SilverStripe\Subsites\Admin\SubsiteAdmin:
MENUTITLE: Подсайты
SilverStripe\Subsites\Extensions\FileSubsites:
has_one_Subsite: Подсайт
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
AllSitesDropdownOpt: 'Все сайты'
SUBSITENOTICE: 'Папки и файлы созданные на основном сайте так же доступны для под-сайтов.'
SubsiteFieldLabel: Подсайт
SilverStripe\Subsites\Extensions\GroupSubsites:
ACCESSALL: 'Все подсайты'
ACCESSONLY: 'Только эти подсайты'
ACCESSRADIOTITLE: 'Разрешить этой группе доступ к'
GlobalGroup: 'глобальная группа'
MANAGE_SUBSITES: 'Управление подсайтами для групп'
MANAGE_SUBSITES_HELP: 'Возможность ограничить права доступа для группы к одному и более подсайтам'
SECURITYTABTITLE: Подсайты
many_many_Subsites: Подсайты
SilverStripe\Subsites\Extensions\LeftAndMainSubsites:
Saved: 'Сохранено, пожалуйста обновите связанные группы'
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
has_one_Subsite: Подсайт
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
CopyAction: Копировать
CopyToSubsite: 'Копировать страницу на подсайт'
CopyToSubsiteWithChildren: 'Включая под-страницы?'
SubsiteOperations: 'Операции над подсайтами'
has_one_Subsite: Подсайт
SilverStripe\Subsites\Model\Subsite:
ConfigurationTab: Конфигурация
CopyMessage: 'Создана копия {title}'
CustomExtraMeta: 'Пользовательские мета-тэги'
CustomMetaDescription: Описание
CustomMetaKeywords: 'Ключевые слова'
CustomMetaTitle: Заголовок
PLURALNAME: Подсайты
PageTypeBlacklistField: 'Запретить данные типы страниц?'
SINGULARNAME: Подсайт
SiteConfigSubtitle: 'ваш слоган здесь'
SiteConfigTitle: 'Название сайта'
ValidateTitle: 'Пожалуйста, добавьте "Заголовок"'
belongs_many_many_Groups: Группы
db_DefaultSite: 'Основной сайт'
db_Language: Язык
db_RedirectURL: 'Ссылка для перенаправления'
db_Theme: Оформление
db_Title: Заголовок
has_many_Domains: Домены
SilverStripe\Subsites\Model\SubsiteDomain:
DOMAIN: Домен
DOMAIN_DESCRIPTION: 'Домен подсайта (без указания протоколов: http:// и https://). Разрешены (*) для обозначения поддоменов.'
ISPRIMARY_DESCRIPTION: 'Отметить как основной домен для данного подсайта'
IS_PRIMARY: 'Это основной домен?'
PLURALNAME: 'Домены подсайтов'
PROTOCOL_AUTOMATIC: Автоматически
PROTOCOL_HTTP: 'http://'
PROTOCOL_HTTPS: 'https://'
Protocol: Протокол
SINGULARNAME: 'Домен подсайта'
db_Domain: Домен
db_Protocol: Протокол
has_one_Subsite: Подсайт
SilverStripe\Subsites\Pages\SubsitesVirtualPage:
DESCRIPTION: 'Отображает содержимое выбранной страницы на другом подсайте'
SINGULARNAME: 'Виртуальная страница подсайта'
SubsiteField: Подсайт
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
ReportDropdown: Сайты
ReportDropdownSubsite: Подсайт
Subsite:
COPYSTRUCTURE: 'Скопировать структуры из:'
NOTEMPLATE: 'Нет шаблона'
Subsites:
DefaultSiteFieldLabel: 'Основной сайт'
DomainFieldLabel: Домен
IsPublicFieldLabel: 'Разрешить публичный доступ'
LanguageFieldLabel: Язык
MainSiteTitle: 'Основной Сайт'
PageTypeBlacklistFieldLabel: 'Запрещённые типы страниц'
PrimaryDomainFieldLabel: 'Основной Домен'
RedirectURLFieldLabel: 'Ссылка для перенаправления'
ThemeFieldLabel: Оформление
TitleFieldLabel: 'Название Подсайта'

37
lang/sk.yml Normal file
View File

@ -0,0 +1,37 @@
sk:
SilverStripe\Subsites\Admin\SubsiteAdmin:
MENUTITLE: Podstránky
SilverStripe\Subsites\Extensions\FileSubsites:
has_one_Subsite: Podstránka
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
SubsiteFieldLabel: Podstránka
SilverStripe\Subsites\Extensions\GroupSubsites:
SECURITYTABTITLE: Podstránky
db_AccessAllSubsites: 'Prístup na všetky podstránky'
many_many_Subsites: Podstránky
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
has_one_Subsite: Podstránka
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
has_one_Subsite: Podstránka
many_many_CrossSubsiteLinkTracking: 'Sledovanie odkazov naprieč podstránkami'
SilverStripe\Subsites\Model\Subsite:
ConfigurationTab: Konfigurácia
CustomExtraMeta: 'Vlastné Meta Tagy'
CustomMetaDescription: Popis
CustomMetaTitle: Názov
PLURALNAME: Podstránky
SINGULARNAME: Podstránka
SiteConfigTitle: 'Názov vášho webu'
belongs_many_many_Groups: Skupiny
db_Language: Jazyk
db_Theme: Téma
db_Title: Názov
SilverStripe\Subsites\Model\SubsiteDomain:
has_one_Subsite: Podstránka
SilverStripe\Subsites\Pages\SubsitesVirtualPage:
SubsiteField: Podstránka
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
ReportDropdownSubsite: Podstránka
Subsites:
LanguageFieldLabel: Jazyk
ThemeFieldLabel: Téma

35
lang/sl.yml Normal file
View File

@ -0,0 +1,35 @@
sl:
SilverStripe\Subsites\Admin\SubsiteAdmin:
MENUTITLE: Podspletišča
SilverStripe\Subsites\Extensions\FileSubsites:
has_one_Subsite: Podspletišče
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
SubsiteFieldLabel: Podspletišče
SilverStripe\Subsites\Extensions\GroupSubsites:
SECURITYTABTITLE: Podspletišča
db_AccessAllSubsites: 'Dostop do vseh podspletišč'
many_many_Subsites: Podspletišča
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
has_one_Subsite: Podspletišče
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
has_one_Subsite: Podspletišče
many_many_CrossSubsiteLinkTracking: 'Spremljanje povezav med podspletišči'
SilverStripe\Subsites\Model\Subsite:
ConfigurationTab: Nastavitve
CustomExtraMeta: 'Meta tagi po meri'
CustomMetaDescription: Opis
CustomMetaTitle: Naziv
PLURALNAME: Podspletišča
SINGULARNAME: Podspletišče
SiteConfigTitle: 'Naziv vašega spletnega mesta'
belongs_many_many_Groups: Skupine
db_Theme: Tema
db_Title: Naziv
SilverStripe\Subsites\Model\SubsiteDomain:
has_one_Subsite: Podspletišče
SilverStripe\Subsites\Pages\SubsitesVirtualPage:
SubsiteField: Podspletišče
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
ReportDropdownSubsite: Podspletišče
Subsites:
ThemeFieldLabel: Tema

13
lang/sr_RS@latin.yml Normal file
View File

@ -0,0 +1,13 @@
sr_RS@latin:
SilverStripe\Subsites\Model\Subsite:
CustomExtraMeta: 'Prilagođene meta oznake'
CustomMetaDescription: Opis
CustomMetaTitle: Naslov
SiteConfigTitle: 'Naziv sajta'
belongs_many_many_Groups: Grupe
db_Language: Jezik
db_Theme: Tema
db_Title: Naslov
Subsites:
LanguageFieldLabel: Jezik
ThemeFieldLabel: Tema

14
lang/sv.yml Normal file
View File

@ -0,0 +1,14 @@
sv:
SilverStripe\Subsites\Model\Subsite:
ConfigurationTab: Konfiguration
CustomExtraMeta: 'Egna metataggar'
CustomMetaDescription: Beskrivning
CustomMetaTitle: Titel
SiteConfigTitle: 'Din Sajts Namn'
belongs_many_many_Groups: Grupper
db_Language: Språk
db_Theme: Tema
db_Title: Titel
Subsites:
LanguageFieldLabel: Språk
ThemeFieldLabel: Tema

13
lang/tr.yml Normal file
View File

@ -0,0 +1,13 @@
tr:
SilverStripe\Subsites\Model\Subsite:
CustomExtraMeta: 'Kişisel Meta Etiketleri'
CustomMetaDescription: ıklama
CustomMetaTitle: Başlık
SiteConfigTitle: 'Site Adınız'
belongs_many_many_Groups: Gruplar
db_Language: Dil
db_Theme: Tema
db_Title: Başlık
Subsites:
LanguageFieldLabel: Dil
ThemeFieldLabel: Tema

View File

@ -1,17 +1,50 @@
tr_TR:
SubsiteAdmin:
MENUTITLE: Alt Siteler
GridFieldAddFromTemplateButton:
AddFromTemplate: 'Add New from Template'
GroupSubsites:
ACCESSALL: 'All subsites'
ACCESSONLY: 'Only these subsites'
ACCESSRADIOTITLE: 'Give this group access to'
SECURITYTABTITLE: Alt Siteler
SubsiteDomain:
SECURITYTABTITLE: 'Alt Siteler'
SilverStripe\CMS\Model\VirtualPage:
EDITCONTENT: 'İçeriği düzenlemek için tıklayınız'
SilverStripe\Subsites\Admin\SubsiteAdmin:
MENUTITLE: 'Alt Siteler'
SilverStripe\Subsites\Extensions\FileSubsites:
has_one_Subsite: 'Alt Site'
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
SubsiteFieldLabel: 'Alt Site'
SilverStripe\Subsites\Extensions\GroupSubsites:
ACCESSALL: 'All subsites'
ACCESSONLY: 'Only these subsites'
ACCESSRADIOTITLE: 'Give this group access to'
SECURITYTABTITLE: 'Alt Siteler'
many_many_Subsites: 'Alt Siteler'
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
has_one_Subsite: 'Alt Site'
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
has_one_Subsite: 'Alt Site'
SilverStripe\Subsites\Model\Subsite:
PLURALNAME: 'Alt Siteler'
SINGULARNAME: 'Alt Site'
SilverStripe\Subsites\Model\SubsiteDomain:
DOMAIN: Domain
IS_PRIMARY: 'Is Primary Domain'
PLURALNAME: 'Subsite Domains'
SINGULARNAME: 'Subsite Domain'
db_Domain: Domain
has_one_Subsite: 'Alt Site'
SilverStripe\Subsites\Pages\SubsitesVirtualPage:
SINGULARNAME: 'Alt Site Sanal Sayfa'
SubsiteField: 'Alt Site'
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
ReportDropdownSubsite: 'Alt Site'
SubsiteAdmin:
MENUTITLE: 'Alt Siteler'
SubsiteDomain:
DOMAIN: Domain
PLURALNAME: 'Subsite Domains'
SINGULARNAME: 'Subsite Domain'
Subsites:
DomainFieldLabel: Domain
SubsitesVirtualPage:
SINGULARNAME: 'Alt Site Sanal Sayfa'

View File

@ -1,67 +1,76 @@
zh:
SubsiteAdmin:
SilverStripe\CMS\Model\VirtualPage:
EDITCONTENT: 点击这里来编辑内容
SilverStripe\Subsites\Admin\SubsiteAdmin:
MENUTITLE: 多个子网站
SubsiteXHRController:
MENUTITLE: 子网站 XHR 控制器
ASSETADMIN:
SUBSITENOTICE: '主网站上创建的文件夹和文件可以被所有子网站访问。'
FileSubsites:
AllSitesDropdownOpt: '所有网站'
SilverStripe\Subsites\Extensions\FileSubsites:
has_one_Subsite: 子网站
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
AllSitesDropdownOpt: 所有网站
SUBSITENOTICE: 主网站上创建的文件夹和文件可以被所有子网站访问。
SubsiteFieldLabel: 子网站
GridFieldAddFromTemplateButton:
AddFromTemplate: '从模板中取出新加入'
GroupSubsites:
ACCESSALL: '所有子网站'
ACCESSONLY: '仅这些子网站'
ACCESSRADIOTITLE: '准许该群进入'
GlobalGroup: '全局小组'
MANAGE_SUBSITES: '管理小组的子网站'
MANAGE_SUBSITES_HELP: '能够将权限限制在一个小组、一个或多个子网站。'
SilverStripe\Subsites\Extensions\GroupSubsites:
ACCESSALL: 所有子网站
ACCESSONLY: 仅这些子网站
ACCESSRADIOTITLE: 准许该群进入
GlobalGroup: 全局小组
MANAGE_SUBSITES: 管理小组的子网站
MANAGE_SUBSITES_HELP: 能够将权限限制在一个小组、一个或多个子网站。
SECURITYTABTITLE: 多个子网站
LeftAndMainSubsites:
Saved: '已保存,请更新相关的页面。'
SiteTreeSubsites:
many_many_Subsites: 多个子网站
SilverStripe\Subsites\Extensions\LeftAndMainSubsites:
Saved: 已保存,请更新相关的页面。
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
has_one_Subsite: 子网站
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
CopyAction: 复制
CopyToSubsite: '将页面复制到子网站'
Subsite:
COPYSTRUCTURE: '复制结构来自:'
CopyToSubsite: 将页面复制到子网站
has_one_Subsite: 子网站
SilverStripe\Subsites\Model\Subsite:
ConfigurationTab: 配置
CopyMessage: '已创建一个 {title} 的副本'
CustomExtraMeta: '自定义 Meta 标签'
CustomMetaDescription: 说明
CustomExtraMeta: 自定义Meta标签
CustomMetaDescription: 描述
CustomMetaKeywords: 关键词
CustomMetaTitle: 标题
DOMAINSAVEFIRST: '只有当您第一次保存后,才能添加域名'
DomainsHeadline: '这个子网站的域名'
DomainsListTitle: 域名
IsPublicHeaderField: '活跃的子网站'
NOTEMPLATE: '没有模板'
CustomMetaTitle: 题目
PLURALNAME: 多个子网站
PageTypeBlacklistField: '禁止页面类型?'
PageTypeBlacklistField: 禁止页面类型?
SINGULARNAME: 子网站
SiteConfigSubtitle: '您的标语在这里'
SiteConfigTitle: '您的网站名称'
TabTitleConfig: 配置
ValidateTitle: '请添加一个“标题”'
SubsiteDomain:
SiteConfigSubtitle: 您的标语在这里
SiteConfigTitle: 您的网站名称
ValidateTitle: 请添加一个“标题”
belongs_many_many_Groups: 群组
db_DefaultSite: 默认网站
db_Language: 语言
db_RedirectURL: '重定向 URL'
db_Theme: 主题
db_Title: 题目
has_many_Domains: 域名
SilverStripe\Subsites\Model\SubsiteDomain:
DOMAIN: 域名
IS_PRIMARY: '是主域名'
PLURALNAME: '多个子网站域名'
SINGULARNAME: '子网站域名'
SubsiteReportWrapper:
PLURALNAME: 多个子网站域名
SINGULARNAME: 子网站域名
db_Domain: 域名
has_one_Subsite: 子网站
SilverStripe\Subsites\Pages\SubsitesVirtualPage:
DESCRIPTION: 显示另一个子网站上一个页面的内容
PLURALNAME: 基本页面
SINGULARNAME: 子网站虚拟页面
SubsiteField: 子网站
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
ReportDropdown: 网站
ReportDropdownSubsite: 子网站
Subsite:
COPYSTRUCTURE: 复制结构来自:
NOTEMPLATE: 没有模板
Subsites:
DefaultSiteFieldLabel: '默认网站'
DefaultSiteFieldLabel: 默认网站
DomainFieldLabel: 域名
IsPublicFieldLabel: '启用公共访问权'
IsPublicFieldLabel: 启用公共访问权
LanguageFieldLabel: 语言
MainSiteTitle: '主网站'
PageTypeBlacklistFieldLabel: '页面类型黑名单'
PrimaryDomainFieldLabel: '主域名'
MainSiteTitle: 主网站
PageTypeBlacklistFieldLabel: 页面类型黑名单
PrimaryDomainFieldLabel: 主域名
RedirectURLFieldLabel: '重定向 URL'
ThemeFieldLabel: 主题
TitleFieldLabel: '子网站名称'
SubsitesVirtualPage:
DESCRIPTION: '显示另一个子网站上一个页面的内容'
PLURALNAME: '基本页面'
SINGULARNAME: '子网站虚拟页面'
SubsiteField: 子网站
TitleFieldLabel: 子网站名称

12
license.md Normal file
View File

@ -0,0 +1,12 @@
Copyright (c) 2016, SilverStripe Limited
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

14
phpcs.xml.dist Normal file
View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="SilverStripe">
<description>CodeSniffer ruleset for SilverStripe coding conventions.</description>
<file>src</file>
<file>tests</file>
<!-- base rules are PSR-2 -->
<rule ref="PSR2" >
<!-- Current exclusions -->
<exclude name="PSR1.Methods.CamelCapsMethodName" />
<exclude name="PSR1.Files.SideEffects.FoundWithSymbols" />
</rule>
</ruleset>

17
phpunit.xml.dist Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/silverstripe/cms/tests/bootstrap.php" colors="true">
<testsuites>
<testsuite name="Default">
<directory>tests/php</directory>
</testsuite>
</testsuites>
<filter>
<whitelist addUncoveredFilesFromWhitelist="true">
<directory suffix=".php">src/</directory>
<exclude>
<directory suffix=".php">tests/</directory>
</exclude>
</whitelist>
</filter>
</phpunit>

View File

@ -0,0 +1,43 @@
<?php
namespace SilverStripe\Subsites\Admin;
use SilverStripe\Admin\ModelAdmin;
use SilverStripe\Forms\GridField\GridFieldDetailForm;
use SilverStripe\Forms\GridField\GridFieldPaginator;
use SilverStripe\Subsites\Forms\GridFieldSubsiteDetailForm;
use SilverStripe\Subsites\Model\Subsite;
/**
* Admin interface to manage and create {@link Subsite} instances.
*
* @package subsites
*/
class SubsiteAdmin extends ModelAdmin
{
private static $managed_models = [Subsite::class];
private static $url_segment = 'subsites';
private static $menu_title = 'Subsites';
private static $menu_icon_class = 'font-icon-tree';
public $showImportForm = false;
private static $tree_class = Subsite::class;
public function getEditForm($id = null, $fields = null)
{
$form = parent::getEditForm($id, $fields);
$grid = $form->Fields()->dataFieldByName(str_replace('\\', '-', Subsite::class));
if ($grid) {
$grid->getConfig()->getComponentByType(GridFieldPaginator::class)->setItemsPerPage(100);
$grid->getConfig()->removeComponentsByType(GridFieldDetailForm::class);
$grid->getConfig()->addComponent(new GridFieldSubsiteDetailForm());
}
return $form;
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace SilverStripe\Subsites\Controller;
use SilverStripe\Admin\LeftAndMain;
use SilverStripe\Security\Member;
use SilverStripe\Security\Permission;
use SilverStripe\Subsites\Model\Subsite;
/**
* Section-agnostic PJAX controller.
*/
class SubsiteXHRController extends LeftAndMain
{
private static $url_segment = 'subsite_xhr';
private static $ignore_menuitem = true;
/**
* Relax the access permissions, so anyone who has access to any CMS subsite can access this controller.
* @param Member|null $member
* @return bool
*/
public function canView($member = null)
{
if (parent::canView($member)) {
return true;
}
if (Subsite::all_accessible_sites()->count() > 0) {
return true;
}
return false;
}
/**
* Allow access if user allowed into the CMS at all.
*/
public function canAccess()
{
// Allow if any cms access is available
return Permission::check([
'CMS_ACCESS', // Supported by 3.1.14 and up
'CMS_ACCESS_LeftAndMain'
]);
}
public function getResponseNegotiator()
{
$negotiator = parent::getResponseNegotiator();
// Register a new callback
$negotiator->setCallback('SubsiteList', function () {
return $this->SubsiteList();
});
return $negotiator;
}
/**
* Provide the list of available subsites as a cms-section-agnostic PJAX handler.
*/
public function SubsiteList()
{
return $this->renderWith(['type' => 'Includes', self::class . '_subsitelist']);
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace SilverStripe\Subsites\Extensions;
use SilverStripe\Control\HTTP;
use SilverStripe\ORM\DataExtension;
/**
* Extension for the BaseElement object to add subsites support for CMS previews
*/
class BaseElementSubsites extends DataExtension
{
/**
* Set SubsiteID to avoid errors when a page doesn't exist on the CMS domain.
*
* @param string &$link
* @param string|null $action
* @return string
*/
public function updatePreviewLink(&$link)
{
// Get subsite ID from the element or from its page. Defaults to 0 automatically.
$subsiteID = $this->owner->SubsiteID;
if (is_null($subsiteID)) {
$page = $this->owner->getPage();
if ($page) {
$subsiteID = $page->SubsiteID;
}
}
$link = HTTP::setGetVar('SubsiteID', intval($subsiteID), $link);
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace SilverStripe\Subsites\Extensions;
use SilverStripe\Core\Extension;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\HiddenField;
use SilverStripe\Subsites\State\SubsiteState;
class CMSPageAddControllerExtension extends Extension
{
public function updatePageOptions(FieldList $fields)
{
$fields->push(HiddenField::create('SubsiteID', 'SubsiteID', SubsiteState::singleton()->getSubsiteId()));
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace SilverStripe\Subsites\Extensions;
use SilverStripe\Core\Extension;
use SilverStripe\Subsites\Model\Subsite;
use SilverStripe\View\SSViewer;
/**
* @package subsites
*/
class ControllerSubsites extends Extension
{
public function controllerAugmentInit()
{
if ($subsite = Subsite::currentSubsite()) {
if ($theme = $subsite->Theme) {
SSViewer::set_themes([$theme, SSViewer::DEFAULT_THEME]);
}
}
}
public function CurrentSubsite()
{
if ($subsite = Subsite::currentSubsite()) {
return $subsite;
}
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace SilverStripe\Subsites\Extensions;
use SilverStripe\Assets\FileNameFilter;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Core\Config\Config;
use SilverStripe\ORM\DataExtension;
use SilverStripe\ORM\DataObject;
use SilverStripe\Subsites\Model\Subsite;
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 ErrorPage::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.
*
* @param string $name
* @param int $statusCode
*/
public function updateErrorFilename(&$name, &$statusCode)
{
$static_filepath = Config::inst()->get($this->owner->ClassName, 'static_filepath');
$subdomainPart = '';
// Try to get current subsite from session
$subsite = Subsite::currentSubsite();
// 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::class, $subsiteID);
} else {
$subsite = null;
}
}
if ($subsite) {
$subdomain = $subsite->domain();
$subdomainPart = "-{$subdomain}";
}
// @todo implement Translatable namespace
if (singleton(SiteTree::class)->hasExtension('Translatable')
&& $locale
&& $locale != Translatable::default_locale()
) {
$fileName = "error-{$statusCode}-{$locale}{$subdomainPart}.html";
} else {
$fileName= "error-{$statusCode}{$subdomainPart}.html";
}
$fileName = FileNameFilter::create()->filter($fileName);
$name = implode('/', [$static_filepath, $fileName]);
}
}

View File

@ -0,0 +1,140 @@
<?php
namespace SilverStripe\Subsites\Extensions;
use SilverStripe\Assets\File;
use SilverStripe\ORM\DataExtension;
use SilverStripe\ORM\DataQuery;
use SilverStripe\ORM\Queries\SQLSelect;
use SilverStripe\Security\Permission;
use SilverStripe\Subsites\Model\Subsite;
use SilverStripe\Subsites\State\SubsiteState;
/**
* Extension for the File object to add subsites support
*
* @package subsites
*/
class FileSubsites extends DataExtension
{
/**
* If this is set to true, all folders created will be default be considered 'global', unless set otherwise
*
* @config
* @var bool
*/
private static $default_root_folders_global = false;
private static $has_one = [
'Subsite' => Subsite::class,
];
/**
* Amends the CMS tree title for folders in the Files & Images section.
* Prefixes a '* ' to the folders that are accessible from all subsites.
*/
public function alternateTreeTitle()
{
if ($this->owner->SubsiteID == 0) {
return ' * ' . $this->owner->Title;
}
return $this->owner->Title;
}
/**
* Update any requests to limit the results to the current site
* @param SQLSelect $query
* @param DataQuery|null $dataQuery
*/
public function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null)
{
if (Subsite::$disable_subsite_filter) {
return;
}
if ($dataQuery && $dataQuery->getQueryParam('Subsite.filter') === false) {
return;
}
// If you're querying by ID, ignore the sub-site - this is a bit ugly... (but it was WAYYYYYYYYY worse)
// @TODO I don't think excluding if SiteTree_ImageTracking is a good idea however because of the SS 3.0 api and
// ManyManyList::removeAll() changing the from table after this function is called there isn't much of a choice
$from = $query->getFrom();
if (isset($from['SiteTree_ImageTracking']) || $query->filtersOnID()) {
return;
}
$subsiteID = SubsiteState::singleton()->getSubsiteId();
if ($subsiteID === null) {
return;
}
// The foreach is an ugly way of getting the first key :-)
foreach ($query->getFrom() as $tableName => $info) {
$where = "\"$tableName\".\"SubsiteID\" IN (0, $subsiteID)";
$query->addWhere($where);
break;
}
$sect = array_values($query->getSelect() ?? []);
$isCounting = strpos($sect[0] ?? '', 'COUNT') !== false;
// Ordering when deleting or counting doesn't apply
if (!$isCounting) {
$query->addOrderBy('"SubsiteID"');
}
}
public function onBeforeWrite()
{
if (!$this->owner->ID && !$this->owner->SubsiteID) {
if ($this->owner->config()->get('default_root_folders_global')) {
$this->owner->SubsiteID = 0;
} else {
$this->owner->SubsiteID = SubsiteState::singleton()->getSubsiteId();
}
}
}
public function onAfterUpload()
{
// If we have a parent, use it's subsite as our subsite
if ($this->owner->Parent()) {
$this->owner->SubsiteID = $this->owner->Parent()->SubsiteID;
} else {
$this->owner->SubsiteID = SubsiteState::singleton()->getSubsiteId();
}
$this->owner->write();
}
public function canEdit($member = null)
{
// Opt out of making opinions if no subsite ID is set yet
if (!$this->owner->SubsiteID) {
return null;
}
// Check the CMS_ACCESS_SecurityAdmin privileges on the subsite that owns this group
$currentSubsiteID = SubsiteState::singleton()->getSubsiteId();
if ($currentSubsiteID && $currentSubsiteID !== $this->owner->SubsiteID) {
return false;
}
return SubsiteState::singleton()->withState(function (SubsiteState $newState) use ($member) {
$newState->setSubsiteId($this->owner->SubsiteID);
return $this->owner->getPermissionChecker()->canEdit($this->owner->ID, $member);
});
}
/**
* Return a piece of text to keep DataObject cache keys appropriately specific
*
* @return string
*/
public function cacheKeyComponent()
{
return 'subsite-' . SubsiteState::singleton()->getSubsiteId();
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace SilverStripe\Subsites\Extensions;
use SilverStripe\Core\Extension;
use SilverStripe\Forms\DropdownField;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\LiteralField;
use SilverStripe\Subsites\Model\Subsite;
class FolderFormFactoryExtension extends Extension
{
/**
* Add subsites-specific fields to the folder editor.
* @param FieldList $fields
*/
public function updateFormFields(FieldList $fields)
{
$sites = Subsite::accessible_sites('CMS_ACCESS_AssetAdmin');
$values = [];
$values[0] = _t(__CLASS__ . '.AllSitesDropdownOpt', 'All sites');
foreach ($sites as $site) {
$values[$site->ID] = $site->Title;
}
ksort($values);
if ($sites) {
// Dropdown needed to move folders between subsites
/** @var @skipUpgrade */
$dropdown = DropdownField::create(
'SubsiteID',
_t(__CLASS__ . '.SubsiteFieldLabel', 'Subsite'),
$values
);
$dropdown->addExtraClass('subsites-move-dropdown');
$fields->push($dropdown);
$fields->push(LiteralField::create(
'Message',
'<p class="alert alert-info">' .
_t(
__CLASS__ . '.SUBSITENOTICE',
'Folders and files created in the main site are accessible by all subsites.'
)
. '</p>'
));
}
}
}

View File

@ -0,0 +1,250 @@
<?php
namespace SilverStripe\Subsites\Extensions;
use SilverStripe\Control\Cookie;
use SilverStripe\Core\Convert;
use SilverStripe\Forms\CheckboxSetField;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\OptionsetField;
use SilverStripe\Forms\ReadonlyField;
use SilverStripe\ORM\DataExtension;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DataQuery;
use SilverStripe\ORM\DB;
use SilverStripe\ORM\Queries\SQLSelect;
use SilverStripe\Security\Group;
use SilverStripe\Security\PermissionProvider;
use SilverStripe\Subsites\Model\Subsite;
use SilverStripe\Subsites\State\SubsiteState;
/**
* Extension for the Group object to add subsites support
*
* @package subsites
*/
class GroupSubsites extends DataExtension implements PermissionProvider
{
private static $db = [
'AccessAllSubsites' => 'Boolean'
];
private static $many_many = [
'Subsites' => Subsite::class
];
private static $defaults = [
'AccessAllSubsites' => true
];
/**
* Migrations for GroupSubsites data.
*/
public function requireDefaultRecords()
{
if (!$this->owner) {
return;
}
// Migration for Group.SubsiteID data from when Groups only had a single subsite
$schema = DataObject::getSchema();
$groupTable = Convert::raw2sql($schema->tableName(Group::class));
$groupFields = DB::field_list($groupTable);
// Detection of SubsiteID field is the trigger for old-style-subsiteID migration
if (isset($groupFields['SubsiteID'])) {
// Migrate subsite-specific data
DB::query('INSERT INTO "Group_Subsites" ("GroupID", "SubsiteID")
SELECT "ID", "SubsiteID" FROM "' . $groupTable . '" WHERE "SubsiteID" > 0');
// Migrate global-access data
DB::query('UPDATE "' . $groupTable . '" SET "AccessAllSubsites" = 1 WHERE "SubsiteID" = 0');
// Move the field out of the way so that this migration doesn't get executed again
DB::get_schema()->renameField($groupTable, 'SubsiteID', '_obsolete_SubsiteID');
// No subsite access on anything means that we've just installed the subsites module.
// Make all previous groups global-access groups
} else {
if (!DB::query('SELECT "Group"."ID" FROM "' . $groupTable . '"
LEFT JOIN "Group_Subsites" ON "Group_Subsites"."GroupID" = "Group"."ID" AND "Group_Subsites"."SubsiteID" > 0
WHERE "AccessAllSubsites" = 1
OR "Group_Subsites"."GroupID" IS NOT NULL ')->value()
) {
DB::query('UPDATE "' . $groupTable . '" SET "AccessAllSubsites" = 1');
}
}
}
public function updateCMSFields(FieldList $fields)
{
if ($this->owner->canEdit()) {
// i18n tab
$fields->findOrMakeTab('Root.Subsites', _t(__CLASS__ . '.SECURITYTABTITLE', 'Subsites'));
$subsites = Subsite::accessible_sites(['ADMIN', 'SECURITY_SUBSITE_GROUP'], true);
$subsiteMap = $subsites->map();
// Prevent XSS injection
$subsiteMap = Convert::raw2xml($subsiteMap->toArray());
// Interface is different if you have the rights to modify subsite group values on
// all subsites
if (isset($subsiteMap[0])) {
$fields->addFieldToTab('Root.Subsites', new OptionsetField(
'AccessAllSubsites',
_t(__CLASS__ . '.ACCESSRADIOTITLE', 'Give this group access to'),
[
1 => _t(__CLASS__ . '.ACCESSALL', 'All subsites'),
0 => _t(__CLASS__ . '.ACCESSONLY', 'Only these subsites'),
]
));
unset($subsiteMap[0]);
$fields->addFieldToTab('Root.Subsites', new CheckboxSetField(
'Subsites',
'',
$subsiteMap
));
} else {
if (sizeof($subsiteMap ?? []) <= 1) {
$fields->addFieldToTab('Root.Subsites', new ReadonlyField(
'SubsitesHuman',
_t(__CLASS__ . '.ACCESSRADIOTITLE', 'Give this group access to'),
reset($subsiteMap)
));
} else {
$fields->addFieldToTab('Root.Subsites', new CheckboxSetField(
'Subsites',
_t(__CLASS__ . '.ACCESSRADIOTITLE', 'Give this group access to'),
$subsiteMap
));
}
}
}
}
/**
* If this group belongs to a subsite, append the subsites title to the group title to make it easy to
* distinguish in the tree-view of the security admin interface.
*
* @param string $title
*/
public function updateTreeTitle(&$title)
{
if ($this->owner->AccessAllSubsites) {
$title = _t(__CLASS__ . '.GlobalGroup', 'global group');
$title = htmlspecialchars($this->owner->Title ?? '', ENT_QUOTES) . ' <i>(' . $title . ')</i>';
} else {
$subsites = Convert::raw2xml(implode(', ', $this->owner->Subsites()->column('Title')));
$title = htmlspecialchars($this->owner->Title ?? '') . " <i>($subsites)</i>";
}
}
/**
* Update any requests to limit the results to the current site
* @param SQLSelect $query
* @param DataQuery|null $dataQuery
*/
public function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null)
{
if (Subsite::$disable_subsite_filter) {
return;
}
if (Cookie::get('noSubsiteFilter') == 'true') {
return;
}
if ($dataQuery && $dataQuery->getQueryParam('Subsite.filter') === false) {
return;
}
// If you're querying by ID, ignore the sub-site - this is a bit ugly...
if (!$query->filtersOnID()) {
$subsiteID = SubsiteState::singleton()->getSubsiteId();
if ($subsiteID === null) {
return;
}
// Don't filter by Group_Subsites if we've already done that
$hasGroupSubsites = false;
foreach ($query->getFrom() as $item) {
if ((is_array($item) && strpos(
$item['table'] ?? '',
'Group_Subsites'
) !== false) || (!is_array($item) && strpos(
$item ?? '',
'Group_Subsites'
) !== false)
) {
$hasGroupSubsites = true;
break;
}
}
if (!$hasGroupSubsites) {
if ($subsiteID) {
$query->addLeftJoin('Group_Subsites', "\"Group_Subsites\".\"GroupID\"
= \"Group\".\"ID\" AND \"Group_Subsites\".\"SubsiteID\" = $subsiteID");
$query->addWhere('("Group_Subsites"."SubsiteID" IS NOT NULL OR
"Group"."AccessAllSubsites" = 1)');
} else {
$query->addWhere('"Group"."AccessAllSubsites" = 1');
}
}
// WORKAROUND for databases that complain about an ORDER BY when the column wasn't selected
// (e.g. SQL Server)
$select = $query->getSelect();
if (isset($select[0]) && !$select[0] == 'COUNT(*)') {
$query->addOrderBy('AccessAllSubsites', 'DESC');
}
}
}
public function onBeforeWrite()
{
// New record test approximated by checking whether the ID has changed.
// Note also that the after write test is only used when we're *not* on a subsite
if ($this->owner->isChanged('ID') && !SubsiteState::singleton()->getSubsiteId()) {
$this->owner->AccessAllSubsites = 1;
}
}
public function onAfterWrite()
{
// New record test approximated by checking whether the ID has changed.
// Note also that the after write test is only used when we're on a subsite
if ($this->owner->isChanged('ID') && $currentSubsiteID = SubsiteState::singleton()->getSubsiteId()) {
$subsites = $this->owner->Subsites();
$subsites->add($currentSubsiteID);
}
}
public function alternateCanEdit()
{
// Find the sites that this group belongs to and the sites where we have appropriate perm.
$accessibleSites = Subsite::accessible_sites('CMS_ACCESS_SecurityAdmin')->column('ID');
$linkedSites = $this->owner->Subsites()->column('ID');
// We are allowed to access this site if at we have CMS_ACCESS_SecurityAdmin permission on
// at least one of the sites
return (bool)array_intersect($accessibleSites ?? [], $linkedSites);
}
public function providePermissions()
{
return [
'SECURITY_SUBSITE_GROUP' => [
'name' => _t(__CLASS__ . '.MANAGE_SUBSITES', 'Manage subsites for groups'),
'category' => _t(
'SilverStripe\\Security\\Permission.PERMISSIONS_CATEGORY',
'Roles and access permissions'
),
'help' => _t(
__CLASS__ . '.MANAGE_SUBSITES_HELP',
'Ability to limit the permissions for a group to one or more subsites.'
),
'sort' => 200
]
];
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace SilverStripe\Subsites\Extensions;
use SilverStripe\CMS\Controllers\CMSMain;
use SilverStripe\Core\Extension;
use SilverStripe\Subsites\State\SubsiteState;
/**
* This extension adds the current Subsite ID as an additional factor to the Hints Cßache Key, which is used to cache
* the Site Tree Hints (which include allowed pagetypes).
*
* @package SilverStripe\Subsites\Extensions
* @see CMSMain::generateHintsCacheKey()
*/
class HintsCacheKeyExtension extends Extension
{
public function updateHintsCacheKey(&$baseKey)
{
$baseKey .= '_Subsite:' . SubsiteState::singleton()->getSubsiteId();
}
}

View File

@ -0,0 +1,414 @@
<?php
namespace SilverStripe\Subsites\Extensions;
use SilverStripe\Admin\AdminRootController;
use SilverStripe\Admin\CMSMenu;
use SilverStripe\Admin\LeftAndMainExtension;
use SilverStripe\CMS\Controllers\CMSPagesController;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\CMS\Controllers\CMSPageEditController;
use SilverStripe\Control\Controller;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Convert;
use SilverStripe\Forms\HiddenField;
use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\DataObject;
use SilverStripe\Security\Member;
use SilverStripe\Security\Permission;
use SilverStripe\Security\Security;
use SilverStripe\Subsites\Controller\SubsiteXHRController;
use SilverStripe\Subsites\Model\Subsite;
use SilverStripe\Subsites\State\SubsiteState;
use SilverStripe\View\ArrayData;
use SilverStripe\View\Requirements;
/**
* Decorator designed to add subsites support to LeftAndMain
*
* @package subsites
*/
class LeftAndMainSubsites extends LeftAndMainExtension
{
private static $allowed_actions = ['CopyToSubsite'];
/**
* Normally SubsiteID=0 on a DataObject means it is only accessible from the special "main site".
* However in some situations SubsiteID=0 will be understood as a "globally accessible" object in which
* case this property is set to true (i.e. in AssetAdmin).
*/
private static $treats_subsite_0_as_global = false;
public function init()
{
Requirements::css('silverstripe/subsites:client/css/LeftAndMain_Subsites.css');
Requirements::javascript('silverstripe/subsites:client/javascript/LeftAndMain_Subsites.js');
Requirements::javascript('silverstripe/subsites:client/javascript/VirtualPage_Subsites.js');
}
/**
* Set the title of the CMS tree
*/
public function getCMSTreeTitle()
{
$subsite = Subsite::currentSubsite();
return $subsite ? Convert::raw2xml($subsite->Title) : _t(__CLASS__.'.SITECONTENTLEFT', 'Site Content');
}
public function updatePageOptions(&$fields)
{
$fields->push(HiddenField::create('SubsiteID', 'SubsiteID', SubsiteState::singleton()->getSubsiteId()));
}
/**
* Find all subsites accessible for current user on this controller.
*
* @param bool $includeMainSite
* @param string $mainSiteTitle
* @param null $member
* @return ArrayList of <a href='psi_element://Subsite'>Subsite</a> instances.
* instances.
*/
public function sectionSites($includeMainSite = true, $mainSiteTitle = 'Main site', $member = null)
{
if ($mainSiteTitle == 'Main site') {
$mainSiteTitle = _t('Subsites.MainSiteTitle', 'Main site');
}
// Rationalise member arguments
if (!$member) {
$member = Security::getCurrentUser();
}
if (!$member) {
return ArrayList::create();
}
if (!is_object($member)) {
$member = DataObject::get_by_id(Member::class, $member);
}
// Collect permissions - honour the LeftAndMain::required_permission_codes, current model requires
// us to check if the user satisfies ALL permissions. Code partly copied from LeftAndMain::canView.
$codes = [];
$extraCodes = Config::inst()->get(get_class($this->owner), 'required_permission_codes');
if ($extraCodes !== false) {
if ($extraCodes) {
$codes = array_merge($codes, (array)$extraCodes);
} else {
$codes[] = sprintf('CMS_ACCESS_%s', get_class($this->owner));
}
} else {
// Check overriden - all subsites accessible.
return Subsite::all_sites();
}
// Find subsites satisfying all permissions for the Member.
$codesPerSite = [];
$sitesArray = [];
foreach ($codes as $code) {
$sites = Subsite::accessible_sites($code, $includeMainSite, $mainSiteTitle, $member);
foreach ($sites as $site) {
// Build the structure for checking how many codes match.
$codesPerSite[$site->ID][$code] = true;
// Retain Subsite objects for later.
$sitesArray[$site->ID] = $site;
}
}
// Find sites that satisfy all codes conjuncitvely.
$accessibleSites = new ArrayList();
foreach ($codesPerSite as $siteID => $siteCodes) {
if (count($siteCodes ?? []) == count($codes ?? [])) {
$accessibleSites->push($sitesArray[$siteID]);
}
}
return $accessibleSites;
}
/*
* Returns a list of the subsites accessible to the current user.
* It's enough for any section to be accessible for the section to be included.
*/
public function Subsites()
{
return Subsite::all_accessible_sites();
}
/*
* Generates a list of subsites with the data needed to
* produce a dropdown site switcher
* @return ArrayList
*/
public function ListSubsites()
{
$list = $this->Subsites();
$currentSubsiteID = SubsiteState::singleton()->getSubsiteId();
if ($list == null || $list->count() == 1 && $list->first()->DefaultSite == true) {
return false;
}
Requirements::javascript('silverstripe/subsites:client/javascript/LeftAndMain_Subsites.js');
$output = ArrayList::create();
foreach ($list as $subsite) {
$currentState = $subsite->ID == $currentSubsiteID ? 'selected' : '';
$output->push(ArrayData::create([
'CurrentState' => $currentState,
'ID' => $subsite->ID,
'Title' => $subsite->Title,
]));
}
return $output;
}
public function alternateMenuDisplayCheck($controllerName)
{
if (!class_exists($controllerName ?? '')) {
return false;
}
// Don't display SubsiteXHRController
if (singleton($controllerName) instanceof SubsiteXHRController) {
return false;
}
// Check subsite support.
if (SubsiteState::singleton()->getSubsiteId() == 0) {
// Main site always supports everything.
return true;
}
// It's not necessary to check access permissions here. Framework calls canView on the controller,
// which in turn uses the Permission API which is augmented by our GroupSubsites.
$controller = singleton($controllerName);
return $controller->hasMethod('subsiteCMSShowInMenu') && $controller->subsiteCMSShowInMenu();
}
public function CanAddSubsites()
{
return Permission::check('ADMIN', 'any', null, 'all');
}
/**
* Helper for testing if the subsite should be adjusted.
* @param string $adminClass
* @param int $recordSubsiteID
* @param int $currentSubsiteID
* @return bool
*/
public function shouldChangeSubsite($adminClass, $recordSubsiteID, $currentSubsiteID)
{
if (Config::inst()->get($adminClass, 'treats_subsite_0_as_global') && $recordSubsiteID == 0) {
return false;
}
if ($recordSubsiteID != $currentSubsiteID) {
return true;
}
return false;
}
/**
* Check if the current controller is accessible for this user on this subsite.
*
* @param Member $member
*/
public function canAccess(Member $member = null)
{
if (!$member) {
$member = Security::getCurrentUser();
}
// Admin can access everything, no point in checking.
if ($member
&& (Permission::checkMember($member, [
'ADMIN', // Full administrative rights
'CMS_ACCESS_LeftAndMain', // Access to all CMS sections
'CMS_ACCESS_CMSMain', // Access to CMS controllers
]))
) {
return true;
}
// Check if we have access to current section on the current subsite.
$accessibleSites = $this->owner->sectionSites(true, 'Main site', $member);
return $accessibleSites->count() && $accessibleSites->find('ID', SubsiteState::singleton()->getSubsiteId());
}
/**
* Prevent accessing disallowed resources. This happens after onBeforeInit has executed,
* so all redirections should've already taken place.
*
* @param Member $member
*/
public function alternateAccessCheck(Member $member = null)
{
return $this->owner->canAccess($member);
}
/**
* Redirect the user to something accessible if the current section/subsite is forbidden.
*
* This is done via onBeforeInit as it needs to be done before the LeftAndMain::init has a
* chance to forbids access via alternateAccessCheck.
*
* If we need to change the subsite we force the redirection to /admin/ so the frontend is
* fully re-synchronised with the internal session. This is better than risking some panels
* showing data from another subsite.
*/
public function onBeforeInit()
{
$request = Controller::curr()->getRequest();
$session = $request->getSession();
$state = SubsiteState::singleton();
// FIRST, check if we need to change subsites due to the URL.
// Catch forced subsite changes that need to cause CMS reloads.
if ($request->getVar('SubsiteID') !== null) {
// Clear current page when subsite changes (or is set for the first time)
if ($state->getSubsiteIdWasChanged()) {
// sessionNamespace() is protected - see for info
$override = $this->owner->config()->get('session_namespace');
$sessionNamespace = $override ? $override : get_class($this->owner);
$session->clear($sessionNamespace . '.currentPage');
}
// Context: Subsite ID has already been set to the state via InitStateMiddleware
// If the user cannot view the current page, redirect to the admin landing section
if (!$this->owner->canView()) {
return $this->owner->redirect(AdminRootController::config()->get('url_base') . '/');
}
$currentController = Controller::curr();
if ($currentController instanceof CMSPageEditController) {
/** @var SiteTree $page */
$page = $currentController->currentPage();
// If the page exists but doesn't belong to the requested subsite, redirect to admin/pages which
// will show a list of the requested subsite's pages
$currentSubsiteId = $request->getVar('SubsiteID');
if ($page && (int) $page->SubsiteID !== (int) $currentSubsiteId) {
return $this->owner->redirect(CMSPagesController::singleton()->Link());
}
// Page does belong to the current subsite, so remove the query string parameter and refresh the page
// Remove the subsiteID parameter and redirect back to the current URL again
$request->offsetSet('SubsiteID', null);
return $this->owner->redirect($request->getURL(true));
}
// Redirect back to the default admin URL
return $this->owner->redirect($request->getURL());
}
// Automatically redirect the session to appropriate subsite when requesting a record.
// This is needed to properly initialise the session in situations where someone opens the CMS via a link.
$record = $this->owner->currentPage();
if ($record
&& isset($record->SubsiteID, $this->owner->urlParams['ID'])
&& is_numeric($record->SubsiteID)
&& $this->shouldChangeSubsite(
get_class($this->owner),
$record->SubsiteID,
SubsiteState::singleton()->getSubsiteId()
)
) {
// Update current subsite
$canViewElsewhere = SubsiteState::singleton()->withState(function ($newState) use ($record) {
$newState->setSubsiteId($record->SubsiteID);
return (bool) $this->owner->canView(Security::getCurrentUser());
});
if ($canViewElsewhere) {
// Redirect to clear the current page
return $this->owner->redirect(
Controller::join_links($this->owner->Link('show'), $record->ID, '?SubsiteID=' . $record->SubsiteID)
);
}
// Redirect to the default CMS section
return $this->owner->redirect(AdminRootController::config()->get('url_base') . '/');
}
// SECOND, check if we need to change subsites due to lack of permissions.
if (!$this->owner->canAccess()) {
$member = Security::getCurrentUser();
// Current section is not accessible, try at least to stick to the same subsite.
$menu = CMSMenu::get_menu_items();
foreach ($menu as $candidate) {
if ($candidate->controller && $candidate->controller != get_class($this->owner)) {
$accessibleSites = singleton($candidate->controller)->sectionSites(true, 'Main site', $member);
if ($accessibleSites->count()
&& $accessibleSites->find('ID', SubsiteState::singleton()->getSubsiteId())
) {
// Section is accessible, redirect there.
return $this->owner->redirect(singleton($candidate->controller)->Link());
}
}
}
// If no section is available, look for other accessible subsites.
foreach ($menu as $candidate) {
if ($candidate->controller) {
$accessibleSites = singleton($candidate->controller)->sectionSites(true, 'Main site', $member);
if ($accessibleSites->count()) {
Subsite::changeSubsite($accessibleSites->First()->ID);
return $this->owner->redirect(singleton($candidate->controller)->Link());
}
}
}
// We have not found any accessible section or subsite. User should be denied access.
// This is handled already by LeftAndMain thanks to alternateAccessCheck
}
// Current site is accessible. Allow through.
return;
}
public function augmentNewSiteTreeItem(&$item)
{
$request = Controller::curr()->getRequest();
$item->SubsiteID = $request->postVar('SubsiteID') ?: SubsiteState::singleton()->getSubsiteId();
}
public function onAfterSave($record)
{
if ($record->hasMethod('NormalRelated') && ($record->NormalRelated() || $record->ReverseRelated())) {
$this->owner->response->addHeader(
'X-Status',
rawurlencode(_t(__CLASS__ . '.Saved', 'Saved, please update related pages.') ?? '')
);
}
}
/**
* @param array $data
* @param Form $form
*/
public function copytosubsite($data, $form)
{
$page = DataObject::get_by_id(SiteTree::class, $data['ID']);
$subsite = DataObject::get_by_id(Subsite::class, $data['CopyToSubsiteID']);
$includeChildren = (isset($data['CopyToSubsiteWithChildren'])) ? $data['CopyToSubsiteWithChildren'] : false;
$newPage = $page->duplicateToSubsite($subsite->ID, $includeChildren);
$response = $this->owner->getResponse();
$response->addHeader('X-Reload', true);
return $this->owner->redirect(Controller::join_links(
$this->owner->Link('show'),
$newPage->ID
));
}
}

View File

@ -0,0 +1,78 @@
<?php
namespace SilverStripe\Subsites\Extensions;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\HiddenField;
use SilverStripe\ORM\DataExtension;
use SilverStripe\ORM\DataQuery;
use SilverStripe\ORM\Queries\SQLSelect;
use SilverStripe\SiteConfig\SiteConfig;
use SilverStripe\Subsites\Model\Subsite;
use SilverStripe\Subsites\State\SubsiteState;
/**
* Extension for the SiteConfig object to add subsites support
*/
class SiteConfigSubsites extends DataExtension
{
private static $has_one = [
'Subsite' => Subsite::class, // The subsite that this page belongs to
];
/**
* Update any requests to limit the results to the current site
* @param SQLSelect $query
* @param DataQuery|null $dataQuery
*/
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...
if ($query->filtersOnID()) {
return;
}
$regexp = '/^(.*\.)?("|`)?SubsiteID("|`)?\s?=/';
foreach ($query->getWhereParameterised($parameters) as $predicate) {
if (preg_match($regexp ?? '', $predicate ?? '')) {
return;
}
}
$subsiteID = SubsiteState::singleton()->getSubsiteId();
if ($subsiteID === null) {
return;
}
$froms = $query->getFrom();
$froms = array_keys($froms ?? []);
$tableName = array_shift($froms);
if ($tableName !== SiteConfig::getSchema()->tableName(SiteConfig::class)) {
return;
}
$query->addWhere("\"$tableName\".\"SubsiteID\" IN ($subsiteID)");
}
public function onBeforeWrite()
{
if ((!is_numeric($this->owner->ID) || !$this->owner->ID) && !$this->owner->SubsiteID) {
$this->owner->SubsiteID = SubsiteState::singleton()->getSubsiteId();
}
}
/**
* Return a piece of text to keep DataObject cache keys appropriately specific
*/
public function cacheKeyComponent()
{
return 'subsite-' . SubsiteState::singleton()->getSubsiteId();
}
public function updateCMSFields(FieldList $fields)
{
$fields->push(HiddenField::create('SubsiteID', 'SubsiteID', SubsiteState::singleton()->getSubsiteId()));
}
}

View File

@ -0,0 +1,577 @@
<?php
namespace SilverStripe\Subsites\Extensions;
use SilverStripe\Dev\Deprecation;
use Page;
use SilverStripe\CMS\Forms\SiteTreeURLSegmentField;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Control\Controller;
use SilverStripe\Control\Director;
use SilverStripe\Control\HTTP;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Convert;
use SilverStripe\Forms\CheckboxField;
use SilverStripe\Forms\DropdownField;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\FormAction;
use SilverStripe\Forms\ToggleCompositeField;
use SilverStripe\i18n\i18n;
use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\DataExtension;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DataQuery;
use SilverStripe\ORM\Map;
use SilverStripe\ORM\Queries\SQLSelect;
use SilverStripe\Security\Member;
use SilverStripe\Security\Security;
use SilverStripe\SiteConfig\SiteConfig;
use SilverStripe\Subsites\Model\Subsite;
use SilverStripe\Subsites\Service\ThemeResolver;
use SilverStripe\Subsites\State\SubsiteState;
use SilverStripe\View\SSViewer;
use SilverStripe\VersionedAdmin\Controllers\HistoryViewerController;
/**
* Extension for the SiteTree object to add subsites support
*/
class SiteTreeSubsites extends DataExtension
{
private static $has_one = [
'Subsite' => Subsite::class, // The subsite that this page belongs to
];
private static $many_many = [
'CrossSubsiteLinkTracking' => SiteTree::class // Stored separately, as the logic for URL rewriting is different
];
private static $many_many_extraFields = [
'CrossSubsiteLinkTracking' => ['FieldName' => 'Varchar']
];
public function isMainSite()
{
return $this->owner->SubsiteID == 0;
}
/**
* Update any requests to limit the results to the current site
* @param SQLSelect $query
* @param DataQuery $dataQuery
*/
public function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null)
{
if (Subsite::$disable_subsite_filter) {
return;
}
if ($dataQuery && $dataQuery->getQueryParam('Subsite.filter') === false) {
return;
}
// If you're querying by ID, ignore the sub-site - this is a bit ugly...
// if(!$query->where
// || (strpos($query->where[0], ".\"ID\" = ") === false
// && strpos($query->where[0], ".`ID` = ") === false && strpos($query->where[0], ".ID = ") === false
// && strpos($query->where[0], "ID = ") !== 0)) {
if ($query->filtersOnID()) {
return;
}
$subsiteID = null;
if (Subsite::$force_subsite) {
$subsiteID = Subsite::$force_subsite;
} else {
$subsiteID = SubsiteState::singleton()->getSubsiteId();
}
if ($subsiteID === null) {
return;
}
// The foreach is an ugly way of getting the first key :-)
foreach ($query->getFrom() as $tableName => $info) {
// The tableName should be SiteTree or SiteTree_Live...
$siteTreeTableName = SiteTree::getSchema()->tableName(SiteTree::class);
if (strpos($tableName ?? '', $siteTreeTableName ?? '') === false) {
break;
}
$query->addWhere("\"$tableName\".\"SubsiteID\" IN ($subsiteID)");
break;
}
}
public function onBeforeWrite()
{
if (!$this->owner->ID && !$this->owner->SubsiteID) {
$this->owner->SubsiteID = SubsiteState::singleton()->getSubsiteId();
}
parent::onBeforeWrite();
}
public function updateCMSFields(FieldList $fields)
{
$subsites = Subsite::accessible_sites('CMS_ACCESS_CMSMain');
if ($subsites && $subsites->count()) {
$subsitesToMap = $subsites->exclude('ID', $this->owner->SubsiteID);
$subsitesMap = $subsitesToMap->map('ID', 'Title');
} else {
$subsitesMap = new Map(ArrayList::create());
}
$viewingPageHistory = Controller::has_curr() && Controller::curr() instanceof HistoryViewerController;
// Master page edit field (only allowed from default subsite to avoid inconsistent relationships)
$isDefaultSubsite = $this->owner->SubsiteID == 0 || $this->owner->Subsite()->DefaultSite;
if ($isDefaultSubsite && $subsitesMap->count() && !$viewingPageHistory) {
$fields->addFieldToTab(
'Root.Main',
ToggleCompositeField::create(
'SubsiteOperations',
_t(__CLASS__ . '.SubsiteOperations', 'Subsite Operations'),
[
DropdownField::create('CopyToSubsiteID', _t(
__CLASS__ . '.CopyToSubsite',
'Copy page to subsite'
), $subsitesMap),
CheckboxField::create(
'CopyToSubsiteWithChildren',
_t(__CLASS__ . '.CopyToSubsiteWithChildren', 'Include children pages?')
),
$copyAction = FormAction::create(
'copytosubsite',
_t(__CLASS__ . '.CopyAction', 'Copy')
)
]
)->setHeadingLevel(4)
);
$copyAction->addExtraClass('btn btn-primary font-icon-save ml-3');
// @todo check if this needs re-implementation
// $copyAction->includeDefaultJS(false);
}
// replace readonly link prefix
$subsite = $this->owner->Subsite();
$nested_urls_enabled = Config::inst()->get(SiteTree::class, 'nested_urls');
/** @var Subsite $subsite */
if ($subsite && $subsite->exists()) {
// Use baseurl from domain
$baseLink = $subsite->absoluteBaseURL();
// Add parent page if enabled
if ($nested_urls_enabled && $this->owner->ParentID) {
$baseLink = Controller::join_links(
$baseLink,
$this->owner->Parent()->RelativeLink(true)
);
}
$urlsegment = $fields->dataFieldByName('URLSegment');
if ($urlsegment && $urlsegment instanceof SiteTreeURLSegmentField) {
$urlsegment->setURLPrefix($baseLink);
}
}
}
/**
* Does the basic duplication, but doesn't write anything this means we can subclass this easier and do more
* complex relation duplication.
*
* Note that when duplicating including children, everything is written.
*
* @param Subsite|int $subsiteID
* @param bool $includeChildren
* @return SiteTree
*/
public function duplicateToSubsitePrep($subsiteID, $includeChildren)
{
if (is_object($subsiteID)) {
$subsiteID = $subsiteID->ID;
}
return SubsiteState::singleton()
->withState(function (SubsiteState $newState) use ($subsiteID, $includeChildren) {
$newState->setSubsiteId($subsiteID);
/** @var SiteTree $page */
$page = $this->owner;
try {
// We have no idea what the ParentID should be, but it shouldn't be the same as it was since
// we're now in a different subsite. As a workaround use the url-segment and subsite ID.
if ($page->Parent()) {
$parentSeg = $page->Parent()->URLSegment;
$newParentPage = Page::get()->filter('URLSegment', $parentSeg)->first();
$originalParentID = $page->ParentID;
if ($newParentPage) {
$page->ParentID = (int) $newParentPage->ID;
} else {
// reset it to the top level, so the user can decide where to put it
$page->ParentID = 0;
}
}
// Disable query filtering by subsite during actual duplication
$originalFilter = Subsite::$disable_subsite_filter;
Subsite::disable_subsite_filter(true);
return $includeChildren ? $page->duplicateWithChildren() : $page->duplicate(false);
} finally {
Subsite::disable_subsite_filter($originalFilter);
// Re-set the original parent ID for the current page
$page->ParentID = $originalParentID;
}
});
}
/**
* When duplicating a page, assign the current subsite ID from the state
*/
public function onBeforeDuplicate()
{
$subsiteId = SubsiteState::singleton()->getSubsiteId();
if ($subsiteId !== null) {
$this->owner->SubsiteID = $subsiteId;
}
}
/**
* Create a duplicate of this page and save it to another subsite
*
* @param Subsite|int $subsiteID The Subsite to copy to, or its ID
* @param boolean $includeChildren Whether to duplicate child pages too
* @return SiteTree The duplicated page
*/
public function duplicateToSubsite($subsiteID = null, $includeChildren = false)
{
/** @var SiteTree|SiteTreeSubsites */
$clone = $this->owner->duplicateToSubsitePrep($subsiteID, $includeChildren);
$clone->invokeWithExtensions('onBeforeDuplicateToSubsite', $this->owner);
if (!$includeChildren) {
// Write the new page if "include children" is false, because it is written by default when it's true.
$clone->write();
}
// Deprecated: manually duplicate any configured relationships
$clone->duplicateSubsiteRelations($this->owner);
$clone->invokeWithExtensions('onAfterDuplicateToSubsite', $this->owner);
return $clone;
}
/**
* Duplicate relations using a static property to define
* which ones we want to duplicate
*
* It may be that some relations are not diostinct to sub site so can stay
* whereas others may need to be duplicated
*
* This was originally deprecated - Use the "cascade_duplicates" config API instead
* Ideally this would be re-deprecated
*
* @param SiteTree $originalPage
*/
public function duplicateSubsiteRelations($originalPage)
{
$thisClass = $originalPage->ClassName;
$relations = Config::inst()->get($thisClass, 'duplicate_to_subsite_relations');
if ($relations && !empty($relations)) {
foreach ($relations as $relation) {
$items = $originalPage->$relation();
foreach ($items as $item) {
$duplicateItem = $item->duplicate(false);
$duplicateItem->{$thisClass.'ID'} = $this->owner->ID;
$duplicateItem->write();
}
}
}
}
/**
* @return SiteConfig
*/
public function alternateSiteConfig()
{
if (!$this->owner->SubsiteID) {
return false;
}
$sc = DataObject::get_one(SiteConfig::class, '"SubsiteID" = ' . $this->owner->SubsiteID);
if (!$sc) {
$sc = new SiteConfig();
$sc->SubsiteID = $this->owner->SubsiteID;
$sc->Title = _t('SilverStripe\\Subsites\\Model\\Subsite.SiteConfigTitle', 'Your Site Name');
$sc->Tagline = _t('SilverStripe\\Subsites\\Model\\Subsite.SiteConfigSubtitle', 'Your tagline here');
$sc->write();
}
return $sc;
}
/**
* Only allow editing of a page if the member satisfies one of the following conditions:
* - Is in a group which has access to the subsite this page belongs to
* - Is in a group with edit permissions on the "main site"
*
* If there are no subsites configured yet, this logic is skipped.
*
* @param Member|null $member
* @return bool|null
*/
public function canEdit($member = null)
{
if (!$member) {
$member = Security::getCurrentUser();
}
// Do not provide any input if there are no subsites configured
if (!Subsite::get()->exists()) {
return null;
}
// Find the sites that this user has access to
$goodSites = Subsite::accessible_sites('CMS_ACCESS_CMSMain', true, 'all', $member)->column('ID');
if (!is_null($this->owner->SubsiteID)) {
$subsiteID = $this->owner->SubsiteID;
} else {
// The relationships might not be available during the record creation when using a GridField.
// In this case the related objects will have empty fields, and SubsiteID will not be available.
//
// We do the second best: fetch the likely SubsiteID from the session. The drawback is this might
// make it possible to force relations to point to other (forbidden) subsites.
$subsiteID = SubsiteState::singleton()->getSubsiteId();
}
// Return true if they have access to this object's site
if (!(in_array(0, $goodSites ?? []) || in_array($subsiteID, $goodSites ?? []))) {
return false;
}
}
/**
* @param null $member
* @return bool
*/
public function canDelete($member = null)
{
if (!$member && $member !== false) {
$member = Security::getCurrentUser();
}
return $this->canEdit($member);
}
/**
* @param null $member
* @return bool
*/
public function canAddChildren($member = null)
{
if (!$member && $member !== false) {
$member = Security::getCurrentUser();
}
return $this->canEdit($member);
}
/**
* @param Member|null $member
* @return bool|null
*/
public function canPublish($member = null)
{
if (!$member && $member !== false) {
$member = Security::getCurrentUser();
}
return $this->canEdit($member);
}
/**
* Called by ContentController::init();
* @param $controller
*/
public static function contentcontrollerInit($controller)
{
/** @var Subsite $subsite */
$subsite = Subsite::currentSubsite();
if ($subsite && $subsite->Theme) {
SSViewer::set_themes(ThemeResolver::singleton()->getThemeList($subsite));
}
$ignore_subsite_locale = Config::inst()->get(self::class, 'ignore_subsite_locale');
if (!$ignore_subsite_locale
&& $subsite
&& $subsite->Language
&& i18n::getData()->validate($subsite->Language)
) {
i18n::set_locale($subsite->Language);
}
}
/**
* @param null $action
* @return string
*/
public function alternateAbsoluteLink($action = null)
{
// Generate the existing absolute URL and replace the domain with the subsite domain.
// This helps deal with Link() returning an absolute URL.
$url = Director::absoluteURL($this->owner->Link($action));
if ($this->owner->SubsiteID) {
$url = preg_replace('/\/\/[^\/]+\//', '//' . $this->owner->Subsite()->domain() . '/', $url ?? '');
}
return $url;
}
/**
* Use the CMS domain for iframed CMS previews to prevent single-origin violations
* and SSL cert problems. Always set SubsiteID to avoid errors because a page doesn't
* exist on the CMS domain.
*
* @param string &$link
* @param string|null $action
* @return string
*/
public function updatePreviewLink(&$link, $action = null)
{
$url = Director::absoluteURL($this->owner->Link($action));
$link = HTTP::setGetVar('SubsiteID', $this->owner->SubsiteID, $url);
return $link;
}
/**
* This function is marked as deprecated for removal in 5.0.0 in silverstripe/cms
* so now simply passes execution to where the functionality exists for backwards compatiblity.
* CMS 4.0.0 SiteTree already throws a SilverStripe deprecation error before calling this function.
* @deprecated 2.2.0 Use updatePreviewLink() instead
*
* @param string|null $action
* @return string
*/
public function alternatePreviewLink($action = null)
{
Deprecation::notice('2.2.0', 'Use updatePreviewLink() instead');
$link = '';
return $this->updatePreviewLink($link, $action);
}
/**
* Inject the subsite ID into the content so it can be used by frontend scripts.
* @param $tags
* @return string
*/
public function MetaTags(&$tags)
{
if ($this->owner->SubsiteID) {
$tags .= '<meta name="x-subsite-id" content="' . $this->owner->SubsiteID . "\" />\n";
}
return $tags;
}
public function augmentSyncLinkTracking()
{
// Set LinkTracking appropriately
$links = HTTP::getLinksIn($this->owner->Content);
$linkedPages = [];
if ($links) {
foreach ($links as $link) {
if (substr($link ?? '', 0, strlen('http://')) == 'http://') {
$withoutHttp = substr($link ?? '', strlen('http://'));
if (strpos($withoutHttp ?? '', '/') &&
strpos($withoutHttp ?? '', '/') < strlen($withoutHttp ?? '')
) {
$domain = substr($withoutHttp ?? '', 0, strpos($withoutHttp ?? '', '/'));
$rest = substr($withoutHttp ?? '', strpos($withoutHttp ?? '', '/') + 1);
$subsiteID = Subsite::getSubsiteIDForDomain($domain);
if ($subsiteID == 0) {
continue;
} // We have no idea what the domain for the main site is, so cant track links to it
$origDisableSubsiteFilter = Subsite::$disable_subsite_filter;
Subsite::disable_subsite_filter(true);
$candidatePage = SiteTree::get()->filter([
'URLSegment' => urldecode($rest),
'SubsiteID' => $subsiteID,
])->first();
Subsite::disable_subsite_filter($origDisableSubsiteFilter);
if ($candidatePage) {
$linkedPages[] = $candidatePage->ID;
} else {
$this->owner->HasBrokenLink = true;
}
}
}
}
}
$this->owner->CrossSubsiteLinkTracking()->setByIDList($linkedPages);
}
/**
* Ensure that valid url segments are checked within the correct subsite of the owner object,
* even if the current subsiteID is set to some other subsite.
*
* @return null|bool Either true or false, or null to not influence result
*/
public function augmentValidURLSegment()
{
// If this page is being filtered in the current subsite, then no custom validation query is required.
$subsite = Subsite::$force_subsite ?: SubsiteState::singleton()->getSubsiteId();
if (empty($this->owner->SubsiteID) || $subsite == $this->owner->SubsiteID) {
return null;
}
// Backup forced subsite
$prevForceSubsite = Subsite::$force_subsite;
Subsite::$force_subsite = $this->owner->SubsiteID;
// Repeat validation in the correct subsite
$isValid = $this->owner->validURLSegment();
// Restore
Subsite::$force_subsite = $prevForceSubsite;
return (bool)$isValid;
}
/**
* Return a piece of text to keep DataObject cache keys appropriately specific
*/
public function cacheKeyComponent()
{
return 'subsite-' . SubsiteState::singleton()->getSubsiteId();
}
/**
* @param Member $member
* @return boolean|null
*/
public function canCreate($member = null)
{
// Typically called on a singleton, so we're not using the Subsite() relation
$subsite = Subsite::currentSubsite();
if ($subsite && $subsite->exists() && $subsite->PageTypeBlacklist) {
// SS 4.1: JSON encoded. SS 4.0, comma delimited
$blacklist = json_decode($subsite->PageTypeBlacklist ?? '', true);
if ($blacklist === false) {
$blacklist = explode(',', $subsite->PageTypeBlacklist ?? '');
}
if (in_array(get_class($this->owner), (array) $blacklist)) {
return false;
}
}
}
}

View File

@ -1,8 +1,12 @@
<?php
namespace SilverStripe\Subsites\Extensions;
use SilverStripe\Core\Extension;
/*
* Simple extension to show admins in the menu of subsites.
* If an admin area should be available to a subsite, you can attach
* If an admin area should be available to a subsite, you can attach
* this class to your admin in config. eg:
*
* MyAdmin::add_extension('SubsiteMenuExtension');
@ -10,10 +14,10 @@
* Or you can include the subsiteCMSShowInMenu function in your admin class and have it return true
*/
class SubsiteMenuExtension extends Extension{
public function subsiteCMSShowInMenu(){
return true;
}
class SubsiteMenuExtension extends Extension
{
public function subsiteCMSShowInMenu()
{
return true;
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace SilverStripe\Subsites\Forms;
use SilverStripe\Forms\GridField\GridFieldDetailForm;
class GridFieldSubsiteDetailForm extends GridFieldDetailForm
{
protected $itemRequestClass = GridFieldSubsiteDetailFormItemRequest::class;
}

View File

@ -0,0 +1,63 @@
<?php
namespace SilverStripe\Subsites\Forms;
use SilverStripe\Forms\DropdownField;
use SilverStripe\Forms\Form;
use SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest;
use SilverStripe\Subsites\Model\Subsite;
class GridFieldSubsiteDetailFormItemRequest extends GridFieldDetailForm_ItemRequest
{
private static $allowed_actions = [
'ItemEditForm',
];
/**
* Builds an item edit form. The arguments to getCMSFields() are the popupController and
* popupFormName, however this is an experimental API and may change.
*
* @todo In the future, we will probably need to come up with a tigher object representing a partially
* complete controller with gaps for extra functionality. This, for example, would be a better way
* of letting Security/login put its log-in form inside a UI specified elsewhere.
*
* @return Form
* @see GridFieldDetailForm_ItemRequest::ItemEditForm()
*/
public function ItemEditForm()
{
$form = parent::ItemEditForm();
if ($this->record->ID == 0) {
$templates = Subsite::get()->sort('Title');
$templateArray = [];
if ($templates) {
$templateArray = $templates->map('ID', 'Title');
}
$templateDropdown = new DropdownField(
'TemplateID',
_t('Subsite.COPYSTRUCTURE', 'Copy structure from:'),
$templateArray
);
$templateDropdown->setEmptyString('(' . _t('Subsite.NOTEMPLATE', 'No template') . ')');
$form->Fields()->addFieldToTab('Root.Main', $templateDropdown);
}
return $form;
}
public function doSave($data, $form)
{
$new_record = $this->record->ID == 0;
if ($new_record && isset($data['TemplateID']) && !empty($data['TemplateID'])) {
$template = Subsite::get()->byID(intval($data['TemplateID']));
if ($template) {
$this->record = $template->duplicate();
}
}
return parent::doSave($data, $form);
}
}

View File

@ -0,0 +1,87 @@
<?php
namespace SilverStripe\Subsites\Forms;
use SilverStripe\Control\Controller;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Forms\TreeDropdownField;
use SilverStripe\Subsites\Model\Subsite;
use SilverStripe\View\Requirements;
use SilverStripe\Subsites\State\SubsiteState;
/**
* Wraps around a TreedropdownField to add ability for temporary
* switching of subsite sessions.
*
* @package subsites
*/
class SubsitesTreeDropdownField extends TreeDropdownField
{
private static $allowed_actions = [
'tree'
];
/**
* @var int
*/
protected $subsiteId = 0;
/**
* Extra HTML classes
*
* @skipUpgrade
* @var string[]
*/
protected $extraClasses = ['SubsitesTreeDropdownField'];
public function Field($properties = [])
{
$html = parent::Field($properties);
Requirements::javascript('silverstripe/subsites:client/javascript/SubsitesTreeDropdownField.js');
return $html;
}
/**
* Sets the subsite ID to use when generating the tree
*
* @param int $id
* @return $this
*/
public function setSubsiteId($id)
{
$this->subsiteId = $id;
return $this;
}
/**
* Get the subsite ID to use when generating the tree
*
* @return int
*/
public function getSubsiteId()
{
return $this->subsiteId;
}
/**
* Get the CMS tree with the provided subsite ID applied to the state
*
* {@inheritDoc}
*/
public function tree(HTTPRequest $request)
{
// Detect subsite ID from the request
if ($request->getVar($this->getName() . '_SubsiteID')) {
$this->setSubsiteId($request->getVar($this->getName() . '_SubsiteID'));
}
$results = SubsiteState::singleton()->withState(function (SubsiteState $newState) use ($request) {
$newState->setSubsiteId($this->getSubsiteId());
return parent::tree($request);
});
return $results;
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace SilverStripe\Subsites\Forms;
use SilverStripe\Forms\TextField;
/**
* A text field that accepts only valid domain names, but allows the wildcard (*) character
*/
class WildcardDomainField extends TextField
{
/**
* Validate this field as a valid hostname
*
* @param Validator $validator
* @return bool
*/
public function validate($validator)
{
if ($this->checkHostname($this->Value())) {
return true;
}
$validator->validationError(
$this->getName(),
_t('DomainNameField.INVALID_DOMAIN', 'Invalid domain name'),
'validation'
);
return false;
}
/**
* Check if the given hostname is valid.
*
* @param string $hostname
* @return bool True if this hostname is valid
*/
public function checkHostname($hostname)
{
return (bool)preg_match('/^([a-z0-9\*]+[\-\.\:])*([a-z0-9\*]+)$/', $hostname ?? '');
}
public function Type()
{
return 'text wildcarddomain';
}
}

View File

@ -0,0 +1,105 @@
<?php
namespace SilverStripe\Subsites\Middleware;
use SilverStripe\Admin\AdminRootController;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\Middleware\HTTPMiddleware;
use SilverStripe\Core\Config\Configurable;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\ORM\Connect\DatabaseException;
use SilverStripe\Subsites\Model\Subsite;
use SilverStripe\Subsites\State\SubsiteState;
class InitStateMiddleware implements HTTPMiddleware
{
use Configurable;
/**
* URL paths that should be considered as admin only, i.e. not frontend
*
* @config
* @var array
*/
private static $admin_url_paths = [
'dev/',
'graphql/',
];
public function process(HTTPRequest $request, callable $delegate)
{
try {
// Initialise and register the State
$state = SubsiteState::create();
Injector::inst()->registerService($state);
// Detect whether the request was made in the CMS area (or other admin-only areas)
$isAdmin = $this->getIsAdmin($request);
$state->setUseSessions($isAdmin);
// Detect the subsite ID
$subsiteId = $this->detectSubsiteId($request);
$state->setSubsiteId($subsiteId);
return $delegate($request);
} catch (DatabaseException $ex) {
$message = $ex->getMessage();
if (strpos($message, 'No database selected') !== false
|| preg_match('/\s*(table|relation) .* does(n\'t| not) exist/i', $message)
) {
// Database is not ready, ignore and continue. Either it doesn't exist or it has no tables
return $delegate($request);
}
throw $ex;
} finally {
// Persist to the session if using the CMS
if ($state->getUseSessions()) {
$request->getSession()->set('SubsiteID', $state->getSubsiteId());
}
}
}
/**
* Determine whether the website is being viewed from an admin protected area or not
*
* @param HTTPRequest $request
* @return bool
*/
public function getIsAdmin(HTTPRequest $request)
{
$adminPaths = static::config()->get('admin_url_paths');
$adminPaths[] = AdminRootController::admin_url();
$currentPath = rtrim($request->getURL() ?? '', '/') . '/';
foreach ($adminPaths as $adminPath) {
if (substr($currentPath ?? '', 0, strlen($adminPath ?? '')) === $adminPath) {
return true;
}
}
return false;
}
/**
* Use the given request to detect the current subsite ID
*
* @param HTTPRequest $request
* @return int
*/
protected function detectSubsiteId(HTTPRequest $request)
{
if ($request->getVar('SubsiteID') !== null) {
return (int) $request->getVar('SubsiteID');
}
if (SubsiteState::singleton()->getUseSessions() && $request->getSession()->get('SubsiteID') !== null) {
return (int) $request->getSession()->get('SubsiteID');
}
$subsiteIdFromDomain = Subsite::getSubsiteIDForDomain($request->getHost());
if ($subsiteIdFromDomain !== null) {
return (int) $subsiteIdFromDomain;
}
// Default fallback
return 0;
}
}

1016
src/Model/Subsite.php Normal file
View File

@ -0,0 +1,1016 @@
<?php
namespace SilverStripe\Subsites\Model;
use SilverStripe\Admin\CMSMenu;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Control\Director;
use SilverStripe\Core\Convert;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Core\Manifest\ModuleLoader;
use SilverStripe\Dev\Deprecation;
use SilverStripe\Forms\CheckboxSetField;
use SilverStripe\Forms\DropdownField;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\GridField\GridField;
use SilverStripe\Forms\GridField\GridFieldConfig_RecordEditor;
use SilverStripe\Forms\HiddenField;
use SilverStripe\Forms\ToggleCompositeField;
use SilverStripe\i18n\Data\Intl\IntlLocales;
use SilverStripe\i18n\i18n;
use SilverStripe\ORM\ArrayLib;
use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\DataList;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DB;
use SilverStripe\ORM\SS_List;
use SilverStripe\Security\Group;
use SilverStripe\Security\Member;
use SilverStripe\Security\Permission;
use SilverStripe\Security\Security;
use SilverStripe\Subsites\Service\ThemeResolver;
use SilverStripe\Subsites\State\SubsiteState;
use SilverStripe\Versioned\Versioned;
use UnexpectedValueException;
/**
* A dynamically created subsite. SiteTree objects can now belong to a subsite.
* You can simulate subsite access without setting up virtual hosts by appending ?SubsiteID=<ID> to the request.
*
* @package subsites
*/
class Subsite extends DataObject
{
private static $table_name = 'Subsite';
/**
* @var boolean $disable_subsite_filter If enabled, bypasses the query decoration
* to limit DataObject::get*() calls to a specific subsite. Useful for debugging. Note that
* for now this is left as a public static property to avoid having to nest and mutate the
* configuration manifest.
*/
public static $disable_subsite_filter = false;
/**
* Allows you to force a specific subsite ID, or comma separated list of IDs.
* Only works for reading. An object cannot be written to more than 1 subsite.
*
* @deprecated 2.0.0 Use SubsiteState::singleton()->withState() instead.
*/
public static $force_subsite = null;
/**
* Whether to write a host-map.php file
*
* @config
* @var boolean
*/
private static $write_hostmap = true;
/**
* Memory cache of accessible sites
*
* @array
*/
protected static $cache_accessible_sites = [];
/**
* Memory cache of subsite id for domains
*
* @var array
*/
protected static $cache_subsite_for_domain = [];
/**
* Numeric array of all themes which are allowed to be selected for all subsites.
* Corresponds to subfolder names within the /themes folder. By default, all themes contained in this folder
* are listed.
*
* @var array
*/
protected static $allowed_themes = [];
/**
* If set to TRUE, don't assume 'www.example.com' and 'example.com' are the same.
* Doesn't affect wildcard matching, so '*.example.com' will match 'www.example.com' (but not 'example.com')
* in both TRUE or FALSE setting.
*
* @config
* @var boolean
*/
private static $strict_subdomain_matching = false;
/**
* Respects the IsPublic flag when retrieving subsites
*
* @config
* @var boolean
*/
private static $check_is_public = true;
/**
* @var array
*/
private static $summary_fields = [
'Title',
'PrimaryDomain',
'IsPublic.Nice'
];
/**
* @var array
*/
private static $db = [
'Title' => 'Varchar(255)',
'RedirectURL' => 'Varchar(255)',
'DefaultSite' => 'Boolean',
'Theme' => 'Varchar',
'Language' => 'Varchar(6)',
// Used to hide unfinished/private subsites from public view.
// If unset, will default to true
'IsPublic' => 'Boolean',
// Comma-separated list of disallowed page types
'PageTypeBlacklist' => 'Text',
];
/**
* @var array
*/
private static $has_many = [
'Domains' => SubsiteDomain::class,
];
/**
* @var array
*/
private static $belongs_many_many = [
'Groups' => Group::class,
];
/**
* @var array
*/
private static $defaults = [
'IsPublic' => 1
];
/**
* @var array
*/
private static $searchable_fields = [
'Title',
'Domains.Domain',
'IsPublic',
];
/**
* @var string
*/
private static $default_sort = '"Title" ASC';
/**
* Set allowed themes
*
* @param array $themes - Numeric array of all themes which are allowed to be selected for all subsites.
*/
public static function set_allowed_themes($themes)
{
self::$allowed_themes = $themes;
}
/**
* Gets the subsite currently set in the session.
*
* @return DataObject The current Subsite
*/
public static function currentSubsite()
{
return Subsite::get()->byID(SubsiteState::singleton()->getSubsiteId());
}
/**
* This function gets the current subsite ID from the session. It used in the backend so Ajax requests
* use the correct subsite. The frontend handles subsites differently. It calls getSubsiteIDForDomain
* directly from ModelAsController::getNestedController. Only gets Subsite instances which have their
* {@link IsPublic} flag set to TRUE.
*
* You can simulate subsite access without creating virtual hosts by appending ?SubsiteID=<ID> to the request.
*
* @return int ID of the current subsite instance
*
* @deprecated 2.0.0 Use SubsiteState::singleton()->getSubsiteId() instead
*/
public static function currentSubsiteID()
{
Deprecation::notice('2.0.0', 'Use SubsiteState::singleton()->getSubsiteId() instead');
return SubsiteState::singleton()->getSubsiteId();
}
/**
* Switch to another subsite through storing the subsite identifier in the current PHP session.
* Only takes effect when {@link SubsiteState::singleton()->getUseSessions()} is set to TRUE.
*
* @param int|Subsite $subsite Either the ID of the subsite, or the subsite object itself
*/
public static function changeSubsite($subsite)
{
// Session subsite change only meaningful if the session is active.
// Otherwise we risk setting it to wrong value, e.g. if we rely on currentSubsiteID.
if (!SubsiteState::singleton()->getUseSessions()) {
return;
}
if (is_object($subsite)) {
$subsiteID = $subsite->ID;
} else {
$subsiteID = $subsite;
}
SubsiteState::singleton()->setSubsiteId($subsiteID);
// Set locale
if (is_object($subsite) && $subsite->Language !== '') {
$locale = (new IntlLocales())->localeFromLang($subsite->Language);
if ($locale) {
i18n::set_locale($locale);
}
}
Permission::reset();
}
/**
* Get a matching subsite for the given host, or for the current HTTP_HOST.
* Supports "fuzzy" matching of domains by placing an asterisk at the start of end of the string,
* for example matching all subdomains on *.example.com with one subsite,
* and all subdomains on *.example.org on another.
*
* @param $host string The host to find the subsite for. If not specified, $_SERVER['HTTP_HOST'] is used.
* @param bool $checkPermissions
* @return int Subsite ID
*/
public static function getSubsiteIDForDomain($host = null, $checkPermissions = true)
{
if ($host == null && isset($_SERVER['HTTP_HOST'])) {
$host = $_SERVER['HTTP_HOST'];
}
// Remove ports, we aren't concerned with them in terms of detecting subsites via domains
$hostParts = explode(':', $host ?? '', 2);
$host = reset($hostParts);
$matchingDomains = null;
$cacheKey = null;
if ($host) {
if (!static::config()->get('strict_subdomain_matching')) {
$host = preg_replace('/^www\./', '', $host ?? '');
}
$currentUserId = Security::getCurrentUser() ? Security::getCurrentUser()->ID : 0;
$cacheKey = implode('_', [$host, $currentUserId, static::config()->get('check_is_public')]);
if (isset(self::$cache_subsite_for_domain[$cacheKey])) {
return self::$cache_subsite_for_domain[$cacheKey];
}
$SQL_host = Convert::raw2sql($host);
$schema = DataObject::getSchema();
/** @skipUpgrade */
$domainTableName = $schema->tableName(SubsiteDomain::class);
if (!DB::get_schema()->hasTable($domainTableName)) {
// Table hasn't been created yet. Might be a dev/build, skip.
return 0;
}
$subsiteTableName = $schema->tableName(__CLASS__);
/** @skipUpgrade */
$matchingDomains = DataObject::get(
SubsiteDomain::class,
"'$SQL_host' LIKE replace(\"{$domainTableName}\".\"Domain\",'*','%')",
'"IsPrimary" DESC'
)->innerJoin(
$subsiteTableName,
'"' . $subsiteTableName . '"."ID" = "SubsiteDomain"."SubsiteID" AND "'
. $subsiteTableName . '"."IsPublic"=1'
);
}
if ($matchingDomains && $matchingDomains->count()) {
$subsiteIDs = array_unique($matchingDomains->column('SubsiteID') ?? []);
$subsiteDomains = array_unique($matchingDomains->column('Domain') ?? []);
if (sizeof($subsiteIDs ?? []) > 1) {
throw new UnexpectedValueException(sprintf(
"Multiple subsites match on '%s': %s",
$host,
implode(',', $subsiteDomains)
));
}
$subsiteID = $subsiteIDs[0];
} else {
if ($default = Subsite::get()->filter('DefaultSite', 1)->setQueriedColumns(['ID'])->first()) {
// Check for a 'default' subsite
$subsiteID = $default->ID;
} else {
// Default subsite id = 0, the main site
$subsiteID = 0;
}
}
if ($cacheKey) {
self::$cache_subsite_for_domain[$cacheKey] = $subsiteID;
}
return $subsiteID;
}
/**
*
* @param string $className
* @param string $filter
* @param string $sort
* @param string $join
* @param string $limit
* @return DataList
*/
public static function get_from_all_subsites($className, $filter = '', $sort = '', $join = '', $limit = '')
{
$result = DataObject::get($className, $filter, $sort, $join, $limit);
$result = $result->setDataQueryParam('Subsite.filter', false);
return $result;
}
/**
* Disable the sub-site filtering; queries will select from all subsites
* @param bool $disabled
*/
public static function disable_subsite_filter($disabled = true)
{
self::$disable_subsite_filter = $disabled;
}
/**
* Flush caches on database reset
*/
public static function on_db_reset()
{
self::$cache_accessible_sites = [];
self::$cache_subsite_for_domain = [];
}
/**
* Return all subsites, regardless of permissions (augmented with main site).
*
* @param bool $includeMainSite
* @param string $mainSiteTitle
* @return SS_List List of <a href='psi_element://Subsite'>Subsite</a> objects (DataList or ArrayList).
* objects (DataList or ArrayList).
*/
public static function all_sites($includeMainSite = true, $mainSiteTitle = 'Main site')
{
$subsites = Subsite::get();
if ($includeMainSite) {
$subsites = $subsites->toArray();
$mainSite = new Subsite();
$mainSite->Title = $mainSiteTitle;
array_unshift($subsites, $mainSite);
$subsites = ArrayList::create($subsites);
}
return $subsites;
}
/*
* Returns an ArrayList of the subsites accessible to the current user.
* It's enough for any section to be accessible for the site to be included.
*
* @return ArrayList of {@link Subsite} instances.
*/
public static function all_accessible_sites($includeMainSite = true, $mainSiteTitle = 'Main site', $member = null)
{
// Rationalise member arguments
if (!$member) {
$member = Security::getCurrentUser();
}
if (!$member) {
return ArrayList::create();
}
if (!is_object($member)) {
$member = DataObject::get_by_id(Member::class, $member);
}
$subsites = ArrayList::create();
// Collect subsites for all sections.
$menu = CMSMenu::get_viewable_menu_items();
foreach ($menu as $candidate) {
if ($candidate->controller) {
$accessibleSites = singleton($candidate->controller)->sectionSites(
$includeMainSite,
$mainSiteTitle,
$member
);
// Replace existing keys so no one site appears twice.
$subsites->merge($accessibleSites);
}
}
$subsites->removeDuplicates();
return $subsites;
}
/**
* Return the subsites that the current user can access by given permission.
* Sites will only be included if they have a Title.
*
* @param $permCode array|string Either a single permission code or an array of permission codes.
* @param $includeMainSite bool If true, the main site will be included if appropriate.
* @param $mainSiteTitle string The label to give to the main site
* @param $member int|Member The member attempting to access the sites
* @return DataList|ArrayList of {@link Subsite} instances
*/
public static function accessible_sites(
$permCode,
$includeMainSite = true,
$mainSiteTitle = 'Main site',
$member = null
) {
// Rationalise member arguments
if (!$member) {
$member = Security::getCurrentUser();
}
if (!$member) {
return new ArrayList();
}
if (!is_object($member)) {
$member = DataObject::get_by_id(Member::class, $member);
}
// Rationalise permCode argument
if (is_array($permCode)) {
$SQL_codes = "'" . implode("', '", Convert::raw2sql($permCode)) . "'";
} else {
$SQL_codes = "'" . Convert::raw2sql($permCode) . "'";
}
// Cache handling
$cacheKey = $SQL_codes . '-' . $member->ID . '-' . $includeMainSite . '-' . $mainSiteTitle;
if (isset(self::$cache_accessible_sites[$cacheKey])) {
return self::$cache_accessible_sites[$cacheKey];
}
/** @skipUpgrade */
$subsites = DataList::create(Subsite::class)
->where("\"Subsite\".\"Title\" != ''")
->leftJoin('Group_Subsites', '"Group_Subsites"."SubsiteID" = "Subsite"."ID"')
->innerJoin(
'Group',
'"Group"."ID" = "Group_Subsites"."GroupID" OR "Group"."AccessAllSubsites" = 1'
)
->innerJoin(
'Group_Members',
"\"Group_Members\".\"GroupID\"=\"Group\".\"ID\" AND \"Group_Members\".\"MemberID\" = $member->ID"
)
->innerJoin(
'Permission',
"\"Group\".\"ID\"=\"Permission\".\"GroupID\"
AND \"Permission\".\"Code\"
IN ($SQL_codes, 'CMS_ACCESS_LeftAndMain', 'ADMIN')"
);
if (!$subsites) {
$subsites = new ArrayList();
}
/** @var DataList $rolesSubsites */
/** @skipUpgrade */
$rolesSubsites = DataList::create(Subsite::class)
->where("\"Subsite\".\"Title\" != ''")
->leftJoin('Group_Subsites', '"Group_Subsites"."SubsiteID" = "Subsite"."ID"')
->innerJoin(
'Group',
'"Group"."ID" = "Group_Subsites"."GroupID" OR "Group"."AccessAllSubsites" = 1'
)
->innerJoin(
'Group_Members',
"\"Group_Members\".\"GroupID\"=\"Group\".\"ID\" AND \"Group_Members\".\"MemberID\" = $member->ID"
)
->innerJoin('Group_Roles', '"Group_Roles"."GroupID"="Group"."ID"')
->innerJoin('PermissionRole', '"Group_Roles"."PermissionRoleID"="PermissionRole"."ID"')
->innerJoin(
'PermissionRoleCode',
"\"PermissionRole\".\"ID\"=\"PermissionRoleCode\".\"RoleID\"
AND \"PermissionRoleCode\".\"Code\"
IN ($SQL_codes, 'CMS_ACCESS_LeftAndMain', 'ADMIN')"
);
if (!$subsites && $rolesSubsites) {
return $rolesSubsites;
}
$subsites = new ArrayList($subsites->toArray());
if ($rolesSubsites) {
foreach ($rolesSubsites as $subsite) {
if (!$subsites->find('ID', $subsite->ID)) {
$subsites->push($subsite);
}
}
}
if ($includeMainSite) {
if (!is_array($permCode)) {
$permCode = [$permCode];
}
if (self::hasMainSitePermission($member, $permCode)) {
$subsites = $subsites->toArray();
$mainSite = new Subsite();
$mainSite->Title = $mainSiteTitle;
array_unshift($subsites, $mainSite);
$subsites = ArrayList::create($subsites);
}
}
self::$cache_accessible_sites[$cacheKey] = $subsites;
return $subsites;
}
/**
* Write a host->domain map to subsites/host-map.php
*
* This is used primarily when using subsites in conjunction with StaticPublisher
*
* @param string $file - filepath of the host map to be written
* @return void
*/
public static function writeHostMap($file = null)
{
if (!static::config()->get('write_hostmap')) {
return;
}
if (!$file) {
$subsitesPath = ModuleLoader::getModule('silverstripe/subsites')->getRelativePath();
$file = Director::baseFolder() . $subsitesPath . '/host-map.php';
}
$hostmap = [];
$subsites = DataObject::get(Subsite::class);
if ($subsites) {
foreach ($subsites as $subsite) {
$domains = $subsite->Domains();
if ($domains) {
foreach ($domains as $domain) {
$domainStr = $domain->Domain;
if (!static::config()->get('strict_subdomain_matching')) {
$domainStr = preg_replace('/^www\./', '', $domainStr ?? '');
}
$hostmap[$domainStr] = $subsite->domain();
}
}
if ($subsite->DefaultSite) {
$hostmap['default'] = $subsite->domain();
}
}
}
$data = "<?php \n";
$data .= "// Generated by Subsite::writeHostMap() on " . date('d/M/y') . "\n";
$data .= '$subsiteHostmap = ' . var_export($hostmap, true) . ';';
if (is_writable(dirname($file ?? '')) || is_writable($file ?? '')) {
file_put_contents($file ?? '', $data);
}
}
/**
* Checks if a member can be granted certain permissions, regardless of the subsite context.
* Similar logic to {@link Permission::checkMember()}, but only returns TRUE
* if the member is part of a group with the "AccessAllSubsites" flag set.
* If more than one permission is passed to the method, at least one of them must
* be granted for if to return TRUE.
*
* @todo Allow permission inheritance through group hierarchy.
*
* @param Member Member to check against. Defaults to currently logged in member
* @param array $permissionCodes
* @return bool
*/
public static function hasMainSitePermission($member = null, $permissionCodes = ['ADMIN'])
{
if (!is_array($permissionCodes)) {
user_error('Permissions must be passed to Subsite::hasMainSitePermission as an array', E_USER_ERROR);
}
if (!$member && $member !== false) {
$member = Security::getCurrentUser();
}
if (!$member) {
return false;
}
if (!in_array('ADMIN', $permissionCodes ?? [])) {
$permissionCodes[] = 'ADMIN';
}
$SQLa_perm = Convert::raw2sql($permissionCodes);
$SQL_perms = join("','", $SQLa_perm);
$memberID = (int)$member->ID;
// Count this user's groups which can access the main site
$groupCount = DB::query("
SELECT COUNT(\"Permission\".\"ID\")
FROM \"Permission\"
INNER JOIN \"Group\"
ON \"Group\".\"ID\" = \"Permission\".\"GroupID\" AND \"Group\".\"AccessAllSubsites\" = 1
INNER JOIN \"Group_Members\"
ON \"Group_Members\".\"GroupID\" = \"Permission\".\"GroupID\"
WHERE \"Permission\".\"Code\"
IN ('$SQL_perms') AND \"Group_Members\".\"MemberID\" = {$memberID}
")->value();
// Count this user's groups which have a role that can access the main site
$roleCount = DB::query("
SELECT COUNT(\"PermissionRoleCode\".\"ID\")
FROM \"Group\"
INNER JOIN \"Group_Members\" ON \"Group_Members\".\"GroupID\" = \"Group\".\"ID\"
INNER JOIN \"Group_Roles\" ON \"Group_Roles\".\"GroupID\"=\"Group\".\"ID\"
INNER JOIN \"PermissionRole\" ON \"Group_Roles\".\"PermissionRoleID\"=\"PermissionRole\".\"ID\"
INNER JOIN \"PermissionRoleCode\" ON \"PermissionRole\".\"ID\"=\"PermissionRoleCode\".\"RoleID\"
WHERE \"PermissionRoleCode\".\"Code\" IN ('$SQL_perms')
AND \"Group\".\"AccessAllSubsites\" = 1
AND \"Group_Members\".\"MemberID\" = {$memberID}
")->value();
// There has to be at least one that allows access.
return ($groupCount + $roleCount > 0);
}
/**
* @todo Possible security issue, don't grant edit permissions to everybody.
* @param bool $member
* @return bool
*/
public function canEdit($member = false)
{
$extended = $this->extendedCan(__FUNCTION__, $member);
if ($extended !== null) {
return $extended;
}
return true;
}
/**
* Show the configuration fields for each subsite
*
* @return FieldList
*/
public function getCMSFields()
{
$this->beforeUpdateCMSFields(function (FieldList $fields) {
if ($this->exists()) {
// Add a GridField for domains to a new tab if the subsite has already been created
$fields->addFieldsToTab('Root.Domains', [
GridField::create(
'Domains',
'',
$this->Domains(),
GridFieldConfig_RecordEditor::create(10)
)
]);
}
// Remove the default scaffolded blacklist field, we replace it with a checkbox set field
// in a wrapper further down. The RedirectURL field is currently not in use.
$fields->removeByName(['PageTypeBlacklist', 'RedirectURL']);
$fields->addFieldToTab('Root.Main', DropdownField::create(
'Language',
$this->fieldLabel('Language'),
Injector::inst()->get(IntlLocales::class)->getLocales()
), 'DefaultSite');
$fields->addFieldsToTab('Root.Main', [
ToggleCompositeField::create(
'PageTypeBlacklistToggle',
_t(__CLASS__ . '.PageTypeBlacklistField', 'Disallow page types?'),
[
CheckboxSetField::create('PageTypeBlacklist', '', $this->getPageTypeMap())
]
)->setHeadingLevel(4),
HiddenField::create('ID', '', $this->ID),
HiddenField::create('IsSubsite', '', 1)
]);
// If there are any themes available, add the dropdown
$themes = $this->allowedThemes();
if (!empty($themes)) {
$fields->addFieldToTab(
'Root.Main',
DropdownField::create('Theme', $this->fieldLabel('Theme'), $this->allowedThemes(), $this->Theme)
->setEmptyString(_t(__CLASS__ . '.ThemeFieldEmptyString', '-')),
'PageTypeBlacklistToggle'
);
}
// Targetted by the XHR PJAX JavaScript to reload the subsite list in the CMS
$fields->fieldByName('Root.Main')->addExtraClass('subsite-model');
// We don't need the Groups many many tab
$fields->removeByName('Groups');
// Rename the main tab to configuration
$fields->fieldByName('Root.Main')->setTitle(_t(__CLASS__ . '.ConfigurationTab', 'Configuration'));
});
return parent::getCMSFields();
}
/**
* Return a list of the different page types available to the CMS
*
* @return array
*/
public function getPageTypeMap()
{
$pageTypeMap = [];
$pageTypes = SiteTree::page_type_classes();
foreach ($pageTypes as $pageType) {
$pageTypeMap[$pageType] = singleton($pageType)->i18n_singular_name();
}
asort($pageTypeMap);
return $pageTypeMap;
}
/**
*
* @param boolean $includerelations
* @return array
*/
public function fieldLabels($includerelations = true)
{
$labels = parent::fieldLabels($includerelations);
$labels['Title'] = _t('Subsites.TitleFieldLabel', 'Subsite Name');
$labels['RedirectURL'] = _t('Subsites.RedirectURLFieldLabel', 'Redirect URL');
$labels['DefaultSite'] = _t('Subsites.DefaultSiteFieldLabel', 'Default site');
$labels['Theme'] = _t('Subsites.ThemeFieldLabel', 'Theme');
$labels['Language'] = _t('Subsites.LanguageFieldLabel', 'Language');
$labels['IsPublic.Nice'] = _t('Subsites.IsPublicFieldLabel', 'Enable public access');
$labels['PageTypeBlacklist'] = _t('Subsites.PageTypeBlacklistFieldLabel', 'Page Type Blacklist');
$labels['Domains.Domain'] = _t('Subsites.DomainFieldLabel', 'Domain');
$labels['PrimaryDomain'] = _t('Subsites.PrimaryDomainFieldLabel', 'Primary Domain');
return $labels;
}
/**
* Return the themes that can be used with this subsite, as an array of themecode => description
*
* @return array
*/
public function allowedThemes()
{
if (($themes = self::$allowed_themes) || ($themes = ThemeResolver::singleton()->getCustomThemeOptions())) {
return ArrayLib::valuekey($themes);
}
$themes = [];
if (is_dir(THEMES_PATH)) {
foreach (scandir(THEMES_PATH) as $theme) {
if ($theme[0] == '.') {
continue;
}
$theme = strtok($theme ?? '', '_');
$themes[$theme] = $theme;
}
ksort($themes);
}
return $themes;
}
/**
* @return string Current locale of the subsite
*/
public function getLanguage()
{
if ($this->getField('Language')) {
return $this->getField('Language');
}
return i18n::get_locale();
}
/**
*
* @return \SilverStripe\ORM\ValidationResult
*/
public function validate()
{
$result = parent::validate();
if (!$this->Title) {
$result->addError(_t(__CLASS__ . '.ValidateTitle', 'Please add a "Title"'));
}
return $result;
}
/**
* Whenever a Subsite is written, rewrite the hostmap and create some default pages
*
* @return void
*/
public function onAfterWrite()
{
Subsite::writeHostMap();
if ($this->isChanged('ID')) {
$this->createDefaultPages();
}
parent::onAfterWrite();
}
/**
* Automatically create default pages for new subsites
*/
protected function createDefaultPages()
{
SubsiteState::singleton()->withState(function (SubsiteState $newState) {
$newState->setSubsiteId($this->ID);
// Silence DB schema output
DB::quiet();
$siteTree = new SiteTree();
$siteTree->requireDefaultRecords();
});
}
/**
* Return the primary domain of this site. Tries to "normalize" the domain name,
* by replacing potential wildcards.
*
* @return string The full domain name of this subsite (without protocol prefix)
*/
public function domain()
{
// Get best SubsiteDomain object
$domainObject = $this->getPrimarySubsiteDomain();
if ($domainObject) {
return $domainObject->SubstitutedDomain;
}
// If there are no objects, default to the current hostname
return Director::host();
}
/**
* Finds the primary {@see SubsiteDomain} object for this subsite
*
* @return SubsiteDomain
*/
public function getPrimarySubsiteDomain()
{
return $this
->Domains()
->sort('"IsPrimary" DESC')
->first();
}
/**
*
* @return string - The full domain name of this subsite (without protocol prefix)
*/
public function getPrimaryDomain()
{
return $this->domain();
}
/**
* Get the absolute URL for this subsite
* @return string
*/
public function absoluteBaseURL()
{
// Get best SubsiteDomain object
$domainObject = $this->getPrimarySubsiteDomain();
if ($domainObject) {
return $domainObject->absoluteBaseURL();
}
// Fall back to the current base url
return Director::absoluteBaseURL();
}
/**
* Javascript admin action to duplicate this subsite
*
* @return string - javascript
*/
public function adminDuplicate()
{
$newItem = $this->duplicate();
$message = _t(
__CLASS__ . '.CopyMessage',
'Created a copy of {title}',
['title' => Convert::raw2js($this->Title)]
);
return <<<JS
statusMessage($message, 'good');
$('Form_EditForm').loadURLFromServer('admin/subsites/show/$newItem->ID');
JS;
}
/**
* Make this subsite the current one
*/
public function activate()
{
Subsite::changeSubsite($this);
}
/**
*
* @param array $permissionCodes
* @return DataList
*/
public function getMembersByPermission($permissionCodes = ['ADMIN'])
{
if (!is_array($permissionCodes)) {
user_error('Permissions must be passed to Subsite::getMembersByPermission as an array', E_USER_ERROR);
}
$SQL_permissionCodes = Convert::raw2sql($permissionCodes);
$SQL_permissionCodes = join("','", $SQL_permissionCodes);
return DataObject::get(
Member::class,
"\"Group\".\"SubsiteID\" = $this->ID AND \"Permission\".\"Code\" IN ('$SQL_permissionCodes')",
'',
'LEFT JOIN "Group_Members" ON "Member"."ID" = "Group_Members"."MemberID"
LEFT JOIN "Group" ON "Group"."ID" = "Group_Members"."GroupID"
LEFT JOIN "Permission" ON "Permission"."GroupID" = "Group"."ID"'
);
}
/**
* Duplicate this subsite
* @param bool $doWrite
* @param string $manyMany
* @return DataObject
*/
public function duplicate($doWrite = true, $manyMany = 'many_many')
{
$duplicate = parent::duplicate($doWrite);
$oldSubsiteID = SubsiteState::singleton()->getSubsiteId();
self::changeSubsite($this->ID);
/*
* Copy data from this object to the given subsite. Does this using an iterative depth-first search.
* This will make sure that the new parents on the new subsite are correct, and there are no funny
* issues with having to check whether or not the new parents have been added to the site tree
* when a page, etc, is duplicated
*/
$stack = [[0, 0]];
while (count($stack ?? []) > 0) {
list($sourceParentID, $destParentID) = array_pop($stack);
$children = Versioned::get_by_stage('Page', 'Live', "\"ParentID\" = $sourceParentID", '');
if ($children) {
foreach ($children as $child) {
self::changeSubsite($duplicate->ID); //Change to destination subsite
$childClone = $child->duplicateToSubsite($duplicate, false);
$childClone->ParentID = $destParentID;
$childClone->writeToStage('Stage');
$childClone->copyVersionToStage('Stage', 'Live');
self::changeSubsite($this->ID); //Change Back to this subsite
array_push($stack, [$child->ID, $childClone->ID]);
}
}
}
self::changeSubsite($oldSubsiteID);
return $duplicate;
}
}

227
src/Model/SubsiteDomain.php Normal file
View File

@ -0,0 +1,227 @@
<?php
namespace SilverStripe\Subsites\Model;
use SilverStripe\Control\Controller;
use SilverStripe\Control\Director;
use SilverStripe\Forms\CheckboxField;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\OptionsetField;
use SilverStripe\ORM\DataObject;
use SilverStripe\Subsites\Forms\WildcardDomainField;
/**
* @property string $Domain domain name of this subsite. Can include wildcards. Do not include the URL scheme here
* @property string $Protocol Required protocol (http or https) if only one is supported. 'automatic' implies
* that any links to this subsite should use the current protocol, and that both are supported.
* @property string $SubstitutedDomain Domain name with all wildcards filled in
* @property string $FullProtocol Full protocol including ://
* @property bool $IsPrimary Is this the primary subdomain?
*/
class SubsiteDomain extends DataObject
{
private static $table_name = 'SubsiteDomain';
/**
*
* @var string
*/
private static $default_sort = '"IsPrimary" DESC';
/** *
* @var array
*/
private static $db = [
'Domain' => 'Varchar(255)',
'Protocol' => "Enum('http,https,automatic','automatic')",
'IsPrimary' => 'Boolean',
];
/**
* Specifies that this subsite is http only
*/
const PROTOCOL_HTTP = 'http';
/**
* Specifies that this subsite is https only
*/
const PROTOCOL_HTTPS = 'https';
/**
* Specifies that this subsite supports both http and https
*/
const PROTOCOL_AUTOMATIC = 'automatic';
/**
* Get the descriptive title for this domain
*
* @return string
*/
public function getTitle()
{
return $this->Domain;
}
/**
*
* @var array
*/
private static $has_one = [
'Subsite' => Subsite::class,
];
/**
* @config
* @var array
*/
private static $summary_fields = [
'Domain',
'IsPrimary',
];
/*** @config
* @var array
*/
private static $casting = [
'SubstitutedDomain' => 'Varchar',
'FullProtocol' => 'Varchar',
'AbsoluteLink' => 'Varchar',
];
/**
* Whenever a Subsite Domain is written, rewrite the hostmap
*
* @return void
*/
public function onAfterWrite()
{
Subsite::writeHostMap();
parent::onAfterWrite();
}
/**
*
* @return FieldList
*/
public function getCMSFields()
{
$protocols = [
self::PROTOCOL_HTTP => _t(__CLASS__ . '.PROTOCOL_HTTP', 'http://'),
self::PROTOCOL_HTTPS => _t(__CLASS__ . '.PROTOCOL_HTTPS', 'https://'),
self::PROTOCOL_AUTOMATIC => _t(__CLASS__ . '.PROTOCOL_AUTOMATIC', 'Automatic')
];
$fields = FieldList::create(
WildcardDomainField::create('Domain', $this->fieldLabel('Domain'), null, 255)
->setDescription(_t(
__CLASS__ . '.DOMAIN_DESCRIPTION',
'Hostname of this subsite (exclude protocol). Allows wildcards (*).'
)),
OptionsetField::create('Protocol', $this->fieldLabel('Protocol'), $protocols)
->setValue($this->Protocol ?: self::PROTOCOL_AUTOMATIC)
->setDescription(_t(
__CLASS__ . '.PROTOCOL_DESCRIPTION',
'When generating links to this subsite, use the selected protocol. <br />' .
'Selecting \'Automatic\' means subsite links will default to the current protocol.'
)),
CheckboxField::create('IsPrimary', $this->fieldLabel('IsPrimary'))
->setDescription(_t(
__CLASS__ . '.ISPRIMARY_DESCRIPTION',
'Mark this as the default domain for this subsite'
))
);
$this->extend('updateCMSFields', $fields);
return $fields;
}
/**
*
* @param bool $includerelations
* @return array
*/
public function fieldLabels($includerelations = true)
{
$labels = parent::fieldLabels($includerelations);
$labels['Domain'] = _t(__CLASS__ . '.DOMAIN', 'Domain');
$labels['Protocol'] = _t(__CLASS__ . '.Protocol', 'Protocol');
$labels['IsPrimary'] = _t(__CLASS__ . '.IS_PRIMARY', 'Is Primary Domain?');
return $labels;
}
/**
* Get the link to this subsite
*
* @return string
*/
public function Link()
{
return $this->getFullProtocol() . $this->Domain;
}
/**
* Gets the full protocol (including ://) for this domain
*
* @return string
*/
public function getFullProtocol()
{
switch ($this->Protocol) {
case self::PROTOCOL_HTTPS:
return 'https://';
case self::PROTOCOL_HTTP:
return 'http://';
default:
return Director::protocol();
}
}
/**
* Retrieves domain name with wildcards substituted with actual values
*
* @todo Refactor domains into separate wildcards / primary domains
*
* @return string
*/
public function getSubstitutedDomain()
{
$currentHost = Director::host();
// If there are wildcards in the primary domain (not recommended), make some
// educated guesses about what to replace them with:
$domain = preg_replace('/\.\*$/', ".{$currentHost}", $this->Domain ?? '');
// Default to "subsite." prefix for first wildcard
// TODO Whats the significance of "subsite" in this context?!
$domain = preg_replace('/^\*\./', "subsite.", $domain ?? '');
// *Only* removes "intermediate" subdomains, so 'subdomain.www.domain.com' becomes 'subdomain.domain.com'
$domain = str_replace('.www.', '.', $domain ?? '');
return $domain;
}
/**
* Get absolute link for this domain
*
* @return string
*/
public function getAbsoluteLink()
{
return $this->getFullProtocol() . $this->getSubstitutedDomain();
}
/**
* Get absolute baseURL for this domain
*
* @return string
*/
public function absoluteBaseURL()
{
return Controller::join_links(
$this->getAbsoluteLink(),
Director::baseURL()
);
}
}

View File

@ -0,0 +1,250 @@
<?php
namespace SilverStripe\Subsites\Pages;
use SilverStripe\CMS\Controllers\CMSPageEditController;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\CMS\Model\VirtualPage;
use SilverStripe\Control\Controller;
use SilverStripe\Core\Config\Config;
use SilverStripe\Forms\DropdownField;
use SilverStripe\Forms\LiteralField;
use SilverStripe\Forms\TextareaField;
use SilverStripe\Forms\TextField;
use SilverStripe\Forms\TreeDropdownField;
use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\DataObject;
use SilverStripe\Subsites\Forms\SubsitesTreeDropdownField;
use SilverStripe\Subsites\Model\Subsite;
use SilverStripe\Subsites\State\SubsiteState;
use SilverStripe\View\ArrayData;
class SubsitesVirtualPage extends VirtualPage
{
private static $table_name = 'SubsitesVirtualPage';
private static $description = 'Displays the content of a page on another subsite';
private static $db = [
'CustomMetaTitle' => 'Varchar(255)',
'CustomMetaKeywords' => 'Varchar(255)',
'CustomMetaDescription' => 'Text',
'CustomExtraMeta' => 'HTMLText'
];
private static $non_virtual_fields = [
'SubsiteID'
];
public function getCMSFields()
{
$fields = parent::getCMSFields();
$subsites = DataObject::get(Subsite::class);
if (!$subsites) {
$subsites = new ArrayList();
} else {
$subsites = ArrayList::create($subsites->toArray());
}
$subsites->push(new ArrayData(['Title' => 'Main site', 'ID' => 0]));
$fields->addFieldToTab(
'Root.Main',
DropdownField::create(
'CopyContentFromID_SubsiteID',
_t(__CLASS__ . '.SubsiteField', 'Subsite'),
$subsites->map('ID', 'Title')
)->addExtraClass('subsitestreedropdownfield-chooser no-change-track'),
'CopyContentFromID'
);
// Setup the linking to the original page.
$pageSelectionField = SubsitesTreeDropdownField::create(
'CopyContentFromID',
_t('SilverStripe\\CMS\\Model\\VirtualPage.CHOOSE', 'Linked Page'),
SiteTree::class,
'ID',
'MenuTitle'
);
$fields->addFieldToTab(
'Root.Main',
TreeDropdownField::create('CopyContentFromID', 'Linked Page', SiteTree::class)
);
if (Controller::has_curr() && Controller::curr()->getRequest()) {
$subsiteID = (int) Controller::curr()->getRequest()->requestVar('CopyContentFromID_SubsiteID');
$pageSelectionField->setSubsiteID($subsiteID);
}
$fields->replaceField('CopyContentFromID', $pageSelectionField);
// Create links back to the original object in the CMS
if ($this->CopyContentFromID) {
$editLink = Controller::join_links(
CMSPageEditController::singleton()->Link('show'),
$this->CopyContentFromID
);
$linkToContent = "
<a class=\"cmsEditlink\" href=\"$editLink\">" .
_t('SilverStripe\\CMS\\Model\\VirtualPage.EDITCONTENT', 'Click here to edit the content') .
'</a>';
$fields->removeByName('VirtualPageContentLinkLabel');
$fields->addFieldToTab(
'Root.Main',
$linkToContentLabelField = LiteralField::create('VirtualPageContentLinkLabel', $linkToContent),
'Title'
);
}
$fields->addFieldToTab(
'Root.Main',
TextField::create(
'CustomMetaTitle',
$this->fieldLabel('CustomMetaTitle')
)->setDescription(_t(__CLASS__ . '.OverrideNote', 'Overrides inherited value from the source')),
'MetaTitle'
);
$fields->addFieldToTab(
'Root.Main',
TextareaField::create(
'CustomMetaKeywords',
$this->fieldLabel('CustomMetaKeywords')
)->setDescription(_t(__CLASS__ . '.OverrideNote', 'Overrides inherited value from the source')),
'MetaKeywords'
);
$fields->addFieldToTab(
'Root.Main',
TextareaField::create(
'CustomMetaDescription',
$this->fieldLabel('CustomMetaDescription')
)->setDescription(_t(__CLASS__ . '.OverrideNote', 'Overrides inherited value from the source')),
'MetaDescription'
);
$fields->addFieldToTab(
'Root.Main',
TextField::create(
'CustomExtraMeta',
$this->fieldLabel('CustomExtraMeta')
)->setDescription(_t(__CLASS__ . '.OverrideNote', 'Overrides inherited value from the source')),
'ExtraMeta'
);
return $fields;
}
public function fieldLabels($includerelations = true)
{
$labels = parent::fieldLabels($includerelations);
$labels['CustomMetaTitle'] = _t('SilverStripe\\Subsites\\Model\\Subsite.CustomMetaTitle', 'Title');
$labels['CustomMetaKeywords'] = _t(
'SilverStripe\\Subsites\\Model\\Subsite.CustomMetaKeywords',
'Keywords'
);
$labels['CustomMetaDescription'] = _t(
'SilverStripe\\Subsites\\Model\\Subsite.CustomMetaDescription',
'Description'
);
$labels['CustomExtraMeta'] = _t(
'SilverStripe\\Subsites\\Model\\Subsite.CustomExtraMeta',
'Custom Meta Tags'
);
return $labels;
}
public function getCopyContentFromID_SubsiteID()
{
if ($this->CopyContentFromID) {
return (int) $this->CopyContentFrom()->SubsiteID;
}
return SubsiteState::singleton()->getSubsiteId();
}
public function getVirtualFields()
{
$fields = parent::getVirtualFields();
foreach ($fields as $k => $v) {
if ($v == 'SubsiteID') {
unset($fields[$k]);
}
}
foreach (self::$db as $field => $type) {
if (in_array($field, $fields ?? [])) {
unset($fields[array_search($field, $fields)]);
}
}
return $fields;
}
public function syncLinkTracking()
{
$oldState = Subsite::$disable_subsite_filter;
Subsite::$disable_subsite_filter = true;
if ($this->CopyContentFromID) {
$this->HasBrokenLink = DataObject::get_by_id(SiteTree::class, $this->CopyContentFromID) ? false : true;
}
Subsite::$disable_subsite_filter = $oldState;
}
public function onBeforeWrite()
{
parent::onBeforeWrite();
if ($this->CustomMetaTitle) {
$this->MetaTitle = $this->CustomMetaTitle;
} else {
$this->MetaTitle = $this->ContentSource()->MetaTitle ?: $this->MetaTitle;
}
if ($this->CustomMetaKeywords) {
$this->MetaKeywords = $this->CustomMetaKeywords;
} else {
$this->MetaKeywords = $this->ContentSource()->MetaKeywords ?: $this->MetaKeywords;
}
if ($this->CustomMetaDescription) {
$this->MetaDescription = $this->CustomMetaDescription;
} else {
$this->MetaDescription = $this->ContentSource()->MetaDescription ?: $this->MetaDescription;
}
if ($this->CustomExtraMeta) {
$this->ExtraMeta = $this->CustomExtraMeta;
} else {
$this->ExtraMeta = $this->ContentSource()->ExtraMeta ?: $this->ExtraMeta;
}
}
public function validURLSegment()
{
$isValid = parent::validURLSegment();
// Veto the validation rules if its false. In this case, some logic
// needs to be duplicated from parent to find out the exact reason the validation failed.
if (!$isValid) {
$filters = [
'URLSegment' => $this->URLSegment,
'ID:not' => $this->ID,
];
if (Config::inst()->get(SiteTree::class, 'nested_urls')) {
$filters['ParentID'] = $this->ParentID ?: 0;
}
$origDisableSubsiteFilter = Subsite::$disable_subsite_filter;
Subsite::disable_subsite_filter();
$existingPage = SiteTree::get()->filter($filters)->first();
Subsite::disable_subsite_filter($origDisableSubsiteFilter);
$existingPageInSubsite = SiteTree::get()->filter($filters)->first();
// If URL has been vetoed because of an existing page,
// be more specific and allow same URLSegments in different subsites
$isValid = !($existingPage && $existingPageInSubsite);
}
return $isValid;
}
}

View File

@ -0,0 +1,85 @@
<?php
namespace SilverStripe\Subsites\Reports;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\TreeMultiselectField;
use SilverStripe\Reports\ReportWrapper;
use SilverStripe\Subsites\Model\Subsite;
/**
* Creates a subsite-aware version of another report.
* Pass another report (or its classname) into the constructor.
*/
class SubsiteReportWrapper extends ReportWrapper
{
/**
* @return FieldList
*/
public function parameterFields()
{
$subsites = Subsite::accessible_sites('CMS_ACCESS_CMSMain', true);
$options = $subsites->toDropdownMap('ID', 'Title');
$subsiteField = TreeMultiselectField::create(
'Subsites',
_t(__CLASS__ . '.ReportDropdown', 'Sites'),
$options
);
$subsiteField->setValue(array_keys($options ?? []));
// We don't need to make the field editable if only one subsite is available
if (sizeof($options ?? []) <= 1) {
$subsiteField = $subsiteField->performReadonlyTransformation();
}
$fields = parent::parameterFields();
if ($fields) {
$fields->insertBefore($subsiteField, $fields->First()->Name());
} else {
$fields = FieldList::create($subsiteField);
}
return $fields;
}
/**
* @return array
*/
public function columns()
{
$columns = parent::columns();
$columns['Subsite.Title'] = Subsite::class;
return $columns;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Querying
/**
* @param arary $params
* @return void
*/
public function beforeQuery($params)
{
// The user has select a few specific sites
if (!empty($params['Subsites'])) {
Subsite::$force_subsite = $params['Subsites'];
// Default: restrict to all accessible sites
} else {
$subsites = Subsite::accessible_sites('CMS_ACCESS_CMSMain');
$options = $subsites->toDropdownMap('ID', 'Title');
Subsite::$force_subsite = join(',', array_keys($options ?? []));
}
}
/**
* @return void
*/
public function afterQuery()
{
// Manually manage the subsite filtering
Subsite::$force_subsite = null;
}
}

View File

@ -0,0 +1,99 @@
<?php
namespace SilverStripe\Subsites\Service;
use SilverStripe\Core\Config\Configurable;
use SilverStripe\Core\Injector\Injectable;
use SilverStripe\Subsites\Model\Subsite;
use SilverStripe\View\SSViewer;
class ThemeResolver
{
use Injectable;
use Configurable;
/**
* Cascading definitions for themes, keyed by the name they should appear under in the CMS. For example:
*
* [
* 'theme-1' => [
* '$public',
* 'starter',
* '$default',
* ],
* 'theme-2' => [
* 'custom',
* 'watea',
* 'starter',
* '$public',
* '$default',
* ]
* ]
*
* @config
* @var null|array[]
*/
private static $theme_options;
/**
* Get the list of themes for the given sub site that can be given to SSViewer::set_themes
*
* @param Subsite $site
* @return array
*/
public function getThemeList(Subsite $site)
{
$themes = array_values(SSViewer::get_themes() ?? []);
$siteTheme = $site->Theme;
if (!$siteTheme) {
return $themes;
}
$customOptions = $this->config()->get('theme_options');
if ($customOptions && isset($customOptions[$siteTheme])) {
return $customOptions[$siteTheme];
}
// Ensure themes don't cascade "up" the list
$index = array_search($siteTheme, $themes ?? []);
if ($index > 0) {
// 4.0 didn't have support for themes in the public webroot
$constant = SSViewer::class . '::PUBLIC_THEME';
$publicConstantDefined = defined($constant ?? '');
// Check if the default is public themes
$publicDefault = $publicConstantDefined && $themes[0] === SSViewer::PUBLIC_THEME;
// Take only those that appear after theme chosen (non-inclusive)
$themes = array_slice($themes ?? [], $index + 1);
// Add back in public
if ($publicDefault) {
array_unshift($themes, SSViewer::PUBLIC_THEME);
}
}
// Add our theme
array_unshift($themes, $siteTheme);
return $themes;
}
/**
* Get a list of custom cascading theme definitions if available
*
* @return null|array
*/
public function getCustomThemeOptions()
{
$config = $this->config()->get('theme_options');
if (!$config) {
return null;
}
return array_keys($config ?? []);
}
}

132
src/State/SubsiteState.php Normal file
View File

@ -0,0 +1,132 @@
<?php
namespace SilverStripe\Subsites\State;
use SilverStripe\Core\Injector\Injectable;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Core\Resettable;
use SilverStripe\Dev\Deprecation;
/**
* SubsiteState provides static access to the current state for subsite related data during a request
*/
class SubsiteState implements Resettable
{
use Injectable;
/**
* @var int|null
*/
protected $subsiteId;
/**
* @var int|null
*/
protected $originalSubsiteId;
/**
* @var bool
*/
protected $useSessions;
/**
* Get the current subsite ID
*
* @return int|null
*/
public function getSubsiteId()
{
return $this->subsiteId;
}
/**
* Set the current subsite ID, and track the first subsite ID set as the "original". This is used to check
* whether the ID has been changed through a request.
*
* @param int $id
* @return $this
*/
public function setSubsiteId($id)
{
if (!ctype_digit((string) $id) && !is_null($id)) {
Deprecation::notice('2.8.0', 'Passing multiple IDs is deprecated, only pass a single ID instead.');
}
if (is_null($this->originalSubsiteId)) {
$this->originalSubsiteId = (int) $id;
}
$this->subsiteId = (int) $id;
return $this;
}
/**
* Get whether to use sessions for storing the subsite ID
*
* @return bool
*/
public function getUseSessions()
{
return $this->useSessions;
}
/**
* Set whether to use sessions for storing the subsite ID
*
* @param bool $useSessions
* @return $this
*/
public function setUseSessions($useSessions)
{
$this->useSessions = $useSessions;
return $this;
}
/**
* Get whether the subsite ID has been changed during a request, based on the original and current IDs
*
* @return bool
*/
public function getSubsiteIdWasChanged()
{
return $this->originalSubsiteId !== $this->getSubsiteId();
}
/**
* Perform a given action within the context of a new, isolated state. Modifications are temporary
* and the existing state will be restored afterwards.
*
* @param callable $callback Callback to run. Will be passed the nested state as a parameter
* @return mixed Result of callback
*/
public function withState(callable $callback)
{
$newState = clone $this;
try {
Injector::inst()->registerService($newState);
return $callback($newState);
} finally {
Injector::inst()->registerService($this);
}
}
/**
* Reset the local cache of the singleton
*/
public static function reset()
{
SubsiteState::singleton()->resetState();
}
/**
* Reset the local cache of this object
*/
public function resetState()
{
$this->originalSubsiteId = null;
$this->subsiteId = null;
$this->useSessions = null;
}
}

View File

@ -0,0 +1,92 @@
<?php
namespace SilverStripe\Subsites\Tasks;
use InvalidArgumentException;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Dev\BuildTask;
use SilverStripe\ORM\DataObject;
use SilverStripe\Subsites\Model\Subsite;
use SilverStripe\Subsites\Pages\SubsitesVirtualPage;
use SilverStripe\Versioned\Versioned;
/**
* Handy alternative to copying pages when creating a subsite through the UI.
*
* Can be used to batch-add new pages after subsite creation, or simply to
* process a large site outside of the UI.
*
* Example: sake dev/tasks/SubsiteCopyPagesTask from=<subsite-source> to=<subsite-target>
*
* @package subsites
*/
class SubsiteCopyPagesTask extends BuildTask
{
protected $title = 'Copy pages to different subsite';
protected $description = '';
private static $segment = 'SubsiteCopyPagesTask';
public function run($request)
{
$subsiteFromId = $request->getVar('from');
if (!is_numeric($subsiteFromId)) {
throw new InvalidArgumentException('Missing "from" parameter');
}
$subsiteFrom = DataObject::get_by_id(Subsite::class, $subsiteFromId);
if (!$subsiteFrom) {
throw new InvalidArgumentException('Subsite not found');
}
$subsiteToId = $request->getVar('to');
if (!is_numeric($subsiteToId)) {
throw new InvalidArgumentException('Missing "to" parameter');
}
$subsiteTo = DataObject::get_by_id(Subsite::class, $subsiteToId);
if (!$subsiteTo) {
throw new InvalidArgumentException('Subsite not found');
}
$useVirtualPages = (bool)$request->getVar('virtual');
Subsite::changeSubsite($subsiteFrom);
// Copy data from this template to the given subsite. Does this using an iterative depth-first search.
// This will make sure that the new parents on the new subsite are correct, and there are no funny
// issues with having to check whether or not the new parents have been added to the site tree
// when a page, etc, is duplicated
$stack = [[0, 0]];
while (count($stack ?? []) > 0) {
list($sourceParentID, $destParentID) = array_pop($stack);
$children = Versioned::get_by_stage(SiteTree::class, 'Live', "\"ParentID\" = $sourceParentID", '');
if ($children) {
foreach ($children as $child) {
if ($useVirtualPages) {
$childClone = new SubsitesVirtualPage();
$childClone->writeToStage('Stage');
$childClone->CopyContentFromID = $child->ID;
$childClone->SubsiteID = $subsiteTo->ID;
} else {
$childClone = $child->duplicateToSubsite($subsiteTo->ID, true);
}
$childClone->ParentID = $destParentID;
$childClone->writeToStage('Stage');
$childClone->copyVersionToStage('Stage', 'Live');
array_push($stack, [$child->ID, $childClone->ID]);
$this->log(sprintf('Copied "%s" (#%d, %s)', $child->Title, $child->ID, $child->Link()));
}
}
unset($children);
}
}
public function log($msg)
{
echo $msg . "\n";
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace SilverStripe\Subsites\Tasks;
use SilverStripe\Dev\Tasks\MigrateFileTask;
use SilverStripe\Subsites\Model\Subsite;
use SilverStripe\Dev\Deprecation;
/**
* @deprecated 2.8.0 Will be removed without equivalent functionality to replace it
*/
class SubsiteMigrateFileTask extends MigrateFileTask
{
public function __construct()
{
Deprecation::withNoReplacement(function () {
Deprecation::notice(
'2.8.0',
'Will be removed without equivalent functionality to replace it',
Deprecation::SCOPE_CLASS
);
});
parent::__construct();
}
public function run($request)
{
$origDisableSubsiteFilter = Subsite::$disable_subsite_filter;
Subsite::disable_subsite_filter(true);
parent::run($request);
Subsite::disable_subsite_filter($origDisableSubsiteFilter);
}
}

View File

@ -1 +0,0 @@
<a href="$NewFromTemplateLink" class="action ss-ui-action-constructive ss-ui-button ui-button ui-widget ui-state-default ui-corner-all new new-link" data-icon="add"><% _t('GridFieldAddFromTemplateButton.AddFromTemplate', 'Add New from Template') %></a>

View File

@ -1,43 +0,0 @@
<div class="cms-menu cms-panel cms-panel-layout west" id="cms-menu" data-layout-type="border">
<div class="cms-logo-header north">
<div class="cms-logo">
<a href="$ApplicationLink" target="_blank" title="$ApplicationName (Version - $CMSVersion)">
$ApplicationName <% if $CMSVersion %><abbr class="version">$CMSVersion</abbr><% end_if %>
</a>
<span><% if $SiteConfig %>$SiteConfig.Title<% else %>$ApplicationName<% end_if %></span>
</div>
<div class="cms-login-status">
<a href="Security/logout" class="logout-link" title="<% _t('LeftAndMain_Menu.LOGOUT','Log out') %>"><% _t('LeftAndMain_Menu.LOGOUT','Log out') %></a>
<% with $CurrentMember %>
<span>
<% _t('LeftAndMain_Menu.Hello','Hi') %>
<a href="{$AbsoluteBaseURL}admin/myprofile" class="profile-link">
<% if $FirstName && $Surname %>$FirstName $Surname<% else_if $FirstName %>$FirstName<% else %>$Email<% end_if %>
</a>
</span>
<% end_with %>
</div>
<% if $ListSubsites %>
<% include SubsiteList %>
<% end_if %>
</div>
<div class="cms-panel-content center">
<ul class="cms-menu-list">
<% loop $MainMenu %>
<li class="$LinkingMode $FirstLast <% if $LinkingMode == 'link' %><% else %>opened<% end_if %>" id="Menu-$Code" title="$Title.ATT">
<a href="$Link" <% if $Code == 'Help' %>target="_blank"<% end_if %>>
<span class="icon icon-16 icon-{$Code.LowerCase}">&nbsp;</span>
<span class="text">$Title</span>
</a>
</li>
<% end_loop %>
</ul>
</div>
<div class="cms-panel-toggle south">
<a class="toggle-expand" href="#"><span>&raquo;</span></a>
<a class="toggle-collapse" href="#"><span>&laquo;</span></a>
</div>
</div>

View File

@ -0,0 +1,22 @@
<div class="cms-mobile-menu-toggle-wrapper"></div>
<div class="fill-height cms-menu cms-panel cms-panel-layout" id="cms-menu" data-layout-type="border" aria-expanded="false">
<div class="cms-menu__header">
<% include SilverStripe\\Admin\\LeftAndMain_MenuLogo %>
<% include SilverStripe\\Admin\\LeftAndMain_MenuStatus %>
<% if $ListSubsites.Count > 1 %>
<% include SilverStripe\\Subsites\\Controller\\SubsiteXHRController_subsitelist %>
<% end_if %>
</div>
<div class="flexbox-area-grow panel--scrollable panel--triple-toolbar cms-panel-content">
<% include SilverStripe\\Admin\\LeftAndMain_MenuList %>
</div>
<div class="toolbar toolbar--south cms-panel-toggle vertical-align-items">
<% include SilverStripe\\Admin\\LeftAndMain_MenuToggle %>
</div>
</div>
<button class="fill-height fill-width cms-menu-mobile-overlay" aria-controls="cms-menu" aria-expanded="false"></button>

View File

@ -1,28 +0,0 @@
<?php
class BaseSubsiteTest extends SapphireTest {
function setUp() {
parent::setUp();
Subsite::$use_session_subsiteid = true;
}
/**
* Avoid subsites filtering on fixture fetching.
*/
function objFromFixture($class, $id) {
Subsite::disable_subsite_filter(true);
$obj = parent::objFromFixture($class, $id);
Subsite::disable_subsite_filter(false);
return $obj;
}
/**
* Tests the initial state of disable_subsite_filter
*/
function testDisableSubsiteFilter() {
$this->assertFalse(Subsite::$disable_subsite_filter);
}
}

View File

@ -1,76 +0,0 @@
<?php
class FileSubsitesTest extends BaseSubsiteTest {
static $fixture_file = 'subsites/tests/SubsiteTest.yml';
function testTrivialFeatures() {
$this->assertTrue(is_array(singleton('FileSubsites')->extraStatics()));
$file = new File();
$file->Name = 'FileTitle';
$file->Title = 'FileTitle';
$this->assertEquals(' * FileTitle', $file->alternateTreeTitle());
$file->SubsiteID = $this->objFromFixture('Subsite', 'domaintest1')->ID;
$this->assertEquals('FileTitle', $file->getTreeTitle());
$this->assertTrue(singleton('Folder')->getCMSFields() instanceof FieldList);
Subsite::changeSubsite(1);
$this->assertEquals($file->cacheKeyComponent(), 'subsite-1');
}
function testWritingSubsiteID() {
$this->objFromFixture('Member', 'admin')->logIn();
$subsite = $this->objFromFixture('Subsite', 'domaintest1');
FileSubsites::$default_root_folders_global = true;
Subsite::changeSubsite(0);
$file = new File();
$file->write();
$file->onAfterUpload();
$this->assertEquals((int)$file->SubsiteID, 0);
Subsite::changeSubsite($subsite->ID);
$this->assertTrue($file->canEdit());
$file = new File();
$file->write();
$this->assertEquals((int)$file->SubsiteID, 0);
$this->assertTrue($file->canEdit());
FileSubsites::$default_root_folders_global = false;
Subsite::changeSubsite($subsite->ID);
$file = new File();
$file->write();
$this->assertEquals($file->SubsiteID, $subsite->ID);
// Test inheriting from parent folder
$folder = new Folder();
$folder->write();
$this->assertEquals($folder->SubsiteID, $subsite->ID);
FileSubsites::$default_root_folders_global = true;
$file = new File();
$file->ParentID = $folder->ID;
$file->onAfterUpload();
$this->assertEquals($folder->SubsiteID, $file->SubsiteID);
}
function testSubsitesFolderDropdown() {
$this->objFromFixture('Member', 'admin')->logIn();
$file = new Folder();
$source = array_values($file->getCMSFields()->dataFieldByName('SubsiteID')->getSource());
asort($source);
$this->assertEquals(array(
'Main site',
'Template',
'Subsite1 Template',
'Subsite2 Template',
'Test 1',
'Test 2',
'Test 3'
), $source);
}
}

View File

@ -1,25 +0,0 @@
<?php
class GroupSubsitesTest extends BaseSubsiteTest {
static $fixture_file = 'subsites/tests/SubsiteTest.yml';
protected $requireDefaultRecordsFrom = array('GroupSubsites');
function testTrivialFeatures() {
$this->assertTrue(is_array(singleton('GroupSubsites')->extraStatics()));
$this->assertTrue(is_array(singleton('GroupSubsites')->providePermissions()));
$this->assertTrue(singleton('Group')->getCMSFields() instanceof FieldList);
}
function testAlternateTreeTitle() {
$group = new Group();
$group->Title = 'The A Team';
$group->AccessAllSubsites = true;
$this->assertEquals($group->getTreeTitle(), 'The A Team <i>(global group)</i>');
$group->AccessAllSubsites = false;
$group->write();
$group->Subsites()->add($this->objFromFixture('Subsite', 'domaintest1'));
$group->Subsites()->add($this->objFromFixture('Subsite', 'domaintest2'));
$this->assertEquals($group->getTreeTitle(), 'The A Team <i>(Test 1, Test 2)</i>');
}
}

View File

@ -1,88 +0,0 @@
<?php
class LeftAndMainSubsitesTest extends FunctionalTest {
static $fixture_file = 'subsites/tests/SubsiteTest.yml';
/**
* Avoid subsites filtering on fixture fetching.
*/
function objFromFixture($class, $id) {
Subsite::disable_subsite_filter(true);
$obj = parent::objFromFixture($class, $id);
Subsite::disable_subsite_filter(false);
return $obj;
}
function testSectionSites() {
$member = $this->objFromFixture('Member', 'subsite1member');
$cmsmain = singleton('CMSMain');
$subsites = $cmsmain->sectionSites(true, "Main site", $member);
$this->assertDOSEquals(array(
array('Title' =>'Subsite1 Template')
), $subsites, 'Lists member-accessible sites for the accessible controller.');
$assetadmin = singleton('AssetAdmin');
$subsites = $assetadmin->sectionSites(true, "Main site", $member);
$this->assertDOSEquals(array(), $subsites, 'Does not list any sites for forbidden controller.');
$member = $this->objFromFixture('Member', 'editor');
$cmsmain = singleton('CMSMain');
$subsites = $cmsmain->sectionSites(true, "Main site", $member);
$this->assertDOSContains(array(
array('Title' =>'Main site')
), $subsites, 'Includes the main site for members who can access all sites.');
}
function testAccessChecksDontChangeCurrentSubsite() {
$admin = $this->objFromFixture("Member","admin");
$this->loginAs($admin);
$ids = array();
$subsite1 = $this->objFromFixture('Subsite', 'domaintest1');
$subsite2 = $this->objFromFixture('Subsite', 'domaintest2');
$subsite3 = $this->objFromFixture('Subsite', 'domaintest3');
$ids[] = $subsite1->ID;
$ids[] = $subsite2->ID;
$ids[] = $subsite3->ID;
$ids[] = 0;
// Enable session-based subsite tracking.
Subsite::$use_session_subsiteid = true;
foreach($ids as $id) {
Subsite::changeSubsite($id);
$this->assertEquals($id, Subsite::currentSubsiteID());
$left = new LeftAndMain();
$this->assertTrue($left->canView(), "Admin user can view subsites LeftAndMain with id = '$id'");
$this->assertEquals($id, Subsite::currentSubsiteID(),
"The current subsite has not been changed in the process of checking permissions for admin user.");
}
}
function testShouldChangeSubsite() {
$l = new LeftAndMain();
Config::inst()->nest();
Config::inst()->update('CMSPageEditController', 'treats_subsite_0_as_global', false);
$this->assertTrue($l->shouldChangeSubsite('CMSPageEditController', 0, 5));
$this->assertFalse($l->shouldChangeSubsite('CMSPageEditController', 0, 0));
$this->assertTrue($l->shouldChangeSubsite('CMSPageEditController', 1, 5));
$this->assertFalse($l->shouldChangeSubsite('CMSPageEditController', 1, 1));
Config::inst()->update('CMSPageEditController', 'treats_subsite_0_as_global', true);
$this->assertFalse($l->shouldChangeSubsite('CMSPageEditController', 0, 5));
$this->assertFalse($l->shouldChangeSubsite('CMSPageEditController', 0, 0));
$this->assertTrue($l->shouldChangeSubsite('CMSPageEditController', 1, 5));
$this->assertFalse($l->shouldChangeSubsite('CMSPageEditController', 1, 1));
Config::inst()->unnest();
}
}

View File

@ -1,38 +0,0 @@
<?php
class SiteConfigSubsitesTest extends BaseSubsiteTest {
static $fixture_file = 'subsites/tests/SubsiteTest.yml';
function testEachSubsiteHasAUniqueSiteConfig() {
$subsite1 = $this->objFromFixture('Subsite', 'domaintest1');
$subsite2 = $this->objFromFixture('Subsite', 'domaintest2');
$this->assertTrue(is_array(singleton('SiteConfigSubsites')->extraStatics()));
Subsite::changeSubsite(0);
$sc = SiteConfig::current_site_config();
$sc->Title = 'RootSite';
$sc->write();
Subsite::changeSubsite($subsite1->ID);
$sc = SiteConfig::current_site_config();
$sc->Title = 'Subsite1';
$sc->write();
Subsite::changeSubsite($subsite2->ID);
$sc = SiteConfig::current_site_config();
$sc->Title = 'Subsite2';
$sc->write();
Subsite::changeSubsite(0);
$this->assertEquals(SiteConfig::current_site_config()->Title, 'RootSite');
Subsite::changeSubsite($subsite1->ID);
$this->assertEquals(SiteConfig::current_site_config()->Title, 'Subsite1');
Subsite::changeSubsite($subsite2->ID);
$this->assertEquals(SiteConfig::current_site_config()->Title, 'Subsite2');
$keys = SiteConfig::current_site_config()->extend('cacheKeyComponent');
$this->assertContains('subsite-' . $subsite2->ID, $keys);
}
}

View File

@ -1,194 +0,0 @@
<?php
class SiteTreeSubsitesTest extends BaseSubsiteTest {
static $fixture_file = 'subsites/tests/SubsiteTest.yml';
protected $extraDataObjects = array(
'SiteTreeSubsitesTest_ClassA',
'SiteTreeSubsitesTest_ClassB'
);
function testPagesInDifferentSubsitesCanShareURLSegment() {
$subsiteMain = $this->objFromFixture('Subsite', 'main');
$subsite1 = $this->objFromFixture('Subsite', 'subsite1');
$pageMain = new SiteTree();
$pageMain->URLSegment = 'testpage';
$pageMain->write();
$pageMain->publish('Stage', 'Live');
$pageMainOther = new SiteTree();
$pageMainOther->URLSegment = 'testpage';
$pageMainOther->write();
$pageMainOther->publish('Stage', 'Live');
$this->assertNotEquals($pageMain->URLSegment, $pageMainOther->URLSegment,
'Pages in same subsite cant share the same URL'
);
Subsite::changeSubsite($subsite1->ID);
$pageSubsite1 = new SiteTree();
$pageSubsite1->URLSegment = 'testpage';
$pageSubsite1->write();
$pageSubsite1->publish('Stage', 'Live');
$this->assertEquals($pageMain->URLSegment, $pageSubsite1->URLSegment,
'Pages in different subsites can share the same URL'
);
}
function testBasicSanity() {
$this->assertTrue(singleton('SiteTree')->getSiteConfig() instanceof SiteConfig);
// The following assert is breaking in Translatable.
$this->assertTrue(singleton('SiteTree')->getCMSFields() instanceof FieldList);
$this->assertTrue(singleton('SubsitesVirtualPage')->getCMSFields() instanceof FieldList);
$this->assertTrue(is_array(singleton('SiteTreeSubsites')->extraStatics()));
}
function testErrorPageLocations() {
$subsite1 = $this->objFromFixture('Subsite', 'domaintest1');
Subsite::changeSubsite($subsite1->ID);
$path = ErrorPage::get_filepath_for_errorcode(500);
$static_path = Config::inst()->get('ErrorPage', 'static_filepath');
$expected_path = $static_path . '/error-500-'.$subsite1->domain().'.html';
$this->assertEquals($expected_path, $path);
}
function testCanEditSiteTree() {
$admin = $this->objFromFixture('Member', 'admin');
$subsite1member = $this->objFromFixture('Member', 'subsite1member');
$subsite2member = $this->objFromFixture('Member', 'subsite2member');
$mainpage = $this->objFromFixture('Page', 'home');
$subsite1page = $this->objFromFixture('Page', 'subsite1_home');
$subsite2page = $this->objFromFixture('Page', 'subsite2_home');
$subsite1 = $this->objFromFixture('Subsite', 'subsite1');
$subsite2 = $this->objFromFixture('Subsite', 'subsite2');
// Cant pass member as arguments to canEdit() because of GroupSubsites
Session::set("loggedInAs", $admin->ID);
$this->assertTrue(
(bool)$subsite1page->canEdit(),
'Administrators can edit all subsites'
);
// @todo: Workaround because GroupSubsites->augmentSQL() is relying on session state
Subsite::changeSubsite($subsite1);
Session::set("loggedInAs", $subsite1member->ID);
$this->assertTrue(
(bool)$subsite1page->canEdit(),
'Members can edit pages on a subsite if they are in a group belonging to this subsite'
);
Session::set("loggedInAs", $subsite2member->ID);
$this->assertFalse(
(bool)$subsite1page->canEdit(),
'Members cant edit pages on a subsite if they are not in a group belonging to this subsite'
);
// @todo: Workaround because GroupSubsites->augmentSQL() is relying on session state
Subsite::changeSubsite(0);
$this->assertFalse(
$mainpage->canEdit(),
'Members cant edit pages on the main site if they are not in a group allowing this'
);
}
/**
* Similar to {@link SubsitesVirtualPageTest->testSubsiteVirtualPageCanHaveSameUrlsegmentAsOtherSubsite()}.
*/
function testTwoPagesWithSameURLOnDifferentSubsites() {
// Set up a couple of pages with the same URL on different subsites
$s1 = $this->objFromFixture('Subsite','domaintest1');
$s2 = $this->objFromFixture('Subsite','domaintest2');
$p1 = new SiteTree();
$p1->Title = $p1->URLSegment = "test-page";
$p1->SubsiteID = $s1->ID;
$p1->write();
$p2 = new SiteTree();
$p2->Title = $p1->URLSegment = "test-page";
$p2->SubsiteID = $s2->ID;
$p2->write();
// Check that the URLs weren't modified in our set-up
$this->assertEquals($p1->URLSegment, 'test-page');
$this->assertEquals($p2->URLSegment, 'test-page');
// Check that if we switch between the different subsites, we receive the correct pages
Subsite::changeSubsite($s1);
$this->assertEquals($p1->ID, SiteTree::get_by_link('test-page')->ID);
Subsite::changeSubsite($s2);
$this->assertEquals($p2->ID, SiteTree::get_by_link('test-page')->ID);
}
function testPageTypesBlacklistInClassDropdown() {
Session::set("loggedInAs", null);
$s1 = $this->objFromFixture('Subsite','domaintest1');
$s2 = $this->objFromFixture('Subsite','domaintest2');
$page = singleton('SiteTree');
$s1->PageTypeBlacklist = 'SiteTreeSubsitesTest_ClassA,ErrorPage';
$s1->write();
Subsite::changeSubsite($s1);
$settingsFields = $page->getSettingsFields()->dataFieldByName('ClassName')->getSource();
$this->assertArrayNotHasKey('ErrorPage',
$settingsFields
);
$this->assertArrayNotHasKey('SiteTreeSubsitesTest_ClassA',
$settingsFields
);
$this->assertArrayHasKey('SiteTreeSubsitesTest_ClassB',
$settingsFields
);
Subsite::changeSubsite($s2);
$settingsFields = $page->getSettingsFields()->dataFieldByName('ClassName')->getSource();
$this->assertArrayHasKey('ErrorPage',
$settingsFields
);
$this->assertArrayHasKey('SiteTreeSubsitesTest_ClassA',
$settingsFields
);
$this->assertArrayHasKey('SiteTreeSubsitesTest_ClassB',
$settingsFields
);
}
function testPageTypesBlacklistInCMSMain() {
Session::set("loggedInAs", null);
$cmsmain = new CMSMain();
$s1 = $this->objFromFixture('Subsite','domaintest1');
$s2 = $this->objFromFixture('Subsite','domaintest2');
$s1->PageTypeBlacklist = 'SiteTreeSubsitesTest_ClassA,ErrorPage';
$s1->write();
Subsite::changeSubsite($s1);
$classes = $cmsmain->PageTypes()->column('ClassName');
$this->assertNotContains('ErrorPage', $classes);
$this->assertNotContains('SiteTreeSubsitesTest_ClassA', $classes);
$this->assertContains('SiteTreeSubsitesTest_ClassB', $classes);
Subsite::changeSubsite($s2);
$classes = $cmsmain->PageTypes()->column("ClassName");
$this->assertContains('ErrorPage', $classes);
$this->assertContains('SiteTreeSubsitesTest_ClassA', $classes);
$this->assertContains('SiteTreeSubsitesTest_ClassB', $classes);
}
}
class SiteTreeSubsitesTest_ClassA extends SiteTree implements TestOnly {}
class SiteTreeSubsitesTest_ClassB extends SiteTree implements TestOnly {}

View File

@ -1,143 +0,0 @@
<?php
class SubsiteAdminFunctionalTest extends FunctionalTest {
static $fixture_file = 'subsites/tests/SubsiteTest.yml';
static $use_draft_site = true;
protected $autoFollowRedirection = false;
/**
* Helper: FunctionalTest is only able to follow redirection once, we want to go all the way.
*/
function getAndFollowAll($url) {
$response = $this->get($url);
while ($location = $response->getHeader('Location')) {
$response = $this->mainSession->followRedirection();
}
echo $response->getHeader('Location');
return $response;
}
/**
* Anonymous user cannot access anything.
*/
function testAnonymousIsForbiddenAdminAccess() {
$response = $this->getAndFollowAll('admin/pages/?SubsiteID=0');
$this->assertRegExp('#^Security/login.*#', $this->mainSession->lastUrl(), 'Admin is disallowed');
$subsite1 = $this->objFromFixture('Subsite', 'subsite1');
$response = $this->getAndFollowAll("admin/pages/?SubsiteID={$subsite1->ID}");
$this->assertRegExp('#^Security/login.*#', $this->mainSession->lastUrl(), 'Admin is disallowed');
$response = $this->getAndFollowAll('SubsiteXHRController');
$this->assertRegExp('#^Security/login.*#', $this->mainSession->lastUrl(),
'SubsiteXHRController is disallowed');
}
/**
* Admin should be able to access all subsites and the main site
*/
function testAdminCanAccessAllSubsites() {
$member = $this->objFromFixture('Member', 'admin');
Session::set("loggedInAs", $member->ID);
$this->getAndFollowAll('admin/pages/?SubsiteID=0');
$this->assertEquals(Subsite::currentSubsiteID(), '0', 'Can access main site.');
$this->assertRegExp('#^admin/pages.*#', $this->mainSession->lastUrl(), 'Lands on the correct section');
$subsite1 = $this->objFromFixture('Subsite', 'subsite1');
$this->getAndFollowAll("admin/pages/?SubsiteID={$subsite1->ID}");
$this->assertEquals(Subsite::currentSubsiteID(), $subsite1->ID, 'Can access other subsite.');
$this->assertRegExp('#^admin/pages.*#', $this->mainSession->lastUrl(), 'Lands on the correct section');
$response = $this->getAndFollowAll('SubsiteXHRController');
$this->assertNotRegExp('#^Security/login.*#', $this->mainSession->lastUrl(),
'SubsiteXHRController is reachable');
}
function testAdminIsRedirectedToObjectsSubsite() {
$member = $this->objFromFixture('Member', 'admin');
Session::set("loggedInAs", $member->ID);
$mainSubsitePage = $this->objFromFixture('Page', 'mainSubsitePage');
$subsite1Home = $this->objFromFixture('Page', 'subsite1_home');
Config::inst()->nest();
Config::inst()->update('CMSPageEditController', 'treats_subsite_0_as_global', false);
Subsite::changeSubsite(0);
$this->getAndFollowAll("admin/pages/edit/show/$subsite1Home->ID");
$this->assertEquals(Subsite::currentSubsiteID(), $subsite1Home->SubsiteID, 'Loading an object switches the subsite');
$this->assertRegExp("#^admin/pages.*#", $this->mainSession->lastUrl(), 'Lands on the correct section');
Config::inst()->update('CMSPageEditController', 'treats_subsite_0_as_global', true);
Subsite::changeSubsite(0);
$this->getAndFollowAll("admin/pages/edit/show/$subsite1Home->ID");
$this->assertEquals(Subsite::currentSubsiteID(), $subsite1Home->SubsiteID, 'Loading a non-main-site object still switches the subsite if configured with treats_subsite_0_as_global');
$this->assertRegExp("#^admin/pages.*#", $this->mainSession->lastUrl(), 'Lands on the correct section');
$this->getAndFollowAll("admin/pages/edit/show/$mainSubsitePage->ID");
$this->assertNotEquals(Subsite::currentSubsiteID(), $mainSubsitePage->SubsiteID, 'Loading a main-site object does not change the subsite if configured with treats_subsite_0_as_global');
$this->assertRegExp("#^admin/pages.*#", $this->mainSession->lastUrl(), 'Lands on the correct section');
Config::inst()->unnest();
}
/**
* User which has AccessAllSubsites set to 1 should be able to access all subsites and main site,
* even though he does not have the ADMIN permission.
*/
function testEditorCanAccessAllSubsites() {
$member = $this->objFromFixture('Member', 'editor');
Session::set("loggedInAs", $member->ID);
$this->getAndFollowAll('admin/pages/?SubsiteID=0');
$this->assertEquals(Subsite::currentSubsiteID(), '0', 'Can access main site.');
$this->assertRegExp('#^admin/pages.*#', $this->mainSession->lastUrl(), 'Lands on the correct section');
$subsite1 = $this->objFromFixture('Subsite', 'subsite1');
$this->getAndFollowAll("admin/pages/?SubsiteID={$subsite1->ID}");
$this->assertEquals(Subsite::currentSubsiteID(), $subsite1->ID, 'Can access other subsite.');
$this->assertRegExp('#^admin/pages.*#', $this->mainSession->lastUrl(), 'Lands on the correct section');
$response = $this->getAndFollowAll('SubsiteXHRController');
$this->assertNotRegExp('#^Security/login.*#', $this->mainSession->lastUrl(),
'SubsiteXHRController is reachable');
}
/**
* Test a member who only has access to one subsite (subsite1) and only some sections (pages and security).
*/
function testSubsiteAdmin() {
$member = $this->objFromFixture('Member', 'subsite1member');
Session::set("loggedInAs", $member->ID);
$subsite1 = $this->objFromFixture('Subsite', 'subsite1');
// Check allowed URL.
$this->getAndFollowAll("admin/pages/?SubsiteID={$subsite1->ID}");
$this->assertEquals(Subsite::currentSubsiteID(), $subsite1->ID, 'Can access own subsite.');
$this->assertRegExp('#^admin/pages.*#', $this->mainSession->lastUrl(), 'Can access permitted section.');
// Check forbidden section in allowed subsite.
$this->getAndFollowAll("admin/assets/?SubsiteID={$subsite1->ID}");
$this->assertEquals(Subsite::currentSubsiteID(), $subsite1->ID, 'Is redirected within subsite.');
$this->assertNotRegExp('#^admin/assets/.*#', $this->mainSession->lastUrl(),
'Is redirected away from forbidden section');
// Check forbidden site, on a section that's allowed on another subsite
$this->getAndFollowAll("admin/pages/?SubsiteID=0");
$this->assertEquals(Subsite::currentSubsiteID(), $subsite1->ID, 'Is redirected to permitted subsite.');
// Check forbidden site, on a section that's not allowed on any other subsite
$this->getAndFollowAll("admin/assets/?SubsiteID=0");
$this->assertEquals(Subsite::currentSubsiteID(), $subsite1->ID, 'Is redirected to first permitted subsite.');
$this->assertNotRegExp('#^Security/login.*#', $this->mainSession->lastUrl(), 'Is not denied access');
// Check the standalone XHR controller.
$response = $this->getAndFollowAll('SubsiteXHRController');
$this->assertNotRegExp('#^Security/login.*#', $this->mainSession->lastUrl(),
'SubsiteXHRController is reachable');
}
}

View File

@ -1,50 +0,0 @@
<?php
class SubsiteAdminTest extends BaseSubsiteTest {
static $fixture_file = 'subsites/tests/SubsiteTest.yml';
function adminLoggedInSession() {
return new Session(array(
'loggedInAs' => $this->idFromFixture('Member', 'admin')
));
}
/**
* Test generation of the view
*/
function testBasicView() {
Subsite::$write_hostmap = false;
$subsite1ID = $this->objFromFixture('Subsite','domaintest1')->ID;
// Open the admin area logged in as admin
$response1 = Director::test('admin/subsites/', null, $this->adminLoggedInSession());
// Confirm that this URL gets you the entire page, with the edit form loaded
$response2 = Director::test("admin/subsites/Subsite/EditForm/field/Subsite/item/$subsite1ID/edit", null, $this->adminLoggedInSession());
$this->assertTrue(strpos($response2->getBody(), 'id="Form_ItemEditForm_ID"') !== false, "Testing Form_ItemEditForm_ID exists");
$this->assertTrue(strpos($response2->getBody(), '<head') !== false, "Testing <head> exists");
}
/**
* Test that the main-site user with ADMIN permissions can access all subsites, regardless
* of whether he is in a subsite-specific group or not.
*/
function testMainsiteAdminCanAccessAllSubsites() {
$member = $this->objFromFixture('Member', 'admin');
Session::set("loggedInAs", $member->ID);
$cmsMain = new CMSMain();
foreach($cmsMain->Subsites() as $subsite) {
$ids[$subsite->ID] = true;
}
$this->assertArrayHasKey(0, $ids, "Main site accessible");
$this->assertArrayHasKey($this->idFromFixture('Subsite','main'), $ids, "Site with no groups inaccesible");
$this->assertArrayHasKey($this->idFromFixture('Subsite','subsite1'), $ids, "Subsite1 Template inaccessible");
$this->assertArrayHasKey($this->idFromFixture('Subsite','subsite2'), $ids, "Subsite2 Template inaccessible");
}
}

View File

@ -1,362 +0,0 @@
<?php
class SubsiteTest extends BaseSubsiteTest {
static $fixture_file = 'subsites/tests/SubsiteTest.yml';
function setUp() {
parent::setUp();
$this->origStrictSubdomainMatching = Subsite::$strict_subdomain_matching;
Subsite::$strict_subdomain_matching = false;
}
function tearDown() {
parent::tearDown();
Subsite::$strict_subdomain_matching = $this->origStrictSubdomainMatching;
}
/**
* Create a new subsite from the template and verify that all the template's pages are copied
*/
function testSubsiteCreation() {
Subsite::$write_hostmap = false;
// Create the instance
$template = $this->objFromFixture('Subsite', 'main');
// Test that changeSubsite is working
Subsite::changeSubsite($template->ID);
$tmplStaff = $this->objFromFixture('Page','staff');
$tmplHome = DataObject::get_one('Page', "\"URLSegment\" = 'home'");
// Publish all the pages in the template, testing that DataObject::get only returns pages from the chosen subsite
$pages = DataObject::get("SiteTree");
$totalPages = $pages->Count();
foreach($pages as $page) {
$this->assertEquals($template->ID, $page->SubsiteID);
$page->publish('Stage', 'Live');
}
// Create a new site
$subsite = $template->duplicate();
// Check title
$this->assertEquals($subsite->Title, $template->Title);
// Another test that changeSubsite is working
$subsite->activate();
$siteHome = DataObject::get_one('Page', "\"URLSegment\" = 'home'");
$this->assertNotEquals($siteHome, false, 'Home Page for subsite not found');
$this->assertEquals($subsite->ID, $siteHome->SubsiteID,
'createInstance() copies existing pages retaining the same URLSegment'
);
Subsite::changeSubsite(0);
}
/**
* Confirm that domain lookup is working
*/
function testDomainLookup() {
// Clear existing fixtures
foreach(DataObject::get('Subsite') as $subsite) $subsite->delete();
foreach(DataObject::get('SubsiteDomain') as $domain) $domain->delete();
// Much more expressive than YML in this case
$subsite1 = $this->createSubsiteWithDomains(array(
'one.example.org' => true,
'one.*' => false,
));
$subsite2 = $this->createSubsiteWithDomains(array(
'two.mysite.com' => true,
'*.mysite.com' => false,
'subdomain.onmultiplesubsites.com' => false,
));
$subsite3 = $this->createSubsiteWithDomains(array(
'three.*' => true, // wildcards in primary domain are not recommended
'subdomain.unique.com' => false,
'*.onmultiplesubsites.com' => false,
));
$this->assertEquals(
$subsite3->ID,
Subsite::getSubsiteIDForDomain('subdomain.unique.com'),
'Full unique match'
);
$this->assertEquals(
$subsite1->ID,
Subsite::getSubsiteIDForDomain('one.example.org'),
'Full match, doesn\'t complain about multiple matches within a single subsite'
);
$failed = false;
try {
Subsite::getSubsiteIDForDomain('subdomain.onmultiplesubsites.com');
} catch(UnexpectedValueException $e) {
$failed = true;
}
$this->assertTrue(
$failed,
'Fails on multiple matches with wildcard vs. www across multiple subsites'
);
$this->assertEquals(
$subsite1->ID,
Subsite::getSubsiteIDForDomain('one.unique.com'),
'Fuzzy match suffixed with wildcard (rule "one.*")'
);
$this->assertEquals(
$subsite2->ID,
Subsite::getSubsiteIDForDomain('two.mysite.com'),
'Matches correct subsite for rule'
);
$this->assertEquals(
$subsite2->ID,
Subsite::getSubsiteIDForDomain('other.mysite.com'),
'Fuzzy match prefixed with wildcard (rule "*.mysite.com")'
);
$this->assertEquals(
0,
Subsite::getSubsiteIDForDomain('unknown.madeup.com'),
"Doesn't match unknown subsite"
);
}
function testStrictSubdomainMatching() {
// Clear existing fixtures
foreach(DataObject::get('Subsite') as $subsite) $subsite->delete();
foreach(DataObject::get('SubsiteDomain') as $domain) $domain->delete();
// Much more expressive than YML in this case
$subsite1 = $this->createSubsiteWithDomains(array(
'example.org' => true,
'example.com' => false,
'*.wildcard.com' => false,
));
$subsite2 = $this->createSubsiteWithDomains(array(
'www.example.org' => true,
'www.wildcard.com' => false,
));
Subsite::$strict_subdomain_matching = false;
$this->assertEquals(
$subsite1->ID,
Subsite::getSubsiteIDForDomain('example.org'),
'Exact matches without strict checking when not using www prefix'
);
$this->assertEquals(
$subsite1->ID,
Subsite::getSubsiteIDForDomain('www.example.org'),
'Matches without strict checking when using www prefix, still matching first domain regardless of www prefix (falling back to subsite primary key ordering)'
);
$this->assertEquals(
$subsite1->ID,
Subsite::getSubsiteIDForDomain('www.example.com'),
'Fuzzy matches without strict checking with www prefix'
);
$this->assertEquals(
0,
Subsite::getSubsiteIDForDomain('www.wildcard.com'),
'Doesn\'t match www prefix without strict check, even if a wildcard subdomain is in place'
);
Subsite::$strict_subdomain_matching = true;
$this->assertEquals(
$subsite1->ID,
Subsite::getSubsiteIDForDomain('example.org'),
'Matches with strict checking when not using www prefix'
);
$this->assertEquals(
$subsite2->ID, // not 1
Subsite::getSubsiteIDForDomain('www.example.org'),
'Matches with strict checking when using www prefix'
);
$this->assertEquals(
0,
Subsite::getSubsiteIDForDomain('www.example.com'),
'Doesn\'t fuzzy match with strict checking when using www prefix'
);
$failed = false;
try {
Subsite::getSubsiteIDForDomain('www.wildcard.com');
} catch(UnexpectedValueException $e) {
$failed = true;
}
$this->assertTrue(
$failed,
'Fails on multiple matches with strict checking and wildcard vs. www'
);
}
protected function createSubsiteWithDomains($domains) {
$subsite = new Subsite(array(
'Title' => 'My Subsite'
));
$subsite->write();
foreach($domains as $domainStr => $isPrimary) {
$domain = new SubsiteDomain(array(
'Domain' => $domainStr,
'IsPrimary' => $isPrimary,
'SubsiteID' => $subsite->ID
));
$domain->write();
}
return $subsite;
}
/**
* Test the Subsite->domain() method
*/
function testDefaultDomain() {
$this->assertEquals('one.example.org',
$this->objFromFixture('Subsite','domaintest1')->domain());
$this->assertEquals('two.mysite.com',
$this->objFromFixture('Subsite','domaintest2')->domain());
$originalHTTPHost = $_SERVER['HTTP_HOST'];
$_SERVER['HTTP_HOST'] = "www.example.org";
$this->assertEquals('three.example.org',
$this->objFromFixture('Subsite','domaintest3')->domain());
$_SERVER['HTTP_HOST'] = "mysite.example.org";
$this->assertEquals('three.mysite.example.org',
$this->objFromFixture('Subsite','domaintest3')->domain());
$this->assertEquals($_SERVER['HTTP_HOST'], singleton('Subsite')->PrimaryDomain);
$this->assertEquals('http://'.$_SERVER['HTTP_HOST'].Director::baseURL(), singleton('Subsite')->absoluteBaseURL());
$_SERVER['HTTP_HOST'] = $originalHTTPHost;
}
function testAllSites() {
$subsites = Subsite::all_sites();
$this->assertDOSEquals(array(
array('Title' =>'Main site'),
array('Title' =>'Template'),
array('Title' =>'Subsite1 Template'),
array('Title' =>'Subsite2 Template'),
array('Title' =>'Test 1'),
array('Title' =>'Test 2'),
array('Title' =>'Test 3')
), $subsites, 'Lists all subsites');
}
function testAllAccessibleSites() {
$member = $this->objFromFixture('Member', 'subsite1member');
$subsites = Subsite::all_accessible_sites(true, 'Main site', $member);
$this->assertDOSEquals(array(
array('Title' =>'Subsite1 Template')
), $subsites, 'Lists member-accessible sites.');
}
/**
* Test Subsite::accessible_sites()
*/
function testAccessibleSites() {
$member1Sites = Subsite::accessible_sites("CMS_ACCESS_CMSMain", false, null,
$this->objFromFixture('Member', 'subsite1member'));
$member1SiteTitles = $member1Sites->column("Title");
sort($member1SiteTitles);
$this->assertEquals('Subsite1 Template', $member1SiteTitles[0], 'Member can get to a subsite via a group');
$adminSites = Subsite::accessible_sites("CMS_ACCESS_CMSMain", false, null,
$this->objFromFixture('Member', 'admin'));
$adminSiteTitles = $adminSites->column("Title");
sort($adminSiteTitles);
$this->assertEquals(array(
'Subsite1 Template',
'Subsite2 Template',
'Template',
'Test 1',
'Test 2',
'Test 3',
), $adminSiteTitles);
$member2Sites = Subsite::accessible_sites("CMS_ACCESS_CMSMain", false, null,
$this->objFromFixture('Member', 'subsite1member2'));
$member2SiteTitles = $member2Sites->column("Title");
sort($member2SiteTitles);
$this->assertEquals('Subsite1 Template', $member2SiteTitles[0], 'Member can get to subsite via a group role');
}
function testhasMainSitePermission() {
$admin = $this->objFromFixture('Member', 'admin');
$subsite1member = $this->objFromFixture('Member', 'subsite1member');
$subsite1admin = $this->objFromFixture('Member', 'subsite1admin');
$allsubsitesauthor = $this->objFromFixture('Member', 'allsubsitesauthor');
$this->assertTrue(
Subsite::hasMainSitePermission($admin),
'Default permissions granted for super-admin'
);
$this->assertTrue(
Subsite::hasMainSitePermission($admin, array("ADMIN")),
'ADMIN permissions granted for super-admin'
);
$this->assertFalse(
Subsite::hasMainSitePermission($subsite1admin, array("ADMIN")),
'ADMIN permissions (on main site) denied for subsite1 admin'
);
$this->assertFalse(
Subsite::hasMainSitePermission($subsite1admin, array("CMS_ACCESS_CMSMain")),
'CMS_ACCESS_CMSMain (on main site) denied for subsite1 admin'
);
$this->assertFalse(
Subsite::hasMainSitePermission($allsubsitesauthor, array("ADMIN")),
'ADMIN permissions (on main site) denied for CMS author with edit rights on all subsites'
);
$this->assertTrue(
Subsite::hasMainSitePermission($allsubsitesauthor, array("CMS_ACCESS_CMSMain")),
'CMS_ACCESS_CMSMain (on main site) granted for CMS author with edit rights on all subsites'
);
$this->assertFalse(
Subsite::hasMainSitePermission($subsite1member, array("ADMIN")),
'ADMIN (on main site) denied for subsite1 subsite1 cms author'
);
$this->assertFalse(
Subsite::hasMainSitePermission($subsite1member, array("CMS_ACCESS_CMSMain")),
'CMS_ACCESS_CMSMain (on main site) denied for subsite1 cms author'
);
}
function testDuplicateSubsite() {
// get subsite1 & create page
$subsite1 = $this->objFromFixture('Subsite','domaintest1');
$subsite1->activate();
$page1 = new Page();
$page1->Title = 'MyAwesomePage';
$page1->write();
$page1->doPublish();
$this->assertEquals($page1->SubsiteID, $subsite1->ID);
// duplicate
$subsite2 = $subsite1->duplicate();
$subsite2->activate();
// change content on dupe
$page2 = DataObject::get_one('Page', "\"Title\" = 'MyAwesomePage'");
$page2->Title = 'MyNewAwesomePage';
$page2->write();
$page2->doPublish();
// check change & check change has not affected subiste1
$subsite1->activate();
$this->assertEquals('MyAwesomePage', DataObject::get_by_id('Page', $page1->ID)->Title);
$subsite2->activate();
$this->assertEquals('MyNewAwesomePage', DataObject::get_by_id('Page', $page2->ID)->Title);
}
}

View File

@ -1,180 +0,0 @@
Subsite:
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
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
PermissionRoleCode:
roleCode1:
Code: CMS_ACCESS_CMSMain
PermissionRole:
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
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
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

View File

@ -1,275 +0,0 @@
<?php
class SubsitesVirtualPageTest extends BaseSubsiteTest {
static $fixture_file = array(
'subsites/tests/SubsiteTest.yml',
'subsites/tests/SubsitesVirtualPageTest.yml',
);
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);
}
function tearDown() {
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
function testVirtualPageFromAnotherSubsite() {
Subsite::$write_hostmap = false;
$subsite = $this->objFromFixture('Subsite', 'subsite2');
Subsite::changeSubsite($subsite->ID);
Subsite::$disable_subsite_filter = false;
$linky = $this->objFromFixture('Page', 'linky');
$svp = new SubsitesVirtualPage();
$svp->CopyContentFromID = $linky->ID;
$svp->SubsiteID = $subsite->ID;
$svp->URLSegment = 'linky';
$svp->write();
$this->assertEquals($svp->SubsiteID, $subsite->ID);
$this->assertEquals($svp->Title, $linky->Title);
}
function testFileLinkRewritingOnVirtualPages() {
// File setup
$this->logInWithPermission('ADMIN');
touch(Director::baseFolder() . '/assets/testscript-test-file.pdf');
// Publish the source page
$page = $this->objFromFixture('SiteTree', 'page1');
$this->assertTrue($page->doPublish());
// Create a virtual page from it, and publish that
$svp = new SubsitesVirtualPage();
$svp->CopyContentFromID = $page->ID;
$svp->write();
$svp->doPublish();
// Rename the file
$file = $this->objFromFixture('File', 'file1');
$file->Name = 'renamed-test-file.pdf';
$file->write();
// Verify that the draft and publish virtual pages both have the corrected link
$this->assertContains('<img src="assets/renamed-test-file.pdf"',
DB::query("SELECT \"Content\" FROM \"SiteTree\" WHERE \"ID\" = $svp->ID")->value());
$this->assertContains('<img src="assets/renamed-test-file.pdf"',
DB::query("SELECT \"Content\" FROM \"SiteTree_Live\" WHERE \"ID\" = $svp->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() {
// Fixture
$p = new Page();
$p->Content = "test content";
$p->write();
$vp = new SubsitesVirtualPage();
$vp->CopyContentFromID = $p->ID;
$vp->write();
// VP is oragne
$this->assertTrue($vp->IsAddedToStage);
// VP is still orange after we publish
$p->doPublish();
$this->fixVersionNumberCache($vp);
$this->assertTrue($vp->IsAddedToStage);
// A new VP created after P's initial construction
$vp2 = new SubsitesVirtualPage();
$vp2->CopyContentFromID = $p->ID;
$vp2->write();
$this->assertTrue($vp2->IsAddedToStage);
// Also remains orange after a republish
$p->Content = "new content";
$p->write();
$p->doPublish();
$this->fixVersionNumberCache($vp2);
$this->assertTrue($vp2->IsAddedToStage);
// VP is now published
$vp->doPublish();
$this->fixVersionNumberCache($vp);
$this->assertTrue($vp->ExistsOnLive);
$this->assertFalse($vp->IsModifiedOnStage);
// P edited, VP and P both go green
$p->Content = "third content";
$p->write();
$this->fixVersionNumberCache($vp, $p);
$this->assertTrue($p->IsModifiedOnStage);
$this->assertTrue($vp->IsModifiedOnStage);
// Publish, VP goes black
$p->doPublish();
$this->fixVersionNumberCache($vp);
$this->assertTrue($vp->ExistsOnLive);
$this->assertFalse($vp->IsModifiedOnStage);
}
/**
* This test ensures published Subsites Virtual Pages immediately reflect updates
* to their published target pages. Note - this has to happen when the virtual page
* is in a different subsite to the page you are editing and republishing,
* otherwise the test will pass falsely due to current subsite ID being the same.
*/
function testPublishedSubsiteVirtualPagesUpdateIfTargetPageUpdates()
{
// create page
$p = new Page();
$p->Content = 'Content';
$p->Title = 'Title';
$p->writeToStage('Stage');
$p->publish('Stage', 'Live');
$this->assertTrue($p->ExistsOnLive);
// change to subsite
$subsite = $this->objFromFixture('Subsite', 'subsite2');
Subsite::changeSubsite($subsite->ID);
Subsite::$disable_subsite_filter = false;
// create svp in subsite
$svp = new SubsitesVirtualPage();
$svp->CopyContentFromID = $p->ID;
$svp->writeToStage('Stage');
$svp->publish('Stage', 'Live');
$this->assertEquals($svp->SubsiteID, $subsite->ID);
$this->assertTrue($svp->ExistsOnLive);
// change back to original subsite ("Main site")
Subsite::changeSubsite(0);
// update original page
$p->Title = 'New Title';
// "save & publish"
$p->writeToStage('Stage');
$p->publish('Stage', 'Live');
$this->assertNotEquals($p->SubsiteID, $subsite->ID);
// reload SVP from database
// can't use DO::get by id because caches.
$svpdb = $svp->get()->byID($svp->ID);
// ensure title changed
$this->assertEquals($svpdb->Title, $p->Title);
}
function testUnpublishingParentPageUnpublishesSubsiteVirtualPages() {
Config::inst()->update('StaticPublisher', 'disable_realtime', true);
// Go to main site, get parent page
$subsite = $this->objFromFixture('Subsite', 'main');
Subsite::changeSubsite($subsite->ID);
$page = $this->objFromFixture('Page', 'importantpage');
// Create two SVPs on other subsites
$subsite = $this->objFromFixture('Subsite', 'subsite1');
Subsite::changeSubsite($subsite->ID);
$vp1 = new SubsitesVirtualPage();
$vp1->CopyContentFromID = $page->ID;
$vp1->write();
$vp1->doPublish();
$subsite = $this->objFromFixture('Subsite', 'subsite2');
Subsite::changeSubsite($subsite->ID);
$vp2 = new SubsitesVirtualPage();
$vp2->CopyContentFromID = $page->ID;
$vp2->write();
$vp2->doPublish();
// Switch back to main site, unpublish source
$subsite = $this->objFromFixture('Subsite', 'main');
Subsite::changeSubsite($subsite->ID);
$page = $this->objFromFixture('Page', 'importantpage');
$page->doUnpublish();
Subsite::changeSubsite($vp1->SubsiteID);
$onLive = Versioned::get_one_by_stage('SubsitesVirtualPage', 'Live', "\"SiteTree_Live\".\"ID\" = ".$vp1->ID);
$this->assertNull($onLive, 'SVP has been removed from live');
$subsite = $this->objFromFixture('Subsite', 'subsite2');
Subsite::changeSubsite($vp2->SubsiteID);
$onLive = Versioned::get_one_by_stage('SubsitesVirtualPage', 'Live', "\"SiteTree_Live\".\"ID\" = ".$vp2->ID);
$this->assertNull($onLive, 'SVP has been removed from live');
}
/**
* Similar to {@link SiteTreeSubsitesTest->testTwoPagesWithSameURLOnDifferentSubsites()}
* and {@link SiteTreeSubsitesTest->testPagesInDifferentSubsitesCanShareURLSegment()}.
*/
function testSubsiteVirtualPageCanHaveSameUrlsegmentAsOtherSubsite() {
Subsite::$write_hostmap = false;
$subsite1 = $this->objFromFixture('Subsite', 'subsite1');
$subsite2 = $this->objFromFixture('Subsite', 'subsite2');
Subsite::changeSubsite($subsite1->ID);
$subsite1Page = $this->objFromFixture('Page', 'subsite1_staff');
$subsite1Page->URLSegment = 'staff';
$subsite1Page->write();
// saving on subsite1, and linking to subsite1
$subsite1Vp = new SubsitesVirtualPage();
$subsite1Vp->CopyContentFromID = $subsite1Page->ID;
$subsite1Vp->SubsiteID = $subsite1->ID;
$subsite1Vp->write();
$this->assertNotEquals(
$subsite1Vp->URLSegment,
$subsite1Page->URLSegment,
"Doesn't allow explicit URLSegment overrides when already existing in same subsite"
);
//Change to subsite 2
Subsite::changeSubsite($subsite2->ID);
// saving in subsite2 (which already has a page with URLSegment 'contact-us'),
// but linking to a page in subsite1
$subsite2Vp = new SubsitesVirtualPage();
$subsite2Vp->CopyContentFromID = $subsite1Page->ID;
$subsite2Vp->SubsiteID = $subsite2->ID;
$subsite2Vp->write();
$this->assertEquals(
$subsite2Vp->URLSegment,
$subsite1Page->URLSegment,
"Does allow explicit URLSegment overrides when only existing in a different subsite"
);
}
function fixVersionNumberCache($page) {
$pages = func_get_args();
foreach($pages as $p) {
Versioned::prepopulate_versionnumber_cache('SiteTree', 'Stage', array($p->ID));
Versioned::prepopulate_versionnumber_cache('SiteTree', 'Live', array($p->ID));
}
}
}

View File

@ -1,10 +0,0 @@
# These need to come first so that SiteTree has the link meta-data written.
File:
file1:
Filename: assets/testscript-test-file.pdf
SiteTree:
page1:
Title: page1
URLSegment: page1
Content: <p><img src="assets/testscript-test-file.pdf" /></p>

View File

@ -1,100 +0,0 @@
<?php
namespace Subsites\Test\Behaviour;
use SilverStripe\BehatExtension\Context\SilverStripeContext,
SilverStripe\BehatExtension\Context\BasicContext,
SilverStripe\BehatExtension\Context\LoginContext,
SilverStripe\BehatExtension\Context\FixtureContext,
SilverStripe\Framework\Test\Behaviour\CmsFormsContext,
SilverStripe\Framework\Test\Behaviour\CmsUiContext,
SilverStripe\Cms\Test\Behaviour;
// PHPUnit
require_once 'PHPUnit/Autoload.php';
require_once 'PHPUnit/Framework/Assert/Functions.php';
/**
* Features context
*
* Context automatically loaded by Behat.
* Uses subcontexts to extend functionality.
*/
class FeatureContext extends SilverStripeContext {
/**
* @var FixtureFactory
*/
protected $fixtureFactory;
/**
* Initializes context.
* Every scenario gets it's own context object.
*
* @param array $parameters context parameters (set them up through behat.yml)
*/
public function __construct(array $parameters) {
parent::__construct($parameters);
$this->useContext('BasicContext', new BasicContext($parameters));
$this->useContext('LoginContext', new LoginContext($parameters));
$this->useContext('CmsFormsContext', new CmsFormsContext($parameters));
$this->useContext('CmsUiContext', new CmsUiContext($parameters));
$fixtureContext = new FixtureContext($parameters);
$fixtureContext->setFixtureFactory($this->getFixtureFactory());
$this->useContext('FixtureContext', $fixtureContext);
// Use blueprints to set user name from identifier
$factory = $fixtureContext->getFixtureFactory();
$blueprint = \Injector::inst()->create('FixtureBlueprint', 'Member');
$blueprint->addCallback('beforeCreate', function($identifier, &$data, &$fixtures) {
if(!isset($data['FirstName'])) $data['FirstName'] = $identifier;
});
$factory->define('Member', $blueprint);
// Auto-publish pages
foreach(\ClassInfo::subclassesFor('SiteTree') as $id => $class) {
$blueprint = \Injector::inst()->create('FixtureBlueprint', $class);
$blueprint->addCallback('afterCreate', function($obj, $identifier, &$data, &$fixtures) {
$obj->publish('Stage', 'Live');
});
$factory->define($class, $blueprint);
}
}
public function setMinkParameters(array $parameters) {
parent::setMinkParameters($parameters);
if(isset($parameters['files_path'])) {
$this->getSubcontext('FixtureContext')->setFilesPath($parameters['files_path']);
}
}
/**
* @return FixtureFactory
*/
public function getFixtureFactory() {
if(!$this->fixtureFactory) {
$this->fixtureFactory = \Injector::inst()->create('BehatFixtureFactory');
}
return $this->fixtureFactory;
}
public function setFixtureFactory(FixtureFactory $factory) {
$this->fixtureFactory = $factory;
}
//
// Place your definition and hook methods here:
//
// /**
// * @Given /^I have done something with "([^"]*)"$/
// */
// public function iHaveDoneSomethingWith($argument) {
// $container = $this->kernel->getContainer();
// $container->get('some_service')->doSomethingWith($argument);
// }
//
}

View File

@ -0,0 +1,29 @@
# See https://github.com/silverstripe/silverstripe-subsites/issues/357
Feature: Insert an internal link into content
As a CMS user
I can insert internal links into my content
So that I can direct users to different parts of my website
Background:
Given a "subsite" "Subsite B"
And a "page" "My page" with "URLSegment"="my-page", "Content"="My page content"
And a "page" "Another page" with "URLSegment"="another-page", "Content"="My other page content"
And I am logged in with "CMS_ACCESS_CMSMain" permissions
Then I go to "admin/pages"
And I click on "My page" in the tree
@javascript
Scenario: I can insert an internal link
# See "insert-a-link.feature" from silverstripe/cms
When I select "My page" in the "Content" HTML field
And I press the "Insert link" HTML field button
And I click "Page on this site" in the ".mce-menu" element
Then I should see an "form#Form_editorInternalLink" element
When I click "(Search or choose Page)" in the ".Select-multi-value-wrapper" element
And I click "Another page" in the ".treedropdownfield__menu" element
And I fill in "my desc" for "Link description"
And I press the "Insert" button
Then the "Content" HTML field should contain "<a title="my desc" href="[sitetree_link"
And the "Content" HTML field should contain "My page</a>"
# Required to avoid "unsaved changes" browser dialog
Then I press the "Save" button

View File

@ -0,0 +1,81 @@
@javascript
Feature: Create and select a subsite
As a CMS user
I want to be able to select a subsite
So that I can edit content for a specific subsite
Background:
# There's a bug where you need CMS_ACCESS_CMSMain rather than CMS_ACCESS_LeftAndMain permissions to
# use subsites as expected
Given the "group" "EDITOR" has permissions "CMS_ACCESS_CMSMain" and "CMS_ACCESS_AssetAdmin" and "FILE_EDIT_ALL"
And a "page" "My page" with "URLSegment"="my-page", "Content"="My page content"
And an "image" "file1.jpg"
And an "image" "file2.jpg"
Scenario: I can operate subsites
# Create subsite as Admin
Given I am logged in with "ADMIN" permissions
Then I go to "admin/subsites"
# Add subsites button is not a regular button, so click using css selector
And I click on the ".btn-toolbar .btn__title" element
And I fill in "Subsite Name" with "My subsite"
And I press "Create"
# Add a file to the main site
When I go to "admin/assets"
And I press the "Add folder" button
And I select "Main site" from "SubsitesSelect"
# Using a short folder name so that it doesn't get truncated on the frontend
And I fill in "Folder name" with "mfol"
And I press the "Create" button
When I go to "admin/assets"
And I click on the file named "mfol" in the gallery
And I attach the file "file1.jpg" to dropzone "gallery-container"
# Change to Editor user
When I go to "/Security/login"
And I press the "Log in as someone else" button
When I am logged in as a member of "EDITOR" group
And I go to "admin/pages"
# Can see main site page on main site
When I go to "admin/pages"
Then I should see "My page" in the tree
# Cannot see main site page on subsite
When I select "My subsite" from "SubsitesSelect"
And I go to "admin/pages"
Then I should not see "My page" in the tree
# Create a page on the subsite
When I press the "Add new" button
And I select the "Page" radio button
And I press the "Create" button
When I fill in "Page name" with "My subsite page"
And I press the "Publish" button
Then I should see "My subsite page"
# Can see main site folders/files from subsite
When I go to "admin/assets"
Then I should see "mfol"
When I click on the file named "mfol" in the gallery
Then I should see "file1"
# Add a file to the subsite
When I go to "admin/assets"
And I select "My subsite" from "SubsitesSelect"
And I press the "Add folder" button
And I fill in "Folder name" with "sfol"
And I press the "Create" button
When I go to "admin/assets"
And I click on the file named "sfol" in the gallery
And I attach the file "file2.jpg" to dropzone "gallery-container"
# Change back to main subsite - cannot see subsite folders/files
When I go to "admin/assets"
And I select "Main site" from "SubsitesSelect"
Then I should see "mfol"
Then I should not see "My subsite page"

View File

@ -4,23 +4,26 @@ Feature: Preview navigation
In order to preview my content
Background:
Given a "subsite" "My subsite"
And a "page" "My page" with "URLSegment"="my-page", "Content"="My page content <a name='aname'>aname</a><a href='other-page'>ahref</a>" and "Subsite"="=>Subsite.My subsite"
And a "page" "Other page" with "URLSegment"="other-page", "Content"="Other page content <form action='my-page'><input type='submit' value='Submit my form'></form>" and "Subsite"="=>Subsite.My subsite"
Given a "member" "Joe" belonging to "Admin Group" with "Email"="joe@test.com" and "Password"="Password1"
And the "group" "Admin Group" has permissions "Full administrative rights"
And I log in with "joe@test.com" and "Password1"
Given a "subsite" "MySubsite"
And a "page" "My page" with "URLSegment"="my-page", "Content"="My page content <a name='aname'>aname</a> <a href='[sitetree_link,id=5]'>ahref</a>" and "SubsiteID"="1"
And a "page" "Other page" with "URLSegment"="other-page", "Content"="Other page content <a href='[sitetree_link,id=4]'>Goto my page</a>" and "SubsiteID"="1"
And the "group" "EDITOR" has permissions "Access to 'Pages' section" and "Access to 'Subsites' section"
And I am logged in as a member of "EDITOR" group
@javascript
Scenario: I can navigate the subsite preview
When I go to "admin"
And I select "My subsite" from "SubsitesSelect"
And I go to "admin/pages"
When I go to "/admin/pages"
And I select "MySubsite" from "SubsitesSelect"
And I click on "My page" in the tree
And I press the "Publish" button
And I click on "Other page" in the tree
And I press the "Publish" button
And I click on "My page" in the tree
And I wait for 3 seconds
And I set the CMS mode to "Preview mode"
And I follow "ahref" in preview
And I wait for 1 second
Then the preview contains "Other page content"
# We are already on the second page, submit the form to return to first one.
When I wait for 3 seconds
And I press "Submit my form" in preview
# We are already on the second page, follow a link to return to first one.
And I follow "Goto my page" in preview
And I wait for 1 second
Then the preview contains "My page content"

View File

@ -0,0 +1,26 @@
Feature: Publish a page
As a CMS user
I can author pages in a new subsite
So that I can separate my website content by site
Background:
Given a "subsite" "Subsite B"
And a "page" "My page" with "URLSegment"="my-page", "Content"="My page content"
And the "group" "EDITOR" has permissions "Access to 'Pages' section" and "Access to 'Subsites' section"
And I am logged in as a member of "EDITOR" group
Then I go to "admin/pages"
@javascript
Scenario: I can publish a new page
Given I select "Subsite B" from "SubsitesSelect"
When I press the "Add new" button
And I press the "Create" button
And I set the CMS mode to "Edit mode"
And I fill in the "Content" HTML field with "<p>Some test content</p>"
Then I should see a "Publish" button
And I should not see a "Published" button
When I press the "Publish" button
And I wait for 3 seconds
Then I should see a "Published" button
And I should see a "Saved" button

BIN
tests/behat/files/file1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
tests/behat/files/file2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,43 @@
<?php
namespace SilverStripe\Subsites\Tests;
use SilverStripe\Core\Config\Config;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Subsites\Model\Subsite;
use SilverStripe\Subsites\State\SubsiteState;
class BaseSubsiteTest extends SapphireTest
{
protected function setUp(): void
{
parent::setUp();
SubsiteState::singleton()->setUseSessions(true);
Config::modify()->set(Subsite::class, 'write_hostmap', false);
Subsite::$force_subsite = null;
}
/**
* Avoid subsites filtering on fixture fetching.
* @param string $className
* @param string $identifier
* @return \SilverStripe\ORM\DataObject
*/
protected function objFromFixture($className, $identifier)
{
Subsite::disable_subsite_filter(true);
$obj = parent::objFromFixture($className, $identifier);
Subsite::disable_subsite_filter(false);
return $obj;
}
/**
* Tests the initial state of disable_subsite_filter
*/
public function testDisableSubsiteFilter()
{
$this->assertFalse(Subsite::$disable_subsite_filter);
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace SilverStripe\Subsites\Tests\Extensions;
use SilverStripe\AssetAdmin\Forms\FolderFormFactory;
use SilverStripe\Assets\Folder;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Forms\Form;
use SilverStripe\Forms\FormFactory;
class FolderFormFactoryExtensionTest extends SapphireTest
{
protected static $fixture_file = 'FolderFormFactoryExtensionTest.yml';
public function testSubsitesFolderDropdown()
{
$this->logInWithPermission('ADMIN');
/** @var Folder $folder */
$folder = $this->objFromFixture(Folder::class, 'folder_a');
/** @var Form $folderForm */
$folderForm = FolderFormFactory::create()->getForm(null, FormFactory::DEFAULT_NAME, [
'Record' => $folder
]);
$source = array_values($folderForm->Fields()->fieldByName('SubsiteID')->getSource() ?? []);
$result = array_values($source ?? []);
$this->assertContains('Main site', $result);
$this->assertContains('Subsite A', $result);
$this->assertContains('Subsite B', $result);
}
}

View File

@ -0,0 +1,11 @@
SilverStripe\Subsites\Model\Subsite:
main:
Title: Template
subsite_a:
Title: Subsite A
subsite_b:
Title: Subsite B
SilverStripe\Assets\Folder:
folder_a:
Title: Folder A

View File

@ -0,0 +1,129 @@
<?php
namespace SilverStripe\Subsites\Tests;
use SilverStripe\Assets\File;
use SilverStripe\Assets\Folder;
use SilverStripe\Core\Config\Config;
use SilverStripe\Forms\FieldList;
use SilverStripe\Subsites\Extensions\FileSubsites;
use SilverStripe\Subsites\Model\Subsite;
use SilverStripe\Security\Member;
class FileSubsitesTest extends BaseSubsiteTest
{
protected static $fixture_file = 'SubsiteTest.yml';
public function testTrivialFeatures()
{
$this->assertTrue(is_array(singleton(FileSubsites::class)->extraStatics()));
$file = new File();
$file->Name = 'FileTitle';
$file->Title = 'FileTitle';
$this->assertEquals(' * FileTitle', $file->alternateTreeTitle());
$file->SubsiteID = $this->objFromFixture(Subsite::class, 'domaintest1')->ID;
$this->assertEquals('FileTitle', $file->getTreeTitle());
$this->assertInstanceOf(FieldList::class, singleton(Folder::class)->getCMSFields());
Subsite::changeSubsite(1);
$this->assertEquals('subsite-1', $file->getExtensionInstance(FileSubsites::class)->cacheKeyComponent());
}
public function testWritingSubsiteID()
{
$this->logInAs('admin');
$subsite = $this->objFromFixture(Subsite::class, 'domaintest1');
Config::modify()->set(FileSubsites::class, 'default_root_folders_global', true);
Subsite::changeSubsite(0);
$file = new File();
$file->write();
$file->onAfterUpload();
$this->assertEquals((int)$file->SubsiteID, 0);
Subsite::changeSubsite($subsite->ID);
$this->assertTrue($file->canEdit());
$file = new File();
$file->write();
$this->assertEquals((int)$file->SubsiteID, 0);
$this->assertTrue($file->canEdit());
Config::modify()->set(FileSubsites::class, 'default_root_folders_global', false);
Subsite::changeSubsite($subsite->ID);
$file = new File();
$file->write();
$this->assertEquals($file->SubsiteID, $subsite->ID);
// Test inheriting from parent folder
$folder = new Folder();
$folder->write();
$this->assertEquals($folder->SubsiteID, $subsite->ID);
Config::modify()->set(FileSubsites::class, 'default_root_folders_global', true);
$file = new File();
$file->ParentID = $folder->ID;
$file->onAfterUpload();
$this->assertEquals($folder->SubsiteID, $file->SubsiteID);
}
/**
* @dataProvider provideTestCanEdit
*/
public function testCanEdit(
string $fileKey,
string $memberKey,
string $currentSubsiteKey,
bool $expected
): void {
$file = $this->objFromFixture(File::class, $fileKey);
$subsiteID = ($currentSubsiteKey === 'mainsite')
? 0 : $this->objFromFixture(Subsite::class, $currentSubsiteKey)->ID;
$member = $this->objFromFixture(Member::class, $memberKey);
Subsite::changeSubsite($subsiteID);
$this->assertSame($expected, $file->canEdit($member));
}
public function provideTestCanEdit(): array
{
$ret = [];
$data = [
// file
'subsite1file' => [
// member - has permissions to edit the file
'filetestyes' => [
// current subite => expected canEdit()
'subsite1' => true,
'subsite2' => false,
'mainsite' => true
],
// member - does not have permissions to edit the file
'filetestno' => [
'subsite1' => false,
'subsite2' => false,
'mainsite' => false
],
],
'mainsitefile' => [
'filetestyes' => [
'subsite1' => true,
'subsite2' => true,
'mainsite' => true
],
'filetestno' => [
'subsite1' => false,
'subsite2' => false,
'mainsite' => false
],
]
];
foreach (array_keys($data) as $fileKey) {
foreach (array_keys($data[$fileKey]) as $memberKey) {
foreach ($data[$fileKey][$memberKey] as $currentSubsiteKey => $expected) {
$ret[] = [$fileKey, $memberKey, $currentSubsiteKey, $expected];
}
}
}
return $ret;
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace SilverStripe\Subsites\Tests;
use SilverStripe\Forms\FieldList;
use SilverStripe\Security\Group;
use SilverStripe\Subsites\Extensions\GroupSubsites;
use SilverStripe\Subsites\Model\Subsite;
class GroupSubsitesTest extends BaseSubsiteTest
{
protected static $fixture_file = 'SubsiteTest.yml';
protected $requireDefaultRecordsFrom = [GroupSubsites::class];
public function testTrivialFeatures()
{
$this->assertIsArray(singleton(GroupSubsites::class)->extraStatics());
$this->assertIsArray(singleton(GroupSubsites::class)->providePermissions());
$this->assertInstanceOf(FieldList::class, singleton(Group::class)->getCMSFields());
}
public function testAlternateTreeTitle()
{
$group = new Group();
$group->Title = 'The A Team';
$group->AccessAllSubsites = true;
$this->assertEquals('The A Team <i>(global group)</i>', $group->getTreeTitle());
$group->AccessAllSubsites = false;
$group->write();
$group->Subsites()->add($this->objFromFixture(Subsite::class, 'domaintest1'));
$group->Subsites()->add($this->objFromFixture(Subsite::class, 'domaintest2'));
$this->assertEquals('The A Team <i>(Test 1, Test 2)</i>', $group->getTreeTitle());
}
}

View File

@ -0,0 +1,80 @@
<?php
namespace SilverStripe\Subsites\Tests;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Subsites\Middleware\InitStateMiddleware;
use SilverStripe\Subsites\Model\Subsite;
use SilverStripe\Subsites\State\SubsiteState;
class InitStateMiddlewareTest extends BaseSubsiteTest
{
protected static $fixture_file = 'SubsiteTest.yml';
/**
* Original value of $_REQUEST
*
* @var array
*/
protected $origServer = [];
protected function setUp(): void
{
parent::setUp();
$this->origServer = $_SERVER;
}
protected function tearDown(): void
{
$_SERVER = $this->origServer;
parent::tearDown();
}
public function testDomainDetectionViaServerHeaders()
{
$_SERVER['HTTP_HOST'] = 'one.example.org';
$this->getMiddleware()->process($this->getRequest(), $this->getCallback());
$expectedSubsite = $this->objFromFixture(Subsite::class, 'domaintest1');
$this->assertEquals($expectedSubsite->ID, $this->getState()->getSubsiteId());
}
public function testDomainDetectionViaRequestOverridesServerHeaders()
{
$_SERVER['HTTP_HOST'] = 'one.example.org';
$this->getMiddleware()->process($this->getRequest('two.mysite.com'), $this->getCallback());
$expectedSubsite = $this->objFromFixture(Subsite::class, 'domaintest2');
$this->assertEquals($expectedSubsite->ID, $this->getState()->getSubsiteId());
}
protected function getMiddleware()
{
return new InitStateMiddleware();
}
protected function getRequest($domain = null)
{
$request = new HTTPRequest('GET', '/test/url');
if ($domain) {
$request->addHeader('host', $domain);
}
return $request;
}
protected function getCallback()
{
return function () {
};
}
protected function getState()
{
return Injector::inst()->get(SubsiteState::class);
}
}

View File

@ -0,0 +1,114 @@
<?php
namespace SilverStripe\Subsites\Tests;
use SilverStripe\Admin\LeftAndMain;
use SilverStripe\AssetAdmin\Controller\AssetAdmin;
use SilverStripe\CMS\Controllers\CMSMain;
use SilverStripe\CMS\Controllers\CMSPageEditController;
use SilverStripe\Core\Config\Config;
use SilverStripe\Dev\FunctionalTest;
use SilverStripe\Security\Member;
use SilverStripe\Subsites\Extensions\LeftAndMainSubsites;
use SilverStripe\Subsites\Model\Subsite;
use SilverStripe\Subsites\State\SubsiteState;
class LeftAndMainSubsitesTest extends FunctionalTest
{
protected static $fixture_file = 'SubsiteTest.yml';
/**
* Avoid subsites filtering on fixture fetching.
* @param string $className
* @param string $identifier
* @return \SilverStripe\ORM\DataObject
*/
protected function objFromFixture($className, $identifier)
{
Subsite::disable_subsite_filter(true);
$obj = parent::objFromFixture($className, $identifier);
Subsite::disable_subsite_filter(false);
return $obj;
}
public function testSectionSites()
{
$member = $this->objFromFixture(Member::class, 'subsite1member');
$cmsmain = singleton(CMSMain::class);
$subsites = $cmsmain->sectionSites(true, 'Main site', $member);
$this->assertListEquals([
['Title' => 'Subsite1 Template']
], $subsites, 'Lists member-accessible sites for the accessible controller.');
$assetadmin = singleton(AssetAdmin::class);
$subsites = $assetadmin->sectionSites(true, 'Main site', $member);
$this->assertListEquals([], $subsites, 'Does not list any sites for forbidden controller.');
$member = $this->objFromFixture(Member::class, 'editor');
$cmsmain = singleton(CMSMain::class);
$subsites = $cmsmain->sectionSites(true, 'Main site', $member);
$this->assertListContains([
['Title' => 'Main site']
], $subsites, 'Includes the main site for members who can access all sites.');
}
public function testAccessChecksDontChangeCurrentSubsite()
{
$this->logInAs('admin');
$ids = [];
$subsite1 = $this->objFromFixture(Subsite::class, 'domaintest1');
$subsite2 = $this->objFromFixture(Subsite::class, 'domaintest2');
$subsite3 = $this->objFromFixture(Subsite::class, 'domaintest3');
$ids[] = $subsite1->ID;
$ids[] = $subsite2->ID;
$ids[] = $subsite3->ID;
$ids[] = 0;
// Enable session-based subsite tracking.
SubsiteState::singleton()->setUseSessions(true);
foreach ($ids as $id) {
Subsite::changeSubsite($id);
$this->assertEquals($id, SubsiteState::singleton()->getSubsiteId());
$left = new LeftAndMain();
$this->assertTrue($left->canView(), "Admin user can view subsites LeftAndMain with id = '$id'");
$this->assertEquals(
$id,
SubsiteState::singleton()->getSubsiteId(),
'The current subsite has not been changed in the process of checking permissions for admin user.'
);
}
}
public function testShouldChangeSubsite()
{
$l = new LeftAndMain();
Config::modify()->set(CMSPageEditController::class, 'treats_subsite_0_as_global', false);
$this->assertTrue($l->shouldChangeSubsite(CMSPageEditController::class, 0, 5));
$this->assertFalse($l->shouldChangeSubsite(CMSPageEditController::class, 0, 0));
$this->assertTrue($l->shouldChangeSubsite(CMSPageEditController::class, 1, 5));
$this->assertFalse($l->shouldChangeSubsite(CMSPageEditController::class, 1, 1));
Config::modify()->set(CMSPageEditController::class, 'treats_subsite_0_as_global', true);
$this->assertFalse($l->shouldChangeSubsite(CMSPageEditController::class, 0, 5));
$this->assertFalse($l->shouldChangeSubsite(CMSPageEditController::class, 0, 0));
$this->assertTrue($l->shouldChangeSubsite(CMSPageEditController::class, 1, 5));
$this->assertFalse($l->shouldChangeSubsite(CMSPageEditController::class, 1, 1));
}
public function testCanAccessWithPassedMember()
{
$memberID = $this->logInWithPermission('ADMIN');
$member = Member::get()->byID($memberID);
/** @var LeftAndMain&LeftAndMainSubsites $leftAndMain */
$leftAndMain = new LeftAndMain();
$this->assertTrue($leftAndMain->canAccess($member));
}
}

View File

@ -0,0 +1,143 @@
<?php
namespace SilverStripe\Subsites\Tests\Service;
use SilverStripe\Core\Config\Config;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Subsites\Model\Subsite;
use SilverStripe\Subsites\Service\ThemeResolver;
use SilverStripe\View\SSViewer;
class ThemeResolverTest extends SapphireTest
{
protected $themeList = [
'$public',
'custom',
'main',
'backup',
SSViewer::DEFAULT_THEME,
];
protected function setUp(): void
{
parent::setUp();
// Setup known theme config
Config::modify()->set(SSViewer::class, 'themes', $this->themeList);
}
public function testSubsiteWithoutThemeReturnsDefaultThemeList()
{
$subsite = new Subsite();
$resolver = new ThemeResolver();
$this->assertSame($this->themeList, $resolver->getThemeList($subsite));
}
public function testSubsiteWithCustomThemePrependsToList()
{
$subsite = new Subsite();
$subsite->Theme = 'subsite';
$resolver = new ThemeResolver();
$expected = array_merge(['subsite'], $this->themeList);
$this->assertSame($expected, $resolver->getThemeList($subsite));
}
public function testSubsiteWithCustomThemeDoesNotCascadeUpTheList()
{
$subsite = new Subsite();
$subsite->Theme = 'main';
$resolver = new ThemeResolver();
$expected = [
'main', // 'main' is moved to the top
'$public', // $public is preserved
// Anything above 'main' is removed
'backup',
SSViewer::DEFAULT_THEME,
];
$this->assertSame($expected, $resolver->getThemeList($subsite));
}
/**
* @dataProvider customThemeDefinitionsAreRespectedProvider
*/
public function testCustomThemeDefinitionsAreRespected($themeOptions, $siteTheme, $expected)
{
Config::modify()->set(ThemeResolver::class, 'theme_options', $themeOptions);
$subsite = new Subsite();
$subsite->Theme = $siteTheme;
$resolver = new ThemeResolver();
$this->assertSame($expected, $resolver->getThemeList($subsite));
}
public function customThemeDefinitionsAreRespectedProvider()
{
return [
// Simple
[
['test' => $expected = [
'subsite',
'backup',
'$public',
SSViewer::DEFAULT_THEME,
]],
'test',
$expected
],
// Many options
[
[
'aye' => [
'aye',
'thing',
SSViewer::DEFAULT_THEME,
],
'bee' => $expected = [
'subsite',
'backup',
'$public',
SSViewer::DEFAULT_THEME,
],
'sea' => [
'mer',
'ocean',
SSViewer::DEFAULT_THEME,
],
],
'bee',
$expected
],
// Conflicting with root definitions
[
['main' => $expected = [
'subsite',
'backup',
'$public',
SSViewer::DEFAULT_THEME,
]],
'main',
$expected
],
// Declaring a theme specifically should still work
[
['test' => [
'subsite',
'backup',
'$public',
SSViewer::DEFAULT_THEME,
]],
'other',
array_merge(['other'], $this->themeList)
],
];
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace SilverStripe\Subsites\Tests;
use SilverStripe\SiteConfig\SiteConfig;
use SilverStripe\Subsites\Extensions\SiteConfigSubsites;
use SilverStripe\Subsites\Model\Subsite;
class SiteConfigSubsitesTest extends BaseSubsiteTest
{
protected static $fixture_file = 'SubsiteTest.yml';
public function testEachSubsiteHasAUniqueSiteConfig()
{
$subsite1 = $this->objFromFixture(Subsite::class, 'domaintest1');
$subsite2 = $this->objFromFixture(Subsite::class, 'domaintest2');
$this->assertTrue(is_array(singleton(SiteConfigSubsites::class)->extraStatics()));
Subsite::changeSubsite(0);
$sc = SiteConfig::current_site_config();
$sc->Title = 'RootSite';
$sc->write();
Subsite::changeSubsite($subsite1->ID);
$sc = SiteConfig::current_site_config();
$sc->Title = 'Subsite1';
$sc->write();
Subsite::changeSubsite($subsite2->ID);
$sc = SiteConfig::current_site_config();
$sc->Title = 'Subsite2';
$sc->write();
Subsite::changeSubsite(0);
$this->assertEquals('RootSite', SiteConfig::current_site_config()->Title);
Subsite::changeSubsite($subsite1->ID);
$this->assertEquals('Subsite1', SiteConfig::current_site_config()->Title);
Subsite::changeSubsite($subsite2->ID);
$this->assertEquals('Subsite2', SiteConfig::current_site_config()->Title);
$keys = SiteConfig::current_site_config()->extend('cacheKeyComponent');
$this->assertContains('subsite-' . $subsite2->ID, $keys);
}
}

View File

@ -0,0 +1,494 @@
<?php
namespace SilverStripe\Subsites\Tests;
use Page;
use SilverStripe\CMS\Controllers\CMSMain;
use SilverStripe\CMS\Controllers\ModelAsController;
use SilverStripe\CMS\Forms\SiteTreeURLSegmentField;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Control\Director;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Convert;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\ErrorPage\ErrorPage;
use SilverStripe\Forms\FieldList;
use SilverStripe\i18n\i18n;
use SilverStripe\Security\Member;
use SilverStripe\SiteConfig\SiteConfig;
use SilverStripe\Subsites\Extensions\SiteTreeSubsites;
use SilverStripe\Subsites\Model\Subsite;
use SilverStripe\Subsites\Pages\SubsitesVirtualPage;
use SilverStripe\Subsites\Service\ThemeResolver;
use SilverStripe\Subsites\Tests\SiteTreeSubsitesTest\TestClassA;
use SilverStripe\Subsites\Tests\SiteTreeSubsitesTest\TestClassB;
use SilverStripe\Subsites\Tests\SiteTreeSubsitesTest\TestErrorPage;
use SilverStripe\Versioned\Versioned;
use SilverStripe\View\SSViewer;
use TractorCow\Fluent\Extension\FluentSiteTreeExtension;
class SiteTreeSubsitesTest extends BaseSubsiteTest
{
protected static $fixture_file = 'SubsiteTest.yml';
protected static $extra_dataobjects = [
TestClassA::class,
TestClassB::class,
TestErrorPage::class
];
protected static $illegal_extensions = [
SiteTree::class => [
FluentSiteTreeExtension::class,
],
];
protected function setUp(): void
{
// We have our own home page fixtures, prevent the default one being created in this test suite.
Config::modify()->set(SiteTree::class, 'create_default_pages', false);
parent::setUp();
}
public function testPagesInDifferentSubsitesCanShareURLSegment()
{
$subsiteMain = $this->objFromFixture(Subsite::class, 'main');
$subsite1 = $this->objFromFixture(Subsite::class, 'subsite1');
$pageMain = new SiteTree();
$pageMain->URLSegment = 'testpage';
$pageMain->write();
$pageMain->copyVersionToStage('Stage', 'Live');
$pageMainOther = new SiteTree();
$pageMainOther->URLSegment = 'testpage';
$pageMainOther->write();
$pageMainOther->copyVersionToStage('Stage', 'Live');
$this->assertNotEquals(
$pageMain->URLSegment,
$pageMainOther->URLSegment,
'Pages in same subsite cant share the same URL'
);
Subsite::changeSubsite($subsite1->ID);
$pageSubsite1 = new SiteTree();
$pageSubsite1->URLSegment = 'testpage';
$pageSubsite1->write();
$pageSubsite1->copyVersionToStage('Stage', 'Live');
$this->assertEquals(
$pageMain->URLSegment,
$pageSubsite1->URLSegment,
'Pages in different subsites can share the same URL'
);
}
public function testBasicSanity()
{
$this->assertInstanceOf(SiteConfig::class, singleton(SiteTree::class)->getSiteConfig());
// The following assert is breaking in Translatable.
$this->assertInstanceOf(FieldList::class, singleton(SiteTree::class)->getCMSFields());
$this->assertInstanceOf(FieldList::class, singleton(SubsitesVirtualPage::class)->getCMSFields());
$this->assertTrue(is_array(singleton(SiteTreeSubsites::class)->extraStatics()));
}
public function errorPageLocationsProvider()
{
return [
['domaintest1', '/error-500-one.example.org.html'],
['domaintestVagrant', '/error-500-localhost8080.html']
];
}
/**
* @dataProvider errorPageLocationsProvider
*/
public function testErrorPageLocations($subsiteFixtureName, $expectedFilename)
{
$static_path = Config::inst()->get(ErrorPage::class, 'static_filepath');
$subsite = $this->objFromFixture(Subsite::class, $subsiteFixtureName);
$expected_path = $static_path . $expectedFilename;
Subsite::changeSubsite($subsite->ID);
$path = TestErrorPage::get_error_filename_spy(500);
$this->assertEquals($expected_path, $path);
}
public function testCanEditSiteTree()
{
$admin = $this->objFromFixture(Member::class, 'admin');
$subsite1member = $this->objFromFixture(Member::class, 'subsite1member');
$subsite2member = $this->objFromFixture(Member::class, 'subsite2member');
$mainpage = $this->objFromFixture('Page', 'home');
$subsite1page = $this->objFromFixture('Page', 'subsite1_home');
$subsite2page = $this->objFromFixture('Page', 'subsite2_home');
$subsite1 = $this->objFromFixture(Subsite::class, 'subsite1');
$subsite2 = $this->objFromFixture(Subsite::class, 'subsite2');
// Cant pass member as arguments to canEdit() because of GroupSubsites
$this->logInAs($admin);
$this->assertTrue(
(bool)$subsite1page->canEdit(),
'Administrators can edit all subsites'
);
// @todo: Workaround because GroupSubsites->augmentSQL() is relying on session state
Subsite::changeSubsite($subsite1);
$this->logInAs($subsite1member->ID);
$this->assertTrue(
(bool)$subsite1page->canEdit(),
'Members can edit pages on a subsite if they are in a group belonging to this subsite'
);
$this->logInAs($subsite2member->ID);
$this->assertFalse(
(bool)$subsite1page->canEdit(),
'Members cant edit pages on a subsite if they are not in a group belonging to this subsite'
);
// @todo: Workaround because GroupSubsites->augmentSQL() is relying on session state
Subsite::changeSubsite(0);
$this->assertFalse(
$mainpage->canEdit(),
'Members cant edit pages on the main site if they are not in a group allowing this'
);
}
/**
* Similar to {@link SubsitesVirtualPageTest->testSubsiteVirtualPageCanHaveSameUrlsegmentAsOtherSubsite()}.
*/
public function testTwoPagesWithSameURLOnDifferentSubsites()
{
// Set up a couple of pages with the same URL on different subsites
$s1 = $this->objFromFixture(Subsite::class, 'domaintest1');
$s2 = $this->objFromFixture(Subsite::class, 'domaintest2');
$p1 = new SiteTree();
$p1->Title = $p1->URLSegment = 'test-page';
$p1->SubsiteID = $s1->ID;
$p1->write();
$p2 = new SiteTree();
$p2->Title = $p1->URLSegment = 'test-page';
$p2->SubsiteID = $s2->ID;
$p2->write();
// Check that the URLs weren't modified in our set-up
$this->assertEquals('test-page', $p1->URLSegment);
$this->assertEquals('test-page', $p2->URLSegment);
// Check that if we switch between the different subsites, we receive the correct pages
Subsite::changeSubsite($s1);
$this->assertEquals($p1->ID, SiteTree::get_by_link('test-page')->ID);
Subsite::changeSubsite($s2);
$this->assertEquals($p2->ID, SiteTree::get_by_link('test-page')->ID);
}
public function testIgnoreSubsiteLocale()
{
$ignore_subsite_locale = Config::inst()->set(SiteTreeSubsites::class, 'ignore_subsite_locale', true);
$subsitePage = $this->objFromFixture(Page::class, 'subsite_locale_about');
Subsite::changeSubsite($subsitePage->SubsiteID);
$controller = ModelAsController::controller_for($subsitePage);
$i18n_locale_before = i18n::get_locale();
SiteTree::singleton()->extend('contentcontrollerInit', $controller);
$i18n_locale_after = i18n::get_locale();
$this->assertEquals($i18n_locale_before, $i18n_locale_after);
}
public function testPageTypesBlacklistInClassDropdown()
{
$this->logInAs('editor');
$s1 = $this->objFromFixture(Subsite::class, 'domaintest1');
$s2 = $this->objFromFixture(Subsite::class, 'domaintest2');
$page = singleton(SiteTree::class);
$s1->PageTypeBlacklist = json_encode([TestClassA::class, ErrorPage::class]);
$s1->write();
Subsite::changeSubsite($s1);
$settingsFields = $page->getSettingsFields()->dataFieldByName('ClassName')->getSource();
$this->assertArrayNotHasKey(
ErrorPage::class,
$settingsFields
);
$this->assertArrayNotHasKey(
TestClassA::class,
$settingsFields
);
$this->assertArrayHasKey(
TestClassB::class,
$settingsFields
);
Subsite::changeSubsite($s2);
$settingsFields = $page->getSettingsFields()->dataFieldByName('ClassName')->getSource();
$this->assertArrayHasKey(
ErrorPage::class,
$settingsFields
);
$this->assertArrayHasKey(
TestClassA::class,
$settingsFields
);
$this->assertArrayHasKey(
TestClassB::class,
$settingsFields
);
}
public function testCopyToSubsite()
{
// Remove baseurl if testing in subdir
Config::modify()->set(Director::class, 'alternate_base_url', '/');
/** @var Subsite $otherSubsite */
$otherSubsite = $this->objFromFixture(Subsite::class, 'subsite1');
$staffPage = $this->objFromFixture(Page::class, 'staff'); // nested page
$contactPage = $this->objFromFixture(Page::class, 'contact'); // top level page
$staffPage2 = $staffPage->duplicateToSubsite($otherSubsite->ID);
$contactPage2 = $contactPage->duplicateToSubsite($otherSubsite->ID);
$this->assertNotEquals($staffPage->ID, $staffPage2->ID);
$this->assertNotEquals($staffPage->SubsiteID, $staffPage2->SubsiteID);
$this->assertNotEquals($contactPage->ID, $contactPage2->ID);
$this->assertNotEquals($contactPage->SubsiteID, $contactPage2->SubsiteID);
$this->assertEmpty($staffPage2->ParentID);
$this->assertEmpty($contactPage2->ParentID);
$this->assertNotEmpty($staffPage->ParentID);
$this->assertEmpty($contactPage->ParentID);
// Staff is shifted to top level and given a unique url segment
$domain = $otherSubsite->domain();
$this->assertEquals('http://' . $domain . '/staff-2/', $staffPage2->AbsoluteLink());
$this->assertEquals('http://' . $domain . '/contact-us-2/', $contactPage2->AbsoluteLink());
}
public function testPageTypesBlacklistInCMSMain()
{
$this->logInAs('editor');
$s1 = $this->objFromFixture(Subsite::class, 'domaintest1');
$s2 = $this->objFromFixture(Subsite::class, 'domaintest2');
$s1->PageTypeBlacklist = json_encode([TestClassA::class, ErrorPage::class]);
$s1->write();
Subsite::changeSubsite($s1);
$cmsmain = CMSMain::create();
$hints = json_decode($cmsmain->SiteTreeHints() ?? '', true);
$classes = $hints['Root']['disallowedChildren'];
$this->assertContains(ErrorPage::class, $classes);
$this->assertContains(TestClassA::class, $classes);
$this->assertNotContains(TestClassB::class, $classes);
Subsite::changeSubsite($s2);
// SS 4.1 and above
if ($cmsmain->hasMethod('getHintsCache')) {
$cmsmain->getHintsCache()->clear();
}
$hints = json_decode($cmsmain->SiteTreeHints() ?? '', true);
$classes = $hints['Root']['disallowedChildren'];
$this->assertNotContains(ErrorPage::class, $classes);
$this->assertNotContains(TestClassA::class, $classes);
$this->assertNotContains(TestClassB::class, $classes);
}
/**
* Tests that url segments between subsites don't conflict, but do conflict within them
*/
public function testValidateURLSegment()
{
$this->logInWithPermission('ADMIN');
// Saving existing page in the same subsite doesn't change urls
$mainHome = $this->objFromFixture(Page::class, 'home');
$mainSubsiteID = $this->idFromFixture(Subsite::class, 'main');
Subsite::changeSubsite($mainSubsiteID);
$mainHome->Content = '<p>Some new content</p>';
$mainHome->write();
$this->assertEquals('home', $mainHome->URLSegment);
$mainHome->publishRecursive();
$mainHomeLive = Versioned::get_one_by_stage('Page', 'Live', sprintf('"SiteTree"."ID" = \'%d\'', $mainHome->ID));
$this->assertEquals('home', $mainHomeLive->URLSegment);
// Saving existing page in another subsite doesn't change urls
Subsite::changeSubsite($mainSubsiteID);
$subsite1Home = $this->objFromFixture('Page', 'subsite1_home');
$subsite1Home->Content = '<p>In subsite 1</p>';
$subsite1Home->write();
$this->assertEquals('home', $subsite1Home->URLSegment);
$subsite1Home->publishRecursive();
$subsite1HomeLive = Versioned::get_one_by_stage(
'Page',
'Live',
sprintf('"SiteTree"."ID" = \'%d\'', $subsite1Home->ID)
);
$this->assertEquals('home', $subsite1HomeLive->URLSegment);
// Creating a new page in a subsite doesn't conflict with urls in other subsites
$subsite1ID = $this->idFromFixture(Subsite::class, 'subsite1');
Subsite::changeSubsite($subsite1ID);
$subsite1NewPage = new Page();
$subsite1NewPage->SubsiteID = $subsite1ID;
$subsite1NewPage->Title = 'Important Page (Subsite 1)';
$subsite1NewPage->URLSegment = 'important-page'; // Also exists in main subsite
$subsite1NewPage->write();
$this->assertEquals('important-page', $subsite1NewPage->URLSegment);
$subsite1NewPage->publishRecursive();
$subsite1NewPageLive = Versioned::get_one_by_stage(
'Page',
'Live',
sprintf('"SiteTree"."ID" = \'%d\'', $subsite1NewPage->ID)
);
$this->assertEquals('important-page', $subsite1NewPageLive->URLSegment);
// Creating a new page in a subsite DOES conflict with urls in the same subsite
$subsite1NewPage2 = new Page();
$subsite1NewPage2->SubsiteID = $subsite1ID;
$subsite1NewPage2->Title = 'Important Page (Subsite 1)';
$subsite1NewPage2->URLSegment = 'important-page'; // Also exists in main subsite
$subsite1NewPage2->write();
$this->assertEquals('important-page-2', $subsite1NewPage2->URLSegment);
$subsite1NewPage2->publishRecursive();
$subsite1NewPage2Live = Versioned::get_one_by_stage(
'Page',
'Live',
sprintf('"SiteTree"."ID" = \'%d\'', $subsite1NewPage2->ID)
);
$this->assertEquals('important-page-2', $subsite1NewPage2Live->URLSegment);
// Original page is left un-modified
$mainSubsiteImportantPageID = $this->idFromFixture('Page', 'importantpage');
$mainSubsiteImportantPage = Page::get()->byID($mainSubsiteImportantPageID);
$this->assertEquals('important-page', $mainSubsiteImportantPage->URLSegment);
$mainSubsiteImportantPage->Content = '<p>New Important Page Content</p>';
$mainSubsiteImportantPage->write();
$this->assertEquals('important-page', $mainSubsiteImportantPage->URLSegment);
}
/**
* @param bool $withChildren
* @param int $expectedChildren
* @dataProvider duplicateToSubsiteProvider
*/
public function testDuplicateToSubsite($withChildren, $expectedChildren)
{
/** @var SiteTree $page */
$page = $this->objFromFixture(Page::class, 'about');
/** @var Subsite $newSubsite */
$newSubsite = $this->objFromFixture(Subsite::class, 'subsite1');
/** @var SiteTree $duplicatedPage */
$duplicatedPage = $page->duplicateToSubsite($newSubsite->ID, $withChildren);
$this->assertInstanceOf(SiteTree::class, $duplicatedPage, 'A new page is returned');
$this->assertEquals($newSubsite->ID, $duplicatedPage->SubsiteID, 'Ensure returned records are on new subsite');
$this->assertCount(1, $page->AllChildren());
$this->assertCount(
$expectedChildren,
$duplicatedPage->AllChildren(),
'Duplicated page also duplicates children'
);
}
/**
* @return array[]
*/
public function duplicateToSubsiteProvider()
{
return [
[true, 1],
[false, 0],
];
}
public function testThemeResolverIsUsedForSettingThemeList()
{
$firstResolver = $this->createMock(ThemeResolver::class);
$firstResolver->expects($this->never())->method('getThemeList');
Injector::inst()->registerService($firstResolver, ThemeResolver::class);
$subsitePage = $this->objFromFixture(Page::class, 'home');
Subsite::changeSubsite($subsitePage->SubsiteID);
$controller = ModelAsController::controller_for($subsitePage);
SiteTree::singleton()->extend('contentcontrollerInit', $controller);
$secondResolver = $this->createMock(ThemeResolver::class);
$secondResolver->expects($this->once())->method('getThemeList');
Injector::inst()->registerService($secondResolver, ThemeResolver::class);
$subsitePage = $this->objFromFixture(Page::class, 'subsite1_home');
Subsite::changeSubsite($subsitePage->SubsiteID);
$controller = ModelAsController::controller_for($subsitePage);
SiteTree::singleton()->extend('contentcontrollerInit', $controller);
}
public function provideAlternateAbsoluteLink()
{
return [
['home', null, 'http://localhost/'],
['home', 'myaction', 'http://localhost/home/myaction'],
['contact', null, 'http://localhost/contact-us/'],
['contact', 'myaction', 'http://localhost/contact-us/myaction'],
['subsite1_home', null, 'http://subsite1.localhost/'],
['subsite1_home', 'myaction', 'http://subsite1.localhost/home/myaction'],
['subsite1_contactus', null, 'http://subsite1.localhost/contact-us/'],
['subsite1_contactus', 'myaction', 'http://subsite1.localhost/contact-us/myaction']
];
}
/**
* @dataProvider provideAlternateAbsoluteLink
* @param string $pageFixtureName
* @param string|null $action
* @param string $expectedAbsoluteLink
*/
public function testAlternateAbsoluteLink($pageFixtureName, $action, $expectedAbsoluteLink)
{
// Setting a control value, in case base url is set for the installation under test
Config::modify()->set(Director::class, 'alternate_base_url', 'http://localhost/');
/** @var Page $page */
$page = $this->objFromFixture(Page::class, $pageFixtureName);
$result = $page->AbsoluteLink($action);
$this->assertEquals($expectedAbsoluteLink, $result);
}
public function testURLSegmentBaseIsSetToSubsiteBaseURL()
{
// This subsite has a domain with 'one.example.org' as the primary domain
/** @var Subsite $subsite */
$subsite = $this->objFromFixture(Subsite::class, 'domaintest1');
Subsite::changeSubsite($subsite);
$page = new SiteTree();
$page->SubsiteID = $subsite->ID;
$page->write();
$fields = $page->getCMSFields();
/** @var SiteTreeURLSegmentField $urlSegmentField */
$urlSegmentField = $fields->dataFieldByName('URLSegment');
$this->assertInstanceOf(SiteTreeURLSegmentField::class, $urlSegmentField);
$this->assertSame('http://one.example.org/', $urlSegmentField->getURLPrefix());
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace SilverStripe\Subsites\Tests\SiteTreeSubsitesTest;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Dev\TestOnly;
class TestClassA extends SiteTree implements TestOnly
{
}

View File

@ -0,0 +1,10 @@
<?php
namespace SilverStripe\Subsites\Tests\SiteTreeSubsitesTest;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Dev\TestOnly;
class TestClassB extends SiteTree implements TestOnly
{
}

View File

@ -0,0 +1,20 @@
<?php
namespace SilverStripe\Subsites\Tests\SiteTreeSubsitesTest;
use SilverStripe\Dev\TestOnly;
use SilverStripe\ErrorPage\ErrorPage;
class TestErrorPage 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);
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace SilverStripe\Subsites\Tests\State;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Subsites\State\SubsiteState;
class SubsiteStateTest extends SapphireTest
{
public function testStateIsInjectable()
{
$this->assertInstanceOf(SubsiteState::class, Injector::inst()->get(SubsiteState::class));
}
public function testGetSubsiteIdWasChanged()
{
$state = new SubsiteState;
// Initial set, doesn't count as being changed
$state->setSubsiteId(123);
$this->assertFalse($state->getSubsiteIdWasChanged());
// Subsequent set with the same value, doesn't count as being changed
$state->setSubsiteId(123);
$this->assertFalse($state->getSubsiteIdWasChanged());
// Subsequent set with new value, counts as changed
$state->setSubsiteId(234);
$this->assertTrue($state->getSubsiteIdWasChanged());
}
public function testWithState()
{
$state = new SubsiteState;
$state->setSubsiteId(123);
$state->withState(function ($newState) use ($state) {
$this->assertInstanceOf(SubsiteState::class, $newState);
$this->assertNotSame($newState, $state);
$newState->setSubsiteId(234);
$this->assertSame(234, $newState->getSubsiteId());
$this->assertSame(123, $state->getSubsiteId());
});
$this->assertSame(123, $state->getSubsiteId());
}
}

View File

@ -0,0 +1,201 @@
<?php
namespace SilverStripe\Subsites\Tests;
use Page;
use SilverStripe\CMS\Controllers\CMSPageEditController;
use SilverStripe\Core\Config\Config;
use SilverStripe\Dev\FunctionalTest;
use SilverStripe\Subsites\Model\Subsite;
class SubsiteAdminFunctionalTest extends FunctionalTest
{
protected static $fixture_file = 'SubsiteTest.yml';
protected $autoFollowRedirection = false;
protected function setUp(): void
{
parent::setUp();
// Ensure all pages are published
/** @var Page $page */
foreach (Page::get() as $page) {
$page->publishSingle();
}
}
/**
* Helper: FunctionalTest is only able to follow redirection once, we want to go all the way.
* @param string $url
* @return \SilverStripe\Control\HTTPResponse
*/
public function getAndFollowAll($url)
{
$response = $this->get($url);
while ($location = $response->getHeader('Location')) {
$response = $this->mainSession->followRedirection();
}
echo $response->getHeader('Location');
return $response;
}
/**
* Anonymous user cannot access anything.
*/
public function testAnonymousIsForbiddenAdminAccess()
{
$this->logOut();
$response = $this->getAndFollowAll('admin/pages/?SubsiteID=0');
$this->assertStringContainsString('Security/login', $this->mainSession->lastUrl(), 'Admin is disallowed');
$subsite1 = $this->objFromFixture(Subsite::class, 'subsite1');
$response = $this->getAndFollowAll("admin/pages/?SubsiteID={$subsite1->ID}");
$this->assertStringContainsString('Security/login', $this->mainSession->lastUrl(), 'Admin is disallowed');
$response = $this->getAndFollowAll('admin/subsite_xhr');
$this->assertStringContainsString(
'Security/login',
$this->mainSession->lastUrl(),
'SubsiteXHRController is disallowed'
);
}
/**
* Admin should be able to access all subsites and the main site
*/
public function testAdminCanAccessAllSubsites()
{
$this->logInAs('admin');
$this->getAndFollowAll('admin/pages/?SubsiteID=0');
$this->assertEquals(0, $this->session()->get('SubsiteID'), 'Can access main site.');
$this->assertStringContainsString('admin/pages', $this->mainSession->lastUrl(), 'Lands on the correct section');
$subsite1 = $this->objFromFixture(Subsite::class, 'subsite1');
$this->getAndFollowAll("admin/pages/?SubsiteID={$subsite1->ID}");
// Check the session manually, since the state is unique to the request, not this test
$this->assertEquals($subsite1->ID, $this->session()->get('SubsiteID'), 'Can access other subsite.');
$this->assertStringContainsString('admin/pages', $this->mainSession->lastUrl(), 'Lands on the correct section');
$response = $this->getAndFollowAll('admin/subsite_xhr');
$this->assertStringNotContainsString(
'Security/login',
$this->mainSession->lastUrl(),
'SubsiteXHRController is reachable'
);
}
public function testAdminIsRedirectedToObjectsSubsite()
{
$this->logInAs('admin');
$mainSubsitePage = $this->objFromFixture(Page::class, 'mainSubsitePage');
$subsite1Home = $this->objFromFixture(Page::class, 'subsite1_home');
// Requesting a page from another subsite will redirect to that subsite
Config::modify()->set(CMSPageEditController::class, 'treats_subsite_0_as_global', false);
$response = $this->get("admin/pages/edit/show/$subsite1Home->ID");
$this->assertEquals(302, $response->getStatusCode());
$this->assertStringContainsString(
'admin/pages/edit/show/' . $subsite1Home->ID . '?SubsiteID=' . $subsite1Home->SubsiteID,
$response->getHeader('Location')
);
// Loading a non-main-site object still switches the subsite if configured with treats_subsite_0_as_global
Config::modify()->set(CMSPageEditController::class, 'treats_subsite_0_as_global', true);
$response = $this->get("admin/pages/edit/show/$subsite1Home->ID");
$this->assertEquals(302, $response->getStatusCode());
$this->assertStringContainsString(
'admin/pages/edit/show/' . $subsite1Home->ID . '?SubsiteID=' . $subsite1Home->SubsiteID,
$response->getHeader('Location')
);
// Loading a main-site object does not change the subsite if configured with treats_subsite_0_as_global
$response = $this->get("admin/pages/edit/show/$mainSubsitePage->ID");
$this->assertEquals(200, $response->getStatusCode());
}
/**
* User which has AccessAllSubsites set to 1 should be able to access all subsites and main site,
* even though he does not have the ADMIN permission.
*/
public function testEditorCanAccessAllSubsites()
{
$this->logInAs('editor');
$this->get('admin/pages/?SubsiteID=0');
$this->assertEquals(0, $this->session()->get('SubsiteID'), 'Can access main site.');
$this->assertStringContainsString('admin/pages', $this->mainSession->lastUrl(), 'Lands on the correct section');
$subsite1 = $this->objFromFixture(Subsite::class, 'subsite1');
$this->get("admin/pages/?SubsiteID={$subsite1->ID}");
$this->assertEquals($subsite1->ID, $this->session()->get('SubsiteID'), 'Can access other subsite.');
$this->assertStringContainsString('admin/pages', $this->mainSession->lastUrl(), 'Lands on the correct section');
$response = $this->get('admin/subsite_xhr');
$this->assertStringNotContainsString(
'Security/login',
$this->mainSession->lastUrl(),
'SubsiteXHRController is reachable'
);
}
/**
* Test a member who only has access to one subsite (subsite1) and only some sections (pages and security).
*/
public function testSubsiteAdmin()
{
$this->markTestSkipped('wip');
$this->logInAs('subsite1member');
$subsite1 = $this->objFromFixture(Subsite::class, 'subsite1');
// Check allowed URL.
$this->getAndFollowAll("admin/pages/?SubsiteID={$subsite1->ID}");
$this->assertEquals($subsite1->ID, $this->session()->get('SubsiteID'), 'Can access own subsite.');
$this->assertStringContainsString(
'admin/pages',
$this->mainSession->lastUrl(),
'Can access permitted section.'
);
// Check forbidden section in allowed subsite.
$this->getAndFollowAll("admin/assets/?SubsiteID={$subsite1->ID}");
$this->assertEquals($subsite1->ID, $this->session()->get('SubsiteID'), 'Is redirected within subsite.');
$this->assertNotContains(
'admin/assets',
$this->mainSession->lastUrl(),
'Is redirected away from forbidden section'
);
// Check forbidden site, on a section that's allowed on another subsite
$this->getAndFollowAll('admin/pages/?SubsiteID=0');
$this->assertEquals(
$this->session()->get('SubsiteID'),
$subsite1->ID,
'Is redirected to permitted subsite.'
);
// Check forbidden site, on a section that's not allowed on any other subsite
$this->getAndFollowAll('admin/assets/?SubsiteID=0');
$this->assertEquals(
$this->session()->get('SubsiteID'),
$subsite1->ID,
'Is redirected to first permitted subsite.'
);
$this->assertStringNotContainsString('Security/login', $this->mainSession->lastUrl(), 'Is not denied access');
// Check the standalone XHR controller.
$response = $this->getAndFollowAll('admin/subsite_xhr');
$this->assertStringNotContainsString(
'Security/login',
$this->mainSession->lastUrl(),
'SubsiteXHRController is reachable'
);
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace SilverStripe\Subsites\Tests;
use SilverStripe\CMS\Controllers\CMSMain;
use SilverStripe\Core\Config\Config;
use SilverStripe\Dev\FunctionalTest;
use SilverStripe\Subsites\Model\Subsite;
class SubsiteAdminTest extends FunctionalTest
{
protected static $fixture_file = 'SubsiteTest.yml';
protected function setUp(): void
{
parent::setUp();
Config::modify()->set(Subsite::class, 'write_hostmap', false);
}
/**
* Test generation of the view
*/
public function testBasicView()
{
$subsite1ID = $this->objFromFixture(Subsite::class, 'domaintest1')->ID;
$this->logInAs('admin');
// Confirm that this URL gets you the entire page, with the edit form loaded
$response = $this->get(
"admin/subsites/SilverStripe-Subsites-Model-Subsite/EditForm/field/"
."SilverStripe-Subsites-Model-Subsite/item/$subsite1ID/edit"
);
$this->assertTrue(
strpos($response->getBody() ?? '', 'id="Form_ItemEditForm_ID"') !== false,
'Testing Form_ItemEditForm_ID exists'
);
$this->assertTrue(strpos($response->getBody() ?? '', '<head') !== false, 'Testing <head> exists');
}
/**
* Test that the main-site user with ADMIN permissions can access all subsites, regardless
* of whether he is in a subsite-specific group or not.
*/
public function testMainsiteAdminCanAccessAllSubsites()
{
$this->logInAs('admin');
$cmsMain = new CMSMain();
foreach ($cmsMain->Subsites() as $subsite) {
$ids[$subsite->ID] = true;
}
$this->assertArrayHasKey(0, $ids, 'Main site accessible');
$this->assertArrayHasKey($this->idFromFixture(Subsite::class, 'main'), $ids, 'Site with no groups inaccesible');
$this->assertArrayHasKey(
$this->idFromFixture(Subsite::class, 'subsite1'),
$ids,
'Subsite1 Template inaccessible'
);
$this->assertArrayHasKey(
$this->idFromFixture(Subsite::class, 'subsite2'),
$ids,
'Subsite2 Template inaccessible'
);
}
}

503
tests/php/SubsiteTest.php Normal file
View File

@ -0,0 +1,503 @@
<?php
namespace SilverStripe\Subsites\Tests;
use Page;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Control\Director;
use SilverStripe\Core\Config\Config;
use SilverStripe\ORM\DataObject;
use SilverStripe\Security\Member;
use SilverStripe\Subsites\Model\Subsite;
use SilverStripe\Subsites\Model\SubsiteDomain;
use SilverStripe\Subsites\State\SubsiteState;
use UnexpectedValueException;
class SubsiteTest extends BaseSubsiteTest
{
protected static $fixture_file = 'SubsiteTest.yml';
protected $usesTransactions = false;
/**
* Original value of $_REQUEST
*
* @var array
*/
protected $origServer = [];
protected function setUp(): void
{
parent::setUp();
Config::modify()
->set(Director::class, 'alternate_base_url', '/')
->set(Subsite::class, 'strict_subdomain_matching', false)
->set(Subsite::class, 'write_hostmap', false);
$this->origServer = $_SERVER;
}
protected function tearDown(): void
{
$_SERVER = $this->origServer;
parent::tearDown();
}
/**
* Create a new subsite from the template and verify that all the template's pages are copied
*/
public function testSubsiteCreation()
{
// Create the instance
$template = $this->objFromFixture(Subsite::class, 'main');
// Test that changeSubsite is working
Subsite::changeSubsite($template->ID);
$this->assertEquals($template->ID, SubsiteState::singleton()->getSubsiteId());
$tmplStaff = $this->objFromFixture('Page', 'staff');
$tmplHome = DataObject::get_one('Page', "\"URLSegment\" = 'home'");
// Publish all the pages in the template, testing that DataObject::get only returns pages
// from the chosen subsite
$pages = DataObject::get(SiteTree::class);
$totalPages = $pages->count();
foreach ($pages as $page) {
$this->assertEquals($template->ID, $page->SubsiteID);
$page->copyVersionToStage('Stage', 'Live');
}
// Create a new site
$subsite = $template->duplicate();
// Check title
$this->assertEquals($subsite->Title, $template->Title);
// Another test that changeSubsite is working
$subsite->activate();
$siteHome = DataObject::get_one('Page', "\"URLSegment\" = 'home'");
$this->assertNotEquals($siteHome, false, 'Home Page for subsite not found');
$this->assertEquals(
$subsite->ID,
$siteHome->SubsiteID,
'createInstance() copies existing pages retaining the same URLSegment'
);
Subsite::changeSubsite(0);
}
/**
* Confirm that domain lookup is working
*/
public function testDomainLookup()
{
// Clear existing fixtures
foreach (DataObject::get(Subsite::class) as $subsite) {
$subsite->delete();
}
foreach (DataObject::get(SubsiteDomain::class) as $domain) {
$domain->delete();
}
// Much more expressive than YML in this case
$subsite1 = $this->createSubsiteWithDomains([
'one.example.org' => true,
'one.*' => false,
]);
$subsite2 = $this->createSubsiteWithDomains([
'two.mysite.com' => true,
'*.mysite.com' => false,
'subdomain.onmultiplesubsites.com' => false,
]);
$subsite3 = $this->createSubsiteWithDomains([
'three.*' => true, // wildcards in primary domain are not recommended
'subdomain.unique.com' => false,
'*.onmultiplesubsites.com' => false,
]);
$this->assertEquals(
$subsite3->ID,
Subsite::getSubsiteIDForDomain('subdomain.unique.com'),
'Full unique match'
);
$this->assertEquals(
$subsite1->ID,
Subsite::getSubsiteIDForDomain('one.example.org'),
'Full match, doesn\'t complain about multiple matches within a single subsite'
);
$failed = false;
try {
Subsite::getSubsiteIDForDomain('subdomain.onmultiplesubsites.com');
} catch (UnexpectedValueException $e) {
$failed = true;
}
$this->assertTrue(
$failed,
'Fails on multiple matches with wildcard vs. www across multiple subsites'
);
$this->assertEquals(
$subsite1->ID,
Subsite::getSubsiteIDForDomain('one.unique.com'),
'Fuzzy match suffixed with wildcard (rule "one.*")'
);
$this->assertEquals(
$subsite2->ID,
Subsite::getSubsiteIDForDomain('two.mysite.com'),
'Matches correct subsite for rule'
);
$this->assertEquals(
$subsite2->ID,
Subsite::getSubsiteIDForDomain('other.mysite.com'),
'Fuzzy match prefixed with wildcard (rule "*.mysite.com")'
);
$this->assertEquals(
0,
Subsite::getSubsiteIDForDomain('unknown.madeup.com'),
"Doesn't match unknown subsite"
);
}
public function testStrictSubdomainMatching()
{
// Clear existing fixtures
foreach (DataObject::get(Subsite::class) as $subsite) {
$subsite->delete();
}
foreach (DataObject::get(SubsiteDomain::class) as $domain) {
$domain->delete();
}
// Much more expressive than YML in this case
$subsite1 = $this->createSubsiteWithDomains([
'example.org' => true,
'example.com' => false,
'*.wildcard.com' => false,
]);
$subsite2 = $this->createSubsiteWithDomains([
'www.example.org' => true,
'www.wildcard.com' => false,
]);
Config::modify()->set(Subsite::class, 'strict_subdomain_matching', false);
$this->assertEquals(
$subsite1->ID,
Subsite::getSubsiteIDForDomain('example.org'),
'Exact matches without strict checking when not using www prefix'
);
$this->assertEquals(
$subsite1->ID,
Subsite::getSubsiteIDForDomain('example.org:1123'),
'Exact matches without strict checking when not using www prefix and ignores port'
);
$this->assertEquals(
$subsite1->ID,
Subsite::getSubsiteIDForDomain('www.example.org'),
'Matches without strict checking when using www prefix, '
.'still matching first domain regardless of www prefix (falling back to subsite primary key ordering)'
);
$this->assertEquals(
$subsite1->ID,
Subsite::getSubsiteIDForDomain('www.example.org:9923'),
'Matches without strict checking when using www prefix, '
.'still matching first domain without prefix (falling back to primary key ordering and ignoring port)'
);
$this->assertEquals(
$subsite1->ID,
Subsite::getSubsiteIDForDomain('www.example.com'),
'Fuzzy matches without strict checking with www prefix'
);
$this->assertEquals(
0,
Subsite::getSubsiteIDForDomain('www.wildcard.com'),
'Doesn\'t match www prefix without strict check, even if a wildcard subdomain is in place'
);
Config::modify()->set(Subsite::class, 'strict_subdomain_matching', true);
$this->assertEquals(
$subsite1->ID,
Subsite::getSubsiteIDForDomain('example.org'),
'Matches with strict checking when not using www prefix'
);
$this->assertEquals(
$subsite1->ID,
Subsite::getSubsiteIDForDomain('example.org:123'),
'Matches with strict checking when not using www prefix and ignores port'
);
$this->assertEquals(
$subsite2->ID, // not 1
Subsite::getSubsiteIDForDomain('www.example.org'),
'Matches with strict checking when using www prefix'
);
$this->assertEquals(
0,
Subsite::getSubsiteIDForDomain('www.example.com'),
'Doesn\'t fuzzy match with strict checking when using www prefix'
);
$failed = false;
try {
Subsite::getSubsiteIDForDomain('www.wildcard.com');
} catch (UnexpectedValueException $e) {
$failed = true;
}
$this->assertTrue(
$failed,
'Fails on multiple matches with strict checking and wildcard vs. www'
);
}
protected function createSubsiteWithDomains($domains)
{
$subsite = new Subsite([
'Title' => 'My Subsite'
]);
$subsite->write();
foreach ($domains as $domainStr => $isPrimary) {
$domain = new SubsiteDomain([
'Domain' => $domainStr,
'IsPrimary' => $isPrimary,
'SubsiteID' => $subsite->ID
]);
$domain->write();
}
return $subsite;
}
/**
* Test the Subsite->domain() method
*/
public function testDefaultDomain()
{
$this->assertEquals(
'one.example.org',
$this->objFromFixture(Subsite::class, 'domaintest1')->domain()
);
$this->assertEquals(
'two.mysite.com',
$this->objFromFixture(Subsite::class, 'domaintest2')->domain()
);
$_SERVER['HTTP_HOST'] = 'www.example.org';
$this->assertEquals(
'three.example.org',
$this->objFromFixture(Subsite::class, 'domaintest3')->domain()
);
$_SERVER['HTTP_HOST'] = 'mysite.example.org';
$this->assertEquals(
'three.mysite.example.org',
$this->objFromFixture(Subsite::class, 'domaintest3')->domain()
);
$this->assertEquals($_SERVER['HTTP_HOST'], singleton(Subsite::class)->PrimaryDomain);
$this->assertEquals(
'http://' . $_SERVER['HTTP_HOST'] . Director::baseURL(),
singleton(Subsite::class)->absoluteBaseURL()
);
}
/**
* Tests that Subsite and SubsiteDomain both respect http protocol correctly
*
* @param string $class Fixture class name
* @param string $identifier Fixture identifier
* @param bool $currentIsSecure Whether the current base URL should be secure
* @param string $expected The expected base URL for the subsite or subsite domain
* @dataProvider domainProtocolProvider
*/
public function testDomainProtocol($class, $identifier, $currentIsSecure, $expected)
{
/** @var Subsite|SubsiteDomain $model */
$model = $this->objFromFixture($class, $identifier);
$protocol = $currentIsSecure ? 'https' : 'http';
Config::modify()->set(Director::class, 'alternate_base_url', $protocol . '://www.mysite.com');
$this->assertSame($expected, $model->absoluteBaseURL());
}
public function domainProtocolProvider()
{
return [
[Subsite::class, 'domaintest2', false, 'http://two.mysite.com/'],
[SubsiteDomain::class, 'dt2a', false, 'http://two.mysite.com/'],
[SubsiteDomain::class, 'dt2b', false, 'http://subsite.mysite.com/'],
[Subsite::class, 'domaintest4', false, 'https://www.primary.com/'],
[SubsiteDomain::class, 'dt4a', false, 'https://www.primary.com/'],
[SubsiteDomain::class, 'dt4b', false, 'http://www.secondary.com/'],
[Subsite::class, 'domaintest5', false, 'http://www.tertiary.com/'],
[SubsiteDomain::class, 'dt5', false, 'http://www.tertiary.com/'],
[Subsite::class, 'domaintest2', true, 'https://two.mysite.com/'],
[SubsiteDomain::class, 'dt2a', true, 'https://two.mysite.com/'],
[SubsiteDomain::class, 'dt2b', true, 'https://subsite.mysite.com/'],
[Subsite::class, 'domaintest4', true, 'https://www.primary.com/'],
[SubsiteDomain::class, 'dt4a', true, 'https://www.primary.com/'],
[SubsiteDomain::class, 'dt4b', true, 'http://www.secondary.com/'],
[Subsite::class, 'domaintest5', true, 'http://www.tertiary.com/'],
[SubsiteDomain::class, 'dt5', true, 'http://www.tertiary.com/'],
];
}
public function testAllSites()
{
$subsites = Subsite::all_sites();
$this->assertListEquals([
['Title' => 'Main site'],
['Title' => 'Template'],
['Title' => 'Subsite1 Template'],
['Title' => 'Subsite2 Template'],
['Title' => 'Test 1'],
['Title' => 'Test 2'],
['Title' => 'Test 3'],
['Title' => 'Test Non-SSL'],
['Title' => 'Test SSL'],
['Title' => 'Test Vagrant VM on port 8080'],
['Title' => 'Locale subsite'],
], $subsites, 'Lists all subsites');
}
public function testAllAccessibleSites()
{
$member = $this->objFromFixture(Member::class, 'subsite1member');
$subsites = Subsite::all_accessible_sites(true, 'Main site', $member);
$this->assertListEquals([
['Title' => 'Subsite1 Template']
], $subsites, 'Lists member-accessible sites.');
}
/**
* Test Subsite::accessible_sites()
*/
public function testAccessibleSites()
{
$member1Sites = Subsite::accessible_sites(
'CMS_ACCESS_CMSMain',
false,
null,
$this->objFromFixture(Member::class, 'subsite1member')
);
$member1SiteTitles = $member1Sites->column('Title');
sort($member1SiteTitles);
$this->assertEquals('Subsite1 Template', $member1SiteTitles[0], 'Member can get to a subsite via a group');
$adminSites = Subsite::accessible_sites(
'CMS_ACCESS_CMSMain',
false,
null,
$this->objFromFixture(Member::class, 'admin')
);
$adminSiteTitles = $adminSites->column('Title');
sort($adminSiteTitles);
$this->assertEquals([
'Locale subsite',
'Subsite1 Template',
'Subsite2 Template',
'Template',
'Test 1',
'Test 2',
'Test 3',
'Test Non-SSL',
'Test SSL',
'Test Vagrant VM on port 8080'
], array_values($adminSiteTitles ?? []));
$member2Sites = Subsite::accessible_sites(
'CMS_ACCESS_CMSMain',
false,
null,
$this->objFromFixture(Member::class, 'subsite1member2')
);
$member2SiteTitles = $member2Sites->column('Title');
sort($member2SiteTitles);
$this->assertEquals('Subsite1 Template', $member2SiteTitles[1], 'Member can get to subsite via a group role');
}
public function testhasMainSitePermission()
{
$admin = $this->objFromFixture(Member::class, 'admin');
$subsite1member = $this->objFromFixture(Member::class, 'subsite1member');
$subsite1admin = $this->objFromFixture(Member::class, 'subsite1admin');
$allsubsitesauthor = $this->objFromFixture(Member::class, 'allsubsitesauthor');
$this->assertTrue(
Subsite::hasMainSitePermission($admin),
'Default permissions granted for super-admin'
);
$this->assertTrue(
Subsite::hasMainSitePermission($admin, ['ADMIN']),
'ADMIN permissions granted for super-admin'
);
$this->assertFalse(
Subsite::hasMainSitePermission($subsite1admin, ['ADMIN']),
'ADMIN permissions (on main site) denied for subsite1 admin'
);
$this->assertFalse(
Subsite::hasMainSitePermission($subsite1admin, ['CMS_ACCESS_CMSMain']),
'CMS_ACCESS_CMSMain (on main site) denied for subsite1 admin'
);
$this->assertFalse(
Subsite::hasMainSitePermission($allsubsitesauthor, ['ADMIN']),
'ADMIN permissions (on main site) denied for CMS author with edit rights on all subsites'
);
$this->assertTrue(
Subsite::hasMainSitePermission($allsubsitesauthor, ['CMS_ACCESS_CMSMain']),
'CMS_ACCESS_CMSMain (on main site) granted for CMS author with edit rights on all subsites'
);
$this->assertFalse(
Subsite::hasMainSitePermission($subsite1member, ['ADMIN']),
'ADMIN (on main site) denied for subsite1 subsite1 cms author'
);
$this->assertFalse(
Subsite::hasMainSitePermission($subsite1member, ['CMS_ACCESS_CMSMain']),
'CMS_ACCESS_CMSMain (on main site) denied for subsite1 cms author'
);
}
public function testDuplicateSubsite()
{
// get subsite1 & create page
$subsite1 = $this->objFromFixture(Subsite::class, 'domaintest1');
$subsite1->activate();
$page1 = new Page();
$page1->Title = 'MyAwesomePage';
$page1->write();
$page1->publishRecursive();
$this->assertEquals($page1->SubsiteID, $subsite1->ID);
// duplicate
$subsite2 = $subsite1->duplicate();
$subsite2->activate();
// change content on dupe
$page2 = DataObject::get_one('Page', "\"Title\" = 'MyAwesomePage'");
$page2->Title = 'MyNewAwesomePage';
$page2->write();
$page2->publishRecursive();
// check change & check change has not affected subiste1
$subsite1->activate();
$this->assertEquals('MyAwesomePage', DataObject::get_by_id('Page', $page1->ID)->Title);
$subsite2->activate();
$this->assertEquals('MyNewAwesomePage', DataObject::get_by_id('Page', $page2->ID)->Title);
}
public function testDefaultPageCreatedWhenCreatingSubsite()
{
$subsite = new Subsite();
$subsite->Title = 'New Subsite';
$subsite->write();
$subsite->activate();
$pages = SiteTree::get();
$this->assertGreaterThanOrEqual(1, $pages->count());
}
}

267
tests/php/SubsiteTest.yml Normal file
View File

@ -0,0 +1,267 @@
SilverStripe\Subsites\Model\Subsite:
main:
Title: Template
subsite1:
Title: Subsite1 Template
Theme: subsiteTheme
subsite2:
Title: Subsite2 Template
domaintest1:
Title: Test 1
domaintest2:
Title: Test 2
domaintest3:
Title: Test 3
domaintest4:
Title: 'Test SSL'
domaintest5:
Title: 'Test Non-SSL'
domaintestVagrant:
Title: 'Test Vagrant VM on port 8080'
subsitelocale:
Title: 'Locale subsite'
Language: 'nl_NL'
SilverStripe\Subsites\Model\SubsiteDomain:
subsite1:
SubsiteID: =>SilverStripe\Subsites\Model\Subsite.subsite1
Domain: subsite1.*
Protocol: automatic
subsite2:
SubsiteID: =>SilverStripe\Subsites\Model\Subsite.subsite2
Domain: subsite2.*
Protocol: automatic
subsitelocale:
SubsiteID: =>SilverStripe\Subsites\Model\Subsite.subsitelocale
Domain: subsitelocale.*
Protocol: automatic
IsPrimary: 1
dt1a:
SubsiteID: =>SilverStripe\Subsites\Model\Subsite.domaintest1
Domain: one.example.org
Protocol: automatic
IsPrimary: 1
dt1b:
SubsiteID: =>SilverStripe\Subsites\Model\Subsite.domaintest1
Domain: one.*
Protocol: automatic
dt2a:
SubsiteID: =>SilverStripe\Subsites\Model\Subsite.domaintest2
Domain: two.mysite.com
Protocol: automatic
IsPrimary: 1
dt2b:
SubsiteID: =>SilverStripe\Subsites\Model\Subsite.domaintest2
Domain: '*.mysite.com'
Protocol: automatic
dt3:
SubsiteID: =>SilverStripe\Subsites\Model\Subsite.domaintest3
Domain: three.*
Protocol: automatic
IsPrimary: 1
dt4a:
SubsiteID: =>SilverStripe\Subsites\Model\Subsite.domaintest4
Domain: www.primary.com
Protocol: https
dt4b:
SubsiteID: =>SilverStripe\Subsites\Model\Subsite.domaintest4
Domain: www.secondary.com
Protocol: http
dt5:
SubsiteID: =>SilverStripe\Subsites\Model\Subsite.domaintest5
Domain: www.tertiary.com
Protocol: http
IsPrimary: 1
dtVagrant:
SubsiteID: =>SilverStripe\Subsites\Model\Subsite.domaintestVagrant
Domain: localhost:8080
Protocol: http
IsPrimary: 1
Page:
mainSubsitePage:
Title: 'MainSubsitePage'
SubsiteID: 0
URLSegment: mainsubsitepage
home:
Title: 'Home'
SubsiteID: =>SilverStripe\Subsites\Model\Subsite.main
URLSegment: home
about:
Title: 'About'
SubsiteID: =>SilverStripe\Subsites\Model\Subsite.main
URLSegment: about
linky:
Title: 'Linky'
SubsiteID: =>SilverStripe\Subsites\Model\Subsite.main
URLSegment: linky
staff:
Title: 'Staff'
ParentID: =>Page.about
SubsiteID: =>SilverStripe\Subsites\Model\Subsite.main
URLSegment: staff
contact:
Title: 'Contact Us'
SubsiteID: =>SilverStripe\Subsites\Model\Subsite.main
URLSegment: contact-us
importantpage:
Title: 'Important Page'
SubsiteID: =>SilverStripe\Subsites\Model\Subsite.main
URLSegment: important-page
subsite1_home:
Title: 'Home (Subsite 1)'
SubsiteID: =>SilverStripe\Subsites\Model\Subsite.subsite1
URLSegment: home
subsite1_contactus:
Title: 'Contact Us (Subsite 1)'
SubsiteID: =>SilverStripe\Subsites\Model\Subsite.subsite1
URLSegment: contact-us
subsite1_staff:
Title: 'Staff'
SubsiteID: =>SilverStripe\Subsites\Model\Subsite.subsite1
URLSegment: staff
subsite2_home:
Title: 'Home (Subsite 2)'
SubsiteID: =>SilverStripe\Subsites\Model\Subsite.subsite2
URLSegment: home
subsite2_contactus:
Title: 'Contact Us (Subsite 2)'
SubsiteID: =>SilverStripe\Subsites\Model\Subsite.subsite2
URLSegment: contact-us
subsite_locale_about:
Title: 'About Locale'
SubsiteID: =>SilverStripe\Subsites\Model\Subsite.subsitelocale
URLSegment: about
SilverStripe\Security\PermissionRoleCode:
roleCode1:
Code: CMS_ACCESS_CMSMain
SilverStripe\Security\PermissionRole:
role1:
Title: role1
Codes: =>SilverStripe\Security\PermissionRoleCode.roleCode1
SilverStripe\Security\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: =>SilverStripe\Subsites\Model\Subsite.subsite1
subsite2_group:
Title: subsite2_group
Code: subsite2_group
AccessAllSubsites: 0
Subsites: =>SilverStripe\Subsites\Model\Subsite.subsite2
subsite1admins:
Title: subsite1admins
Code: subsite1admins
AccessAllSubsites: 0
Subsites: =>SilverStripe\Subsites\Model\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: =>SilverStripe\Security\PermissionRole.role1
filetest:
Title: filetest
Code: filetest
AccessAllSubsites: 1
SilverStripe\Security\Permission:
admin:
Code: ADMIN
GroupID: =>SilverStripe\Security\Group.admin
editor1:
Code: CMS_ACCESS_CMSMain
GroupID: =>SilverStripe\Security\Group.editor
editor2:
Code: SITETREE_VIEW_ALL
GroupID: =>SilverStripe\Security\Group.editor
editor3:
Code: VIEW_DRAFT_CONTENT
GroupID: =>SilverStripe\Security\Group.editor
accesscmsmain1:
Code: CMS_ACCESS_CMSMain
GroupID: =>SilverStripe\Security\Group.subsite1_group
accesscmsmain2:
Code: CMS_ACCESS_CMSMain
GroupID: =>SilverStripe\Security\Group.subsite2_group
accesscmsmain3:
Code: CMS_ACCESS_CMSMain
GroupID: =>SilverStripe\Security\Group.subsite1admins
accesscmsmain4:
Code: CMS_ACCESS_CMSMain
GroupID: =>SilverStripe\Security\Group.allsubsitesauthors
securityaccess1:
Code: CMS_ACCESS_SecurityAdmin
GroupID: =>SilverStripe\Security\Group.subsite1_group
securityaccess2:
Code: CMS_ACCESS_SecurityAdmin
GroupID: =>SilverStripe\Security\Group.subsite2_group
adminsubsite1:
Code: ADMIN
GroupID: =>SilverStripe\Security\Group.subsite1admins
filetest:
Code: CMS_ACCESS_CMSMain
GroupID: =>SilverStripe\Security\Group.filetest
SilverStripe\Security\Member:
admin:
FirstName: Admin
Surname: User
Email: admin@test.com
Password: rangi
Groups: =>SilverStripe\Security\Group.admin
editor:
FirstName: Editor
Surname: User
Email: editor@test.com
Password: rangi
Groups: =>SilverStripe\Security\Group.editor
subsite1member:
Email: subsite1member@test.com
Groups: =>SilverStripe\Security\Group.subsite1_group
subsite2member:
Email: subsite2member@test.com
Groups: =>SilverStripe\Security\Group.subsite2_group
subsite1admin:
Email: subsite1admin@test.com
Groups: =>SilverStripe\Security\Group.subsite1admins
allsubsitesauthor:
Email: allsubsitesauthor@test.com
Groups: =>SilverStripe\Security\Group.allsubsitesauthors
subsite1member2:
Email: subsite1member2@test.com
Groups: =>SilverStripe\Security\Group.subsite1_group_via_role
filetestyes:
Email: filetestyes@test.com
Groups: =>SilverStripe\Security\Group.filetest
filetestno:
Email: filetestno@test.com
SilverStripe\SiteConfig\SiteConfig:
config:
CanCreateTopLevelType: LoggedInUsers
SilverStripe\Assets\File:
subsite1file:
Name: subsitefile.pdf
Title: subsitefile
SubsiteID: =>SilverStripe\Subsites\Model\Subsite.subsite1
CanEditType: OnlyTheseUsers
EditorGroups: =>SilverStripe\Security\Group.filetest
mainsitefile:
Name: mainsitefile.pdf
Title: mainsitefile
SubsiteID: 0
CanEditType: OnlyTheseUsers
EditorGroups: =>SilverStripe\Security\Group.filetest

View File

@ -0,0 +1,47 @@
<?php
namespace SilverStripe\Subsites\Tests;
use SilverStripe\Dev\FunctionalTest;
class SubsiteXHRControllerTest extends FunctionalTest
{
protected static $fixture_file = 'SubsiteTest.yml';
public function testCanView()
{
// Test unauthenticated access
$this->logOut();
$result = $this->get('admin/subsite_xhr', null, [
'X-Pjax' => 'SubsiteList',
'X-Requested-With' => 'XMLHttpRequest'
]);
$this->assertEquals(403, $result->getStatusCode());
// Login with NO permissions
$this->logInWithPermission('NOT_CMS_PERMISSION');
$result = $this->get('admin/subsite_xhr', null, [
'X-Pjax' => 'SubsiteList',
'X-Requested-With' => 'XMLHttpRequest'
]);
$this->assertEquals(403, $result->getStatusCode());
// Test cms user
$this->logInWithPermission('CMS_ACCESS_CMSMain');
$result = $this->get('admin/subsite_xhr', null, [
'X-Pjax' => 'SubsiteList',
'X-Requested-With' => 'XMLHttpRequest'
]);
$this->assertEquals(200, $result->getStatusCode());
// SilverStripe 4.0-4.2: text/json. >=4.3: application/json
$this->assertStringContainsString('json', $result->getHeader('Content-Type'));
$body = $result->getBody();
$this->assertStringContainsString('Main site', $body);
$this->assertStringContainsString('Test 1', $body);
$this->assertStringContainsString('Test 2', $body);
$this->assertStringContainsString('Test 3', $body);
}
}

View File

@ -0,0 +1,351 @@
<?php
namespace SilverStripe\Subsites\Tests;
use Page;
use SilverStripe\Assets\File;
use SilverStripe\Assets\Filesystem;
use SilverStripe\Assets\Tests\Storage\AssetStoreTest\TestAssetStore;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Control\Director;
use SilverStripe\Core\Config\Config;
use SilverStripe\ORM\DB;
use SilverStripe\Subsites\Model\Subsite;
use SilverStripe\Subsites\Pages\SubsitesVirtualPage;
use SilverStripe\Versioned\Versioned;
class SubsitesVirtualPageTest extends BaseSubsiteTest
{
protected static $fixture_file = [
'SubsiteTest.yml',
'SubsitesVirtualPageTest.yml',
];
protected function setUp(): void
{
parent::setUp();
Config::modify()->set(Subsite::class, 'write_hostmap', false);
// Set backend root to /DataDifferencerTest
TestAssetStore::activate('SubsitesVirtualPageTest');
// Create a test files for each of the fixture references
$file = $this->objFromFixture(File::class, 'file1');
$page = $this->objFromFixture(SiteTree::class, 'page1');
$fromPath = __DIR__ . '/testscript-test-file.pdf';
$destPath = TestAssetStore::getLocalPath($file);
Filesystem::makeFolder(dirname($destPath ?? ''));
copy($fromPath ?? '', $destPath ?? '');
// Hack in site link tracking after the fact
$page->Content = '<p><img src="' . $file->getURL() . '" data-fileid="' . $file->ID . '" /></p>';
$page->write();
}
protected function tearDown(): void
{
TestAssetStore::reset();
parent::tearDown();
}
// Attempt to bring main:linky to subsite2:linky
public function testVirtualPageFromAnotherSubsite()
{
$subsite = $this->objFromFixture(Subsite::class, 'subsite2');
Subsite::changeSubsite($subsite->ID);
Subsite::$disable_subsite_filter = false;
$linky = $this->objFromFixture(Page::class, 'linky');
$svp = new SubsitesVirtualPage();
$svp->CopyContentFromID = $linky->ID;
$svp->SubsiteID = $subsite->ID;
$svp->URLSegment = 'linky';
$svp->write();
$this->assertEquals($svp->SubsiteID, $subsite->ID);
$this->assertEquals($svp->Title, $linky->Title);
}
public function testFileLinkRewritingOnVirtualPages()
{
$this->markTestSkipped('File handling needs update');
// File setup
$this->logInWithPermission('ADMIN');
touch(Director::baseFolder() . '/assets/testscript-test-file.pdf');
// Publish the source page
$page = $this->objFromFixture(SiteTree::class, 'page1');
$this->assertTrue($page->publishSingle());
// Create a virtual page from it, and publish that
$svp = new SubsitesVirtualPage();
$svp->CopyContentFromID = $page->ID;
$svp->write();
$svp->publishSingle();
// Rename the file
$file = $this->objFromFixture(File::class, 'file1');
$file->Name = 'renamed-test-file.pdf';
$file->write();
// Verify that the draft and publish virtual pages both have the corrected link
$this->assertContains(
'<img src="/assets/SubsitesVirtualPageTest/464dedb70a/renamed-test-file.pdf"',
DB::query("SELECT \"Content\" FROM \"SiteTree\" WHERE \"ID\" = $svp->ID")->value()
);
$this->assertContains(
'<img src="/assets/SubsitesVirtualPageTest/464dedb70a/renamed-test-file.pdf"',
DB::query("SELECT \"Content\" FROM \"SiteTree_Live\" WHERE \"ID\" = $svp->ID")->value()
);
}
public function testSubsiteVirtualPagesArentInappropriatelyPublished()
{
$this->markTestSkipped('Needs some update or refactoring');
// Fixture
$p = new Page();
$p->Content = 'test content';
$p->write();
$vp = new SubsitesVirtualPage();
$vp->CopyContentFromID = $p->ID;
$vp->write();
// VP is oragne
$this->assertTrue($vp->IsAddedToStage);
// VP is still orange after we publish
$p->publishSingle();
$this->fixVersionNumberCache($vp);
$this->assertTrue($vp->IsAddedToStage);
// A new VP created after P's initial construction
$vp2 = new SubsitesVirtualPage();
$vp2->CopyContentFromID = $p->ID;
$vp2->write();
$this->assertTrue($vp2->IsAddedToStage);
// Also remains orange after a republish
$p->Content = 'new content';
$p->write();
$p->publishSingle();
$this->fixVersionNumberCache($vp2);
$this->assertTrue($vp2->IsAddedToStage);
// VP is now published
$vp->publishSingle();
$this->fixVersionNumberCache($vp);
$this->assertTrue($vp->ExistsOnLive);
$this->assertFalse($vp->IsModifiedOnStage);
// P edited, VP and P both go green
$p->Content = 'third content';
$p->write();
$this->fixVersionNumberCache($vp, $p);
$this->assertTrue($p->IsModifiedOnStage);
$this->assertTrue($vp->IsModifiedOnStage);
// Publish, VP goes black
$p->publishSingle();
$this->fixVersionNumberCache($vp);
$this->assertTrue($vp->ExistsOnLive);
$this->assertFalse($vp->IsModifiedOnStage);
}
/**
* This test ensures published Subsites Virtual Pages immediately reflect updates
* to their published target pages. Note - this has to happen when the virtual page
* is in a different subsite to the page you are editing and republishing,
* otherwise the test will pass falsely due to current subsite ID being the same.
*/
public function testPublishedSubsiteVirtualPagesUpdateIfTargetPageUpdates()
{
$this->markTestSkipped('Needs some update or refactoring');
// create page
$p = new Page();
$p->Content = 'Content';
$p->Title = 'Title';
$p->writeToStage('Stage');
$p->copyVersionToStage('Stage', 'Live');
$this->assertTrue($p->ExistsOnLive);
// change to subsite
$subsite = $this->objFromFixture(Subsite::class, 'subsite2');
Subsite::changeSubsite($subsite->ID);
Subsite::$disable_subsite_filter = false;
// create svp in subsite
$svp = new SubsitesVirtualPage();
$svp->CopyContentFromID = $p->ID;
$svp->write();
$svp->writeToStage('Stage');
$svp->copyVersionToStage('Stage', 'Live');
$this->assertEquals($svp->SubsiteID, $subsite->ID);
$this->assertTrue($svp->ExistsOnLive);
// change back to original subsite ("Main site")
Subsite::changeSubsite(0);
// update original page
$p->Title = 'New Title';
// "save & publish"
$p->writeToStage('Stage');
$p->copyVersionToStage('Stage', 'Live');
$this->assertNotEquals($p->SubsiteID, $subsite->ID);
// reload SVP from database
// can't use DO::get by id because caches.
$svpdb = $svp->get()->byID($svp->ID);
// ensure title changed
$this->assertEquals($svpdb->Title, $p->Title);
}
public function testUnpublishingParentPageUnpublishesSubsiteVirtualPages()
{
$this->markTestIncomplete('@todo fix this test');
Config::modify()->set('StaticPublisher', 'disable_realtime', true);
// Go to main site, get parent page
$subsite = $this->objFromFixture(Subsite::class, 'main');
Subsite::changeSubsite($subsite->ID);
$page = $this->objFromFixture('Page', 'importantpage');
// Create two SVPs on other subsites
$subsite = $this->objFromFixture(Subsite::class, 'subsite1');
Subsite::changeSubsite($subsite->ID);
$vp1 = new SubsitesVirtualPage();
$vp1->CopyContentFromID = $page->ID;
$vp1->write();
$vp1->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
$subsite = $this->objFromFixture(Subsite::class, 'subsite2');
Subsite::changeSubsite($subsite->ID);
$vp2 = new SubsitesVirtualPage();
$vp2->CopyContentFromID = $page->ID;
$vp2->write();
$vp2->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
// Switch back to main site, unpublish source
$subsite = $this->objFromFixture(Subsite::class, 'main');
Subsite::changeSubsite($subsite->ID);
$page = $this->objFromFixture('Page', 'importantpage');
$page->doUnpublish();
Subsite::changeSubsite($vp1->SubsiteID);
$onLive = Versioned::get_one_by_stage(SubsitesVirtualPage::class, 'Live', '"SiteTree_Live"."ID" = ' . $vp1->ID);
$this->assertNull($onLive, 'SVP has been removed from live');
$subsite = $this->objFromFixture(Subsite::class, 'subsite2');
Subsite::changeSubsite($vp2->SubsiteID);
$onLive = Versioned::get_one_by_stage(SubsitesVirtualPage::class, 'Live', '"SiteTree_Live"."ID" = ' . $vp2->ID);
$this->assertNull($onLive, 'SVP has been removed from live');
}
/**
* Similar to {@link SiteTreeSubsitesTest->testTwoPagesWithSameURLOnDifferentSubsites()}
* and {@link SiteTreeSubsitesTest->testPagesInDifferentSubsitesCanShareURLSegment()}.
*/
public function testSubsiteVirtualPageCanHaveSameUrlsegmentAsOtherSubsite()
{
$this->markTestIncomplete('@todo fix this test');
$subsite1 = $this->objFromFixture(Subsite::class, 'subsite1');
$subsite2 = $this->objFromFixture(Subsite::class, 'subsite2');
Subsite::changeSubsite($subsite1->ID);
$subsite1Page = $this->objFromFixture(Page::class, 'subsite1_staff');
$subsite1Page->URLSegment = 'staff';
$subsite1Page->write();
// saving on subsite1, and linking to subsite1
$subsite1Vp = new SubsitesVirtualPage();
$subsite1Vp->CopyContentFromID = $subsite1Page->ID;
$subsite1Vp->SubsiteID = $subsite1->ID;
$subsite1Vp->write();
$this->assertNotEquals(
$subsite1Vp->URLSegment,
$subsite1Page->URLSegment,
"Doesn't allow explicit URLSegment overrides when already existing in same subsite"
);
//Change to subsite 2
Subsite::changeSubsite($subsite2->ID);
// saving in subsite2 (which already has a page with URLSegment 'contact-us'),
// but linking to a page in subsite1
$subsite2Vp = new SubsitesVirtualPage();
$subsite2Vp->CopyContentFromID = $subsite1Page->ID;
$subsite2Vp->SubsiteID = $subsite2->ID;
$subsite2Vp->write();
$this->assertEquals(
$subsite2Vp->URLSegment,
$subsite1Page->URLSegment,
'Does allow explicit URLSegment overrides when only existing in a different subsite'
);
// When changing subsites and re-saving this page, it doesn't trigger a change
Subsite::changeSubsite($subsite1->ID);
$subsite1Page->write();
$subsite2Vp->write();
$this->assertEquals(
$subsite2Vp->URLSegment,
$subsite1Page->URLSegment,
"SubsiteVirtualPage doesn't change urls when being written in another subsite"
);
}
protected function fixVersionNumberCache($page)
{
$pages = func_get_args();
foreach ($pages as $p) {
Versioned::prepopulate_versionnumber_cache(SiteTree::class, 'Stage', [$p->ID]);
Versioned::prepopulate_versionnumber_cache(SiteTree::class, 'Live', [$p->ID]);
}
}
public function testValidURLSegmentWithUniquePageAndNestedURLs()
{
SiteTree::config()->set('nested_urls', true);
$newPage = new SubsitesVirtualPage();
$newPage->Title = 'My new page';
$newPage->URLSegment = 'my-new-page';
$this->assertTrue($newPage->validURLSegment());
}
public function testValidURLSegmentWithExistingPageInSubsite()
{
$subsite1 = $this->objFromFixture(Subsite::class, 'subsite1');
Subsite::changeSubsite($subsite1->ID);
SiteTree::config()->set('nested_urls', false);
$similarContactUsPage = new SubsitesVirtualPage();
$similarContactUsPage->Title = 'Similar to Contact Us in Subsite 1';
$similarContactUsPage->URLSegment = 'contact-us';
$this->assertFalse($similarContactUsPage->validURLSegment());
}
public function testValidURLSegmentWithExistingPageInAnotherSubsite()
{
$subsite1 = $this->objFromFixture(Subsite::class, 'subsite1');
Subsite::changeSubsite($subsite1->ID);
$similarStaffPage = new SubsitesVirtualPage();
$similarStaffPage->Title = 'Similar to Staff page in main site';
$similarStaffPage->URLSegment = 'staff';
$this->assertFalse($similarStaffPage->validURLSegment());
}
}

View File

@ -0,0 +1,12 @@
# These need to come first so that SiteTree has the link meta-data written.
SilverStripe\Assets\File:
file1:
FileFilename: testscript-test-file.pdf
FileHash: 464dedb70af0dc7f8f3360e7f3ae43cbbf1cdf4e
Name: testscript-test-file.pdf
SilverStripe\CMS\Model\SiteTree:
page1:
Title: page1
URLSegment: page1
Content: '<p><img src="/assets/SubsitesVirtualPageTest/464dedb70a/testscript-test-file.pdf" /></p>'

View File

@ -0,0 +1,89 @@
<?php
namespace SilverStripe\Subsites\Tests;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Subsites\Forms\WildcardDomainField;
/**
* Tests {@see WildcardDomainField}
*/
class WildcardDomainFieldTest extends SapphireTest
{
/**
* Check that valid domains are accepted
*
* @dataProvider validDomains
* @param $domain
*/
public function testValidDomains($domain)
{
$field = new WildcardDomainField('DomainField');
$this->assertTrue($field->checkHostname($domain), "Validate that {$domain} is a valid domain name");
}
/**
* Check that valid domains are accepted
*
* @dataProvider invalidDomains
* @param $domain
*/
public function testInvalidDomains($domain)
{
$field = new WildcardDomainField('DomainField');
$this->assertFalse($field->checkHostname($domain), "Validate that {$domain} is an invalid domain name");
}
/**
* Check that valid domains are accepted
*
* @dataProvider validWildcards
* @param $domain
*/
public function testValidWildcards($domain)
{
$field = new WildcardDomainField('DomainField');
$this->assertTrue($field->checkHostname($domain), "Validate that {$domain} is a valid domain wildcard");
}
public function validDomains()
{
return [
['www.mysite.com'],
['domain7'],
['mysite.co.n-z'],
['subdomain.my-site.com'],
['subdomain.mysite'],
['subdomain.mysite.com:80'],
['mysite:80']
];
}
public function invalidDomains()
{
return [
['-mysite'],
['.mysite'],
['mys..ite'],
['mysite-'],
['mysite.'],
['-mysite.*'],
['.mysite.*'],
['mys..ite.*'],
['*.mysite-'],
['*.mysite.'],
[':1234']
];
}
public function validWildcards()
{
return [
['*.mysite.com'],
['mys*ite.com'],
['*.my-site.*'],
['*']
];
}
}

Binary file not shown.