Compare commits

...

458 Commits
1.4.0 ... 2

Author SHA1 Message Date
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
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 58b8476ede Merge branch '1' 2017-09-08 13:57:10 +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
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
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 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
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
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
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
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
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
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
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
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 7ee8bdbf72 Merge pull request #176 from jason-zz/patch-1
Update SubsiteDomain.php
2016-02-04 17:26:56 +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 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
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 2803573a9c Update master branch to 2.0.0 for 4.x compatibility 2015-11-25 11:45:34 +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
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
helpfulrobot a9ee70c5dd Added standard editor config 2015-11-19 13:26:50 +13:00
Damian Mooyman 5e18e8dca3 Merge remote-tracking branch 'origin/1.1'
Conflicts:
	.travis.yml
2015-11-13 19:06:09 +13:00
Will Rossiter b979b38694 FIX: #138 allow subsite summary fields to be customized 2015-10-21 09:09:10 +13:00
Will Rossiter 9c9e0bfa94 Correct link to editing a subsite virtual page. 2015-07-27 10:57:10 +12: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
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
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
Jason d99785d773 Update SubsiteDomain.php
Add $default_sort.
2014-12-17 10:44:43 +11:00
146 changed files with 6974 additions and 4041 deletions

View File

@ -6,18 +6,17 @@
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = tab
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.yml]
[*.{yml,feature}]
indent_size = 2
indent_style = space
[{.travis.yml,package.json}]
[{.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

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

View File

@ -1,34 +0,0 @@
# See https://github.com/silverstripe/silverstripe-travis-support for setup details
language: php
sudo: false
dist: precise
php:
- 5.4
env:
- DB=MYSQL CORE_RELEASE=3.5
matrix:
include:
- php: 5.3
env: DB=PGSQL CORE_RELEASE=3.4
- php: 5.6
env: DB=MYSQL CORE_RELEASE=3
- php: 5.4
env: DB=MYSQL CORE_RELEASE=3.6
- php: 7.1
env: DB=MYSQL CORE_RELEASE=3.6
before_script:
- composer self-update || true
- git clone git://github.com/silverstripe/silverstripe-travis-support.git ~/travis-support
- php ~/travis-support/travis_setup.php --source `pwd` --target ~/builds/ss
- cd ~/builds/ss
- composer install
script:
- vendor/bin/phpunit subsites/tests

9
.tx/config Normal file
View File

@ -0,0 +1,9 @@
[main]
host = https://www.transifex.com
[o:silverstripe:p:silverstripe-subsites:r:master]
file_filter = lang/<lang>.yml
source_file = lang/en.yml
source_lang = en
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

View File

@ -4,6 +4,18 @@ 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

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.
@ -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](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,27 +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,10 +1,11 @@
---
Name: mysiteconfig
After: 'framework/*','cms/*'
Name: subsiteconfig
After:
- 'framework/*'
---
AssetAdmin:
SilverStripe\AssetAdmin\Controller\AssetAdmin:
treats_subsite_0_as_global: true
Director:
rules:
'SubsiteXHRController': 'SubsiteXHRController'
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,193 +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)));
$fields->addFieldToTab(
'Root.Main',
DropdownField::create(
"CopyContentFromID_SubsiteID",
_t('SubsitesVirtualPage.SubsiteField', "Subsite"),
$subsites->map('ID', 'Title')
)->addExtraClass('subsitestreedropdownfield-chooser no-change-track'),
'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()->requestVar('CopyContentFromID_SubsiteID');
$pageSelectionField->setSubsiteID($subsiteID);
}
$fields->replaceField('CopyContentFromID', $pageSelectionField);
// Create links back to the original object in the CMS
if ($this->CopyContentFromID) {
$editLink = "admin/pages/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 getCopyContentFromID_SubsiteID()
{
return ($this->CopyContentFromID) ? (int)$this->CopyContentFrom()->SubsiteID : (int)Session::get('SubsiteID');
}
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;
}
}
}
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
{
public function updatePageOptions(&$fields)
{
$fields->push(new HiddenField('SubsiteID', 'SubsiteID', Subsite::currentSubsiteID()));
}
}

View File

@ -1,43 +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.
*/
public 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,141 +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
public 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.
*/
public function alternateTreeTitle()
{
if ($this->owner->SubsiteID == 0) {
return " * " . $this->owner->Title;
} else {
return $this->owner->Title;
}
}
/**
* Add subsites-specific fields to the folder editor.
*/
public 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
*/
public 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();
if (isset($from['SiteTree_ImageTracking']) || $query->filtersOnID()) {
return;
}
$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 (!$isCounting) {
$query->addOrderBy("\"SubsiteID\"");
}
}
public 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();
}
}
}
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 = Subsite::currentSubsiteID();
}
$this->owner->write();
}
public 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
*/
public function cacheKeyComponent()
{
return 'subsite-'.Subsite::currentSubsiteID();
}
}

View File

@ -1,195 +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.
*/
public function requireDefaultRecords()
{
// Migration for Group.SubsiteID data from when Groups only had a single subsite
$groupFields = DB::field_list('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::get_schema()->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
} elseif (!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');
}
}
public 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.
*/
public 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
*/
public 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;
}
}
}
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') && !Subsite::currentSubsiteID()) {
$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 = Subsite::currentSubsiteID()) {
$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 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,338 +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;
public 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
*/
public function getCMSTreeTitle()
{
$subsite = Subsite::currentSubSite();
return $subsite ? Convert::raw2xml($subsite->Title) : _t('LeftAndMain.SITECONTENTLEFT');
}
public 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.
*/
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 = 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.
*/
public 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']);
//Redirect to clear the current page
if ($this->owner->canView(Member::currentUser())) {
//Redirect to clear the current page
return $this->owner->redirect($this->owner->Link());
}
//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 clear the current page
return $this->owner->redirect($this->owner->Link());
}
//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;
}
public function augmentNewSiteTreeItem(&$item)
{
$item->SubsiteID = isset($_POST['SubsiteID']) ? $_POST['SubsiteID'] : Subsite::currentSubsiteID();
}
public 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.')));
}
}
/**
* @param array $data
* @param Form $form
*/
public function copytosubsite($data, $form)
{
$page = DataObject::get_by_id('SiteTree', $data['ID']);
$subsite = DataObject::get_by_id('Subsite', $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

@ -1,63 +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
*/
public 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->filtersOnID()) {
return;
}
$regexp = '/^(.*\.)?("|`)?SubsiteID("|`)?\s?=/';
foreach ($query->getWhereParameterised($parameters) as $predicate) {
if (preg_match($regexp, $predicate)) {
return;
}
}
/*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)");
}
public 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
*/
public function cacheKeyComponent()
{
return 'subsite-'.Subsite::currentSubsiteID();
}
public function updateCMSFields(FieldList $fields)
{
$fields->push(new HiddenField('SubsiteID', 'SubsiteID', Subsite::currentSubsiteID()));
}
}

View File

@ -1,398 +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")
);
public function isMainSite()
{
if ($this->owner->SubsiteID == 0) {
return true;
}
return false;
}
/**
* Update any requests to limit the results to the current site
*/
public function augmentSQL(SQLQuery &$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;
}
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;
}
}
public function onBeforeWrite()
{
if (!$this->owner->ID && !$this->owner->SubsiteID) {
$this->owner->SubsiteID = Subsite::currentSubsiteID();
}
parent::onBeforeWrite();
}
public 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->addFieldsToTab(
'Root.Main',
ToggleCompositeField::create('SubsiteOperations',
_t('SiteTreeSubsites.SubsiteOperations', 'Subsite Operations'),
array(
new DropdownField("CopyToSubsiteID", _t('SiteTreeSubsites.CopyToSubsite', "Copy page to subsite"), $subsitesMap),
new CheckboxField("CopyToSubsiteWithChildren", _t('SiteTreeSubsites.CopyToSubsiteWithChildren', 'Include children pages?')),
$copyAction = new InlineFormAction(
"copytosubsite",
_t('SiteTreeSubsites.CopyAction', "Copy")
)
)
)->setHeadingLevel(4)
);
$copyAction->includeDefaultJS(false);
}
// replace readonly link prefix
$subsite = $this->owner->Subsite();
$nested_urls_enabled = Config::inst()->get('SiteTree', 'nested_urls');
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');
$urlsegment->setURLPrefix($baseLink);
}
}
/**
* @return SiteConfig
*/
public 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
*/
public 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
*/
public function canDelete($member = null)
{
if (!$member && $member !== false) {
$member = Member::currentUser();
}
return $this->canEdit($member);
}
/**
* @return boolean
*/
public function canAddChildren($member = null)
{
if (!$member && $member !== false) {
$member = Member::currentUser();
}
return $this->canEdit($member);
}
/**
* @return boolean
*/
public 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 int|Subsite $subsiteID The Subsite to copy to, or its ID
* @param bool $includeChildren Recursively copy child Pages.
* @param int $parentID Where to place the Page in the SiteTree's structure.
*
* @return SiteTree duplicated page
*/
public function duplicateToSubsite($subsiteID = null, $includeChildren = false, $parentID = 0)
{
if ($subsiteID instanceof Subsite) {
$subsiteID = $subsiteID->ID;
}
$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;
$page->ParentID = $parentID;
// MasterPageID is here for legacy purposes, to satisfy the subsites_relatedpages module
$page->MasterPageID = $this->owner->ID;
$page->write();
Subsite::changeSubsite($oldSubsite);
if($includeChildren) {
foreach($this->owner->AllChildren() as $child) {
$child->duplicateToSubsite($subsiteID, $includeChildren, $page->ID);
}
}
return $page;
}
/**
* Called by ContentController::init();
*/
public static function contentcontrollerInit($controller)
{
$subsite = Subsite::currentSubsite();
if ($subsite && $subsite->Theme) {
Config::inst()->update('SSViewer', 'theme', Subsite::currentSubsite()->Theme);
}
if ($subsite && i18n::validate_locale($subsite->Language)) {
i18n::set_locale($subsite->Language);
}
}
public 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.
*/
public 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.
*/
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 = 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);
}
/**
* 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 ?: Subsite::currentSubsiteID();
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-'.Subsite::currentSubsiteID();
}
/**
* @param 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) {
$blacklisted = explode(',', $subsite->PageTypeBlacklist);
// All subclasses need to be listed explicitly
if (in_array($this->owner->class, $blacklisted)) {
return false;
}
}
}
}

View File

@ -1,48 +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');
public function Field($properties = array())
{
$html = parent::Field($properties);
Requirements::javascript('subsites/javascript/SubsitesTreeDropdownField.js');
return $html;
}
public function setSubsiteID($id)
{
$this->subsiteID = $id;
}
public function getSubsiteID()
{
return $this->subsiteID;
}
public function tree(SS_HTTPRequest $request)
{
$oldSubsiteID = Session::get('SubsiteID');
Session::set('SubsiteID', $this->subsiteID);
$results = parent::tree($request);
Session::set('SubsiteID', $oldSubsiteID);
return $results;
}
}

1
codecov.yml Normal file
View File

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

View File

@ -2,7 +2,7 @@
"name": "silverstripe/subsites",
"description": "Run multiple sites from a single SilverStripe install.",
"license": "BSD-3-Clause",
"type": "silverstripe-module",
"type": "silverstripe-vendormodule",
"keywords": [
"silverstripe",
"subsites",
@ -15,11 +15,30 @@
}
],
"require": {
"silverstripe/framework": "~3.2",
"silverstripe/cms": "~3.2"
"php": "^7.4 || ^8.0",
"silverstripe/framework": "^4.10",
"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": "~3.7@stable"
"phpunit/phpunit": "^9.5",
"squizlabs/php_codesniffer": "^3.0"
},
"extra": []
"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,96 +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,
#Form_ItemEditForm .subsites-move-dropdown {
display:block;
}

View File

@ -48,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.

View File

@ -1,5 +1,7 @@
---
title: Working with multiple websites
summary: Setting up and editing multiple websites using SilverStripe
---
# Working with multiple sites
@ -45,4 +47,4 @@ Subsites can be used for various different reasons here are some of the common o
## Documentation
* [Set up](set_up.md)
* [Working with subsites](working_with.md)
* [Working with subsites](working_with.md)

View File

@ -1,3 +1,6 @@
---
title: Setting up
---
# Setting up
## Creating subsites
@ -102,4 +105,4 @@ For example, say a subsite user publishes a new Company Page before it was forbi
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.
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,3 +1,6 @@
---
title: Working with subsites
---
# Working with subsites
## Managing content across subsites
@ -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,31 +0,0 @@
(function($) {
$.entwine('ss', function($) {
/**
* Choose a subsite from which to select pages.
* Needs to clear tree dropdowns in case selection is changed.
*/
$('.subsitestreedropdownfield-chooser').entwine({
onchange: function() {
// TODO Data binding between two fields
// TODO create resetField method on API instead
var fields = $('.SubsitesTreeDropdownField');
fields.setValue(null);
fields.setTitle(null);
fields.find('.tree-holder').empty();
}
});
/**
* Add selected subsite from separate dropdown to the request parameters
* before asking for the tree.
*/
$('.TreeDropdownField.SubsitesTreeDropdownField').entwine({
getRequestParams: function() {
var name = this.find(':input[type=hidden]:first').attr('name') + '_SubsiteID',
source = $('[name=' + name + ']'), params = {};
params[name] = source.length ? source.val() : null;
return params;
}
});
});
})(jQuery);

View File

@ -1,53 +1,68 @@
ar:
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: 'كافة المواقع'
SUBSITENOTICE: 'يمكن الوصول إلى المجلدات والملفات التي تم إنشاؤها في الموقع الرئيسي من طرف كل المواقع الفرعية.'
SubsiteFieldLabel: 'الموقع الفرعي'
GridFieldAddFromTemplateButton:
AddFromTemplate: 'اضف جديد من القالب'
GroupSubsites:
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: 'كلمات البحث'
CustomMetaTitle: عنوان
DOMAINSAVEFIRST: 'لا يمكنك إضافة النطاقات إلا بعد القيام بالحفظ لأول مرة'
DomainsHeadline: 'نطاقات هذا الموقع الفرعي'
DomainsListTitle: النطاقات
IsPublicHeaderField: 'موقع فرعي نشط'
NOTEMPLATE: 'بدون قالب'
PLURALNAME: 'المواقع الفرعية'
PageTypeBlacklistField: 'عدم السماح بأصناف الصفحات؟'
SINGULARNAME: 'الموقع الفرعي'
SiteConfigSubtitle: 'هنا سطر الوصف الخاص بك'
SiteConfigTitle: 'اسم موقعك'
TabTitleConfig: 'المواصفات'
ValidateTitle: 'الرجاء إضافة "عنوان"'
SubsiteAdmin:
MENUTITLE: 'مواقع فرعية'
SubsiteDomain:
DOMAIN: نطاق
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:
ReportDropdown: 'المواقع'
SubsiteXHRController:
MENUTITLE: 'المراقب XHR للموقع الفرعي'
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: النطاق
@ -57,9 +72,5 @@ ar:
PageTypeBlacklistFieldLabel: 'نوع الصفحة قائمة سوداء'
PrimaryDomainFieldLabel: 'النطاق الأساسي'
RedirectURLFieldLabel: 'إعادة توجيه عنوان موقع الويب'
ThemeFieldLabel: 'المحور'
ThemeFieldLabel: المحور
TitleFieldLabel: 'اسم الموقع الفرعي'
SubsitesVirtualPage:
DESCRIPTION: 'يعرض محتوى صفحة على موقع فرعي آخر'
SINGULARNAME: 'الصفحة الإفتراضية للمواقع الفرعية'
SubsiteField: 'الموقع الفرعي'

View File

@ -2,6 +2,36 @@ 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:

View File

@ -1,14 +1,17 @@
de:
ASSETADMIN:
SUBSITENOTICE: 'Auf Ordner und Dateien der Hauptseite kann von allen Subsites zugegriffen werden.'
DomainNameField:
INVALID_DOMAIN: 'Ungültige Domain'
FileSubsites:
AllSitesDropdownOpt: 'Alle Subseiten'
SilverStripe\CMS\Model\VirtualPage:
EDITCONTENT: 'Klicken Sie hier, um den Inhalt zu bearbeiten'
SilverStripe\Subsites\Admin\SubsiteAdmin:
MENUTITLE: Subsites
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.'
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'
@ -16,49 +19,74 @@ 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.
LeftAndMain_Menu:
Hello: Hallo
LOGOUT: Abmelden
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'
SubsiteAdmin:
MENUTITLE: Subsites
SubsiteDomain:
DOMAIN: Domain
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 Hauptdomain?'
PLURALNAME: 'Subsite Domains'
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: 'Subsite Domain'
SubsiteReportWrapper:
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
SubsiteXHRController:
MENUTITLE: SubsiteXHRController
ReportDropdownSubsite: Subseite
Subsite:
COPYSTRUCTURE: 'Struktur kopieren von:'
NOTEMPLATE: 'Kein Template'
Subsites:
DefaultSiteFieldLabel: 'Standard Seite'
DomainFieldLabel: Domäne
@ -70,10 +98,3 @@ de:
RedirectURLFieldLabel: Weierleitungs-URL
ThemeFieldLabel: Theme
TitleFieldLabel: 'Name der Subsite'
SubsitesVirtualPage:
DESCRIPTION: 'Zeigt den Inhalt einer anderen Seite von einer anderen Subsite an'
PLURALNAME: 'Basis Seiten'
SINGULARNAME: 'Subsites Virtuelle Seite'
SubsiteField: Subsite
VirtualPage:
EDITCONTENT: 'Klicken Sie hier, um den Inhalt zu bearbeiten'

View File

@ -1,14 +1,19 @@
en:
ASSETADMIN:
SUBSITENOTICE: 'Folders and files created in the main site are accessible by all subsites.'
DomainNameField:
INVALID_DOMAIN: 'Invalid domain name'
FileSubsites:
SilverStripe\CMS\Model\VirtualPage:
EDITCONTENT: 'Click here to edit the content'
SilverStripe\Subsites\Admin\SubsiteAdmin:
MENUTITLE: Subsites
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'
@ -16,52 +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.'
LeftAndMain_Menu:
Hello: Hi
LOGOUT: 'Log out'
SiteTreeSubsites:
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
has_one_Subsite: Subsite
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
CopyAction: Copy
CopyToSubsite: 'Copy page to subsite'
CopyToSubsiteWithChildren: 'Include children pages?'
SubsiteOperations: 'Subsite Operations'
Subsite:
COPYSTRUCTURE: 'Copy structure from:'
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"'
SubsiteAdmin:
MENUTITLE: Subsites
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
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: 'Mark this as the default domain for this subsite'
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
SubsiteXHRController:
MENUTITLE: SubsiteXHRController
ReportDropdownAll: All
ReportDropdownSubsite: Subsite
Subsite:
COPYSTRUCTURE: 'Copy structure from:'
NOTEMPLATE: 'No template'
Subsites:
DefaultSiteFieldLabel: 'Default site'
DomainFieldLabel: Domain
@ -72,12 +110,4 @@ en:
PrimaryDomainFieldLabel: 'Primary Domain'
RedirectURLFieldLabel: 'Redirect URL'
ThemeFieldLabel: Theme
ThemeFieldEmptyString: ''
TitleFieldLabel: 'Subsite Name'
SubsitesVirtualPage:
DESCRIPTION: 'Displays the content of a page on another subsite'
PLURALNAME: 'Base Pages'
SINGULARNAME: 'Subsites Virtual Page'
SubsiteField: Subsite
VirtualPage:
EDITCONTENT: 'Click here to edit the content'

View File

@ -1,67 +1,105 @@
eo:
ASSETADMIN:
SUBSITENOTICE: 'Dosierujoj kaj dosieroj kreitaj en la ĉefa retejo estas alireblaj de ĉiuj retejoj'
DomainNameField:
INVALID_DOMAIN: 'Nevalida domajna nomo'
FileSubsites:
SilverStripe\CMS\Model\VirtualPage:
EDITCONTENT: 'Alklaku ĉi tie por redakti la enhavon'
SilverStripe\Subsites\Admin\SubsiteAdmin:
MENUTITLE: Subretejoj
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.'
LeftAndMain_Menu:
Hello: Saluton
LOGOUT: Elsaluti
SiteTreeSubsites:
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
has_one_Subsite: Subretejo
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
CopyAction: Kopio
CopyToSubsite: 'Kopii paĝon al subretejo'
CopyToSubsiteWithChildren: 'Ĉu inkluzivi paĝidojn?'
SubsiteOperations: 'Subretejaj operacioj'
Subsite:
COPYSTRUCTURE: 'Kopii strukturon de:'
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"'
SubsiteAdmin:
MENUTITLE: Subretejoj
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
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: 'Marki ĉi tion kiel la aprioran domajnon por ĉi tiu subretejo'
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
SubsiteXHRController:
MENUTITLE: SubsiteXHRController
ReportDropdownAll: Ĉiuj
ReportDropdownSubsite: Subretejo
Subsite:
COPYSTRUCTURE: 'Kopii strukturon de:'
NOTEMPLATE: 'Mankas ŝablono'
Subsites:
DefaultSiteFieldLabel: 'Apriora retejo'
DomainFieldLabel: Domajno
@ -73,10 +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: 'Bazaj paĝoj'
SINGULARNAME: 'Virtuala paĝo de subretejoj'
SubsiteField: Subretejo
VirtualPage:
EDITCONTENT: 'Alklaku ĉi tie por redakti la enhavon'

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

View File

@ -1,46 +1,59 @@
fa_IR:
DomainNameField:
INVALID_DOMAIN: 'نام دامنه نامعتبر'
FileSubsites:
SilverStripe\CMS\Model\VirtualPage:
EDITCONTENT: 'برای ویرایش محتوا اینجا را کلیک کنید'
SilverStripe\Subsites\Admin\SubsiteAdmin:
MENUTITLE: 'زیر سایت ها'
SilverStripe\Subsites\Extensions\FileSubsites:
has_one_Subsite: 'زیر سایت'
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
AllSitesDropdownOpt: 'تمامی سایت ها'
SubsiteFieldLabel: 'زیر سایت ها'
GroupSubsites:
SubsiteFieldLabel: 'زیر سایت'
SilverStripe\Subsites\Extensions\GroupSubsites:
ACCESSALL: 'تمامی زیر سایت ها'
ACCESSONLY: 'فقط این زیر سایت ها'
SECURITYTABTITLE: 'زیر سایت ها'
LeftAndMain_Menu:
Hello: 'سلام'
LOGOUT: خروج
Subsite:
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: عنوان
DomainsHeadline: 'دامنه های این زیر سایت'
DomainsListTitle: 'دامنه ها'
IsPublicHeaderField: 'زیر سایت فعال'
NOTEMPLATE: 'بدون قالب'
PLURALNAME: 'زیر سایت ها'
SINGULARNAME: 'زیر سایت'
SiteConfigTitle: 'نام سایت شما'
TabTitleConfig: پیکربندی
SubsiteAdmin:
MENUTITLE: 'زیر سایت ها'
SubsiteDomain:
DOMAIN: 'دامنه'
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: 'دمنه زیرسایت'
SubsiteReportWrapper:
db_Domain: دامنه
db_Protocol: پروتکل
has_one_Subsite: 'زیر سایت'
SilverStripe\Subsites\Pages\SubsitesVirtualPage:
SubsiteField: 'زیر سایت'
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
ReportDropdown: 'سایت ها'
ReportDropdownSubsite: 'زیر سایت'
Subsite:
NOTEMPLATE: 'بدون قالب'
Subsites:
DefaultSiteFieldLabel: 'سایت پیش فرض'
DomainFieldLabel: 'دامنه'
DomainFieldLabel: دامنه
LanguageFieldLabel: زبان
PrimaryDomainFieldLabel: 'دامنه اولیه'
ThemeFieldLabel: پوسته
TitleFieldLabel: 'نام زیر سایت'
SubsitesVirtualPage:
PLURALNAME: 'صفحه اصلی'
SubsiteField: 'زیر سایت'
VirtualPage:
EDITCONTENT: 'برای ویرایش محتوا اینجا را کلیک کنید'

View File

@ -1,67 +1,84 @@
fi:
ASSETADMIN:
SUBSITENOTICE: 'Kansiot ja tiedostot, jotka on luotu pääsivustolla, ovat käytettävissä kaikissa alisivustoissa.'
DomainNameField:
INVALID_DOMAIN: 'Virheellinen domain-nimi'
FileSubsites:
SilverStripe\CMS\Model\VirtualPage:
EDITCONTENT: 'Napsauta tässä muokataksesi sisältöä'
SilverStripe\Subsites\Admin\SubsiteAdmin:
MENUTITLE: Alasivustot
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'
ACCESSRADIOTITLE: 'Anna ryhmän oikeudet »'
GlobalGroup: Globaaliryhmä
MANAGE_SUBSITES: 'Hallinnoi ryhmien alisivustoja'
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.'
LeftAndMain_Menu:
Hello: Hei
LOGOUT: 'Kirjaudu ulos'
SiteTreeSubsites:
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
has_one_Subsite: Alasivusto
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
CopyAction: Kopioi
CopyToSubsite: 'Kopioi sivu alisivustolle'
CopyToSubsite: 'Kopioi sivu alasivustolle'
CopyToSubsiteWithChildren: 'Sisällytä alasivut?'
SubsiteOperations: Alisivustotoiminnot
Subsite:
COPYSTRUCTURE: 'Kopioi rakenne kohteesta:'
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"'
SubsiteAdmin:
MENUTITLE: Alasivustot
SubsiteDomain:
DOMAIN: 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_DESCRIPTION: 'Merkitse tämä oletusdomainiksi tälle alisivustolle'
PROTOCOL_HTTP: 'http://'
PROTOCOL_HTTPS: 'https://'
Protocol: Protokolla
SINGULARNAME: 'Alisivuston domain-osoite'
SubsiteReportWrapper:
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: Alasivusto
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
ReportDropdown: Sivustot
SubsiteXHRController:
MENUTITLE: SubsiteXHRController
ReportDropdownSubsite: Alasivusto
Subsite:
COPYSTRUCTURE: 'Kopioi rakenne kohteesta:'
NOTEMPLATE: 'Ei sivupohjaa'
Subsites:
DefaultSiteFieldLabel: Oletussivusto
DomainFieldLabel: Domain
@ -73,10 +90,3 @@ fi:
RedirectURLFieldLabel: 'Edelleenohjaus URL'
ThemeFieldLabel: Teema
TitleFieldLabel: 'Alisivuston nimi'
SubsitesVirtualPage:
DESCRIPTION: 'Näyttää sisällön toisen alisivuston sivulta'
PLURALNAME: Pohjasivut
SINGULARNAME: 'Alisivuston Virtuaalisivu'
SubsiteField: Alisivu
VirtualPage:
EDITCONTENT: 'Napsauta tästä muokataksesi sisältöä'

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

View File

@ -1,14 +1,17 @@
hr:
ASSETADMIN:
SUBSITENOTICE: 'Direktoriji i datoteke kreirane u glavnom sajtu su dostupne svim podsajtovima.'
DomainNameField:
INVALID_DOMAIN: 'Netočan naziv domene'
FileSubsites:
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
GridFieldAddFromTemplateButton:
AddFromTemplate: 'Dodaj novi iz predloška'
GroupSubsites:
SilverStripe\Subsites\Extensions\GroupSubsites:
ACCESSALL: 'Svi podsajtovi'
ACCESSONLY: 'Samo ovi podsajtovi'
ACCESSRADIOTITLE: 'Dodijeli ovoj grupi pristup za'
@ -16,50 +19,61 @@ hr:
MANAGE_SUBSITES: 'Upravljaj podsajtove za grupe'
MANAGE_SUBSITES_HELP: 'Mogućnost limitiranja prava za grupu za jedan ili više podsajtova.'
SECURITYTABTITLE: Podsajtovi
LeftAndMainSubsites:
many_many_Subsites: Podsajtovi
SilverStripe\Subsites\Extensions\LeftAndMainSubsites:
Saved: 'Spremljeno, molimo osvježite povezane stranice.'
LeftAndMain_Menu:
Hello: Pozdrav
LOGOUT: Odjava
SiteTreeSubsites:
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
has_one_Subsite: Podsajt
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
CopyAction: Kopiraj
CopyToSubsite: 'Kopiraj stranicu u podsajt'
Subsite:
COPYSTRUCTURE: 'Kopiraj strukturu od:'
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
DOMAINSAVEFIRST: 'Možete dodati domene nakon prvog spremanja'
DomainsHeadline: 'Domene za ove podsajtove'
DomainsListTitle: Domene
IsPublicHeaderField: 'Aktivni podsajtovi'
NOTEMPLATE: 'Nema predloška'
PLURALNAME: Podsajtovi
PageTypeBlacklistField: 'Ne dopuštaj tipove stranica?'
SINGULARNAME: Podsajt
SiteConfigSubtitle: 'vaš slogan ovdje'
SiteConfigTitle: 'Naziv vašeg weba'
TabTitleConfig: Konfiguracija
ValidateTitle: 'Molimo dodajte "Naslov"'
SubsiteAdmin:
MENUTITLE: Podsajtovi
SubsiteDomain:
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_DESCRIPTION: 'Označi kao zadanu domenu za ovu podstranicu'
PROTOCOL_HTTP: 'http://'
PROTOCOL_HTTPS: 'https://'
Protocol: Protokol
SINGULARNAME: 'Domena podsajta'
SubsiteReportWrapper:
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
SubsiteXHRController:
MENUTITLE: SubsiteXHRController
ReportDropdownSubsite: Podsajt
Subsite:
COPYSTRUCTURE: 'Kopiraj strukturu od:'
NOTEMPLATE: 'Nema predloška'
Subsites:
DefaultSiteFieldLabel: 'Zadani sajt'
DomainFieldLabel: Domena
@ -71,10 +85,3 @@ hr:
RedirectURLFieldLabel: 'Link preusmjeravanja'
ThemeFieldLabel: Tema
TitleFieldLabel: 'Naziv podsajta'
SubsitesVirtualPage:
DESCRIPTION: 'Prikazuje sadržaj stranice na drugom podsajtu'
PLURALNAME: 'Bazna stranica'
SINGULARNAME: 'Virtualna stranica podsajta'
SubsiteField: Podsajt
VirtualPage:
EDITCONTENT: 'Klikni ovdje za uređivanje sadržaja'

View File

@ -3,6 +3,38 @@ id:
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'
@ -15,5 +47,6 @@ id:
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,16 +1,60 @@
ja:
GridFieldAddFromTemplateButton:
AddFromTemplate: 'テンプレートから新しく追加'
AddFromTemplate: テンプレートから新しく追加
GroupSubsites:
ACCESSALL: '全てのサブサイト'
ACCESSALL: 全てのサブサイト
ACCESSONLY: これらのサブサイトのみ
ACCESSRADIOTITLE: 'このグループに選択先へのアクセス権を与える'
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: サブサイト
SubsiteDomain:
DOMAIN: ドメイン
PLURALNAME: サブサイトのドメイン
SINGULARNAME: サブサイトのドメイン
Subsites:
DomainFieldLabel: ドメイン
LanguageFieldLabel: 言語
ThemeFieldLabel: テーマ
SubsitesVirtualPage:
SINGULARNAME: サブサイトの仮想ページ

View File

@ -1,37 +1,37 @@
lt:
FileSubsites:
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
AllSitesDropdownOpt: 'Visos svetainės'
GridFieldAddFromTemplateButton:
AddFromTemplate: 'Sukurti naują pagal šabloną'
LeftAndMainSubsites:
SilverStripe\Subsites\Extensions\LeftAndMainSubsites:
Saved: 'Išsaugota, prašome atnaujinti susijusius puslapius'
LeftAndMain_Menu:
Hello: Sveiki
LOGOUT: Atsijungti
SiteTreeSubsites:
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
CopyAction: Kopijuoti
CopyToSubsite: 'Kopijuoti puslapį į kitą svetainę'
Subsite:
COPYSTRUCTURE: 'Kopijuoti struktūrą iš:'
SilverStripe\Subsites\Model\Subsite:
ConfigurationTab: Nustatymai
CopyMessage: 'Sukurta {title} kopija'
CustomExtraMeta: 'Kitos meta žymės'
CustomMetaDescription: Aprašymas
CustomMetaKeywords: Raktažodžiai
CustomMetaTitle: Pavadinimas
DomainsHeadline: 'Šio puslapio domenai'
DomainsListTitle: Domenai
NOTEMPLATE: 'Nėra šablono'
PageTypeBlacklistField: 'Neleidžiami puslapių tipai'
SiteConfigSubtitle: 'Jūsų svetainės šūkis'
SiteConfigTitle: 'Jūsų svetainės pavadinimas'
TabTitleConfig: Nustatymai
ValidateTitle: 'Prašome įvesti "Pavadinimą"'
SubsiteDomain:
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
SubsiteReportWrapper:
db_Domain: Domenas
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
ReportDropdown: Svetainės
SubsiteXHRController:
MENUTITLE: SubsiteXHRController
Subsite:
COPYSTRUCTURE: 'Kopijuoti struktūrą iš:'
NOTEMPLATE: 'Nėra šablono'
Subsites:
DefaultSiteFieldLabel: 'Pagrindinė svetainė'
DomainFieldLabel: Domenas
@ -41,5 +41,3 @@ lt:
PrimaryDomainFieldLabel: 'Pagrindinis domenas'
RedirectURLFieldLabel: 'Nukreipimo nuoroda'
ThemeFieldLabel: Tema
SubsitesVirtualPage:
PLURALNAME: 'Baziniai puslapiai'

View File

@ -1,12 +1,15 @@
mi:
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'
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'
GridFieldAddFromTemplateButton:
AddFromTemplate: 'Tāpiri Hōu mai i te Tātauira'
GroupSubsites:
SilverStripe\Subsites\Extensions\GroupSubsites:
ACCESSALL: 'Ngā pae iti katoa'
ACCESSONLY: 'Ko ēnei pae iti anake'
ACCESSRADIOTITLE: 'Tukuna tēnei rōpū kia uru ki'
@ -14,40 +17,52 @@ mi:
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:
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'
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'
PageTypeBlacklistField: 'Me whakakāhore ngā momo whārangi?'
SINGULARNAME: 'Pae iti'
SiteConfigSubtitle: 'Tō rārangi tautuhinga ki konei'
SiteConfigTitle: 'Tō Ingoa Pae'
TabTitleConfig: Whirihoranga
ValidateTitle: 'Tāurua he "Taitara"'
SubsiteAdmin:
MENUTITLE: 'Ngā pae iti'
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
PLURALNAME: 'Ngā Rohe Pae Iti'
SINGULARNAME: 'Rohe Pae Iti'
SubsiteReportWrapper:
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'
SubsiteXHRController:
MENUTITLE: ManaXHRPaeiti
ReportDropdownSubsite: 'Pae iti'
Subsite:
COPYSTRUCTURE: 'Tāurutia te hanganga mai i:'
NOTEMPLATE: 'Kāore he tātauira'
Subsites:
DefaultSiteFieldLabel: 'Pae taunoa'
DomainFieldLabel: Rohe
@ -59,7 +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'
SINGULARNAME: 'Whārangi Mariko Pae Iti'
SubsiteField: 'Pae iti'

View File

@ -6,11 +6,46 @@ nb_NO:
ACCESSONLY: 'Only these subsites'
ACCESSRADIOTITLE: 'Give this group access to'
SECURITYTABTITLE: subdomener
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
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,32 +1,103 @@
nl:
FileSubsites:
DomainNameField:
INVALID_DOMAIN: 'Ongeldige domein naam'
SilverStripe\CMS\Model\VirtualPage:
EDITCONTENT: 'Klik hier om inhoud aan te passen'
SilverStripe\Subsites\Admin\SubsiteAdmin:
MENUTITLE: Subsites
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
SubsiteAdmin:
MENUTITLE: Subsites
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:
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

@ -6,11 +6,51 @@ pl_PL:
ACCESSONLY: 'Tylko te podwitryny'
ACCESSRADIOTITLE: 'Daj tej grupie dostęp do'
SECURITYTABTITLE: Podwitryny
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
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'

View File

@ -1,14 +1,17 @@
ru:
ASSETADMIN:
SUBSITENOTICE: 'Папки и файлы созданные на основном сайте так же доступны для под-сайтов.'
DomainNameField:
INVALID_DOMAIN: 'Неверный домен'
FileSubsites:
SilverStripe\CMS\Model\VirtualPage:
EDITCONTENT: 'Нажмите для изменения содержимого'
SilverStripe\Subsites\Admin\SubsiteAdmin:
MENUTITLE: Подсайты
SilverStripe\Subsites\Extensions\FileSubsites:
has_one_Subsite: Подсайт
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
AllSitesDropdownOpt: 'Все сайты'
SUBSITENOTICE: 'Папки и файлы созданные на основном сайте так же доступны для под-сайтов.'
SubsiteFieldLabel: Подсайт
GridFieldAddFromTemplateButton:
AddFromTemplate: 'Добавить из шаблона'
GroupSubsites:
SilverStripe\Subsites\Extensions\GroupSubsites:
ACCESSALL: 'Все подсайты'
ACCESSONLY: 'Только эти подсайты'
ACCESSRADIOTITLE: 'Разрешить этой группе доступ к'
@ -16,52 +19,61 @@ ru:
MANAGE_SUBSITES: 'Управление подсайтами для групп'
MANAGE_SUBSITES_HELP: 'Возможность ограничить права доступа для группы к одному и более подсайтам'
SECURITYTABTITLE: Подсайты
LeftAndMainSubsites:
many_many_Subsites: Подсайты
SilverStripe\Subsites\Extensions\LeftAndMainSubsites:
Saved: 'Сохранено, пожалуйста обновите связанные группы'
LeftAndMain_Menu:
Hello: Здравствуйте
LOGOUT: 'Выход'
SiteTreeSubsites:
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
has_one_Subsite: Подсайт
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
CopyAction: Копировать
CopyToSubsite: 'Копировать страницу на подсайт'
CopyToSubsiteWithChildren: 'Включая под-страницы?'
SubsiteOperations: 'Операции над подсайтами'
Subsite:
COPYSTRUCTURE: 'Скопировать структуры из:'
has_one_Subsite: Подсайт
SilverStripe\Subsites\Model\Subsite:
ConfigurationTab: Конфигурация
CopyMessage: 'Создана копия {title}'
CustomExtraMeta: 'Пользовательские мета-тэги'
CustomMetaDescription: Описание
CustomMetaKeywords: 'Ключевые слова'
CustomMetaTitle: Заголовок
DOMAINSAVEFIRST: 'Вы можете добавлять домены только после первого сохранения'
DomainsHeadline: 'Домены для этого подсайта'
DomainsListTitle: Домены
IsPublicHeaderField: 'Активный подсайт'
NOTEMPLATE: 'Нет шаблона'
PLURALNAME: Подсайты
PageTypeBlacklistField: 'Запретить данные типы страниц?'
SINGULARNAME: Подсайт
SiteConfigSubtitle: 'ваш слоган здесь'
SiteConfigTitle: 'Название сайта'
TabTitleConfig: Конфигурация
ValidateTitle: 'Пожалуйста, добавьте "Заголовок"'
SubsiteAdmin:
MENUTITLE: Подсайты
SubsiteDomain:
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_DESCRIPTION: 'Отметить как основной домен для данного подсайта'
PROTOCOL_HTTP: 'http://'
PROTOCOL_HTTPS: 'https://'
Protocol: Протокол
SINGULARNAME: 'Домен подсайта'
SubsiteReportWrapper:
db_Domain: Домен
db_Protocol: Протокол
has_one_Subsite: Подсайт
SilverStripe\Subsites\Pages\SubsitesVirtualPage:
DESCRIPTION: 'Отображает содержимое выбранной страницы на другом подсайте'
SINGULARNAME: 'Виртуальная страница подсайта'
SubsiteField: Подсайт
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
ReportDropdown: Сайты
SubsiteXHRController:
MENUTITLE: SubsiteXHRController
ReportDropdownSubsite: Подсайт
Subsite:
COPYSTRUCTURE: 'Скопировать структуры из:'
NOTEMPLATE: 'Нет шаблона'
Subsites:
DefaultSiteFieldLabel: 'Основной сайт'
DomainFieldLabel: Домен
@ -73,10 +85,3 @@ ru:
RedirectURLFieldLabel: 'Ссылка для перенаправления'
ThemeFieldLabel: Оформление
TitleFieldLabel: 'Название Подсайта'
SubsitesVirtualPage:
DESCRIPTION: 'Отображает содержимое выбранной страницы на другом подсайте'
PLURALNAME: 'Базовые страницы'
SINGULARNAME: 'Виртуальная страница подсайта'
SubsiteField: Подсайт
VirtualPage:
EDITCONTENT: 'Нажмите для изменения содержимого'

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

@ -6,11 +6,45 @@ tr_TR:
ACCESSONLY: 'Only these subsites'
ACCESSRADIOTITLE: 'Give this group access to'
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
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,57 +1,72 @@
zh:
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: 所有网站
SUBSITENOTICE: 主网站上创建的文件夹和文件可以被所有子网站访问。
SubsiteFieldLabel: 子网站
GridFieldAddFromTemplateButton:
AddFromTemplate: '从模板中取出新加入'
GroupSubsites:
SilverStripe\Subsites\Extensions\GroupSubsites:
ACCESSALL: 所有子网站
ACCESSONLY: '仅这些子网站'
ACCESSRADIOTITLE: '准许该群进入'
GlobalGroup: '全局小组'
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: 复制结构来自:
has_one_Subsite: 子网站
SilverStripe\Subsites\Model\Subsite:
ConfigurationTab: 配置
CopyMessage: '已创建一个 {title} 的副本'
CustomExtraMeta: '自定义 Meta 标签'
CustomMetaDescription: 说明
CustomMetaKeywords: '关键词'
CustomMetaTitle: '标题'
DOMAINSAVEFIRST: '只有当您第一次保存后,才能添加域名'
DomainsHeadline: 这个子网站的域名
DomainsListTitle: 域名
IsPublicHeaderField: 活跃的子网站
NOTEMPLATE: 没有模板
CustomExtraMeta: 自定义Meta标签
CustomMetaDescription: 描述
CustomMetaKeywords: 关键词
CustomMetaTitle: 题目
PLURALNAME: 多个子网站
PageTypeBlacklistField: 禁止页面类型?
SINGULARNAME: 子网站
SiteConfigSubtitle: '您的标语在这里'
SiteConfigSubtitle: 您的标语在这里
SiteConfigTitle: 您的网站名称
TabTitleConfig: '配置'
ValidateTitle: '请添加一个“标题”'
SubsiteAdmin:
MENUTITLE: 多个子网站
SubsiteDomain:
ValidateTitle: 请添加一个“标题”
belongs_many_many_Groups: 群组
db_DefaultSite: 默认网站
db_Language: 语言
db_RedirectURL: '重定向 URL'
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: 网站
SubsiteXHRController:
MENUTITLE: '子网站 XHR 控制器'
ReportDropdownSubsite: 子网站
Subsite:
COPYSTRUCTURE: 复制结构来自:
NOTEMPLATE: 没有模板
Subsites:
DefaultSiteFieldLabel: 默认网站
DomainFieldLabel: 域名
IsPublicFieldLabel: '启用公共访问权'
IsPublicFieldLabel: 启用公共访问权
LanguageFieldLabel: 语言
MainSiteTitle: 主网站
PageTypeBlacklistFieldLabel: 页面类型黑名单
@ -59,7 +74,3 @@ zh:
RedirectURLFieldLabel: '重定向 URL'
ThemeFieldLabel: 主题
TitleFieldLabel: 子网站名称
SubsitesVirtualPage:
DESCRIPTION: '显示另一个子网站上一个页面的内容'
SINGULARNAME: 子网站虚拟页面
SubsiteField: 子网站

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

@ -1,20 +1,33 @@
<?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()) {
if (parent::canView($member)) {
return true;
}
if (Subsite::all_accessible_sites()->count()>0) {
if (Subsite::all_accessible_sites()->count() > 0) {
return true;
}
@ -26,21 +39,20 @@ class SubsiteXHRController extends LeftAndMain
*/
public function canAccess()
{
// Allow if any cms access is available
return Permission::check(array(
'CMS_ACCESS', // Supported by 3.1.14 and up
'CMS_ACCESS_LeftAndMain'
));
// 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();
$self = $this;
// Register a new callback
$negotiator->setCallback('SubsiteList', function () use (&$self) {
return $self->SubsiteList();
$negotiator->setCallback('SubsiteList', function () {
return $this->SubsiteList();
});
return $negotiator;
@ -51,6 +63,6 @@ class SubsiteXHRController extends LeftAndMain
*/
public function SubsiteList()
{
return $this->renderWith('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

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

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');

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

@ -1,45 +1,53 @@
<?php
class GridFieldSubsiteDetailForm extends GridFieldDetailForm
{
protected $itemRequestClass='GridFieldSubsiteDetailForm_ItemRequest';
}
class GridFieldSubsiteDetailForm_ItemRequest extends GridFieldDetailForm_ItemRequest
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 = array(
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();
$form = parent::ItemEditForm();
if ($this->record->ID == 0) {
$templates = Subsite::get()->sort('Title');
$templateArray = array();
$templateArray = [];
if ($templates) {
$templateArray = $templates->map('ID', 'Title');
}
$templateDropdown = new DropdownField('TemplateID', _t('Subsite.COPYSTRUCTURE', 'Copy structure from:'), $templateArray);
$templateDropdown = new DropdownField(
'TemplateID',
_t('Subsite.COPYSTRUCTURE', 'Copy structure from:'),
$templateArray
);
$templateDropdown->setEmptyString('(' . _t('Subsite.NOTEMPLATE', 'No template') . ')');
$form->Fields()->addFieldToTab('Root.Configuration', $templateDropdown);
$form->Fields()->addFieldToTab('Root.Main', $templateDropdown);
}
return $form;
}
public function doSave($data, $form)
{
$new_record = $this->record->ID == 0;

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

@ -1,4 +1,7 @@
<?php
namespace SilverStripe\Subsites\Forms;
use SilverStripe\Forms\TextField;
/**
* A text field that accepts only valid domain names, but allows the wildcard (*) character
@ -19,8 +22,8 @@ class WildcardDomainField extends TextField
$validator->validationError(
$this->getName(),
_t("DomainNameField.INVALID_DOMAIN", "Invalid domain name"),
"validation"
_t('DomainNameField.INVALID_DOMAIN', 'Invalid domain name'),
'validation'
);
return false;
}
@ -33,7 +36,7 @@ class WildcardDomainField extends TextField
*/
public function checkHostname($hostname)
{
return (bool)preg_match('/^([a-z0-9\*]+[\-\.])*([a-z0-9\*]+)$/', $hostname);
return (bool)preg_match('/^([a-z0-9\*]+[\-\.\:])*([a-z0-9\*]+)$/', $hostname ?? '');
}
public function Type()

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;
}
}

View File

@ -1,4 +1,38 @@
<?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.
@ -7,65 +41,135 @@
*/
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;
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.
* 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
*/
public static $write_hostmap = true;
private static $write_hostmap = true;
/**
* Memory cache of accessible sites
*
* @array
*/
private static $_cache_accessible_sites = array();
protected static $cache_accessible_sites = [];
/**
* Memory cache of subsite id for domains
*
* @var array
*/
private static $_cache_subsite_for_domain = array();
protected static $cache_subsite_for_domain = [];
/**
* @var array $allowed_themes Numeric array of all themes which are allowed to be selected for all subsites.
* 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
*/
private static $allowed_themes = array();
protected static $allowed_themes = [];
/**
* @var Boolean If set to TRUE, don't assume 'www.example.com' and 'example.com' are the same.
* 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
*/
public static $strict_subdomain_matching = false;
private static $strict_subdomain_matching = false;
/**
* @var boolean Respects the IsPublic flag when retrieving subsites
* Respects the IsPublic flag when retrieving subsites
*
* @config
* @var boolean
*/
public static $check_is_public = true;
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
@ -80,13 +184,11 @@ class Subsite extends DataObject
/**
* Gets the subsite currently set in the session.
*
* @uses ControllerSubsites->controllerAugmentInit()
* @return Subsite
* @return DataObject The current Subsite
*/
public static function currentSubsite()
{
// get_by_id handles caching so we don't have to
return DataObject::get_by_id('Subsite', self::currentSubsiteID());
return Subsite::get()->byID(SubsiteState::singleton()->getSubsiteId());
}
/**
@ -97,30 +199,19 @@ class Subsite extends DataObject
*
* 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
*
* @return int ID of the current subsite instance
*
* @deprecated 2.0.0 Use SubsiteState::singleton()->getSubsiteId() instead
*/
public static function currentSubsiteID()
{
$id = null;
if (isset($_GET['SubsiteID'])) {
$id = (int)$_GET['SubsiteID'];
} elseif (Subsite::$use_session_subsiteid) {
$id = Session::get('SubsiteID');
}
if ($id === null) {
$id = self::getSubsiteIDForDomain();
}
return (int)$id;
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 Subsite::$use_session_subsiteid} is set to TRUE.
* 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
*/
@ -128,7 +219,7 @@ class Subsite extends DataObject
{
// 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) {
if (!SubsiteState::singleton()->getUseSessions()) {
return;
}
@ -138,17 +229,17 @@ class Subsite extends DataObject
$subsiteID = $subsite;
}
Session::set('SubsiteID', (int)$subsiteID);
SubsiteState::singleton()->setSubsiteId($subsiteID);
// Set locale
if (is_object($subsite) && $subsite->Language != '') {
$locale = i18n::get_locale_from_lang($subsite->Language);
if (is_object($subsite) && $subsite->Language !== '') {
$locale = (new IntlLocales())->localeFromLang($subsite->Language);
if ($locale) {
i18n::set_locale($locale);
}
}
Permission::flush_permission_cache();
Permission::reset();
}
/**
@ -157,7 +248,8 @@ class Subsite extends DataObject
* 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.
* @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)
@ -166,30 +258,52 @@ class Subsite extends DataObject
$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 (!self::$strict_subdomain_matching) {
$host = preg_replace('/^www\./', '', $host);
if (!static::config()->get('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];
$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",
"'$SQL_host' LIKE replace(\"SubsiteDomain\".\"Domain\",'*','%')",
"\"IsPrimary\" DESC"
)->innerJoin('Subsite', "\"Subsite\".\"ID\" = \"SubsiteDomain\".\"SubsiteID\" AND \"Subsite\".\"IsPublic\"=1");
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) {
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,
@ -198,16 +312,18 @@ class Subsite extends DataObject
}
$subsiteID = $subsiteIDs[0];
} elseif ($default = Subsite::get()->filter('DefaultSite', 1)->setQueriedColumns(array('ID'))->first()) {
// Check for a 'default' subsite
$subsiteID = $default->ID;
} else {
// Default subsite id = 0, the main site
$subsiteID = 0;
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;
self::$cache_subsite_for_domain[$cacheKey] = $subsiteID;
}
return $subsiteID;
@ -222,7 +338,7 @@ class Subsite extends DataObject
* @param string $limit
* @return DataList
*/
public static function get_from_all_subsites($className, $filter = "", $sort = "", $join = "", $limit = "")
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);
@ -231,6 +347,7 @@ class Subsite extends DataObject
/**
* Disable the sub-site filtering; queries will select from all subsites
* @param bool $disabled
*/
public static function disable_subsite_filter($disabled = true)
{
@ -242,16 +359,19 @@ class Subsite extends DataObject
*/
public static function on_db_reset()
{
self::$_cache_accessible_sites = array();
self::$_cache_subsite_for_domain = array();
self::$cache_accessible_sites = [];
self::$cache_subsite_for_domain = [];
}
/**
* Return all subsites, regardless of permissions (augmented with main site).
*
* @return SS_List List of {@link Subsite} objects (DataList or ArrayList).
* @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")
public static function all_sites($includeMainSite = true, $mainSiteTitle = 'Main site')
{
$subsites = Subsite::get();
@ -274,20 +394,20 @@ class Subsite extends DataObject
*
* @return ArrayList of {@link Subsite} instances.
*/
public static function all_accessible_sites($includeMainSite = true, $mainSiteTitle = "Main site", $member = null)
public static function all_accessible_sites($includeMainSite = true, $mainSiteTitle = 'Main site', $member = null)
{
// Rationalise member arguments
if (!$member) {
$member = Member::currentUser();
$member = Security::getCurrentUser();
}
if (!$member) {
return new ArrayList();
return ArrayList::create();
}
if (!is_object($member)) {
$member = DataObject::get_by_id('Member', $member);
$member = DataObject::get_by_id(Member::class, $member);
}
$subsites = new ArrayList();
$subsites = ArrayList::create();
// Collect subsites for all sections.
$menu = CMSMenu::get_viewable_menu_items();
@ -314,22 +434,27 @@ class Subsite extends DataObject
* 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
* @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)
{
public static function accessible_sites(
$permCode,
$includeMainSite = true,
$mainSiteTitle = 'Main site',
$member = null
) {
// Rationalise member arguments
if (!$member) {
$member = Member::currentUser();
$member = Security::getCurrentUser();
}
if (!$member) {
return new ArrayList();
}
if (!is_object($member)) {
$member = DataObject::get_by_id('Member', $member);
$member = DataObject::get_by_id(Member::class, $member);
}
// Rationalise permCode argument
@ -341,29 +466,54 @@ class Subsite extends DataObject
// Cache handling
$cacheKey = $SQL_codes . '-' . $member->ID . '-' . $includeMainSite . '-' . $mainSiteTitle;
if (isset(self::$_cache_accessible_sites[$cacheKey])) {
return self::$_cache_accessible_sites[$cacheKey];
if (isset(self::$cache_accessible_sites[$cacheKey])) {
return self::$cache_accessible_sites[$cacheKey];
}
$subsites = DataList::create('Subsite')
/** @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')");
->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')
/** @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')");
->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;
@ -381,19 +531,19 @@ class Subsite extends DataObject
if ($includeMainSite) {
if (!is_array($permCode)) {
$permCode = array($permCode);
$permCode = [$permCode];
}
if (self::hasMainSitePermission($member, $permCode)) {
$subsites=$subsites->toArray();
$subsites = $subsites->toArray();
$mainSite = new Subsite();
$mainSite->Title = $mainSiteTitle;
array_unshift($subsites, $mainSite);
$subsites=ArrayList::create($subsites);
$subsites = ArrayList::create($subsites);
}
}
self::$_cache_accessible_sites[$cacheKey] = $subsites;
self::$cache_accessible_sites[$cacheKey] = $subsites;
return $subsites;
}
@ -408,16 +558,17 @@ class Subsite extends DataObject
*/
public static function writeHostMap($file = null)
{
if (!self::$write_hostmap) {
if (!static::config()->get('write_hostmap')) {
return;
}
if (!$file) {
$file = Director::baseFolder().'/subsites/host-map.php';
$subsitesPath = ModuleLoader::getModule('silverstripe/subsites')->getRelativePath();
$file = Director::baseFolder() . $subsitesPath . '/host-map.php';
}
$hostmap = array();
$hostmap = [];
$subsites = DataObject::get('Subsite');
$subsites = DataObject::get(Subsite::class);
if ($subsites) {
foreach ($subsites as $subsite) {
@ -425,8 +576,8 @@ class Subsite extends DataObject
if ($domains) {
foreach ($domains as $domain) {
$domainStr = $domain->Domain;
if (!self::$strict_subdomain_matching) {
$domainStr = preg_replace('/^www\./', '', $domainStr);
if (!static::config()->get('strict_subdomain_matching')) {
$domainStr = preg_replace('/^www\./', '', $domainStr ?? '');
}
$hostmap[$domainStr] = $subsite->domain();
}
@ -441,8 +592,8 @@ class Subsite extends DataObject
$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);
if (is_writable(dirname($file ?? '')) || is_writable($file ?? '')) {
file_put_contents($file ?? '', $data);
}
}
@ -456,25 +607,25 @@ class Subsite extends DataObject
* @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
* @param array $permissionCodes
* @return bool
*/
public static function hasMainSitePermission($member = null, $permissionCodes = array('ADMIN'))
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 = Member::currentUser();
$member = Security::getCurrentUser();
}
if (!$member) {
return false;
}
if (!in_array("ADMIN", $permissionCodes)) {
$permissionCodes[] = "ADMIN";
if (!in_array('ADMIN', $permissionCodes ?? [])) {
$permissionCodes[] = 'ADMIN';
}
$SQLa_perm = Convert::raw2sql($permissionCodes);
@ -483,96 +634,45 @@ class Subsite extends DataObject
// 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();
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();
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);
}
/**
*
* @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
* @param bool $member
* @return bool
*/
public function canEdit($member = false)
{
$extended = $this->extendedCan(__FUNCTION__, $member);
if ($extended !== null) {
return $extended;
}
return true;
}
@ -583,79 +683,82 @@ class Subsite extends DataObject
*/
public function getCMSFields()
{
if ($this->ID != 0) {
$domainTable = GridField::create(
"Domains",
_t('Subsite.DomainsListTitle', "Domains"),
$this->Domains(),
GridFieldConfig_RecordEditor::create(10)
);
} else {
$domainTable = LiteralField::create(
'Domains',
'<p>'._t('Subsite.DOMAINSAVEFIRST', 'You can only add domains after saving for the first time').'</p>'
);
}
$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)
)
]);
}
$languageSelector = new DropdownField(
'Language',
$this->fieldLabel('Language'),
i18n::get_common_locales()
);
// 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 = [];
$pageTypeMap = array();
$pageTypes = SiteTree::page_type_classes();
foreach ($pageTypes as $pageType) {
$pageTypeMap[$pageType] = singleton($pageType)->i18n_singular_name();
}
asort($pageTypeMap);
$fields = FieldList::create(
$subsiteTabs = TabSet::create('Root',
Tab::create(
'Configuration',
_t('Subsite.TabTitleConfig', 'Configuration'),
HeaderField::create($this->getClassName() . ' configuration', 2),
TextField::create('Title', $this->fieldLabel('Title'), $this->Title),
HeaderField::create(
_t('Subsite.DomainsHeadline', "Domains for this subsite")
),
$domainTable,
$languageSelector,
// new TextField('RedirectURL', 'Redirect to URL', $this->RedirectURL),
CheckboxField::create('DefaultSite', $this->fieldLabel('DefaultSite'), $this->DefaultSite),
CheckboxField::create('IsPublic', $this->fieldLabel('IsPublic'), $this->IsPublic),
LiteralField::create(
'PageTypeBlacklistToggle',
sprintf(
'<div class="field"><a href="#" id="PageTypeBlacklistToggle">%s</a></div>',
_t('Subsite.PageTypeBlacklistField', 'Disallow page types?')
)
),
CheckboxSetField::create(
'PageTypeBlacklist',
false,
$pageTypeMap
)
)
),
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.Configuration',
DropdownField::create('Theme', $this->fieldLabel('Theme'), $this->allowedThemes(), $this->Theme)->setEmptyString(_t('Subsite.ThemeFieldEmptyString', '')), 'PageTypeBlacklistToggle');
}
$subsiteTabs->addExtraClass('subsite-model');
$this->extend('updateCMSFields', $fields);
return $fields;
return $pageTypeMap;
}
/**
@ -671,7 +774,7 @@ class Subsite extends DataObject
$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['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');
@ -679,19 +782,6 @@ class Subsite extends DataObject
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
*
@ -699,22 +789,22 @@ class Subsite extends DataObject
*/
public function allowedThemes()
{
if ($themes = $this->stat('allowed_themes')) {
if (($themes = self::$allowed_themes) || ($themes = ThemeResolver::singleton()->getCustomThemeOptions())) {
return ArrayLib::valuekey($themes);
} else {
$themes = array();
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;
}
$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;
}
/**
@ -724,35 +814,53 @@ class Subsite extends DataObject
{
if ($this->getField('Language')) {
return $this->getField('Language');
} else {
return i18n::get_locale();
}
return i18n::get_locale();
}
/**
*
* @return ValidationResult
* @return \SilverStripe\ORM\ValidationResult
*/
public function validate()
{
$result = parent::validate();
if (!$this->Title) {
$result->error(_t('Subsite.ValidateTitle', 'Please add a "Title"'));
$result->addError(_t(__CLASS__ . '.ValidateTitle', 'Please add a "Title"'));
}
return $result;
}
/**
* Whenever a Subsite is written, rewrite the hostmap
* 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.
@ -768,7 +876,7 @@ class Subsite extends DataObject
}
// If there are no objects, default to the current hostname
return $_SERVER['HTTP_HOST'];
return Director::host();
}
/**
@ -809,14 +917,6 @@ class Subsite extends DataObject
return Director::absoluteBaseURL();
}
/**
* @todo getClassName is redundant, already stored as a database field?
*/
public function getClassName()
{
return $this->class;
}
/**
* Javascript admin action to duplicate this subsite
*
@ -826,14 +926,14 @@ class Subsite extends DataObject
{
$newItem = $this->duplicate();
$message = _t(
'Subsite.CopyMessage',
__CLASS__ . '.CopyMessage',
'Created a copy of {title}',
array('title' => Convert::raw2js($this->Title))
['title' => Convert::raw2js($this->Title)]
);
return <<<JS
statusMessage($message, 'good');
$('Form_EditForm').loadURLFromServer('admin/subsites/show/$newItem->ID');
statusMessage($message, 'good');
$('Form_EditForm').loadURLFromServer('admin/subsites/show/$newItem->ID');
JS;
}
@ -850,7 +950,7 @@ JS;
* @param array $permissionCodes
* @return DataList
*/
public function getMembersByPermission($permissionCodes = array('ADMIN'))
public function getMembersByPermission($permissionCodes = ['ADMIN'])
{
if (!is_array($permissionCodes)) {
user_error('Permissions must be passed to Subsite::getMembersByPermission as an array', E_USER_ERROR);
@ -860,23 +960,26 @@ JS;
$SQL_permissionCodes = join("','", $SQL_permissionCodes);
return DataObject::get(
'Member',
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\""
'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)
public function duplicate($doWrite = true, $manyMany = 'many_many')
{
$duplicate = parent::duplicate($doWrite);
$oldSubsiteID = Session::get('SubsiteID');
$oldSubsiteID = SubsiteState::singleton()->getSubsiteId();
self::changeSubsite($this->ID);
/*
@ -885,8 +988,8 @@ JS;
* 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) {
$stack = [[0, 0]];
while (count($stack ?? []) > 0) {
list($sourceParentID, $destParentID) = array_pop($stack);
$children = Versioned::get_by_stage('Page', 'Live', "\"ParentID\" = $sourceParentID", '');
@ -897,11 +1000,11 @@ JS;
$childClone = $child->duplicateToSubsite($duplicate, false);
$childClone->ParentID = $destParentID;
$childClone->writeToStage('Stage');
$childClone->publish('Stage', 'Live');
$childClone->copyVersionToStage('Stage', 'Live');
self::changeSubsite($this->ID); //Change Back to this subsite
array_push($stack, array($child->ID, $childClone->ID));
array_push($stack, [$child->ID, $childClone->ID]);
}
}
}

View File

@ -1,5 +1,15 @@
<?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
@ -10,15 +20,23 @@
*/
class SubsiteDomain extends DataObject
{
private static $table_name = 'SubsiteDomain';
/**
*
* @var string
*/
private static $default_sort = '"IsPrimary" DESC';
/** *
* @var array
*/
private static $db = array(
"Domain" => "Varchar(255)",
"Protocol" => "Enum('http,https,automatic','automatic')",
"IsPrimary" => "Boolean",
);
private static $db = [
'Domain' => 'Varchar(255)',
'Protocol' => "Enum('http,https,automatic','automatic')",
'IsPrimary' => 'Boolean',
];
/**
* Specifies that this subsite is http only
@ -49,28 +67,27 @@ class SubsiteDomain extends DataObject
*
* @var array
*/
private static $has_one = array(
"Subsite" => "Subsite",
);
private static $has_one = [
'Subsite' => Subsite::class,
];
/**
* @config
* @var array
*/
private static $summary_fields = array(
private static $summary_fields = [
'Domain',
'IsPrimary',
);
];
/**
* @config
/*** @config
* @var array
*/
private static $casting = array(
private static $casting = [
'SubstitutedDomain' => 'Varchar',
'FullProtocol' => 'Varchar',
'AbsoluteLink' => 'Varchar',
);
];
/**
* Whenever a Subsite Domain is written, rewrite the hostmap
@ -80,35 +97,36 @@ class SubsiteDomain extends DataObject
public function onAfterWrite()
{
Subsite::writeHostMap();
parent::onAfterWrite();
}
/**
*
* @return \FieldList
*
* @return FieldList
*/
public function getCMSFields()
{
$protocols = array(
self::PROTOCOL_HTTP => _t('SubsiteDomain.PROTOCOL_HTTP', 'http://'),
self::PROTOCOL_HTTPS => _t('SubsiteDomain.PROTOCOL_HTTPS', 'https://'),
self::PROTOCOL_AUTOMATIC => _t('SubsiteDomain.PROTOCOL_AUTOMATIC', 'Automatic')
);
$fields = new FieldList(
$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(
'SubsiteDomain.DOMAIN_DESCRIPTION',
__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(
'SubsiteDomain.PROTOCOL_DESCRIPTION',
__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(
'SubsiteDomain.PROTOCOL_DESCRIPTION',
__CLASS__ . '.ISPRIMARY_DESCRIPTION',
'Mark this as the default domain for this subsite'
))
);
@ -118,16 +136,16 @@ class SubsiteDomain extends DataObject
}
/**
*
*
* @param bool $includerelations
* @return array
*/
public function fieldLabels($includerelations = true)
{
$labels = parent::fieldLabels($includerelations);
$labels['Domain'] = _t('SubsiteDomain.DOMAIN', 'Domain');
$labels['Protocol'] = _t('SubsiteDomain.Protocol', 'Protocol');
$labels['IsPrimary'] = _t('SubsiteDomain.IS_PRIMARY', 'Is Primary Domain?');
$labels['Domain'] = _t(__CLASS__ . '.DOMAIN', 'Domain');
$labels['Protocol'] = _t(__CLASS__ . '.Protocol', 'Protocol');
$labels['IsPrimary'] = _t(__CLASS__ . '.IS_PRIMARY', 'Is Primary Domain?');
return $labels;
}
@ -151,17 +169,11 @@ class SubsiteDomain extends DataObject
{
switch ($this->Protocol) {
case self::PROTOCOL_HTTPS:
{
return 'https://';
}
case self::PROTOCOL_HTTP:
{
return 'http://';
}
default:
{
return Director::protocol();
}
}
}
@ -174,18 +186,18 @@ class SubsiteDomain extends DataObject
*/
public function getSubstitutedDomain()
{
$currentHost = $_SERVER['HTTP_HOST'];
$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);
$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);
$domain = preg_replace('/^\*\./', "subsite.", $domain ?? '');
// *Only* removes "intermediate" subdomains, so 'subdomain.www.domain.com' becomes 'subdomain.domain.com'
$domain = str_replace('.www.', '.', $domain);
$domain = str_replace('.www.', '.', $domain ?? '');
return $domain;
}

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

@ -1,66 +1,82 @@
<?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 SS_ReportWrapper
class SubsiteReportWrapper extends ReportWrapper
{
///////////////////////////////////////////////////////////////////////////////////////////
// Filtering
/**
* @return FieldList
*/
public function parameterFields()
{
$subsites = Subsite::accessible_sites('CMS_ACCESS_CMSMain', true);
$options = $subsites->toDropdownMap('ID', 'Title');
$subsiteField = new TreeMultiselectField(
$subsiteField = TreeMultiselectField::create(
'Subsites',
_t('SubsiteReportWrapper.ReportDropdown', 'Sites'),
_t(__CLASS__ . '.ReportDropdown', 'Sites'),
$options
);
$subsiteField->setValue(array_keys($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) {
if (sizeof($options ?? []) <= 1) {
$subsiteField = $subsiteField->performReadonlyTransformation();
}
$fields = parent::parameterFields();
if ($fields) {
$fields->insertBefore($subsiteField, $fields->First()->Name());
} else {
$fields = new FieldList($subsiteField);
$fields = FieldList::create($subsiteField);
}
return $fields;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Columns
/**
* @return array
*/
public function columns()
{
$columns = parent::columns();
$columns['Subsite.Title'] = "Subsite";
$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
// 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));
Subsite::$force_subsite = join(',', array_keys($options ?? []));
}
}
/**
* @return void
*/
public function afterQuery()
{
// Manually manage the subsite filtering

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

@ -1,8 +1,19 @@
<?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
* 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>
@ -12,16 +23,17 @@
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', $subsiteFromId);
$subsiteFrom = DataObject::get_by_id(Subsite::class, $subsiteFromId);
if (!$subsiteFrom) {
throw new InvalidArgumentException('Subsite not found');
}
@ -30,7 +42,7 @@ class SubsiteCopyPagesTask extends BuildTask
if (!is_numeric($subsiteToId)) {
throw new InvalidArgumentException('Missing "to" parameter');
}
$subsiteTo = DataObject::get_by_id('Subsite', $subsiteToId);
$subsiteTo = DataObject::get_by_id(Subsite::class, $subsiteToId);
if (!$subsiteTo) {
throw new InvalidArgumentException('Subsite not found');
}
@ -43,11 +55,11 @@ class SubsiteCopyPagesTask extends BuildTask
// 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) {
$stack = [[0, 0]];
while (count($stack ?? []) > 0) {
list($sourceParentID, $destParentID) = array_pop($stack);
$children = Versioned::get_by_stage('SiteTree', 'Live', "\"ParentID\" = $sourceParentID", '');
$children = Versioned::get_by_stage(SiteTree::class, 'Live', "\"ParentID\" = $sourceParentID", '');
if ($children) {
foreach ($children as $child) {
@ -59,11 +71,11 @@ class SubsiteCopyPagesTask extends BuildTask
} 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));
$childClone->copyVersionToStage('Stage', 'Live');
array_push($stack, [$child->ID, $childClone->ID]);
$this->log(sprintf('Copied "%s" (#%d, %s)', $child->Title, $child->ID, $child->Link()));
}

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,45 +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="$LogoutURL" class="logout-link font-icon-logout" title="<%t LeftAndMain_Menu_ss.LOGOUT 'Log out' %>"></a>
<% with $CurrentMember %>
<span>
<%t LeftAndMain_Menu_ss.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" $AttributesHTML>
<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">
<button class="sticky-toggle" type="button" title="Sticky nav">Sticky nav</button>
<span class="sticky-status-indicator">auto</span>
<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,31 +0,0 @@
<?php
class BaseSubsiteTest extends SapphireTest
{
public function setUp()
{
parent::setUp();
Subsite::$use_session_subsiteid = true;
Subsite::$force_subsite = null;
}
/**
* Avoid subsites filtering on fixture fetching.
*/
public 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
*/
public function testDisableSubsiteFilter()
{
$this->assertFalse(Subsite::$disable_subsite_filter);
}
}

View File

@ -1,96 +0,0 @@
<?php
class FileSubsitesTest extends BaseSubsiteTest
{
public static $fixture_file = 'subsites/tests/SubsiteTest.yml';
/**
* Disable other file extensions
*
* @var array
*/
protected $illegalExtensions = array(
'File' => array(
'SecureFileExtension',
'VersionedFileExtension'
),
'SiteTree' => array(
'Translatable',
)
);
public 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');
}
public 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);
}
public 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',
'Subsite1 Template',
'Subsite2 Template',
'Template',
'Test 1',
'Test 2',
'Test 3',
'Test Non-SSL',
'Test SSL',
), array_values($source));
}
}

View File

@ -1,28 +0,0 @@
<?php
class GroupSubsitesTest extends BaseSubsiteTest
{
public static $fixture_file = 'subsites/tests/SubsiteTest.yml';
protected $requireDefaultRecordsFrom = array('GroupSubsites');
public function testTrivialFeatures()
{
$this->assertTrue(is_array(singleton('GroupSubsites')->extraStatics()));
$this->assertTrue(is_array(singleton('GroupSubsites')->providePermissions()));
$this->assertTrue(singleton('Group')->getCMSFields() instanceof FieldList);
}
public 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,90 +0,0 @@
<?php
class LeftAndMainSubsitesTest extends FunctionalTest
{
public static $fixture_file = 'subsites/tests/SubsiteTest.yml';
/**
* Avoid subsites filtering on fixture fetching.
*/
public function objFromFixture($class, $id)
{
Subsite::disable_subsite_filter(true);
$obj = parent::objFromFixture($class, $id);
Subsite::disable_subsite_filter(false);
return $obj;
}
public 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.');
}
public 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.");
}
}
public 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,317 +0,0 @@
<?php
class SiteTreeSubsitesTest extends BaseSubsiteTest
{
public static $fixture_file = 'subsites/tests/SubsiteTest.yml';
protected $extraDataObjects = array(
'SiteTreeSubsitesTest_ClassA',
'SiteTreeSubsitesTest_ClassB'
);
protected $illegalExtensions = array(
'SiteTree' => array('Translatable')
);
public 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'
);
}
public 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()));
}
public 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);
}
public 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()}.
*/
public 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);
}
public function testPageTypesBlacklistInClassDropdown()
{
$editor = $this->objFromFixture('Member', 'editor');
Session::set("loggedInAs", $editor->ID);
$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
);
}
public function testCopyToSubsite() {
// Remove baseurl if testing in subdir
Config::inst()->update('Director', 'alternate_base_url', '/');
/** @var Subsite $otherSubsite */
$otherSubsite = $this->objFromFixture('Subsite', 'subsite1');
$staffPage = $this->objFromFixture('Page', 'staff'); // nested page
$contactPage = $this->objFromFixture('Page', '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()
{
$editor = $this->objFromFixture('Member', 'editor');
Session::set("loggedInAs", $editor->ID);
$cmsmain = new CMSMain();
$s1 = $this->objFromFixture('Subsite', 'domaintest1');
$s2 = $this->objFromFixture('Subsite', 'domaintest2');
$s1->PageTypeBlacklist = 'SiteTreeSubsitesTest_ClassA,ErrorPage';
$s1->write();
Subsite::changeSubsite($s1);
$hints = Convert::json2array($cmsmain->SiteTreeHints());
$classes = $hints['Root']['disallowedChildren'];
$this->assertContains('ErrorPage', $classes);
$this->assertContains('SiteTreeSubsitesTest_ClassA', $classes);
$this->assertNotContains('SiteTreeSubsitesTest_ClassB', $classes);
Subsite::changeSubsite($s2);
$hints = Convert::json2array($cmsmain->SiteTreeHints());
$classes = $hints['Root']['disallowedChildren'];
$this->assertNotContains('ErrorPage', $classes);
$this->assertNotContains('SiteTreeSubsitesTest_ClassA', $classes);
$this->assertNotContains('SiteTreeSubsitesTest_ClassB', $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', 'home');
$mainSubsiteID = $this->idFromFixture('Subsite', 'main');
Subsite::changeSubsite($mainSubsiteID);
$mainHome->Content = '<p>Some new content</p>';
$mainHome->write();
$this->assertEquals('home', $mainHome->URLSegment);
$mainHome->doPublish();
$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->doPublish();
$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', '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->doPublish();
$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->doPublish();
$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);
}
function testCopySubsiteWithChildren() {
$page = $this->objFromFixture('Page', 'about');
$newSubsite = $this->objFromFixture('Subsite', 'subsite1');
$moved = $page->duplicateToSubsite($newSubsite->ID, true);
$this->assertEquals($moved->SubsiteID, $newSubsite->ID, 'Ensure returned records are on new subsite');
$this->assertEquals($moved->AllChildren()->count(), $page->AllChildren()->count(), 'All pages are copied across');
}
function testCopySubsiteWithoutChildren() {
$page = $this->objFromFixture('Page', 'about');
$newSubsite = $this->objFromFixture('Subsite', 'subsite2');
$moved = $page->duplicateToSubsite($newSubsite->ID, false);
$this->assertEquals($moved->SubsiteID, $newSubsite->ID, 'Ensure returned records are on new subsite');
$this->assertEquals($moved->AllChildren()->count(), 0, 'All pages are copied across');
}
}
class SiteTreeSubsitesTest_ClassA extends SiteTree implements TestOnly
{
}
class SiteTreeSubsitesTest_ClassB extends SiteTree implements TestOnly
{
}

View File

@ -1,150 +0,0 @@
<?php
class SubsiteAdminFunctionalTest extends FunctionalTest
{
public static $fixture_file = 'subsites/tests/SubsiteTest.yml';
public static $use_draft_site = true;
protected $autoFollowRedirection = false;
/**
* Helper: FunctionalTest is only able to follow redirection once, we want to go all the way.
*/
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()
{
$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
*/
public 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');
}
public 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.
*/
public 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).
*/
public 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,51 +0,0 @@
<?php
class SubsiteAdminTest extends BaseSubsiteTest
{
public static $fixture_file = 'subsites/tests/SubsiteTest.yml';
public function adminLoggedInSession()
{
return new Session(array(
'loggedInAs' => $this->idFromFixture('Member', 'admin')
));
}
/**
* Test generation of the view
*/
public 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.
*/
public 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,220 +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
domaintest4:
Title: 'Test SSL'
domaintest5:
Title: 'Test Non-SSL'
SubsiteDomain:
subsite1:
SubsiteID: =>Subsite.subsite1
Domain: subsite1.*
Protocol: automatic
subsite2:
SubsiteID: =>Subsite.subsite2
Domain: subsite2.*
Protocol: automatic
dt1a:
SubsiteID: =>Subsite.domaintest1
Domain: one.example.org
Protocol: automatic
IsPrimary: 1
dt1b:
SubsiteID: =>Subsite.domaintest1
Domain: one.*
Protocol: automatic
dt2a:
SubsiteID: =>Subsite.domaintest2
Domain: two.mysite.com
Protocol: automatic
IsPrimary: 1
dt2b:
SubsiteID: =>Subsite.domaintest2
Domain: *.mysite.com
Protocol: automatic
dt3:
SubsiteID: =>Subsite.domaintest3
Domain: three.*
Protocol: automatic
IsPrimary: 1
dt4a:
SubsiteID: =>Subsite.domaintest4
Domain: www.primary.com
Protocol: https
dt4b:
SubsiteID: =>Subsite.domaintest4
Domain: www.secondary.com
Protocol: http
dt5:
SubsiteID: =>Subsite.domaintest5
Domain: www.tertiary.com
Protocol: http
IsPrimary: 1
Page:
mainSubsitePage:
Title: MainSubsitePage
SubsiteID: 0
URLSegment: mainsubsitepage
home:
Title: Home
SubsiteID: =>Subsite.main
URLSegment: home
about:
Title: About
SubsiteID: =>Subsite.main
URLSegment: about
linky:
Title: Linky
SubsiteID: =>Subsite.main
URLSegment: linky
staff:
Title: Staff
ParentID: =>Page.about
SubsiteID: =>Subsite.main
URLSegment: staff
contact:
Title: Contact Us
SubsiteID: =>Subsite.main
URLSegment: contact-us
importantpage:
Title: Important Page
SubsiteID: =>Subsite.main
URLSegment: important-page
subsite1_home:
Title: Home (Subsite 1)
SubsiteID: =>Subsite.subsite1
URLSegment: home
subsite1_contactus:
Title: Contact Us (Subsite 1)
SubsiteID: =>Subsite.subsite1
URLSegment: contact-us
subsite1_staff:
Title: Staff
SubsiteID: =>Subsite.subsite1
URLSegment: staff
subsite2_home:
Title: Home (Subsite 2)
SubsiteID: =>Subsite.subsite2
URLSegment: home
subsite2_contactus:
Title: Contact Us (Subsite 2)
SubsiteID: =>Subsite.subsite2
URLSegment: contact-us
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
SiteConfig:
config:
CanCreateTopLevelType: LoggedInUsers

View File

@ -1,44 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: dmooyman
* Date: 27/05/16
* Time: 3:10 PM
*/
class SubsiteXHRControllerTest extends FunctionalTest
{
protected static $fixture_file = 'SubsiteTest.yml';
public function testCanView() {
// Test unauthenticated access
Session::clear('MemberID');
$result = $this->get('SubsiteXHRController', null, array(
'X-Pjax' => 'SubsiteList',
'X-Requested-With' => 'XMLHttpRequest'
));
$this->assertEquals(403, $result->getStatusCode());
// Login with NO permissions
$this->logInWithPermission('NOT_CMS_PERMISSION');
$result = $this->get('SubsiteXHRController', null, array(
'X-Pjax' => 'SubsiteList',
'X-Requested-With' => 'XMLHttpRequest'
));
$this->assertEquals(403, $result->getStatusCode());
// Test cms user
$this->logInWithPermission('CMS_ACCESS_CMSMain');
$result = $this->get('SubsiteXHRController', null, array(
'X-Pjax' => 'SubsiteList',
'X-Requested-With' => 'XMLHttpRequest'
));
$this->assertEquals(200, $result->getStatusCode());
$this->assertEquals('text/json', $result->getHeader('Content-Type'));
$body = $result->getBody();
$this->assertContains('Main site', $body);
$this->assertContains('Test 1', $body);
$this->assertContains('Test 2', $body);
$this->assertContains('Test 3', $body);
}
}

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,72 +0,0 @@
<?php
/**
* Tests {@see WildcardDomainField}
*/
class WildcardDomainFieldTest extends SapphireTest {
/**
* Check that valid domains are accepted
*
* @dataProvider validDomains
*/
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
*/
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
*/
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 array(
array('www.mysite.com'),
array('domain7'),
array('mysite.co.n-z'),
array('subdomain.my-site.com'),
array('subdomain.mysite')
);
}
public function invalidDomains() {
return array(
array('-mysite'),
array('.mysite'),
array('mys..ite'),
array('mysite-'),
array('mysite.'),
array('-mysite.*'),
array('.mysite.*'),
array('mys..ite.*'),
array('*.mysite-'),
array('*.mysite.')
);
}
public function validWildcards() {
return array(
array('*.mysite.com'),
array('mys*ite.com'),
array('*.my-site.*'),
array('*')
);
}
}

View File

View File

@ -1,110 +0,0 @@
<?php
namespace Subsites\Test\Behaviour;
if (!class_exists('SilverStripe\BehatExtension\Context\SilverStripeContext')) {
return;
}
use SilverStripe\BehatExtension\Context\SilverStripeContext;
use SilverStripe\BehatExtension\Context\BasicContext;
use SilverStripe\BehatExtension\Context\LoginContext;
use SilverStripe\BehatExtension\Context\FixtureContext;
use SilverStripe\Framework\Test\Behaviour\CmsFormsContext;
use SilverStripe\Framework\Test\Behaviour\CmsUiContext;
use 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

@ -1,37 +1,43 @@
<?php
namespace SilverStripe\Subsites\Tests;
use SilverStripe\SiteConfig\SiteConfig;
use SilverStripe\Subsites\Extensions\SiteConfigSubsites;
use SilverStripe\Subsites\Model\Subsite;
class SiteConfigSubsitesTest extends BaseSubsiteTest
{
public static $fixture_file = 'subsites/tests/SubsiteTest.yml';
protected static $fixture_file = 'SubsiteTest.yml';
public function testEachSubsiteHasAUniqueSiteConfig()
{
$subsite1 = $this->objFromFixture('Subsite', 'domaintest1');
$subsite2 = $this->objFromFixture('Subsite', 'domaintest2');
$subsite1 = $this->objFromFixture(Subsite::class, 'domaintest1');
$subsite2 = $this->objFromFixture(Subsite::class, 'domaintest2');
$this->assertTrue(is_array(singleton(SiteConfigSubsites::class)->extraStatics()));
$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');
$this->assertEquals('RootSite', SiteConfig::current_site_config()->Title);
Subsite::changeSubsite($subsite1->ID);
$this->assertEquals(SiteConfig::current_site_config()->Title, 'Subsite1');
$this->assertEquals('Subsite1', SiteConfig::current_site_config()->Title);
Subsite::changeSubsite($subsite2->ID);
$this->assertEquals(SiteConfig::current_site_config()->Title, 'Subsite2');
$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'
);
}
}

View File

@ -1,37 +1,46 @@
<?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
{
public static $fixture_file = 'subsites/tests/SubsiteTest.yml';
protected static $fixture_file = 'SubsiteTest.yml';
/**
* Original value of {@see SubSite::$strict_subdomain_matching}
*
* @var bool
*/
protected $origStrictSubdomainMatching = null;
protected $usesTransactions = false;
/**
* Original value of $_REQUEST
*
* @var array
*/
protected $origServer = array();
protected $origServer = [];
public function setUp()
protected function setUp(): void
{
parent::setUp();
Config::inst()->update('Director', 'alternate_base_url', '/');
$this->origStrictSubdomainMatching = Subsite::$strict_subdomain_matching;
Config::modify()
->set(Director::class, 'alternate_base_url', '/')
->set(Subsite::class, 'strict_subdomain_matching', false)
->set(Subsite::class, 'write_hostmap', false);
$this->origServer = $_SERVER;
Subsite::$strict_subdomain_matching = false;
}
public function tearDown()
protected function tearDown(): void
{
$_SERVER = $this->origServer;
Subsite::$strict_subdomain_matching = $this->origStrictSubdomainMatching;
parent::tearDown();
}
@ -41,23 +50,22 @@ class SubsiteTest extends BaseSubsiteTest
*/
public function testSubsiteCreation()
{
Subsite::$write_hostmap = false;
// Create the instance
$template = $this->objFromFixture('Subsite', 'main');
$template = $this->objFromFixture(Subsite::class, 'main');
// Test that changeSubsite is working
Subsite::changeSubsite($template->ID);
$this->assertEquals($template->ID, Subsite::currentSubsiteID());
$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");
$totalPages = $pages->Count();
// 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->publish('Stage', 'Live');
$page->copyVersionToStage('Stage', 'Live');
}
// Create a new site
@ -71,7 +79,9 @@ class SubsiteTest extends BaseSubsiteTest
$siteHome = DataObject::get_one('Page', "\"URLSegment\" = 'home'");
$this->assertNotEquals($siteHome, false, 'Home Page for subsite not found');
$this->assertEquals($subsite->ID, $siteHome->SubsiteID,
$this->assertEquals(
$subsite->ID,
$siteHome->SubsiteID,
'createInstance() copies existing pages retaining the same URLSegment'
);
@ -84,28 +94,28 @@ class SubsiteTest extends BaseSubsiteTest
public function testDomainLookup()
{
// Clear existing fixtures
foreach (DataObject::get('Subsite') as $subsite) {
foreach (DataObject::get(Subsite::class) as $subsite) {
$subsite->delete();
}
foreach (DataObject::get('SubsiteDomain') as $domain) {
foreach (DataObject::get(SubsiteDomain::class) as $domain) {
$domain->delete();
}
// Much more expressive than YML in this case
$subsite1 = $this->createSubsiteWithDomains(array(
$subsite1 = $this->createSubsiteWithDomains([
'one.example.org' => true,
'one.*' => false,
));
$subsite2 = $this->createSubsiteWithDomains(array(
]);
$subsite2 = $this->createSubsiteWithDomains([
'two.mysite.com' => true,
'*.mysite.com' => false,
'subdomain.onmultiplesubsites.com' => false,
));
$subsite3 = $this->createSubsiteWithDomains(array(
]);
$subsite3 = $this->createSubsiteWithDomains([
'three.*' => true, // wildcards in primary domain are not recommended
'subdomain.unique.com' => false,
'*.onmultiplesubsites.com' => false,
));
]);
$this->assertEquals(
$subsite3->ID,
@ -158,35 +168,47 @@ class SubsiteTest extends BaseSubsiteTest
public function testStrictSubdomainMatching()
{
// Clear existing fixtures
foreach (DataObject::get('Subsite') as $subsite) {
foreach (DataObject::get(Subsite::class) as $subsite) {
$subsite->delete();
}
foreach (DataObject::get('SubsiteDomain') as $domain) {
foreach (DataObject::get(SubsiteDomain::class) as $domain) {
$domain->delete();
}
// Much more expressive than YML in this case
$subsite1 = $this->createSubsiteWithDomains(array(
$subsite1 = $this->createSubsiteWithDomains([
'example.org' => true,
'example.com' => false,
'*.wildcard.com' => false,
));
$subsite2 = $this->createSubsiteWithDomains(array(
]);
$subsite2 = $this->createSubsiteWithDomains([
'www.example.org' => true,
'www.wildcard.com' => false,
));
]);
Subsite::$strict_subdomain_matching = 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)'
'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,
@ -199,13 +221,18 @@ class SubsiteTest extends BaseSubsiteTest
'Doesn\'t match www prefix without strict check, even if a wildcard subdomain is in place'
);
Subsite::$strict_subdomain_matching = true;
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'),
@ -230,16 +257,16 @@ class SubsiteTest extends BaseSubsiteTest
protected function createSubsiteWithDomains($domains)
{
$subsite = new Subsite(array(
$subsite = new Subsite([
'Title' => 'My Subsite'
));
]);
$subsite->write();
foreach ($domains as $domainStr => $isPrimary) {
$domain = new SubsiteDomain(array(
$domain = new SubsiteDomain([
'Domain' => $domainStr,
'IsPrimary' => $isPrimary,
'SubsiteID' => $subsite->ID
));
]);
$domain->write();
}
@ -251,93 +278,101 @@ class SubsiteTest extends BaseSubsiteTest
*/
public function testDefaultDomain()
{
$this->assertEquals('one.example.org',
$this->objFromFixture('Subsite', 'domaintest1')->domain());
$this->assertEquals(
'one.example.org',
$this->objFromFixture(Subsite::class, 'domaintest1')->domain()
);
$this->assertEquals('two.mysite.com',
$this->objFromFixture('Subsite', 'domaintest2')->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', 'domaintest3')->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', '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')->PrimaryDomain);
$this->assertEquals('http://'.$_SERVER['HTTP_HOST'].Director::baseURL(), singleton('Subsite')->absoluteBaseURL());
$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() {
// domaintest2 has 'protocol'
$subsite2 = $this->objFromFixture('Subsite', 'domaintest2');
$domain2a = $this->objFromFixture('SubsiteDomain', 'dt2a');
$domain2b = $this->objFromFixture('SubsiteDomain', 'dt2b');
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());
}
// domaintest4 is 'https' (primary only)
$subsite4 = $this->objFromFixture('Subsite', 'domaintest4');
$domain4a = $this->objFromFixture('SubsiteDomain', 'dt4a');
$domain4b = $this->objFromFixture('SubsiteDomain', 'dt4b'); // secondary domain is http only though
// domaintest5 is 'http'
$subsite5 = $this->objFromFixture('Subsite', 'domaintest5');
$domain5a = $this->objFromFixture('SubsiteDomain', 'dt5');
// Check protocol when current protocol is http://
$_SERVER['HTTP_HOST'] = 'www.mysite.com';
$_SERVER['HTTPS'] = '';
$this->assertEquals('http://two.mysite.com/', $subsite2->absoluteBaseURL());
$this->assertEquals('http://two.mysite.com/', $domain2a->absoluteBaseURL());
$this->assertEquals('http://subsite.mysite.com/', $domain2b->absoluteBaseURL());
$this->assertEquals('https://www.primary.com/', $subsite4->absoluteBaseURL());
$this->assertEquals('https://www.primary.com/', $domain4a->absoluteBaseURL());
$this->assertEquals('http://www.secondary.com/', $domain4b->absoluteBaseURL());
$this->assertEquals('http://www.tertiary.com/', $subsite5->absoluteBaseURL());
$this->assertEquals('http://www.tertiary.com/', $domain5a->absoluteBaseURL());
// Check protocol when current protocol is https://
$_SERVER['HTTP_HOST'] = 'www.mysite.com';
$_SERVER['HTTPS'] = 'ON';
$this->assertEquals('https://two.mysite.com/', $subsite2->absoluteBaseURL());
$this->assertEquals('https://two.mysite.com/', $domain2a->absoluteBaseURL());
$this->assertEquals('https://subsite.mysite.com/', $domain2b->absoluteBaseURL());
$this->assertEquals('https://www.primary.com/', $subsite4->absoluteBaseURL());
$this->assertEquals('https://www.primary.com/', $domain4a->absoluteBaseURL());
$this->assertEquals('http://www.secondary.com/', $domain4b->absoluteBaseURL());
$this->assertEquals('http://www.tertiary.com/', $subsite5->absoluteBaseURL());
$this->assertEquals('http://www.tertiary.com/', $domain5a->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->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'),
array('Title' => 'Test Non-SSL'),
array('Title' => 'Test SSL')
), $subsites, 'Lists all subsites');
$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', 'subsite1member');
$member = $this->objFromFixture(Member::class, 'subsite1member');
$subsites = Subsite::all_accessible_sites(true, 'Main site', $member);
$this->assertDOSEquals(array(
array('Title' =>'Subsite1 Template')
), $subsites, 'Lists member-accessible sites.');
$this->assertListEquals([
['Title' => 'Subsite1 Template']
], $subsites, 'Lists member-accessible sites.');
}
/**
@ -345,17 +380,26 @@ class SubsiteTest extends BaseSubsiteTest
*/
public function testAccessibleSites()
{
$member1Sites = Subsite::accessible_sites("CMS_ACCESS_CMSMain", false, null,
$this->objFromFixture('Member', 'subsite1member'));
$member1SiteTitles = $member1Sites->column("Title");
$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', 'admin'));
$adminSiteTitles = $adminSites->column("Title");
$adminSites = Subsite::accessible_sites(
'CMS_ACCESS_CMSMain',
false,
null,
$this->objFromFixture(Member::class, 'admin')
);
$adminSiteTitles = $adminSites->column('Title');
sort($adminSiteTitles);
$this->assertEquals(array(
$this->assertEquals([
'Locale subsite',
'Subsite1 Template',
'Subsite2 Template',
'Template',
@ -363,55 +407,58 @@ class SubsiteTest extends BaseSubsiteTest
'Test 2',
'Test 3',
'Test Non-SSL',
'Test SSL'
), array_values($adminSiteTitles));
'Test SSL',
'Test Vagrant VM on port 8080'
], array_values($adminSiteTitles ?? []));
$member2Sites = Subsite::accessible_sites(
"CMS_ACCESS_CMSMain", false, null,
$this->objFromFixture('Member', 'subsite1member2')
'CMS_ACCESS_CMSMain',
false,
null,
$this->objFromFixture(Member::class, 'subsite1member2')
);
$member2SiteTitles = $member2Sites->column("Title");
$member2SiteTitles = $member2Sites->column('Title');
sort($member2SiteTitles);
$this->assertEquals('Subsite1 Template', $member2SiteTitles[0], 'Member can get to subsite via a group role');
$this->assertEquals('Subsite1 Template', $member2SiteTitles[1], 'Member can get to subsite via a group role');
}
public function testhasMainSitePermission()
{
$admin = $this->objFromFixture('Member', 'admin');
$subsite1member = $this->objFromFixture('Member', 'subsite1member');
$subsite1admin = $this->objFromFixture('Member', 'subsite1admin');
$allsubsitesauthor = $this->objFromFixture('Member', 'allsubsitesauthor');
$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, array("ADMIN")),
Subsite::hasMainSitePermission($admin, ['ADMIN']),
'ADMIN permissions granted for super-admin'
);
$this->assertFalse(
Subsite::hasMainSitePermission($subsite1admin, array("ADMIN")),
Subsite::hasMainSitePermission($subsite1admin, ['ADMIN']),
'ADMIN permissions (on main site) denied for subsite1 admin'
);
$this->assertFalse(
Subsite::hasMainSitePermission($subsite1admin, array("CMS_ACCESS_CMSMain")),
Subsite::hasMainSitePermission($subsite1admin, ['CMS_ACCESS_CMSMain']),
'CMS_ACCESS_CMSMain (on main site) denied for subsite1 admin'
);
$this->assertFalse(
Subsite::hasMainSitePermission($allsubsitesauthor, array("ADMIN")),
Subsite::hasMainSitePermission($allsubsitesauthor, ['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")),
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, array("ADMIN")),
Subsite::hasMainSitePermission($subsite1member, ['ADMIN']),
'ADMIN (on main site) denied for subsite1 subsite1 cms author'
);
$this->assertFalse(
Subsite::hasMainSitePermission($subsite1member, array("CMS_ACCESS_CMSMain")),
Subsite::hasMainSitePermission($subsite1member, ['CMS_ACCESS_CMSMain']),
'CMS_ACCESS_CMSMain (on main site) denied for subsite1 cms author'
);
}
@ -419,12 +466,12 @@ class SubsiteTest extends BaseSubsiteTest
public function testDuplicateSubsite()
{
// get subsite1 & create page
$subsite1 = $this->objFromFixture('Subsite', 'domaintest1');
$subsite1 = $this->objFromFixture(Subsite::class, 'domaintest1');
$subsite1->activate();
$page1 = new Page();
$page1->Title = 'MyAwesomePage';
$page1->write();
$page1->doPublish();
$page1->publishRecursive();
$this->assertEquals($page1->SubsiteID, $subsite1->ID);
// duplicate
@ -434,7 +481,7 @@ class SubsiteTest extends BaseSubsiteTest
$page2 = DataObject::get_one('Page', "\"Title\" = 'MyAwesomePage'");
$page2->Title = 'MyNewAwesomePage';
$page2->write();
$page2->doPublish();
$page2->publishRecursive();
// check change & check change has not affected subiste1
$subsite1->activate();
@ -442,4 +489,15 @@ class SubsiteTest extends BaseSubsiteTest
$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

@ -1,52 +1,63 @@
<?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
{
public static $fixture_file = array(
'subsites/tests/SubsiteTest.yml',
'subsites/tests/SubsitesVirtualPageTest.yml',
);
protected static $fixture_file = [
'SubsiteTest.yml',
'SubsitesVirtualPageTest.yml',
];
protected $illegalExtensions = array(
'SiteTree' => array('Translatable')
);
public function setUp()
protected function setUp(): void
{
parent::setUp();
$this->logInWithPermission('ADMIN');
$fh = fopen(Director::baseFolder() . '/assets/testscript-test-file.pdf', "w");
fwrite($fh, str_repeat('x', 1000000));
fclose($fh);
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();
}
public function tearDown()
protected function tearDown(): void
{
TestAssetStore::reset();
parent::tearDown();
$testFiles = array(
'/assets/testscript-test-file.pdf',
'/assets/renamed-test-file.pdf',
'/assets/renamed-test-file-second-time.pdf',
);
foreach ($testFiles as $file) {
if (file_exists(Director::baseFolder().$file)) {
unlink(Director::baseFolder().$file);
}
}
}
// Attempt to bring main:linky to subsite2:linky
public function testVirtualPageFromAnotherSubsite()
{
Subsite::$write_hostmap = false;
$subsite = $this->objFromFixture('Subsite', 'subsite2');
$subsite = $this->objFromFixture(Subsite::class, 'subsite2');
Subsite::changeSubsite($subsite->ID);
Subsite::$disable_subsite_filter = false;
$linky = $this->objFromFixture('Page', 'linky');
$linky = $this->objFromFixture(Page::class, 'linky');
$svp = new SubsitesVirtualPage();
$svp->CopyContentFromID = $linky->ID;
@ -61,48 +72,43 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest
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', 'page1');
$this->assertTrue($page->doPublish());
$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->doPublish();
$svp->publishSingle();
// Rename the file
$file = $this->objFromFixture('File', 'file1');
$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/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',
$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()
);
foreach ($testFiles as $file) {
if (file_exists(Director::baseFolder().$file)) {
unlink(Director::baseFolder().$file);
}
}
}
public function testSubsiteVirtualPagesArentInappropriatelyPublished()
{
$this->markTestSkipped('Needs some update or refactoring');
// Fixture
$p = new Page();
$p->Content = "test content";
$p->Content = 'test content';
$p->write();
$vp = new SubsitesVirtualPage();
$vp->CopyContentFromID = $p->ID;
@ -112,7 +118,7 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest
$this->assertTrue($vp->IsAddedToStage);
// VP is still orange after we publish
$p->doPublish();
$p->publishSingle();
$this->fixVersionNumberCache($vp);
$this->assertTrue($vp->IsAddedToStage);
@ -123,21 +129,21 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest
$this->assertTrue($vp2->IsAddedToStage);
// Also remains orange after a republish
$p->Content = "new content";
$p->Content = 'new content';
$p->write();
$p->doPublish();
$p->publishSingle();
$this->fixVersionNumberCache($vp2);
$this->assertTrue($vp2->IsAddedToStage);
// VP is now published
$vp->doPublish();
$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->Content = 'third content';
$p->write();
$this->fixVersionNumberCache($vp, $p);
@ -145,7 +151,7 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest
$this->assertTrue($vp->IsModifiedOnStage);
// Publish, VP goes black
$p->doPublish();
$p->publishSingle();
$this->fixVersionNumberCache($vp);
$this->assertTrue($vp->ExistsOnLive);
$this->assertFalse($vp->IsModifiedOnStage);
@ -159,16 +165,18 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest
*/
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->publish('Stage', 'Live');
$p->copyVersionToStage('Stage', 'Live');
$this->assertTrue($p->ExistsOnLive);
// change to subsite
$subsite = $this->objFromFixture('Subsite', 'subsite2');
$subsite = $this->objFromFixture(Subsite::class, 'subsite2');
Subsite::changeSubsite($subsite->ID);
Subsite::$disable_subsite_filter = false;
@ -177,7 +185,7 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest
$svp->CopyContentFromID = $p->ID;
$svp->write();
$svp->writeToStage('Stage');
$svp->publish('Stage', 'Live');
$svp->copyVersionToStage('Stage', 'Live');
$this->assertEquals($svp->SubsiteID, $subsite->ID);
$this->assertTrue($svp->ExistsOnLive);
@ -188,7 +196,7 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest
$p->Title = 'New Title';
// "save & publish"
$p->writeToStage('Stage');
$p->publish('Stage', 'Live');
$p->copyVersionToStage('Stage', 'Live');
$this->assertNotEquals($p->SubsiteID, $subsite->ID);
// reload SVP from database
@ -201,41 +209,43 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest
public function testUnpublishingParentPageUnpublishesSubsiteVirtualPages()
{
Config::inst()->update('StaticPublisher', 'disable_realtime', true);
$this->markTestIncomplete('@todo fix this test');
Config::modify()->set('StaticPublisher', 'disable_realtime', true);
// Go to main site, get parent page
$subsite = $this->objFromFixture('Subsite', 'main');
$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', 'subsite1');
$subsite = $this->objFromFixture(Subsite::class, 'subsite1');
Subsite::changeSubsite($subsite->ID);
$vp1 = new SubsitesVirtualPage();
$vp1->CopyContentFromID = $page->ID;
$vp1->write();
$vp1->doPublish();
$vp1->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
$subsite = $this->objFromFixture('Subsite', 'subsite2');
$subsite = $this->objFromFixture(Subsite::class, 'subsite2');
Subsite::changeSubsite($subsite->ID);
$vp2 = new SubsitesVirtualPage();
$vp2->CopyContentFromID = $page->ID;
$vp2->write();
$vp2->doPublish();
$vp2->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
// Switch back to main site, unpublish source
$subsite = $this->objFromFixture('Subsite', 'main');
$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', 'Live', "\"SiteTree_Live\".\"ID\" = ".$vp1->ID);
$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', 'subsite2');
$subsite = $this->objFromFixture(Subsite::class, 'subsite2');
Subsite::changeSubsite($vp2->SubsiteID);
$onLive = Versioned::get_one_by_stage('SubsitesVirtualPage', 'Live', "\"SiteTree_Live\".\"ID\" = ".$vp2->ID);
$onLive = Versioned::get_one_by_stage(SubsitesVirtualPage::class, 'Live', '"SiteTree_Live"."ID" = ' . $vp2->ID);
$this->assertNull($onLive, 'SVP has been removed from live');
}
@ -245,12 +255,13 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest
*/
public function testSubsiteVirtualPageCanHaveSameUrlsegmentAsOtherSubsite()
{
Subsite::$write_hostmap = false;
$subsite1 = $this->objFromFixture('Subsite', 'subsite1');
$subsite2 = $this->objFromFixture('Subsite', 'subsite2');
$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', 'subsite1_staff');
$subsite1Page = $this->objFromFixture(Page::class, 'subsite1_staff');
$subsite1Page->URLSegment = 'staff';
$subsite1Page->write();
@ -259,6 +270,7 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest
$subsite1Vp->CopyContentFromID = $subsite1Page->ID;
$subsite1Vp->SubsiteID = $subsite1->ID;
$subsite1Vp->write();
$this->assertNotEquals(
$subsite1Vp->URLSegment,
$subsite1Page->URLSegment,
@ -277,13 +289,13 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest
$this->assertEquals(
$subsite2Vp->URLSegment,
$subsite1Page->URLSegment,
"Does allow explicit URLSegment overrides when only existing in a different subsite"
'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();
// 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,
@ -291,12 +303,49 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest
);
}
public function fixVersionNumberCache($page)
protected 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));
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.