Compare commits

...

735 Commits
0.2.6 ... 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
Robbie Averill e4702dc132 Remove obsolete branch-alias 2018-02-22 09:25:11 +13:00
Dylan Wagstaff 299f12765c
Merge pull request #344 from creative-commoners/pulls/2.0/fix-subsite-redirect
FIX Redirect user back to the full current URL after changing subsites
2018-02-09 10:21:24 +13:00
Damian Mooyman 5279748931
Merge pull request #342 from creative-commoners/pulls/2.0/fix-extension-classes
FIX Use correct class names for Settings and Subsites admin menu items
2018-02-08 11:06:58 +13:00
Robbie Averill ab24aa6d9f FIX Redirect user back to the full current URL after changing subsites 2018-02-08 00:23:00 +13:00
Robbie Averill 510d80683a FIX Use correct class names for Settings and Subsites admin menu items 2018-02-07 19:50:36 +13:00
Daniel Hensby 73943afd5b
Merge pull request #338 from creative-commoners/pulls/2.0/fix-validation-error
Pulls/2.0/fix validation error
2018-02-01 17:02:32 +00:00
Raissa North 34c0c4946c FIX Hide subsite operations when no subsites exist 2018-02-01 15:41:54 +13:00
Raissa North 5404dafac1 FIX Fix unit tests for installations in which the base url is set 2018-02-01 14:26:12 +13:00
Raissa North e405dff946 FIX Add an empty string to the subsite operations dropdown.
This prevents a subsite from being chosen as the default option, assuming this is undesired
2018-02-01 14:24:04 +13:00
Raissa North 809b1d9b85 Fix switch statement syntax and replace deprecated doPublish() method. 2018-02-01 13:24:48 +13:00
Raissa North 098660e27d Refactor GridFieldSubsiteDetailForm_ItemRequest and $_cache_accessible_sites
Remove underscores to comply with PSR-2
2018-02-01 13:22:48 +13:00
Raissa North 8b5f593999 Fix line length 2018-02-01 13:19:02 +13:00
Raissa North db69c486a0 FIX: Turn SilverStripe Map into an array to give an accurate falsey value 2018-01-31 17:25:32 +13:00
Raissa North bb5f2c07e1 Merge branch 'master' of https://github.com/silverstripe/silverstripe-subsites into pulls/2.0/fix-validation-error 2018-01-31 11:02:19 +13:00
Raissa North aa370f8df6 FIX Replace deprecated doPublish() method with publishRecursive() 2018-01-31 10:10:09 +13:00
Raissa North 4f15894f86 Add codesniffer ruleset 2018-01-31 10:08:21 +13:00
Daniel Hensby cde41c4409
Merge pull request #337 from creative-commoners/pulls/2.0/fix-subsites-treedropdown
FIX Subsites VirtualPage link to edit main page and TreeDropdownField API implementations
2018-01-30 20:38:48 +00:00
Robbie Averill 34b3fbf04d FIX Subsites VirtualPage link to edit main page and TreeDropdownField API implementations 2018-01-30 16:21:45 +13:00
Robbie Averill 6fb0af0f93 FIX Typo in requirements call to TreeDropdownField javascript resource 2018-01-26 16:09:42 +13:00
Dylan Wagstaff 8eee411d5a
Merge pull request #335 from creative-commoners/pulls/2.0/more-configging
API Convert most of Subsite public statics to config properties
2018-01-23 12:49:22 +13:00
Robbie Averill 17427fd251 Ignore host-map.php in phpcs, and ensure it is disabled in unit tests 2018-01-23 12:09:48 +13:00
Robbie Averill 6bbf988fda API Convert most of Subsite public statics to config properties 2018-01-23 12:09:48 +13:00
Robbie Averill 73f5bcaecc
Merge pull request #336 from mikenz/patch-2
Fix alignment of menu toggle icons
2018-01-23 10:39:10 +13:00
Dylan Wagstaff 17e0db47a1
Merge pull request #334 from creative-commoners/pulls/2.0/fix-file-subsites
API Move subsite dropdown logic for folders into FolderFormFactoryExtension
2018-01-23 10:37:38 +13:00
Mike Cochrane d02b668885
Fix alignment of menu toggle icons 2018-01-22 23:01:59 +13:00
Robbie Averill a88ac5d05d Switch from precise to trusty in Travis 2018-01-18 16:27:00 +13:00
Robbie Averill ace068b93d FIX Move extensions to correct folder location for PSR-4 compatibility 2018-01-18 16:23:11 +13:00
Robbie Averill 4f174ffd6e NEW Add test for FolderFormFactoryExtension with a separate fixture 2018-01-18 16:22:35 +13:00
Robbie Averill a0836bed79 NEW Add PHP 7.2 to Travis build matrix 2018-01-18 16:04:09 +13:00
Robbie Averill 1dd1f2ce6c API Move subsite dropdown logic for folders into FolderFormFactoryExtension 2018-01-18 16:03:58 +13:00
Robbie Averill a201e7e953
Merge pull request #332 from mikenz/css-did-nothing
Remove or update CSS that wasn't doing anything
2018-01-08 10:08:14 +13:00
Mike Cochrane 4e297d920b Remove or update CSS that wasn't doing anything 2018-01-05 04:29:54 +00:00
Damian Mooyman 1cdae5024a
Merge pull request #329 from mikenz/patch-1
Bugfix: Don't assume all forms have a URLSegment field
2017-12-14 14:55:45 +13:00
Mike Cochrane 157db89dbf
Bugfix: Don't assume all forms have a URLSegment field 2017-12-14 14:17:30 +13:00
Robbie Averill 727efb0ea0
Merge pull request #328 from jyrkij/parent-calls
Add parent call to onAfterWrite
2017-12-14 09:44:41 +13:00
Jyrki Lilja 01d09695a9 Add parent call to onAfterWrite 2017-12-13 14:53:09 +02:00
Damian Mooyman 33622c440b
Merge pull request #326 from creative-commoners/pulls/2.0/vendorise
NEW Convert to vendor module
2017-12-06 08:55:05 +13:00
Robbie Averill b529947362 Update constraint for silverstripe/serve dependency 2017-12-04 15:33:29 +13:00
Robbie Averill bcab2af5af FIX Update Scrutinizer configuration to run new build system 2017-12-04 15:29:15 +13:00
Robbie Averill 95a71566f1 NEW Convert to vendor module 2017-12-04 15:26:17 +13:00
Damian Mooyman 0ebf9509bf
Merge pull request #325 from jsirish/refactor/canEditExtend
Subsite - allow canEdit to be extended
2017-11-23 15:38:05 +13:00
Jason Irish 8fcfe55ba8 Subsite - allow canEdit to be extended 2017-11-22 12:22:57 -06:00
Daniel Hensby 7bf430ba83
Merge branch 'pull/322' 2017-10-11 16:17:20 +01:00
Mike Cochrane 1dcba49e5c SubsitesTreeDropdownField: Get the tree in the context of the correct subsite 2017-10-06 11:09:58 +13:00
Daniel Hensby 0157bd0c05 Merge pull request #321 from mikenz/resetable-state
Implement Resettable interface on SubsiteState so it gets reset between unit tests
2017-10-05 17:22:56 +01:00
Robbie Averill 4a1f571f1c Merge pull request #319 from mikenz/lost-namespaces
Add missing use lines, fix some class names, remove unused use lines
2017-10-05 19:06:33 +13:00
Mike Cochrane a2a86c6eb1 Implement Resettable interface on SubsiteState so it gets reset between unit tests 2017-10-05 04:52:53 +00:00
Mike Cochrane 7301099241 Add missing use lines, fix some class names, remove unused use lines 2017-10-05 03:09:47 +00:00
Will Rossiter c2a81f76ae Merge pull request #320 from creative-commoners/pulls/2.0/vendorise-ci
FIX Use vendor file paths for CI references to core
2017-10-05 10:15:52 +13:00
Robbie Averill fa6b80624c FIX Use vendor file paths for CI references to core 2017-10-05 09:57:46 +13:00
Will Rossiter 3996810fa9 Merge pull request #318 from creative-commoners/pulls/2.0/subsite-management-ux-fixes
NEW Remove Subsite Configuration header, rename Main tab and remove Domains GridField title
2017-10-02 20:24:36 +13:00
Robbie Averill 62df0b45a5 NEW Remove Subsite Configuration header, rename Main tab and remove Domains GridField title 2017-10-02 16:34:21 +13:00
Will Rossiter 4328f8aa35 Merge pull request #316 from creative-commoners/pulls/2.0/text-collection-fixes
FIX Add localisation for missing SITECONTENTLEFT and run text collector
2017-09-27 09:00:08 +13:00
Robbie Averill 9822807a58 FIX Add localisation for missing SITECONTENTLEFT and run text collector 2017-09-26 14:49:05 +13:00
Franco Springveldt c343542106 Merge pull request #315 from creative-commoners/pulls/2.0/minor-ux-improvements
FIX Update XHR controller hook selector and refactor getCMSFields to use scaffolding and separate tabs
2017-09-21 11:44:41 +12:00
Robbie Averill 961185a6fd FIX Make subsite configuration CMS fields header label translatable 2017-09-21 10:17:35 +12:00
Robbie Averill 95e437d828 FIX Update XHR controller hook selector and refactor getCMSFields to use scaffolding and separate tabs 2017-09-19 14:51:14 +12:00
Robbie Averill d9fb455d3d Merge pull request #313 from wernerkrauss/fix-309
update test for handling subsite specific themes
2017-09-13 16:47:45 +12:00
Werner M. Krauß e83435b2b6 update test for handling subsite specific themes
fixes #309
2017-09-13 06:13:55 +02:00
Will Rossiter ea00ac3520 Merge pull request #312 from creative-commoners/pulls/2.0/add-readme-badges
DOCS Add Scrutinizer and codecov badges to readme
2017-09-13 13:42:35 +12:00
Will Rossiter ed8efc2144 Merge pull request #311 from creative-commoners/pulls/2.0/fix-dataprovider
FIX Update visibility of test dataprovider and update doc block
2017-09-13 13:28:18 +12:00
Robbie Averill 05953f1758 DOCS Add Scrutinizer and codecov badges to readme 2017-09-13 11:57:12 +12:00
Robbie Averill 02a0895f4a FIX Update visibility of test dataprovider and update doc block 2017-09-13 11:51:32 +12:00
Robbie Averill 0f5c6008e1 FIX PHPCS violation in Subsite 2017-09-13 09:06:47 +12:00
Will Rossiter 20e1c016f1 Merge pull request #310 from wilr/pulls/4.0/fix-blacklist-toggle
FIX Page type blacklist not matching classes correctly (Fixes 297)
2017-09-13 08:28:15 +12:00
Will Rossiter 188b02df6b FIX Page type blacklist not matching classes correctly (Fixes 297)
Tidied up the UI, removed custom javascript in favour of core Toggle field.
2017-09-12 09:50:26 +12:00
prij 32385e580d Excluded colon from domain field validation 2017-09-12 07:20:42 +12:00
Will Rossiter 784c3fca5e Merge pull request #305 from creative-commoners/pulls/2.0/namespace-translations
NEW Namespace translations, implement into class localisation calls
2017-09-11 12:06:07 +12:00
Robbie Averill 1704d1d033 FIX Update class name in upgrader map 2017-09-11 11:50:49 +12:00
Robbie Averill 9bccfffe4e NEW Namespace translations, implement into class localisation calls 2017-09-11 11:50:49 +12:00
Will Rossiter b8ab1eda7e Merge pull request #301 from creative-commoners/pulls/2.0/move-code-to-src
Rename "code" to "src" for PSR-4 consistency
2017-09-11 11:46:09 +12:00
Robbie Averill 94755775ba Rename "code" to "src" for PSR-4 consistency 2017-09-11 11:44:52 +12:00
Will Rossiter 83077ff78f Merge pull request #303 from creative-commoners/pulls/2.0/buildtask-segments
NEW Add segment to SubsiteCopyPageTask
2017-09-11 11:31:57 +12:00
Daniel Hensby 60b259ebb5 Merge pull request #308 from wernerkrauss/feature-alternateabsolutelink-action
alternateAbsoluteLink() respects action parameter
2017-09-08 12:13:33 +01:00
Daniel Hensby 1452e3600b
Travis fixes 2017-09-08 12:06:38 +01:00
Werner M. Krauß 774dee91d6
alternateAbsoluteLink() respects action parameter
fixes #275
2017-09-08 12:06:37 +01:00
Robbie Averill 40b8e102d0 Merge branch '1' 2017-09-08 15:01:26 +12:00
Robbie Averill bbf4b67e23 Merge branch '1.3' into 1 2017-09-08 14:58:21 +12:00
Robbie Averill 827f4f2fe9 Merge branch '1.2' into 1.3 2017-09-08 14:58:01 +12:00
Robbie Averill f0b762cd0d Merge branch '1.1' into 1.2 2017-09-08 14:46:50 +12:00
Robbie Averill c1bf1bcc21 Merge branch '1.0' into 1.1 2017-09-08 14:41:11 +12:00
Robbie Averill 2cbdeba69a BUG Remove Behat tests from Travis matrix for SS3 2017-09-08 14:31:02 +12:00
Robbie Averill 58b8476ede Merge branch '1' 2017-09-08 13:57:10 +12:00
Robbie Averill e84c4f5d7d Merge branch '1.3' into 1 2017-09-08 13:55:16 +12:00
Robbie Averill 4884bdde05 Merge branch '1.2' into 1.3 2017-09-08 13:54:28 +12:00
Robbie Averill 4328c20b45 Merge branch '1.1' into 1.2 2017-09-08 13:54:05 +12:00
Robbie Averill 669995d544 Use precise distro on Travis 2017-09-08 13:52:59 +12:00
Robbie Averill 20b81fc4b0 Merge branch '1.0' into 1.1 2017-09-08 13:52:05 +12:00
Damian Mooyman 9d186d6e7a Merge pull request #307 from creative-commoners/pulls/2.0/upgrade-exclusions
Add upgrade exclusion rules
2017-09-08 13:09:40 +12:00
Damian Mooyman bd26907abf Merge pull request #306 from creative-commoners/pulls/1.0/remove-transifex
SS3 translations are frozen. Please contribute translation fixes via pull requests to lang files.
2017-09-08 13:09:23 +12:00
Daniel Hensby f7bdc570d1 Merge pull request #300 from wernerkrauss/sanitise-error-filename
Sanitise filenames for error pages
2017-09-07 14:23:00 +01:00
Werner M. Krauß 2da5828e90 Sanitise filenames for error pages
fixes #299
2017-09-07 11:19:07 +02:00
Robbie Averill de2220754c Add upgrade exclusion rules 2017-09-07 17:01:49 +12:00
Robbie Averill df4f68be08 SS3 translations are frozen. Please contribute translation fixes via pull requests to lang files. 2017-09-07 16:47:42 +12:00
Damian Mooyman aab69e0baa Merge pull request #304 from creative-commoners/pulls/2.0/namespace-templates
API Move templates to namespaced location, remove unused templates
2017-09-07 16:43:46 +12:00
Robbie Averill 8c4a4e743c API Move templates to namespaced location, remove unused templates
templates/LeftAndMain_Menu.ss looks to be a copy of the fully namespaced version, which is no longer used - removed. GridFieldAddFromTemplateButton also looks to be unused, so was also removed.
2017-09-07 16:20:01 +12:00
Robbie Averill e331c51abb NEW Add segment to SubsiteCopyPageTask 2017-09-07 16:00:40 +12:00
Franco Springveldt a7fee1729e Merge pull request #302 from creative-commoners/pulls/2.0/fix-db-not-ready-delegation
FIX Ensure delegated requests are continued when DB builds fail in middleware
2017-09-07 15:58:53 +12:00
Robbie Averill 56accd0856 FIX Ensure delegated requests are continued when DB builds fail in middleware 2017-09-07 15:54:53 +12:00
Damian Mooyman adfa7257d7 Merge pull request #294 from creative-commoners/pulls/2.0/fix-tests
Fix up test suite, re-enable Behat, update further SS4 API changes
2017-09-06 10:26:05 +12:00
Robbie Averill 19aeb8fd64 API Add getSubsiteIdWasChanged calculated dynamically 2017-09-05 13:48:28 +12:00
Robbie Averill 65f85faff6 FIX Catch database exceptions in middleware when DB is not ready, set session ID after delegation 2017-09-05 12:07:49 +12:00
Robbie Averill 1ac6e78bb3 FIX Remove session coupling, leave it to middleware. Use state instead. 2017-09-04 11:45:21 +12:00
Robbie Averill 793b46ede3 FIX Update i18n::validate_locale for SS4 2017-09-01 12:33:16 +12:00
Robbie Averill 314f218306 Merge branch '1' 2017-09-01 12:30:43 +12:00
Robbie Averill ffe0d61c4f Merge branch '1.3' into 1 2017-09-01 12:29:48 +12:00
Robbie Averill c3471ece1c Merge pull request #288 from gregsmirnov/pulls/1.3/fix-subsite-page-language
Fix page rendering with proper subsite locale
2017-09-01 12:22:34 +12:00
Robbie Averill b9582167c7 Mark SubsitesVirtualPage tests as incomplete, need to be fixed later 2017-08-31 14:17:47 +12:00
Robbie Averill d934fbe08c FIX Update behat tests and add configuration 2017-08-31 10:21:46 +12:00
Robbie Averill 1a9797c185 FIX Remove last use of static session methods, update some namespaces and assertion fixes 2017-08-31 09:44:09 +12:00
Damian Mooyman 46bcffaa86 Merge pull request #293 from creative-commoners/pulls/2.0/add-subsite-state
NEW Add SubsiteState and initialisation middleware
2017-08-30 17:42:41 +12:00
Robbie Averill b0087b9035 FIX Allow persisted subsite IDs to session from state, fix remaining unit tests 2017-08-30 15:29:13 +12:00
Robbie Averill c155855100 FIX Update API changes in ErrorPage and typo in extension config class name 2017-08-30 12:14:11 +12:00
Robbie Averill c620ff02f4 FIX LeftAndMain references to owner class, replace Member::currentUser usage 2017-08-30 12:04:43 +12:00
Robbie Averill 38031887a9 FIX Update alternateTreeTitle to updateTreeTitle 2017-08-30 11:54:42 +12:00
Robbie Averill e129cafa94 NEW Add SubsiteState and initialisation middleware, replace Subsite::currentSubsiteID use 2017-08-30 11:47:11 +12:00
Damian Mooyman 5cf2d87e59 Merge pull request #291 from creative-commoners/pulls/2.0/update-travis-config
Update Travis configuration for PHP 5.6-7.1, add composer dependencies and codecov.io config
2017-08-29 18:18:04 +12:00
Robbie Averill ce360aa383 Comment out Behat Travis runs, needs some love 2017-08-29 17:49:01 +12:00
Robbie Averill c081de1202 FIX Replace static assertions with instance calls 2017-08-29 17:43:29 +12:00
Robbie Averill 7ffaf61aeb FIX Update tests API implementations, add missing fixture namespaces 2017-08-29 17:43:29 +12:00
Robbie Averill 8d8ee14cc2 FIX Run SS standard PHP linter, separate SiteTree test mock classes 2017-08-29 17:43:28 +12:00
Robbie Averill 9fdc1d6607 Update Travis configuration for PHP 5.6-7.1, add composer dependencies and codecov.io config 2017-08-29 17:43:14 +12:00
Damian Mooyman 60725e7b5c Merge pull request #289 from wernerkrauss/fix-ss-4-master
SilverStripe 4 Compatibility
2017-08-29 16:47:29 +12:00
Garion Herman 8be959f747 Migrate excluded report to YAML config. 2017-08-28 22:23:39 +12:00
Garion Herman ddefef11d6 Fix Subsite picker UI, reformat CSS. 2017-08-28 22:10:40 +12:00
Garion Herman af6f499cac Add non-blank default to ThemeFieldEmptyString translation. 2017-08-28 22:05:01 +12:00
Garion Herman 9a289a2e17 Merge branch 'master' into fix-ss-4-master 2017-08-28 21:52:32 +12:00
Franco Springveldt 428a5f958e Update translations 2017-08-25 16:44:47 +12:00
Gregory Smirnov ab81117c5e Fix page rendering with proper subsite locale 2017-08-10 20:14:59 +02:00
Robbie Averill a13bf10184 Merge remote-tracking branch 'origin/1' 2017-08-02 16:39:32 +12:00
Robbie Averill 03e52101bb Revert "SS4 namespaces compatibility"
This reverts commit e8f5f58bb0.
2017-08-02 15:51:37 +12:00
Robbie Averill 9b09eb94e0 Merge remote-tracking branch 'origin/1.3' into 1 2017-08-02 15:10:09 +12:00
Robbie Averill 4d38452435 Update branch alias for 1.4.x-dev 2017-08-02 14:55:15 +12:00
Robbie Averill 9b97f8af89 Use dist:precise for Travis builds to ensure PHP 5.3 works 2017-08-02 14:52:30 +12:00
Robbie Averill a4f647c9ed Merge remote-tracking branch 'origin/1.3' into 1 2017-08-02 14:49:52 +12:00
Robbie Averill 43d128d520 Merge remote-tracking branch 'origin/1.2' into 1.3 2017-08-02 14:48:26 +12:00
Robbie Averill 6eed988317 Merge remote-tracking branch 'origin/1.1' into 1.2 2017-08-02 14:47:53 +12:00
Garion Herman 5ec1c94410 Bring session / validation calls in line with SS4b1 API, replace icon. 2017-07-25 14:25:58 +12:00
Garion Herman 438836bfcc Shift LeftAndMain_Menu template to force override. 2017-07-21 12:30:59 +12:00
Garion Herman 32c45d0856 Merge branch 'fix-ss-4-alpha-7' into fix-ss-4-master 2017-07-21 12:27:57 +12:00
Robbie Averill 8bc9728104 Merge pull request #287 from timkung/hotfix/default-subsite-query
Adjusting query used in getSubsiteIDForDomain
2017-07-19 18:22:16 +12:00
Tim Kung 24ebd1c9f9 adjusting query used in getSubsiteIDForDomain to prevent new DB fields being added to the SQL call if they are not yet added to the DB 2017-07-19 17:57:23 +12:00
Daniel Hensby 83b0afe4d4 Merge pull request #282 from creative-commoners/pulls/1.3/travis-php7
Add PHP7 + SS3.6 build to Travis configuration
2017-06-15 17:00:11 +01:00
Robbie Averill 5be5f1c3b3 Add PHP7 + SS3.6 build to Travis configuration 2017-06-15 11:41:17 +12:00
Werner M. Krauß 2b7c3d1f85 Updating README 2017-06-08 08:35:06 +02:00
Werner M. Krauß 877f4f5f9d Add Subsite theme as main theme; allow cascading of themes 2017-06-07 12:18:35 +02:00
Garion Herman 1975861aec Fix Duplicate to Subsite functionality. 2017-06-04 15:05:59 +12:00
Garion Herman c2484365cf Fix misc. Subsite docblocks, remove DataObject::get_by_id call. 2017-06-04 14:34:38 +12:00
Garion Herman 49fbfcb459 Adapt to Permission::reset, DataObject::duplicate API changes. 2017-06-04 14:33:04 +12:00
Garion Herman 21a8c56217 Adapt SubsiteXHRController to LeftAndMain API changes. 2017-06-04 14:30:51 +12:00
Werner M. Krauß a4a1ab6a78 formatting code 2017-06-01 15:57:53 +02:00
Werner M. Krauß 55c7240425 fixing code analysis: simplify if statements 2017-06-01 15:56:28 +02:00
Werner M. Krauß 9673c881c1 fixing code analysis: simplify if-return statements 2017-06-01 15:48:01 +02:00
Werner M. Krauß fea1684f5c fixing code analysis: use static::assertContains 2017-06-01 15:44:32 +02:00
Werner M. Krauß 9d3c4506af fixing code analysis: fix parameter in method call 2017-06-01 15:43:30 +02:00
Werner M. Krauß 849c0061e8 fixing code analysis: phpunit: use assertInstanceOf 2017-06-01 15:32:46 +02:00
Werner M. Krauß 25754e1158 fixing code analysis: updating phpdoc 2017-06-01 15:32:45 +02:00
Werner M. Krauß 17010f39a3 fixing code analysis: removing unneeded else statements 2017-06-01 15:32:44 +02:00
Werner M. Krauß abe1ac9fe6 fixing code analysis: replacing deprecated methods 2017-06-01 15:32:38 +02:00
Werner M. Krauß 4fc13b19f7 fixing code analysis: removing unnecessary parenthesis 2017-06-01 15:32:36 +02:00
Werner M. Krauß e7ad086641 fixing code analysis: safely use single quotes 2017-06-01 15:32:30 +02:00
Werner M. Krauß 76852594a8 fixing code analysis: public method and case mismatch 2017-06-01 15:31:30 +02:00
Werner M. Krauß e86cc55ba6 fixing a PHP7 only statement to be 5.6 compatible 2017-06-01 14:43:25 +02:00
Werner M. Krauß e03e7d9ce9 fixing a namespaced ORM call 2017-06-01 14:42:56 +02:00
Werner M. Krauß 212e4797b0 marking some tests as skipped that need more refactoring 2017-05-31 06:41:45 +02:00
Werner M. Krauß 6f1e1ab953 SubsiteVirtualPage: SubsiteID should not be a virtual field 2017-05-30 21:43:37 +02:00
Werner M. Krauß 709cbfa2c8 fix SubsiteAdminTest 2017-05-30 21:21:15 +02:00
Werner M. Krauß f348f5fa97 fix SiteConfigSubsites AugmentSQL: get the right table name to filter 2017-05-30 20:50:52 +02:00
Werner M. Krauß 997459caf3 Get some more tests working
Updating config and i18n calls
2017-05-30 20:50:22 +02:00
Werner M. Krauß 492f437589 fixing FileSubsitesTest
call extension directly in test, as Versioned now is also applied to File
and has this method
2017-05-30 15:15:54 +02:00
Werner M. Krauß e2bdd5ca41 fixing tests: adjusting yml file, update Config::modify usage 2017-05-30 15:14:28 +02:00
Tim Kung 5d3af16aaf - namespacing all classes
- moving all phpunit tests into tests/php
- moving all extensions from _config.php into config.yml and removing obsolete _config.php
- moving GridFieldSubsiteDetailForm_ItemRequest into own file

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

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

How to test:

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

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

Conflicts:
	tests/SiteTreeSubsitesTest.php
2015-04-30 22:34:28 +12:00
Ingo Schommer c18a0a266f Adjusted tests to new SiteTree->canCreate() logic in 3.1.11+
Checks SiteConfig permissions by default now
2015-04-30 22:34:28 +12:00
Ingo Schommer 999c115961 Merge pull request #173 from dhensby/master
Adding .editorconfig
2015-04-30 19:51:48 +12:00
Damian Mooyman c389f36eda Merge pull request #184 from unclecheese/patch-2
MINOR: Update documentation to have more clarity around risks/benefits
2015-03-25 11:43:03 +13:00
unclecheese 2d4d35b1a7 MINOR: Update documentation to have more clarity around risks/benefits 2015-03-25 10:07:12 +13:00
Sean Harvey d701afcb61 Merge pull request #183 from tractorcow/pulls/1.0/update-test-permissions
Fix subsites to use correct permissions
2015-03-23 14:58:11 +13:00
Damian Mooyman 2595d655cb Fix subsites to use correct permissions
See http://www.silverstripe.org/software/download/security-releases/ss-2015-008-sitetree-creation-permission-vulnerability
2015-03-23 14:35:52 +13:00
Daniel Hensby 831a918f8d Merge pull request #181 from tractorcow/pulls/3.2-compat
Use 3.2 compatible API
2015-02-24 16:52:56 +00:00
Damian Mooyman 73e0202dec Merge pull request #180 from micmania1/fix-unnecessary-redirect
FIX: Removed unnecessary redirect.
2015-02-24 14:52:11 +13:00
Damian Mooyman 5b9af35566 Use 3.2 compatible API 2015-02-24 09:34:34 +13:00
Damian Mooyman e6c8dff7be Merge remote-tracking branch 'origin/1.0'
Conflicts:
	.travis.yml
	code/extensions/LeftAndMainSubsites.php
	composer.json
2015-02-24 09:12:38 +13:00
micmania1 3ca2861c2d FIX: Removed unnecessary redirect. This is early enough in the script that the correct subsite will be used from hereon. 2015-02-17 01:13:56 +00:00
John Milmine 94edb694b7 redoing bugfix 7b11e979fa removed by cac77703 2015-02-12 08:14:49 +13:00
Damian Mooyman ebebff248f Merge pull request #172 from dnadesign/redirect_fix_between_CMS_sections
redirect_fix_between_CMS_sections
2015-02-09 09:44:43 +13:00
Jason d99785d773 Update SubsiteDomain.php
Add $default_sort.
2014-12-17 10:44:43 +11:00
Daniel Hensby 6d6667aa08 Adding .editorconfig 2014-12-04 15:48:23 +00:00
John Milmine 91591a3752 redirect_fix_between_CMS_sections
previously if you were editing settings and you changed subsites ti would revert you to /admin, now it stays within your current controller
2014-11-24 15:32:05 +13:00
James Cocker 498d6e0619 Fixes #135: LeftAndMain switching between subsites
When trying to switch to a different subsite from a page's editing view, it wouldn't switch. This was partly due to a $record always existing due to the homepage fallback on currentPageID : https://github.com/silverstripe/silverstripe-cms/blob/3.1/code/controllers/CMSMain.php#L816

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

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

Tested changing subsites from /admin/pages, from page edit view, from a page edit URL, and from other CMS sections such as Files and Security, and all seems to be working perfectly now.
2014-11-24 15:25:53 +13:00
Will Rossiter 0520b57f84 Merge pull request #145 from purplespider/patch-2
Fixes #135: LeftAndMain switching between subsites
2014-11-24 15:11:12 +13:00
Damian Mooyman b6f59741d6 Update master-alias to follow stable release versioning 2014-11-19 14:12:34 +13:00
Damian Mooyman bf747f98be Update translations 2014-11-19 12:09:52 +13:00
Damian Mooyman 533c7fae8d Merge pull request #163 from unclecheese/patch-1
Update subsites caveat to align more with other references.
2014-10-06 11:05:45 +13:00
unclecheese 8acc4a230a Update subsites caveat to align more with other references.
e.g. here https://www.cwp.govt.nz/about/how-does-cwp-work/

Rewording of subsites caveat
2014-10-06 10:59:58 +13:00
Sean Harvey 62f47628e3 Merge pull request #158 from tractorcow/pulls/0.5/compat
Fix composer and travis to framework 3.1
2014-08-27 09:23:21 +12:00
Damian Mooyman 71e3b9db2d Fix composer and travis to framework 3.1 2014-08-27 09:14:52 +12:00
Sean Harvey 4203a707bc Fixing travis for 3.1 builds 2014-08-27 09:06:16 +12:00
Sean Harvey ba6cb193b7 Merge pull request #157 from tractorcow/pulls/0.6/branch
BUG Fix incompatibility with framework 3.2
2014-08-26 18:06:40 +12:00
Damian Mooyman a97b0d33eb BUG Fix incompatibility with framework 3.2 2014-08-26 11:42:50 +12:00
Sean Harvey ffe6c34565 Merge pull request #156 from wecodenl/master
Bugfix for urls with %27 in the url
2014-08-23 11:57:43 +12:00
Juul Hobert 2e32eab6ae Bugfix for urls with %28 in the url 2014-08-22 13:21:18 +02:00
Damian Mooyman 25c0341715 Updated translations 2014-08-21 14:48:48 +12:00
Damian Mooyman b19e86e402 Update translations 2014-08-21 14:16:12 +12:00
Sean Harvey f27ba9094b Updating translations 2014-08-20 09:05:37 +12:00
Sean Harvey e5ea8ebc35 Merge pull request #155 from shoaibali/master
Removed hard coding of HTTP protocol
2014-08-19 09:16:48 +12:00
Shoaib Ali 1f2cb4380d Removed hard coding of HTTP protocol 2014-08-18 21:03:52 +12:00
Damian Mooyman a3b2be734f Merge pull request #154 from halkyon/irrelevant_permission_removal
Removing unused permission SUBSITE_ASSETS_CREATE_SUBSITE
2014-08-18 11:50:43 +12:00
Sean Harvey 1477155653 Removing unused permission SUBSITE_ASSETS_CREATE_SUBSITE
This isn't used, according to the description it would limit the list
of subsites you can choose to apply a File/Folder to. However, this
dropdown is shown to the user based on whether they have access to
that subsite, so this unused permission code isn't needed.
2014-08-18 11:31:03 +12:00
Mateusz Uzdowski 07257ddc79 Fix minor styling issue with a list. 2014-08-15 13:29:03 +12:00
Damian Mooyman 71b5842f79 Merge pull request #153 from silverstripe-rebelalliance/plat100
NEW: Adding more user documentation with a FAQ
2014-08-14 17:08:26 +12:00
Kirk Mayo 8fe6c045fa NEW: Adding more user documentation with a FAQ 2014-08-14 15:37:48 +12:00
Sean Harvey ccf125a4d6 Merge pull request #151 from stojg/pull/prevent-xss-attacks
Security: XSS can be injected in the group edit view
2014-08-01 10:51:38 +12:00
Stig Lindqvist bd5bd877fd Security: XSS can be injected in the group edit view 2014-08-01 10:48:44 +12:00
Damian Mooyman f75c501e0d Merge pull request #150 from silverstripe-elliot/docs/setup
PLAT-63 update documentation for disallowed page types
2014-07-23 15:33:44 +12:00
Elliot Sawyer 1ac46b60b0 PLAT-63 update documentation for disallowed page types 2014-07-23 15:29:36 +12:00
Mateusz U 4b54951e9e Merge pull request #149 from silverstripe-elliot/SubDomain-XSS
Sanitise domain name field
2014-07-16 16:18:22 +12:00
Elliot Sawyer 205754854c Sanitise domain name field to prevent XSS attack on the CMS
PWC identified an issue with the subsites module that would allow someone with authenticated access to attack other CMS users, such as "stealing the session ID and hijacking an authenticated user's session".
I can't imagine a case where HTML would ever be allowed in the subdomain of a website, so it's a good practice to strip it out anyway.

Steps to reproduce the original issue:
1. Enter a subsite name and mark as the default site.
2. Add a new domain named <script>alert(2)</script> and mark it as primary
3. Switch to the new subsite.
4. Make a new Page. This will execute a javascript alert containing "2".

MINOR update documentation for onBeforeWrite()
MINOR add @property attributes into docblock
2014-07-16 15:43:05 +12:00
Damian Mooyman 72a457aebb Merge pull request #105 from mateusz/loadfragment-in-use
Use the new loadFragment API.
2014-07-10 11:48:41 +12:00
Mateusz Uzdowski 66d1e68b85 Use the new loadFragment API.
Only to be merged after the
https://github.com/silverstripe/silverstripe-framework/pull/2352 is
available, and only after Subsites 1.0.0 has been released.
2014-07-09 09:29:40 +12:00
Damian Mooyman 028aa11800 Merge pull request #144 from purplespider/patch-1
Fixes #139: Broken URL Segment CMS Links
2014-06-16 08:39:43 +12:00
James Cocker 47df87f62c Fixes #135: LeftAndMain switching between subsites
When trying to switch to a different subsite from a page's editing view, it wouldn't switch. This was partly due to a $record always existing due to the homepage fallback on currentPageID : https://github.com/silverstripe/silverstripe-cms/blob/3.1/code/controllers/CMSMain.php#L816

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

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

Tested changing subsites from /admin/pages, from page edit view, from a page edit URL, and from other CMS sections such as Files and Security, and all seems to be working perfectly now.
2014-06-04 16:41:28 +01:00
James Cocker 38e4bc196d Fixes #139 - Broken URL Segment CMS Links
Fixes issue #139 using normann's solution that seems to work perfectly with both long and short links.
2014-06-04 13:12:28 +01:00
TeamCity 2a6c913cd8 Updated master strings 2014-05-22 23:05:04 +12:00
Damian Mooyman 0f78671293 Updated translation masters 2014-05-20 14:50:24 +12:00
Mateusz U a651ee2bed Merge pull request #136 from mateusz/fix-link-rewrite
BUG Fixes to link rewriting when previewing subsites.
2014-04-02 13:51:12 +13:00
Mateusz Uzdowski 9cf7a1453f BUG Fixes to link rewriting when previewing subsites.
* JS error with href-less links.
* All forms get injected hidden fields, even though the loop attempts to
check for only the ones that submit locally.
* Also check for action-less forms.

Requires
https://github.com/silverstripe/silverstripe-framework/pull/3000 to be
merged for the Framework.
https://github.com/silverstripe-labs/silverstripe-testsession/pull/11
2014-04-02 13:39:01 +13:00
TeamCity 83d52806d7 Updated master strings 2014-02-10 23:07:00 +13:00
Mateusz Uzdowski ae38074202 Add new lang strings, convert to JS. 2014-01-24 14:37:01 +13:00
Mateusz Uzdowski 3f7a760dbf Pull language strings from Transifex. 2014-01-23 10:51:05 +13:00
Mateusz Uzdowski 6d8f852cd4 Update language strings. 2014-01-22 16:41:45 +13:00
Mateusz U ef30571e6f Merge pull request #133 from mateusz/docs-security
Make sure the security implication of subsites is clear in docs.
2014-01-21 15:34:00 -08:00
Mateusz Uzdowski 213356d6bc Make sure the security implication of subsites is clear in docs. 2014-01-22 12:27:53 +13:00
Mateusz U 67a66dbd3d Merge pull request #129 from mandrew/docupdate
Updates to documentation
2014-01-16 14:37:25 -08:00
Michael Andrewartha 7163fbe155 Refactored some of the text to make more sense 2014-01-17 11:14:40 +13:00
Michael Andrewartha 286a570dd0 Updates to documentation, added better intro and duplicating page
content instructions

- Adding documentation on using the ‘Disallow page types’ feature.
- Fix links
- Re-word documentation to clarify important points.
- Add new content from Sig, tidy up existing content.
- MINOR: Formatting update & draw attention to links at the bottom.
2014-01-17 10:10:52 +13:00
Sean Harvey 4e20228c2e Merge pull request #132 from mateusz/session-can-edit
Make canEdit fall back to session if the object's SubsiteID not there.
2014-01-14 14:07:32 -08:00
Mateusz U e5b72df1d4 Merge pull request #130 from madmatt/pulls/permission-fix
Allow ‘ADMIN’ and ‘CMS_ACCESS_LeftAndMain’ CMS access. Fixes CWPBUG-113
2014-01-12 12:59:56 -08:00
Mateusz Uzdowski 82159e38d3 Make canEdit fall back to session if the object's SubsiteID not there.
This problem manifests when a GridField-managed relationship tries to
create an object that references the container from canEdit - the
container in this case has empty fields.

An example of that is a HomePage with CarouselItem - if the
CarouselItem::canEdit tries to call $this->Page()->canEdit(), the "Page"
will be a dummy object, not the actual instance of the HomePage that's
doing the manipulation.

This is similar to the behaviour of SiteTree::canEdit, which solves
this situation by falling back to "return
$this->getSiteConfig()->canEdit($member);"
2014-01-10 09:58:53 +13:00
Matt Peel fb5d791444 BUGFIX: permissions to check the ‘CMS_ACCESS_LeftAndMain’ global permission.
‘CMS_ACCESS_LeftAndMain’ is used by the PermissionCheckboxSetField to allow
applicable Members to access all CMS sections. There are then further
permissions to restrict the Members (e.g. ‘CMS_ACCESS_LeftAndMain’ will give you
access to the ‘Pages’ section, but you still need the ‘Edit any page’ permission
to actually edit anything).

This patch ensures that the subsites module follows those permissions, and
doesn’t unnecessarily deny permission to legitimate users.
2014-01-10 09:31:44 +13:00
Matt Peel 083194857e Allow ‘ADMIN’ and ‘CMS_ACCESS_LeftAndMain’ access to CMS. Fixes CWPBUG-113.
Previously, only the global ‘ADMIN’ permission was allowing users to bypass the
stricter Permission check. We also need to allow the ‘CMS_ACCESS_LeftAndMain’
permission to bypass this check, as otherwise a user who is in a Group with the
‘Access to all CMS sections’ permission set (which only sets the
CMS_ACCESS_LeftAndMain permission code and no others) would be denied access to
the CMS for that sub site.
2014-01-09 17:12:47 +13:00
Mateusz U d21c92a9e3 Merge pull request #125 from nedmas/patch-2
FIX: Ensure that ChangeTrackerOptions doesn't get overriden
2013-12-18 16:51:41 -08:00
Tom Densham 33e50ffe6f FIX: Ensure that ChangeTrackerOptions doesn't get overriden
From @hafriedlander:
Hi. Sorry, I was going to have a look at this on the back of that issue @chillu raised but you beat me to it. There's a couple of edge cases that aren't obvious that come from ChangeTrackerOptions being an object, and might need an Entwine API extension to fix nicely.

Objects in entwine properties are a bit dangerous, because javascript always passes them by reference instead of cloning them. Entwine also doesn't clone them when using them as default values.

The result is that this patch will repeatedly add that selector to the result every time getChangeTrackerOptions is called, so it'll be there once the first time it's called, twice the second, etc.

The right fix at the moment would look like:
```php
$('.cms-edit-form').entwine({
  getChangeTrackerOptions: function() {
    // Figure out if we're still returning the default value
    var isDefault = (this.entwineData('ChangeTrackerOptions') === undefined);
    // Get the current options
    var opts = this._super();

    if (isDefault) {
      // If it is the default then...
      // clone the object (so we don't modify the original),
      var opts = $.extend({}, opts);
      // modify it,
      opts.ignoreFieldSelector +=', input[name=IsSubsite]';
      // then set the clone as the value on this element
      // (so next call to this method gets this same clone)
      this.setChangeTrackerOptions(opts);
    }

    return opts;
});
```
This is super ugly though, non-obvious, and could maybe be handled better in the entwine layer.

See https://github.com/silverstripe/silverstripe-subsites/pull/125
2013-12-16 09:39:42 +00:00
Stig Lindqvist a0f537142f Merge pull request #127 from mateusz/refactor-access
BUG Refactor the access checks and initial subsite redirections.
2013-12-04 12:37:30 -08:00
Mateusz Uzdowski 58b926af25 BUG Refactor the access checks and initial subsite redirections.
Remove the special AJAX handling to simplify the code. Now redirection
will be forced on any request that changes the subsite to re-synchronise
with the frontend.

Introduce canAccess method, and add it to alternateAccessCheck to make
sure this subsite-specific chceck is also done in situations that are
not captured by onBeforeInit.
2013-12-04 17:34:27 +13:00
Stig Lindqvist e6f054f55b Merge pull request #126 from mateusz/session-fix
Do not change the session-stored subsite, if session is not enabled.
2013-11-25 16:35:57 -08:00
Mateusz Uzdowski a771e2239b Do not change the session-stored subsite, if session is not enabled.
This causes issues with Security::findAnAdmistrator which incorrectly
forces the current session-stored subsite to 0 - it uses
Subsite::currentSubsiteID before the session support is enabled, and
hence obtains wrong value.
2013-11-26 13:12:17 +13:00
Mateusz U c04208ed79 Merge pull request #121 from stojg/pr/cleanup
Minor cleanup of subsite code
2013-11-25 16:10:55 -08:00
Damian Mooyman d21881d7b4 Merge pull request #123 from stojg/make-subsite-domain-decoratable
SubsiteDomain don't call decorators updateCMSFields
2013-11-17 11:51:08 -08:00
Ingo Schommer 51e8d98707 Fixed translation namespacing
The TEMPLATE.ss.ENTITY wording stuffs up the YAML
parser in transifex, which made most translations
invisible to SilverStripe since they're indented wrongly.
Also removed empty FR file since Transifex complains about it on upload.
2013-11-14 23:18:01 +01:00
Stig Lindqvist ff7328ea94 Adding docblocks to SubsiteDomain 2013-11-15 09:50:21 +13:00
Stig Lindqvist b7f1c66de7 Make SubsiteDomain#getCMSFields extendable 2013-11-15 09:50:15 +13:00
Stig Lindqvist 859bde1257 Reorder methods and variables to follow the SS coding conventions
The coding conventions is mentioned here http://doc.silverstripe.org/framework/en/trunk/misc/coding-conventions#class-member-ordering
2013-11-11 12:09:27 +13:00
Stig Lindqvist 7bb36eae7b Adding docblocks and visibility keywords to methods 2013-11-11 11:56:02 +13:00
Stig Lindqvist dc7a0560fb Removed Subsite::set_allowed_domains()
Removed documentation and code since the method has been throwing user error since 2010-03-01
2013-11-11 11:56:02 +13:00
Stig Lindqvist cc0349026e Removed unused variable 2013-11-11 11:34:52 +13:00
Stig Lindqvist 6fb36eab9f Merge pull request #120 from mateusz/subsite-model-switch
BUG Prevent session-interface mismatch.
2013-11-07 15:06:16 -08:00
Mateusz Uzdowski aacaee08cd BUG Prevent session-interface mismatch.
Disables transparent subsite switch on AJAX requests.

Makes sure the subsite is appropriately set up when opening up the CMS
with a link to subsited object.
2013-11-08 11:37:10 +13:00
Ingo Schommer c26a405d83 Updated Arabic/Teo Reo/Chinese translations 2013-11-06 12:13:02 +01:00
Ingo Schommer 5ff3b691d7 More globalisation 2013-10-30 13:44:06 +01:00
Ingo Schommer dcae115723 Renamed en_US.yml to en.yml
More consistent with transifex source file mapping
2013-10-30 00:19:21 +01:00
Mateusz U 97c2db6386 Merge pull request #117 from mateusz/translations
Update pl_PL translation from transifex.
2013-10-24 19:43:21 -07:00
Mateusz Uzdowski af5bdaf367 Update pl_PL translation from transifex. 2013-10-25 15:42:12 +13:00
Mateusz U da9aa30859 Merge pull request #116 from mateusz/translations
Add transifex config file. Add missing files and merge translations.
2013-10-24 16:58:33 -07:00
Mateusz Uzdowski 43036854c5 Add transifex config file. Add missing files and merge translations. 2013-10-25 12:45:32 +13:00
Ingo Schommer c0e6d1ad38 Added unit test around "forbidden section" redirection
See https://github.com/silverstripe/silverstripe-subsites/pull/115
2013-10-23 01:50:55 +02:00
Ingo Schommer 8b5a1c92b2 Hide subsites dropdown for collapsed sidebar
Its cut off otherwise, and not really operational.
This is consistent with hiding the "hi <user>" string
as default CMS behaviour.
2013-10-23 01:38:17 +02:00
Ingo Schommer 7c100f90d2 Merge pull request #115 from mateusz/admin-access
Fix CMS Admin access issues
2013-10-23 01:34:30 +02:00
Mateusz Uzdowski d85412adf7 Fix the test coverage for the subsite access changes. 2013-10-18 11:58:11 +13:00
Mateusz Uzdowski 5b00ba352f API Refactor to always redirect to accessible Admin location.
Tries to find an accessible section in the current site, falls back to
searching across all sites and all sections.

Also adds more powerful and generic functionss:
Subsites::all_sites - get the full list
Subsites::all_accessible_sites - get Member accessible list
LeftAndMainExtension::sectionSites - get section-specific list
2013-10-16 16:40:20 +13:00
Mateusz Uzdowski 91cca0c64d BUG Move the SubsiteList PJAX request to a dedicated Controller.
Currently the request cannot be made if one doesn't have access to the
SubsiteAdmin section, which often happens with subsite-specific admins.
2013-10-16 13:20:54 +13:00
Simon Welsh fc07486f9b Merge pull request #108 from adrexia/docs
Update Documentation
2013-09-11 21:15:04 -07:00
Simon Welsh 5c541358c9 Merge pull request #107 from spronkey/106-get-from-all-subsites
Fix for issue #106 get_from_all_subsites to force immediate eval instead of lazy with DataList
2013-09-11 20:38:12 -07:00
spronkey 23e9cd40a0 Better fix for #106 using DataQuery queryParams. Thanks simon_w 2013-09-12 15:33:18 +12:00
Naomi Guyer a76b3c7808 Update Documentation
* Updated graphics
* Updated information around global dropdown
* Added Information about supporting subsites in modeladmins
* Changed 'working.md' to 'working_with.md', for clearer menu naming
2013-09-12 14:42:00 +12:00
spronkey 586d88562c Added test case for subsites virtual page onAfterWrite issue, plus changed get_from_all_subsites method to immediately eval and return an ArrayList, instead of lazy eval DataList. Fixes #106 2013-09-12 14:23:42 +12:00
Mateusz U 110ce7751d Merge pull request #104 from adrexia/subsites-ui
API: Subsite support for menu of cms (hides admins that don't declare support) (fixes #101 and #89 )
2013-08-21 18:16:29 -07:00
Naomi Guyer 37843f447e API: Subsite support for menu of cms (hide admins that don't declare support) (fixes #101 and #89 )
* Hide admins without subsite support from subsites menu
* Add subsite support to default site areas
* Enable reloading of subsites switcher dropdown when navigating the
site, and when editing subsite areas

API Fix parallel pjax menu fetching for subsites.
- thanks Mateusz!

Delint LeftAndMain_Subsites.js
2013-08-22 13:02:46 +12:00
Mateusz Uzdowski 2d41dc62bf Change dependency versions to work for 3.1.0-rc1. 2013-08-13 12:05:30 +12:00
Simon Welsh 9192954596 Use correct jQuery variable 2013-07-11 17:31:59 +12:00
Ingo Schommer b49c86bd0b Require subsite title (fixes #26) 2013-07-10 16:28:08 +02:00
Ingo Schommer 6a9003e8df Less intrusive doSave() overwrite of GridFieldDetailForm
Fixes issues with valiation errors not showing due
to lack of PjaxResponseNegotiator support (parent implementation has changed).
2013-07-10 16:15:04 +02:00
Ingo Schommer 0e9c3344ac Removed disabled tests (#29)
They use all kinds of outdated APIs (in test system and CMS controllers),
and the function rename makes it unclear that they're actually disabled.
These kinds of tests should be performed through Behat anyway.
2013-07-10 15:55:16 +02:00
Ingo Schommer e6832aadca 3.1 allowed_actions fixes for SubsitesTreeDropdownField 2013-07-10 15:31:39 +02:00
Ingo Schommer 99d242f3ae Fixed JS indentation 2013-07-10 12:30:55 +02:00
Frank Mullenger 7bf6e89320 BUGFIX: Session var for active subsite out of sync with current subsite. Refs silverstripe/silverstripe-subsites#93. 2013-07-10 12:24:42 +02:00
Ingo Schommer ac507ddc7b $allowed_actions for GridField subclass
Taken from https://github.com/silverstripe/silverstripe-subsites/pull/94,
thanks @frankmullenger
2013-07-10 11:46:09 +02:00
Sean Harvey b17e49fcd0 Merge pull request #92 from adrexia/icon
Add custom menu-icon
2013-05-28 15:30:02 -07:00
Naomi Guyer c970889516 Add custom menu-icon 2013-05-29 10:15:17 +12:00
Sean Harvey b2ffb7c89e BUG Fixing "Add new" not adding the page to the correct subsite 2013-05-28 12:17:16 +12:00
Sean Harvey 1538e49e1b Merge pull request #90 from mateusz/previews-by-id
Change the preview iframe to use injected SubsiteID GET/POST params.
2013-05-25 18:29:59 -07:00
Mateusz Uzdowski 0fb49ba502 Change the preview iframe to use injected SubsiteID GET/POST params.
Use the CMS domain for fetching the preview content to avoid SSL
errors and single-origin violations on the iframe.
2013-05-26 13:18:52 +12:00
Sean Harvey beb913d8d0 Merge pull request #88 from adrexia/siteconfig
BUG: Subsite save SiteConfig overwrites MainSite SiteConfig (Issue #15)
2013-05-25 16:49:24 -07:00
Naomi Guyer 3ad7ddcfce BUG: Subsite save SiteConfig overwrites MainSite SiteConfig (Issue #15)
Implemented the fix mentioned in the bug ticket within the subsites
settings extension (use current subsite id)
2013-05-24 16:26:57 +12:00
Sean Harvey 7965c86120 Merge pull request #86 from adrexia/patch-1
BUG: Fix filter issue in IE9
2013-05-14 20:05:42 -07:00
Naomi Guyer 1b06db7ef1 BUG: Fix filter issue in IE9
IE9's filter is overriding the dark background on the subsite's dropdown.
2013-05-14 21:27:09 +12:00
Sean Harvey 3c6000111c BUG FileSubsites doesn't respect $disable_subsite_filter 2013-05-09 12:18:48 +12:00
Sean Harvey 42e5156585 Updating docs for 3.x, the location of subsites dropdown changed. 2013-05-08 16:57:08 +12:00
Sean Harvey f35a42ec64 BUG LeftAndMainSubsites::Subsites() fails with limited CMS access
Users with non-ADMIN permissions won't see the dropdown of available
subsites, because LeftAndMainSubsites::Subsites() will check if
the user has a non-existent code CMS_ACCESS_CMSPagesController.

Fallback to checking required_permission_codes first, and failing
that, check for CMS_ACCESS_LeftAndMain
2013-05-08 15:36:18 +12:00
Ingo Schommer 69ace90bc1 Fixed composer constraints 2013-05-07 15:06:36 +03:00
Sean Harvey f0de6f2d87 fixing test to check for all extended cache keys, not just the immediate 2013-05-07 15:11:11 +12:00
Sean Harvey c641808f64 Merge pull request #67 from ARNHOE/patch-1
added nl.yml language
2013-05-06 04:35:19 -07:00
Sean Harvey f39ee67c4a Fixing test for postgres, make sure the array is indexed from 0 2013-05-06 23:25:20 +12:00
Sean Harvey 6e4f22deab Fixing incorrect order of items in array for test 2013-05-06 23:12:59 +12:00
Sean Harvey cf1a1cb6dd Fixing test for PostgreSQL 2013-05-06 23:07:16 +12:00
Sean Harvey a6990394cc Partially reverting 7c5310e1a2 2013-05-06 22:59:18 +12:00
Sean Harvey 7c5310e1a2 Fixing more uses of statics in Subsite class 2013-05-06 22:54:23 +12:00
Sean Harvey 7fee2aeea3 Defining static as private for 3.1+ 2013-05-06 22:44:19 +12:00
Sean Harvey 306cb79b19 Fixing more statics that weren't private, and nested_urls deprecated usage 2013-05-06 22:32:40 +12:00
Sean Harvey 8ffeeb1ee6 Fixing declaration of statics to private for 3.1+ compatibility 2013-05-06 22:21:09 +12:00
Sean Harvey 3275f4768c Removing underscore from "Default site" text 2013-05-06 22:04:42 +12:00
Ingo Schommer 24acc31ba6 Allow core dependencies in dev-master through composer 2013-05-06 09:32:59 +03:00
Sean Harvey 36f9547c8a Merge pull request #79 from adrexia/ui
Style site selector dropdown
2013-05-01 19:50:45 -07:00
Naomi Guyer 1a8122ea89 Style site selector dropdown
Made dropdown receive chzn, and applied a dark style to the dropdown to
match the ui of the cms
2013-05-01 17:04:41 +12:00
Sean Harvey ffaaed072f Removing bits of test that broke because of removal of related pages 2013-05-01 17:01:56 +12:00
Sean Harvey 0247765563 API Removed related pages functionality, this is now a separate module
If you need this functionality still, please checkout
github.com/silverstripe-labs/silverstripe-subsites_relatedpages
2013-05-01 12:35:17 +12:00
Sean Harvey fbddc076b8 Remove underscore from label 2013-04-30 17:53:09 +12:00
Sean Harvey 7e7777ec6a Removed 3.0 as a test target 2013-04-30 16:03:00 +12:00
Sean Harvey 6f9a1bf6fb Testing initial state of disable_subsite_filter 2013-04-30 15:50:45 +12:00
Sean Harvey 411abed7b8 Removing tests related to MetaTitle, this field doesn't exist anymore 2013-04-30 15:42:36 +12:00
Sean Harvey 3848f90a5a Removed use of deprecated Object::get_static() 2013-04-30 15:34:26 +12:00
Sean Harvey c9d5627e9f Updating test to use Config instead of static setting 2013-04-30 15:25:21 +12:00
Sean Harvey 2812f6311c Removing redundant class, relies on TableListField which was removed 2013-04-30 15:25:08 +12:00
Sean Harvey 9658af5cc8 Fixing subsites not working with i18n in SilverStripe 3.1
Tries to access i18n::$likely_subtags directly. Config changes in
3.1 now prevent this from happening and fails.
i18n::get_locale_from_lang() is used instead to provide the locale.
2013-04-30 15:15:11 +12:00
Sean Harvey 397f74a561 Removing underscore from text 2013-04-30 15:08:47 +12:00
Sean Harvey 8a280b1865 BUG Fixing deprecated setEmptyString() 2013-04-30 15:08:13 +12:00
Sean Harvey 5a75b31226 Merge pull request #78 from robert-h-curry/page-description
Add description to Subsites Virtual Page
2013-04-28 17:34:29 -07:00
Robert Curry 5effc64607 Add description to Subsites Virtual Page 2013-04-29 12:25:29 +12:00
Sean Harvey c185843e94 Merge pull request #75 from mlewis-everley/patch-1
Make LeftAndMain redirect direct to admin
2013-04-25 19:10:16 -07:00
Sean Harvey 734d08e2b5 Merge pull request #77 from robert-h-curry/gitignore
Add gitignore
2013-04-25 19:09:38 -07:00
Sean Harvey 8a1775a8db Merge pull request #76 from robert-h-curry/3.1-docs
Add markdown version of docs, upgrade docs to 3.1
2013-04-25 19:09:25 -07:00
Robert Curry 7ae5e40e64 Add gitignore 2013-04-26 14:04:53 +12:00
Robert Curry 21d2d69824 Add markdown version of docs, upgrade to 3.1 2013-04-26 14:03:57 +12:00
Morven Lewis-Everley 5747704c97 Make LeftAndMain redirect direct to admin
Currently, using the Subsites dropdown in the admin interface causes the CMS to reload to admin/pages. This can cause issues if you have set another interface as your default (other than CMSMain).
2013-04-25 19:29:56 +02:00
Sean Harvey 6dcc30071a Updating README and composer.json for 3.1 requirements 2013-04-04 15:17:09 +13:00
Sean Harvey 355bb8ca8f Merge pull request #72 from robert-h-curry/3.1-compatibility
Extension and config fixes for 3.1
2013-04-03 19:14:27 -07:00
Robert Curry 90c16117f3 Extension and config fixes for 3.1 2013-04-04 15:10:22 +13:00
Sean Harvey 01176ec6ed Merge pull request #73 from robert-h-curry/new-sitetree-hack
Remove unnecessary hack. Fixes #69 and #71.
2013-04-03 13:06:06 -07:00
Ingo Schommer d57ce9f1a8 Travis support 2013-03-29 09:50:12 +01:00
Robert Curry af7db1b452 Remove unnecessary hack. Fixes #69 and #71. 2013-03-27 15:27:09 +13:00
Sean Harvey 3c447a3218 Updating LeftAndMain_Menu template override with latest changes 2013-02-05 12:29:46 +13:00
Sean Harvey 0968eac399 BUG Fixing misspelled variable not being able to select subsite for Folder 2013-02-04 15:21:08 +13:00
Sean Harvey bf1390f0f8 BUG Fixing saving of Subsite records from SubsiteAdmin
LeftAndMain assumes tree_class exists, and tries to use it,
but causes a non-object error because of querying with a NULL
class name.
2013-02-04 14:49:51 +13:00
Sean Harvey 413cc0040e Fixing width of subsites dropdown to match default CMS menu width 2013-02-04 14:35:47 +13:00
ARNHOE 538a2fee50 added nl.yml language 2013-01-29 03:04:29 -08:00
Ingo Schommer 82f1d980e8 Merge remote-tracking branch 'origin/0.3'
Conflicts:
	code/SubsiteAdmin.php
	code/extensions/LeftAndMainSubsites.php
	code/extensions/SiteTreeSubsites.php
	code/model/Subsite.php
	composer.json
	javascript/SubsitesTreeDropdownField.js
	lang/en_US.php
	tests/SubsiteAdminTest.php
	tests/SubsiteTest.php
	tests/SubsiteTest.yml
	tests/SubsitesVirtualPageTest.php
2013-01-03 21:03:26 +01:00
Ingo Schommer ff32702cb9 Added composer.json 2013-01-03 14:55:25 +01:00
Ingo Schommer dfeb52de87 Fix test failures caused by subsite filtering on fixture retrieval 2013-01-03 14:51:04 +01:00
Ingo Schommer 601e8d6c68 Fixed fixture class references 2013-01-03 14:15:41 +01:00
Ingo Schommer 172752a9f4 NEW Access to non-public subsites for logged-in users
Also added caching subsite domain mapping
2013-01-03 14:10:32 +01:00
Ingo Schommer 18bcda48d3 BUG Allow usage of SubsiteTreeDropdownField when SubsiteID is set in PHP, not through CopyContentFromID dropdown 2013-01-03 14:10:30 +01:00
Ingo Schommer 25f83daf0e NEW Copy page to different subsite, select MasterPageID
This is also the only UI-facing way to set a master page
after the initial copy action when creating a new subsite.
Shows "edit" link when master page is already set.
2013-01-03 14:10:20 +01:00
Ingo Schommer c952db1cb1 SubsiteCopyPagesTask 2013-01-03 14:10:14 +01:00
Ingo Schommer f909aad7b4 API CHANGE Removed Subsite_Template subclass, as it unnecessarily duplicates the Subsite->duplicate() logic in Subsite_Template->createInstance(). It also arbitrarily limits duplication of subsites to templates. 2013-01-03 14:10:08 +01:00
169 changed files with 9760 additions and 3859 deletions

23
.editorconfig Normal file
View File

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

7
.gitattributes vendored Normal file
View File

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

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

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

View File

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

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

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

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

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

2
.gitignore vendored Normal file
View File

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

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

51
CHANGELOG.md Normal file
View File

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

24
LICENSE
View File

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

274
README.md
View File

@ -1,84 +1,95 @@
# Subsites Module
[![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
The subsites module allows multiple websites to run from a single installation of SilverStripe, and share users, content, and assets between them. 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 from the headquarters flow down into the branches. The branches can have separate users/admins, and information that is individual. The website templates can also be different.
The subsites module provides a convenient way of running multiple websites from a single installation of SilverStripe,
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
from the headquarters flow down into the branches. The branches can hold information that is individual and the website
templates can also be different.
<div class="warning" markdown='1'>
All separation of content achieved with this module should be viewed as cosmetic and not appropriate for
security-critical applications. The module gives some control over access rights especially in the "Pages" and "Files"
area of the CMS, but other sections' separation is much weaker: for example giving someone any of the "Roles and access
permissions" will imply that the person will likely be able to escalate his/her privileges to the global admin role.
</div>
For user documentation please see:
1. [Setting up subsites](docs/en/userguide/set_up.md)
1. [Working with subsites](docs/en/userguide/working_with.md)
## Features & limitations
### Features:
* Each subsite appears as a standalone website from a users prospective
* No need to duplicate existing code as all subsites use the same codebase as the main site
* You can set individual permissions on each subsite domain name
* Ability to copy a page and its content from the main site into a subsite
* Create translations of subsite pages
* Schedule the publishing of subsite pages
* The database is shared between subsites (meaning duplicating content is easy)
* When recovering from a disaster it's much easier to bring up a new copy of a single environment with 100 subsites than it is to bring up 100 environments.
### 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.
* 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 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.0
* Silverstripe 4.x
## Installation
* Create necessary tables by visiting `http://<yoursite>/dev/build` (you should see a `Subsite` table created, among other things). You don't need to run this command for every subsite.
* Login to the CMS as an administrator. You should now see a "Subsites" entry on the main menu.
* Create a new subsite, giving it a name and a subdomain. The subdomain 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`
* Once you have created and saved your new subsite, go back to the "Site Content" section. In the top-right, there should be 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 updated. It should be empty at this stage.
* Login to the CMS as an administrator. You should now see a "Subsites" entry on the main menu, access that section now.
* Hit the "Add Subsite" button to create a new subsite.
* 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
### Limit allowed domains
You can list available domains for your subsites (Example: subdomain.''domain''.tld). The subsites are generally identified only by their subdomain part (Example: ''subdomain''.domain.tld).
This example would let you create subsites such as ''wellington.mycompany.com'' or ''london.mycompany.org''
*mysite/_config.php*
:::php
Subsite::set_allowed_domains(array(
'mycompany.com',
'mycompany.org'
));
If you would like to be able to choose any domain for your subsite, rather than subdomains off a common base, then list top-level domains in your `set_allowed_domains()` list.
In this example, your subsite name (e.g. ''silverstripe''), will be appended to a much shorter base domain (e.g. ''co.nz'', or ''org''). This would let you create subsites with domains such as ''silverstripe.org'' or ''example.co.nz''
*mysite/_config.php*
:::php
Subsite::set_allowed_domains(array(
'com',
'org',
'co.nz',
'org.nz',
));
You can mix the two together, if you want to have some subsites hosted off subdomains of your mail site, and some subsites hosted off their own domains. In this example, you could set up subsites at ''wellington.example.com'', ''othersite.co.nz'', or ''thirdsite.org''.
*mysite/_config.php*
:::php
Subsite::set_allowed_domains(array(
'example.com',
'com',
'org',
'co.nz',
'org.nz',
));
Note that every site also has a ''www.''-prefixed version of the domain available. For example, if your subsite is accessible from ''wellington.example.org'' then it will also be accessible from '''www.wellington.example.org''.
### Strict Subdomain Matching ###
The module tries to provide sensible defaults, in which it regards `example.com` and `www.example.com`
as the same domains. In case you want to distinguish between these variations,
set `Subsite::$strict_subdomain_matching` to TRUE. This won't affect wildcard/asterisk checks,
but removes the ambiguity about default subdomains.
The module tries to provide sensible defaults, in which it regards `example.com` and `www.example.com` as the same domains. In case you want to distinguish between these variations, set `Subsite::$strict_subdomain_matching` to TRUE. This won't affect wildcard/asterisk checks, but removes the ambiguity about default subdomains.
### Permissions ###
Groups can be associated with one or more subsites, in which case the granted permissions
only apply to this subsite. Even the `ADMIN` permission only grants super-user rights on certain
subsites by default. If you want to create a super-user regardless of subsites association,
please use the `Group.AccessAllSubsites` property ("Give this group access to all subsites"),
together with the `ADMIN` permission.
Groups can be associated with one or more subsites, in which case the granted page- and asset-related permissions
only apply to this subsite.
Note that creating a Subsite-specific group, and giving it permissions unrelated to content editing and asset management
will result in members of this group being able to escalate their privileges. An example here is giving that group
"Full administrative rights" or some of the "Roles and access permissions", in which case it is possible for the member
of that group to simply add himself to the global "Administrators" group or change his own group to having access to all
sites.
The subsites module should be viewed as providing a convenience of visual separation for the sites on the interface
level, rather than a fully tight security model for managing many sites on the same CMS (it is still the same CMS).
### Access created domains
@ -90,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
@ -101,7 +160,94 @@ Not all themes might be suitable or adapted for all subsites. You can optionally
:::php
Subsite::set_allowed_themes(array('blackcandy','mytheme'));
### Enable Subsite support on DataObjects
To make your DataObject subsite aware, include a SubsiteID on your DataObject. eg:
*MyDataObject.php*
:::php
private static $has_one = array(
'Subsite' => 'Subsite'
);
Include the current SubsiteID as a hidden field on getCMSFields, or updateCMSFields. eg:
*MyDataObject.php*
:::php
public function getCMSFields() {
$fields = parent::getCMSFields();
if(class_exists(Subsite::class)){
$fields->push(new HiddenField('SubsiteID','SubsiteID', SubsiteState::singleton()->getSubsiteId()));
}
return $fields;
}
To limit your admin gridfields to the current Subsite records, you can do something like this:
*MyAdmin.php*
:::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::class)){
$list = $gridField->getList()->filter(['SubsiteID'=>SubsiteState::singleton()->getSubsiteId()]);
$gridField->setList($list);
}
return $form;
}
### Enable menu support for custom areas in subsites
Custom admin areas, by default, will not show in the menu of a subsite. Not all admins are adapted for or appropriate to show within a subsite. If your admin does have subsite support, or is intentionally global, you can enable the show in menu option either by applying:
*mysite/_config.php*
:::php
MyAdmin::add_extension('SubsiteMenuExtension');
or by defining the subsiteCMSShowInMenu function in your admin:
*MyAdmin.php*
:::php
public function subsiteCMSShowInMenu(){
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
By default, each subsite is available to the public (= not logged-in),
provided a correct host mapping is set up. A subsite can be marked as non-public
in its settings, in which case it only shows if a user with CMS permissions is logged in.
This is useful to create and check subsites on a live system before publishing them.
Please note that you need to filter for this manually in your own queries:
$publicSubsites = DataObject::get(
'Subsite',
Subsite::$check_is_public ? '"IsPublic"=1' : '';
);
To ensure the logged-in status of a member is carried across to subdomains,
you also need to configure PHP session cookies to be set
for all subdomains:
// Example matching subsite1.example.org and www.example.org
Session::set_cookie_domain('.example.org');
## Screenshots
![](docs/en/_images/subsites-module-adminscreenshot-new.png)
![](docs/en/_images/subsites-module-admindropdown.png)
![](docs/en/_images/subsites-module-adminscreenshot.png)

View File

@ -1,21 +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.
*/
Object::add_extension('SiteTree', 'SiteTreeSubsites');
// Hack - this ensures that the SiteTree defineMethods gets called before any of its subclasses...
new SiteTree();
Object::add_extension('ContentController', 'ControllerSubsites');
Object::add_extension('LeftAndMain', 'LeftAndMainSubsites');
Object::add_extension('LeftAndMain', 'ControllerSubsites');
Object::add_extension('Group', 'GroupSubsites');
Object::add_extension('File', 'FileSubsites');
Object::add_extension('ErrorPage', 'ErrorPageSubsite');
Object::add_extension('SiteConfig', 'SiteConfigSubsites');
SS_Report::add_excluded_reports('SubsiteReportWrapper');

11
_config/config.yml Normal file
View File

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

75
_config/extensions.yml Normal file
View File

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

9
_config/legacy.yml Normal file
View File

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

12
_config/middleware.yml Normal file
View File

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

30
behat.yml Normal file
View File

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

View File

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

View File

@ -0,0 +1,141 @@
/*jslint browser: true, nomen: true*/
/*global $, window, jQuery*/
(function($) {
'use strict';
$.entwine('ss', function($) {
$('#SubsitesSelect').entwine({
onadd:function(){
this.on('change', function(){
window.location.search=$.query.set('SubsiteID', $(this).val());
});
}
});
/*
* Reload subsites dropdown when links are processed
*/
$('.cms-container .cms-menu-list li a').entwine({
onclick: function(e) {
$('.cms-container').loadFragment('admin/subsite_xhr', 'SubsiteList');
this._super(e);
}
});
/*
* 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('admin/subsite_xhr', 'SubsiteList');
this._super(e);
}
});
/*
* Reload subsites dropdown when subsites are added or names are modified
*/
$('.cms-container .tab.subsite-model').entwine({
onadd: function(e) {
$('.cms-container').loadFragment('admin/subsite_xhr', 'SubsiteList');
this._super(e);
}
});
// Subsite tab of Group editor
$('#Form_ItemEditForm_AccessAllSubsites').entwine({
/**
* Constructor: onmatch
*/
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
*/
getChangeTrackerOptions: function() {
// Figure out if we're still returning the default value
var isDefault = (this.entwineData('ChangeTrackerOptions') === undefined);
// Get the current options
var opts = this._super();
if (isDefault) {
// If it is the default then...
// clone the object (so we don't modify the original),
var opts = $.extend({}, opts);
// modify it,
opts.ignoreFieldSelector +=', input[name=IsSubsite]';
// then set the clone as the value on this element
// (so next call to this method gets this same clone)
this.setChangeTrackerOptions(opts);
}
return opts;
}
});
$('.cms-edit-form input[name=action_copytosubsite]').entwine({
onclick: function(e) {
var form = this.closest('form');
form.trigger('submit', [this]);
}
});
});
$.entwine('ss.preview', function($){
$('.cms-preview').entwine({
/**
* 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::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.
*/
onafterIframeAdjustedForPreview: function(event, doc) {
var subsiteId = $(doc).find('meta[name=x-subsite-id]').attr('content');
if (!subsiteId) {
return;
}
// Inject the SubsiteID into internal links.
$(doc).find('a').each(function() {
var href = $(this).attr('href');
if (typeof href!=='undefined' && !href.match(/^http:\/\//)) {
$(this).attr('href', $.path.addSearchParams(href, {
'SubsiteID': subsiteId
}));
}
});
// Inject the SubsiteID as a hidden input into all forms submitting to the local site.
$(doc).find('form').each(function() {
var action = $(this).attr('action');
if (typeof action!=='undefined' && !action.match(/^http:\/\//)) {
$(this).append('<input type=hidden name="SubsiteID" value="' + subsiteId + '" >');
}
});
}
});
});
}(jQuery));

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

View File

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

View File

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

View File

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

View File

@ -1,40 +0,0 @@
<?php
class ErrorPageSubsite extends DataExtension {
/**
* Alter file path to generated a static (static) error page file to handle error page template on different sub-sites
*
* @see Error::get_filepath_for_errorcode()
*
* FIXME since {@link Subsite::currentSubsite()} partly relies on Session, viewing other sub-site (including main site) between
* opening ErrorPage in the CMS and publish ErrorPage causes static error page to get generated incorrectly.
*/
function alternateFilepathForErrorcode($statusCode, $locale = null) {
$static_filepath = Object::get_static($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,115 +0,0 @@
<?php
/**
* Extension for the File object to add subsites support
*
* @package subsites
*/
class FileSubsites extends DataExtension {
// If this is set to true, all folders created will be default be
// considered 'global', unless set otherwise
static $default_root_folders_global = false;
public static $has_one=array(
'Subsite' => 'Subsite',
);
/**
* Amends the CMS tree title for folders in the Files & Images section.
* Prefixes a '* ' to the folders that are accessible from all subsites.
*/
function alternateTreeTitle() {
if($this->owner->SubsiteID == 0) return " * " . $this->owner->Title;
else return $this->owner->Title;
}
/**
* Add subsites-specific fields to the folder editor.
*/
function updateCMSFields(FieldList $fields) {
if($this->owner instanceof Folder) {
$sites = Subsite::accessible_sites('CMS_ACCESS_AssetAdmin');
$dropdownValues = array();
$dropdownValues[0] = 'All sites';
foreach ($sites as $site) {
$dropDownValues[$site->ID] = $site->Title;
}
ksort($dropdownValues);
if($sites)$fields->push(new DropdownField("SubsiteID", "Subsite", $dropdownValues));
}
}
/**
* Update any requests to limit the results to the current site
*/
function augmentSQL(SQLQuery &$query) {
// If you're querying by ID, ignore the sub-site - this is a bit ugly... (but it was WAYYYYYYYYY worse)
//@TODO I don't think excluding if SiteTree_ImageTracking is a good idea however because of the SS 3.0 api and ManyManyList::removeAll() changing the from table after this function is called there isn't much of a choice
$from = $query->getFrom();
$where = $query->getWhere();
if(!isset($from['SiteTree_ImageTracking']) && !($where && preg_match('/\.(\'|"|`|)ID(\'|"|`|)/', $where[0]))) {
$subsiteID = (int) Subsite::currentSubsiteID();
// The foreach is an ugly way of getting the first key :-)
foreach($query->getFrom() as $tableName => $info) {
$where = "\"$tableName\".\"SubsiteID\" IN (0, $subsiteID)";
$query->addWhere($where);
break;
}
$sect=array_values($query->getSelect());
$isCounting = strpos($sect[0], 'COUNT') !== false;
// Ordering when deleting or counting doesn't apply
if(!$query->getDelete() && !$isCounting) {
$query->addOrderBy("\"SubsiteID\"");
}
}
}
function onBeforeWrite() {
if (!$this->owner->ID && !$this->owner->SubsiteID) {
if (self::$default_root_folders_global) {
$this->owner->SubsiteID = 0;
} else {
$this->owner->SubsiteID = Subsite::currentSubsiteID();
}
}
}
function onAfterUpload() {
// If we have a parent, use it's subsite as our subsite
if ($this->owner->Parent()) {
$this->owner->SubsiteID = $this->owner->Parent()->SubsiteID;
} else {
$this->owner->SubsiteID = Subsite::currentSubsiteID();
}
$this->owner->write();
}
function canEdit($member = null) {
// Check the CMS_ACCESS_SecurityAdmin privileges on the subsite that owns this group
$subsiteID = Session::get('SubsiteID');
if($subsiteID&&$subsiteID == $this->owner->SubsiteID) {
return true;
} else {
Session::set('SubsiteID', $this->owner->SubsiteID);
$access = Permission::check('CMS_ACCESS_AssetAdmin');
Session::set('SubsiteID', $subsiteID);
return $access;
}
}
/**
* Return a piece of text to keep DataObject cache keys appropriately specific
*/
function cacheKeyComponent() {
return 'subsite-'.Subsite::currentSubsiteID();
}
}

View File

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

View File

@ -1,174 +0,0 @@
<?php
/**
* Decorator designed to add subsites support to LeftAndMain
*
* @package subsites
*/
class LeftAndMainSubsites extends Extension {
function init() {
Requirements::css('subsites/css/LeftAndMain_Subsites.css');
Requirements::javascript('subsites/javascript/LeftAndMain_Subsites.js');
Requirements::javascript('subsites/javascript/VirtualPage_Subsites.js');
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
$this->owner->redirect('admin/pages');
}
}
/**
* Set the title of the CMS tree
*/
function getCMSTreeTitle() {
$subsite = Subsite::currentSubSite();
return $subsite ? Convert::raw2xml($subsite->Title) : _t('LeftAndMain.SITECONTENTLEFT');
}
function updatePageOptions(&$fields) {
$fields->push(new HiddenField('SubsiteID', 'SubsiteID', Subsite::currentSubsiteID()));
}
public function Subsites() {
$accessPerm = 'CMS_ACCESS_'. $this->owner->class;
switch($this->owner->class) {
case "AssetAdmin":
$subsites = Subsite::accessible_sites($accessPerm, true, "Shared files & images");
break;
case "SecurityAdmin":
$subsites = Subsite::accessible_sites($accessPerm, true, "Groups accessing all sites");
if($subsites->find('ID',0)) {
$subsites->push(new ArrayData(array('Title' => 'All groups', 'ID' => -1)));
}
break;
case "CMSMain":
// If there's a default site then main site has no meaning
$showMainSite = !DataObject::get_one('Subsite',"\"DefaultSite\"=1 AND \"IsPublic\"=1");
$subsites = Subsite::accessible_sites($accessPerm, $showMainSite);
break;
case "SubsiteAdmin":
$subsites = Subsite::accessible_sites('ADMIN', true);
break;
default:
$subsites = Subsite::accessible_sites($accessPerm);
break;
}
return $subsites;
}
public function SubsiteList() {
$list = $this->Subsites();
$currentSubsiteID = Subsite::currentSubsiteID();
if($list->Count() > 1) {
$output = '<select id="SubsitesSelect">';
foreach($list as $subsite) {
$selected = $subsite->ID == $currentSubsiteID ? ' selected="selected"' : '';
$output .= "\n<option value=\"{$subsite->ID}\"$selected>". Convert::raw2xml($subsite->Title) . "</option>";
}
$output .= '</select>';
Requirements::javascript('subsites/javascript/LeftAndMain_Subsites.js');
return $output;
} else if($list->Count() == 1) {
if($list->First()->DefaultSite==false) {
$output = '<select id="SubsitesSelect">';
$output .= "\n<option value=\"0\">". _t('LeftAndMainSubsites.DEFAULT_SITE', '_Default Site') . "</option>";
foreach($list as $subsite) {
$selected = $subsite->ID == $currentSubsiteID ? ' selected="selected"' : '';
$output .= "\n<option value=\"{$subsite->ID}\"$selected>". Convert::raw2xml($subsite->Title) . "</option>";
}
$output .= '</select>';
Requirements::javascript('subsites/javascript/LeftAndMain_Subsites.js');
return $output;
}else {
return '<span>'.$list->First()->Title.'</span>';
}
}
}
public function CanAddSubsites() {
return Permission::check("ADMIN", "any", null, "all");
}
/**
* Alternative security checker for LeftAndMain.
* If security isn't found, then it will switch to a subsite where we do have access.
*/
public function alternateAccessCheck() {
$className = $this->owner->class;
// Switch to the subsite of the current page
if ($this->owner->class == 'CMSMain' && $currentPage = $this->owner->currentPage()) {
if (Subsite::currentSubsiteID() != $currentPage->SubsiteID) {
Subsite::changeSubsite($currentPage->SubsiteID);
}
}
// Switch to a subsite that this user can actually access.
$member = Member::currentUser();
if($member && Permission::checkMember($member, 'ADMIN')) return true; // admin can access all subsites
$sites = Subsite::accessible_sites("CMS_ACCESS_{$this->owner->class}", true)->map('ID', 'Title');
if(is_object($sites)) $sites = $sites->toArray();
if($sites && !isset($sites[Subsite::currentSubsiteID()])) {
$siteIDs = array_keys($sites);
Subsite::changeSubsite($siteIDs[0]);
return true;
}
// Switch to a different top-level menu item
$menu = CMSMenu::get_menu_items();
foreach($menu as $candidate) {
if($candidate->controller != $this->owner->class) {
$sites = Subsite::accessible_sites("CMS_ACCESS_{$candidate->controller}", true)->map('ID', 'Title');
if(is_object($sites)) $sites = $sites->toArray();
if($sites && !isset($sites[Subsite::currentSubsiteID()])) {
$siteIDs = array_keys($sites);
Subsite::changeSubsite($siteIDs[0]);
$cClass = $candidate->controller;
$cObj = new $cClass();
$this->owner->redirect($cObj->Link());
return null;
}
}
}
// If all of those fail, you really don't have access to the CMS
return null;
}
function augmentNewSiteTreeItem(&$item) {
$item->SubsiteID = isset($_POST['SubsiteID']) ? $_POST['SubsiteID'] : Subsite::currentSubsiteID();
}
function onAfterSave($record) {
if($record->hasMethod('NormalRelated') && ($record->NormalRelated() || $record->ReverseRelated())) {
$this->owner->response->addHeader('X-Status', rawurlencode(_t('LeftAndMainSubsites.Saved', 'Saved, please update related pages.')));
}
}
}

View File

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

View File

@ -1,334 +0,0 @@
<?php
/**
* Extension for the SiteTree object to add subsites support
*/
class SiteTreeSubsites extends DataExtension {
static $template_variables = array(
'((Company Name))' => 'Title'
);
static $template_fields = array(
"URLSegment",
"Title",
"MenuTitle",
"Content",
"MetaTitle",
"MetaDescription",
"MetaKeywords",
);
/**
* Set the fields that will be copied from the template.
* Note that ParentID and Sort are implied.
*/
static function set_template_fields($fieldList) {
self::$template_fields = $fieldList;
}
public static $has_one=array(
'Subsite' => 'Subsite', // The subsite that this page belongs to
'MasterPage' => 'SiteTree',// Optional; the page that is the content master
);
public static $has_many=array(
'RelatedPages' => 'RelatedPageLink'
);
public static $many_many=array(
'CrossSubsiteLinkTracking' => 'SiteTree' // Stored separately, as the logic for URL rewriting is different
);
public static $belongs_many_many=array(
'BackCrossSubsiteLinkTracking' => 'SiteTree'
);
public static $many_many_extraFields=array(
"CrossSubsiteLinkTracking" => array("FieldName" => "Varchar")
);
function isMainSite() {
if($this->owner->SubsiteID == 0) return true;
return false;
}
/**
* Update any requests to limit the results to the current site
*/
function augmentSQL(SQLQuery &$query) {
if(Subsite::$disable_subsite_filter) return;
// Don't run on delete queries, since they are always tied to
// a specific ID.
if ($query->getDelete()) return;
// If you're querying by ID, ignore the sub-site - this is a bit ugly...
// if(!$query->where || (strpos($query->where[0], ".\"ID\" = ") === false && strpos($query->where[0], ".`ID` = ") === false && strpos($query->where[0], ".ID = ") === false && strpos($query->where[0], "ID = ") !== 0)) {
if (!$query->where || (!preg_match('/\.(\'|"|`|)ID(\'|"|`|)( ?)=/', $query->where[0]))) {
if (Subsite::$force_subsite) $subsiteID = Subsite::$force_subsite;
else {
/*if($context = DataObject::context_obj()) $subsiteID = (int)$context->SubsiteID;
else */$subsiteID = (int)Subsite::currentSubsiteID();
}
// The foreach is an ugly way of getting the first key :-)
foreach($query->getFrom() as $tableName => $info) {
// The tableName should be SiteTree or SiteTree_Live...
if(strpos($tableName,'SiteTree') === false) break;
$query->addWhere("\"$tableName\".\"SubsiteID\" IN ($subsiteID)");
break;
}
}
}
function onBeforeWrite() {
if(!$this->owner->ID && !$this->owner->SubsiteID) $this->owner->SubsiteID = Subsite::currentSubsiteID();
parent::onBeforeWrite();
}
function updateCMSFields(FieldList $fields) {
if($this->owner->MasterPageID) $fields->addFieldToTab('Root.Main', new HeaderField('This page\'s content is copied from a master page: ' . $this->owner->MasterPage()->Title, 2), 'Title');
// replace readonly link prefix
$subsite = $this->owner->Subsite();
if($subsite && $subsite->ID) {
$baseUrl = 'http://' . $subsite->domain() . '/';
$baseLink = Controller::join_links (
$baseUrl,
(SiteTree::nested_urls() && $this->owner->ParentID ? $this->owner->Parent()->RelativeLink(true) : null)
);
$url = (strlen($baseLink) > 36 ? "..." .substr($baseLink, -32) : $baseLink);
$urlsegment = $fields->dataFieldByName('URLSegment');
$urlsegment->setURLPrefix($url);
}
$relatedCount = 0;
$reverse = $this->ReverseRelated();
if($reverse) $relatedCount += $reverse->Count();
$normalRelated = $this->NormalRelated();
if($normalRelated) $relatedCount += $normalRelated->Count();
$tabName = $relatedCount ? 'Related (' . $relatedCount . ')' : 'Related';
$tab = $fields->findOrMakeTab('Root.Related', $tabName);
// Related pages
$tab->push(new LiteralField('RelatedNote',
'<p>You can list pages here that are related to this page.<br />When this page is updated, you will get a reminder to check whether these related pages need to be updated as well.</p>'));
$tab->push(
$related=new GridField('RelatedPages', 'Related Pages', $this->owner->RelatedPages(), GridFieldConfig_Base::create())
);
$related->setModelClass('RelatedPageLink');
// The 'show' link doesn't provide any useful info
//$related->setPermissions(array('add', 'edit', 'delete'));
if($reverse) {
$text = '<p>In addition, this page is marked as related by the following pages: </p><p>';
foreach($reverse as $rpage) {
$text .= $rpage->RelatedPageAdminLink(true) . " - " . $rpage->AbsoluteLink(true) . "<br />\n";
}
$text .= '</p>';
$tab->push(new LiteralField('ReverseRelated', $text));
}
}
/**
* Returns the RelatedPageLink objects that are reverse-associated with this page.
*/
function ReverseRelated() {
return DataObject::get('RelatedPageLink', "\"RelatedPageLink\".\"RelatedPageID\" = {$this->owner->ID}
AND R2.\"ID\" IS NULL", '')
->innerJoin('SiteTree', "\"SiteTree\".\"ID\" = \"RelatedPageLink\".\"MasterPageID\"")
->leftJoin('RelatedPageLink', "R2.\"MasterPageID\" = {$this->owner->ID} AND R2.\"RelatedPageID\" = \"RelatedPageLink\".\"MasterPageID\"", 'R2');
}
function NormalRelated() {
$return = new ArrayList();
$links = DataObject::get('RelatedPageLink', '"MasterPageID" = ' . $this->owner->ID);
if($links) foreach($links as $link) {
if($link->RelatedPage()->exists()) {
$return->push($link->RelatedPage());
}
}
return $return->Count() > 0 ? $return : false;
}
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 = 'Your Site Name';
$sc->Tagline = 'your tagline here';
$sc->write();
}
return $sc;
}
/**
* Only allow editing of a page if the member satisfies one of the following conditions:
* - Is in a group which has access to the subsite this page belongs to
* - Is in a group with edit permissions on the "main site"
*
* @return boolean
*/
function canEdit($member = null) {
if(!$member) $member = Member::currentUser();
// Find the sites that this user has access to
$goodSites = Subsite::accessible_sites('CMS_ACCESS_CMSMain',true,'all',$member)->column('ID');
// Return true if they have access to this object's site
if(!(in_array(0, $goodSites) || in_array($this->owner->SubsiteID, $goodSites))) return false;
}
/**
* @return boolean
*/
function canDelete($member = null) {
if(!$member && $member !== FALSE) $member = Member::currentUser();
return $this->canEdit($member);
}
/**
* @return boolean
*/
function canAddChildren($member = null) {
if(!$member && $member !== FALSE) $member = Member::currentUser();
return $this->canEdit($member);
}
/**
* @return boolean
*/
function canPublish($member = null) {
if(!$member && $member !== FALSE) $member = Member::currentUser();
return $this->canEdit($member);
}
/**
* Create a duplicate of this page and save it to another subsite
* @param $subsiteID int|Subsite The Subsite to copy to, or its ID
* @param $isTemplate boolean If this is true, then the current page will be treated as the template, and MasterPageID will be set
*/
public function duplicateToSubsite($subsiteID = null, $isTemplate = true) {
if(is_object($subsiteID)) {
$subsite = $subsiteID;
$subsiteID = $subsite->ID;
} else $subsite = DataObject::get_by_id('Subsite', $subsiteID);
$oldSubsite=Subsite::currentSubsiteID();
if($subsiteID) {
Subsite::changeSubsite($subsiteID);
}else {
$subsiteID=$oldSubsite;
}
$page = $this->owner->duplicate(false);
$page->CheckedPublicationDifferences = $page->AddedToStage = true;
$subsiteID = ($subsiteID ? $subsiteID : $oldSubsite);
$page->SubsiteID = $subsiteID;
if($isTemplate) $page->MasterPageID = $this->owner->ID;
$page->write();
Subsite::changeSubsite($oldSubsite);
return $page;
}
/**
* Called by ContentController::init();
*/
static function contentcontrollerInit($controller) {
// Need to set the SubsiteID to null incase we've been in the CMS
Session::set('SubsiteID', null);
$subsite = Subsite::currentSubsite();
if($subsite && $subsite->Theme) SSViewer::set_theme(Subsite::currentSubsite()->Theme);
}
/**
* Called by ModelAsController::init();
*/
static function modelascontrollerInit($controller) {
// Need to set the SubsiteID to null incase we've been in the CMS
Session::set('SubsiteID', null);
}
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;
}
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\" = '" . urldecode( $rest). "' AND \"SubsiteID\" = " . $subsiteID, false);
Subsite::disable_subsite_filter($origDisableSubsiteFilter);
if($candidatePage) {
$linkedPages[] = $candidatePage->ID;
} else {
$this->owner->HasBrokenLink = true;
}
}
}
}
$this->owner->CrossSubsiteLinkTracking()->setByIDList($linkedPages);
}
/**
* Return a piece of text to keep DataObject cache keys appropriately specific
*/
function cacheKeyComponent() {
return 'subsite-'.Subsite::currentSubsiteID();
}
/**
* @param Member
* @return boolean|null
*/
function canCreate($member = null) {
// Typically called on a singleton, so we're not using the Subsite() relation
$subsite = Subsite::currentSubsite();
if($subsite && $subsite->exists() && $subsite->PageTypeBlacklist) {
$blacklisted = explode(',', $subsite->PageTypeBlacklist);
// All subclasses need to be listed explicitly
if(in_array($this->owner->class, $blacklisted)) return false;
}
}
}

View File

@ -1,65 +0,0 @@
<?php
class GridFieldSubsiteDetailForm extends GridFieldDetailForm {
protected $itemRequestClass='GridFieldSubsiteDetailForm_ItemRequest';
}
class GridFieldSubsiteDetailForm_ItemRequest extends GridFieldDetailForm_ItemRequest {
/**
* Builds an item edit form. The arguments to getCMSFields() are the popupController and
* popupFormName, however this is an experimental API and may change.
*
* @todo In the future, we will probably need to come up with a tigher object representing a partially
* complete controller with gaps for extra functionality. This, for example, would be a better way
* of letting Security/login put its log-in form inside a UI specified elsewhere.
*
* @return Form
* @see GridFieldDetailForm_ItemRequest::ItemEditForm()
*/
function ItemEditForm() {
$form=parent::ItemEditForm();
if($this->record->ID == 0) {
$templates = Subsite_Template::get()->sort('Title');
$templateArray = array();
if($templates) {
$templateArray = $templates->map('ID', 'Title');
}
$form->Fields()->addFieldToTab('Root.Configuration', new DropdownField('TemplateID', 'Copy structure from:', $templateArray, null, null, "(No template)"));
}
return $form;
}
function doSave($data, $form) {
$new_record = $this->record->ID == 0;
if($new_record && isset($data['TemplateID']) && !empty($data['TemplateID'])) {
$template = Subsite_Template::get()->byID(intval($data['TemplateID']));
if($template) {
$this->record=$template->createInstance($data['Title']);
}
}
try {
$form->saveInto($this->record);
$this->record->write();
$this->gridField->getList()->add($this->record);
} catch(ValidationException $e) {
$form->sessionMessage($e->getResult()->message(), 'bad');
return Controller::curr()->redirectBack();
}
// TODO Save this item into the given relationship
$message = sprintf(
_t('GridFieldDetailForm.Saved', 'Saved %s %s'),
$this->record->singular_name(),
'<a href="' . $this->Link('edit') . '">"' . htmlspecialchars($this->record->Title, ENT_QUOTES) . '"</a>'
);
$form->sessionMessage($message, 'good');
return Controller::curr()->redirect($this->Link());
}
}

View File

@ -1,11 +0,0 @@
<?php
class SubsiteAgnosticTableListField extends TableListField {
function getQuery() {
$oldState = Subsite::$disable_subsite_filter;
Subsite::$disable_subsite_filter = true;
$return = parent::getQuery();
Subsite::$disable_subsite_filter = $oldState;
return $return;
}
}

View File

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

View File

@ -1,89 +0,0 @@
<?php
/**
* This DataObject only exists to provide a link between related pages.
* Unfortunately, there is no way to provide a decent GUI otherwise.
*/
class RelatedPageLink extends DataObject {
static $db = array(
);
static $has_one = array(
'RelatedPage' => 'SiteTree',
// Note: The *last* matching has_one relation to SiteTree is used as the link field for the
// has_many (RelatedPages) on SiteTree. This isn't obvious and the framework could be
// extended in a future version to allow for explicit selection of a has_one relation to
// bind a has_many to.
'MasterPage' => 'SiteTree',
);
public static $summary_fields=array(
'RelatedPageAdminLink' => 'Page',
'AbsoluteLink' => 'URL',
);
function getCMSFields() {
$subsites = Subsite::accessible_sites("CMS_ACCESS_CMSMain");
if(!$subsites) $subsites = new ArrayList();
if(Subsite::hasMainSitePermission(null, array("CMS_ACCESS_CMSMain"))) {
$subsites = Subsite::accessible_sites("CMS_ACCESS_CMSMain", true);
}
if($subsites->Count()) {
$subsiteSelectionField = new DropdownField(
"CopyContentFromID_SubsiteID",
"Subsite",
$subsites->map('ID', 'Title'),
($this->CopyContentFromID) ? $this->CopyContentFrom()->SubsiteID : Session::get('SubsiteID')
);
}
// Setup the linking to the original page.
$pageSelectionField = new SubsitesTreeDropdownField(
"RelatedPageID",
_t('VirtualPage.CHOOSE', "Choose a page to link to"),
"SiteTree",
"ID",
"MenuTitle"
);
if (isset($_GET['RelatedPageID_SubsiteID'])) $pageSelectionField->setSubsiteID($_GET['RelatedPageID_SubsiteID']);
$pageSelectionField->setFilterFunction(create_function('$item', 'return $item->ClassName != "VirtualPage";'));
if($subsites->Count()) $fields = new FieldList($subsiteSelectionField, $pageSelectionField);
else $fields = new FieldList($pageSelectionField);
return $fields;
}
function RelatedPageAdminLink($master = false) {
$page = $master ? Dataobject::get_by_id("SiteTree", $this->MasterPageID) : Dataobject::get_by_id("SiteTree", $this->RelatedPageID);
$otherPage = $master ? Dataobject::get_by_id("SiteTree", $this->RelatedPageID) : Dataobject::get_by_id("SiteTree", $this->MasterPageID);
if(!$page || !$otherPage) return;
// Use cmsEditlink only when moving between different pages in the same subsite.
$classClause = ($page->SubsiteID == $otherPage->SubsiteID) ? ' class="cmsEditlink"' : '';
return '<a href="admin/pages/edit/show/' . $page->ID . "\"$classClause>" . Convert::raw2xml($page->Title) . '</a>';
}
function AbsoluteLink($master = false) {
$page = $master ? Dataobject::get_by_id("SiteTree", $this->MasterPageID) : Dataobject::get_by_id("SiteTree", $this->RelatedPageID);
if(!$page) return;
$url = $page->AbsoluteLink();
}
function canView($member = null) {
return $this->MasterPage()->canView($member);
}
function canEdit($member = null) {
return $this->MasterPage()->canView($member);
}
function canDelete($member = null) {
return $this->MasterPage()->canDelete($member);
}
}

View File

@ -1,685 +0,0 @@
<?php
/**
* A dynamically created subsite. SiteTree objects can now belong to a subsite.
* You can simulate subsite access without setting up virtual hosts by appending ?SubsiteID=<ID> to the request.
*
* @package subsites
*/
class Subsite extends DataObject implements PermissionProvider {
/**
* @var boolean $disable_subsite_filter If enabled, bypasses the query decoration
* to limit DataObject::get*() calls to a specific subsite. Useful for debugging.
*/
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.
*/
static $force_subsite = null;
static $write_hostmap = true;
static $default_sort = "\"Title\" ASC";
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',
);
static $has_one = array(
);
static $has_many = array(
'Domains' => 'SubsiteDomain',
);
static $belongs_many_many = array(
"Groups" => "Group",
);
static $defaults = array(
'IsPublic' => 1
);
static $searchable_fields = array(
'Title' => array(
'title' => 'Subsite Name'
),
'Domains.Domain' => array(
'title' => 'Domain name'
),
'IsPublic' => array(
'title' => 'Active subsite',
),
);
static $summary_fields = array(
'Title' => 'Subsite Name',
'PrimaryDomain' => 'Primary Domain',
'IsPublic' => 'Active subsite',
);
/**
* Memory cache of accessible sites
*/
private static $_cache_accessible_sites = array();
/**
* @var array $allowed_themes Numeric array of all themes which are allowed to be selected for all subsites.
* Corresponds to subfolder names within the /themes folder. By default, all themes contained in this folder
* are listed.
*/
protected static $allowed_themes = array();
/**
* @var Boolean If set to TRUE, don't assume 'www.example.com' and 'example.com' are the same.
* Doesn't affect wildcard matching, so '*.example.com' will match 'www.example.com' (but not 'example.com')
* in both TRUE or FALSE setting.
*/
static $strict_subdomain_matching = false;
static function set_allowed_domains($domain){
user_error('Subsite::set_allowed_domains() is deprecated; it is no longer necessary '
. 'because users can now enter any domain name', E_USER_NOTICE);
}
static function set_allowed_themes($themes) {
self::$allowed_themes = $themes;
}
/**
* Return the themes that can be used with this subsite, as an array of themecode => description
*/
function allowedThemes() {
if($themes = $this->stat('allowed_themes')) {
return ArrayLib::valuekey($themes);
} else {
$themes = array();
if(is_dir('../themes/')) {
foreach(scandir('../themes/') as $theme) {
if($theme[0] == '.') continue;
$theme = strtok($theme,'_');
$themes[$theme] = $theme;
}
ksort($themes);
}
return $themes;
}
}
public function getLanguage() {
if($this->getField('Language')) {
return $this->getField('Language');
} else {
return i18n::get_locale();
}
}
/**
* Whenever a Subsite is written, rewrite the hostmap
*
* @return void
*/
public function onAfterWrite() {
Subsite::writeHostMap();
parent::onAfterWrite();
}
/**
* Return the primary domain of this site. Tries to "normalize" the domain name,
* by replacing potential wildcards.
*
* @return string The full domain name of this subsite (without protocol prefix)
*/
function domain() {
if($this->ID) {
$domains = DataObject::get("SubsiteDomain", "\"SubsiteID\" = $this->ID", "\"IsPrimary\" DESC","", 1);
if($domains && $domains->Count()>0) {
$domain = $domains->First()->Domain;
// If there are wildcards in the primary domain (not recommended), make some
// educated guesses about what to replace them with:
$domain = preg_replace('/\.\*$/',".$_SERVER[HTTP_HOST]", $domain);
// Default to "subsite." prefix for first wildcard
// TODO Whats the significance of "subsite" in this context?!
$domain = preg_replace('/^\*\./',"subsite.", $domain);
// *Only* removes "intermediate" subdomains, so 'subdomain.www.domain.com' becomes 'subdomain.domain.com'
$domain = str_replace('.www.','.', $domain);
return $domain;
}
// SubsiteID = 0 is often used to refer to the main site, just return $_SERVER['HTTP_HOST']
} else {
return $_SERVER['HTTP_HOST'];
}
}
function getPrimaryDomain() {
return $this->domain();
}
function absoluteBaseURL() {
return "http://" . $this->domain() . Director::baseURL();
}
/**
* Show the configuration fields for each subsite
*/
function getCMSFields() {
if($this->ID!=0) {
$domainTable = new GridField("Domains", "Domains", $this->Domains(), GridFieldConfig_RecordEditor::create(10));
}else {
$domainTable = new LiteralField('Domains', '<p>'._t('Subsite.DOMAINSAVEFIRST', '_You can only add domains after saving for the first time').'</p>');
}
$languageSelector = new DropdownField('Language', 'Language', i18n::get_common_locales());
$pageTypeMap = array();
$pageTypes = SiteTree::page_type_classes();
foreach($pageTypes as $pageType) {
$pageTypeMap[$pageType] = singleton($pageType)->i18n_singular_name();
}
asort($pageTypeMap);
$fields = new FieldList(
new TabSet('Root',
new Tab('Configuration',
new HeaderField($this->getClassName() . ' configuration', 2),
new TextField('Title', 'Name of subsite:', $this->Title),
new HeaderField("Domains for this subsite"),
$domainTable,
$languageSelector,
// new TextField('RedirectURL', 'Redirect to URL', $this->RedirectURL),
new CheckboxField('DefaultSite', 'Default site', $this->DefaultSite),
new CheckboxField('IsPublic', 'Enable public access', $this->IsPublic),
new DropdownField('Theme','Theme', $this->allowedThemes(), $this->Theme),
new LiteralField(
'PageTypeBlacklistToggle',
sprintf(
'<div class="field"><a href="#" id="PageTypeBlacklistToggle">%s</a></div>',
_t('Subsite.PageTypeBlacklistField', 'Disallow page types?')
)
),
new CheckboxSetField(
'PageTypeBlacklist',
false,
$pageTypeMap
)
)
),
new HiddenField('ID', '', $this->ID),
new HiddenField('IsSubsite', '', 1)
);
$this->extend('updateCMSFields', $fields);
return $fields;
}
/**
* @todo getClassName is redundant, already stored as a database field?
*/
function getClassName() {
return $this->class;
}
function getCMSActions() {
return new FieldList(
new FormAction('callPageMethod', "Create copy", null, 'adminDuplicate')
);
}
function adminDuplicate() {
$newItem = $this->duplicate();
$JS_title = Convert::raw2js($this->Title);
return <<<JS
statusMessage('Created a copy of $JS_title', 'good');
$('Form_EditForm').loadURLFromServer('admin/subsites/show/$newItem->ID');
JS;
}
/**
* Gets the subsite currently set in the session.
*
* @uses ControllerSubsites->controllerAugmentInit()
*
* @return Subsite
*/
static function currentSubsite() {
// get_by_id handles caching so we don't have to
return DataObject::get_by_id('Subsite', self::currentSubsiteID());
}
/**
* This function gets the current subsite ID from the session. It used in the backend so Ajax requests
* use the correct subsite. The frontend handles subsites differently. It calls getSubsiteIDForDomain
* directly from ModelAsController::getNestedController. Only gets Subsite instances which have their
* {@link IsPublic} flag set to TRUE.
*
* You can simulate subsite access without creating virtual hosts by appending ?SubsiteID=<ID> to the request.
*
* @todo Pass $request object from controller so we don't have to rely on $_GET
*
* @param boolean $cache
* @return int ID of the current subsite instance
*/
static function currentSubsiteID() {
if(isset($_GET['SubsiteID'])) $id = (int)$_GET['SubsiteID'];
else $id = Session::get('SubsiteID');
if($id === NULL) {
$id = self::getSubsiteIDForDomain();
Session::set('SubsiteID', $id);
}
return (int)$id;
}
/**
* Switch to another subsite.
*
* @param int|Subsite $subsite Either the ID of the subsite, or the subsite object itself
*/
static function changeSubsite($subsite) {
if(is_object($subsite)) $subsiteID = $subsite->ID;
else $subsiteID = $subsite;
Session::set('SubsiteID', (int)$subsiteID);
// Set locale
if (is_object($subsite) && $subsite->Language != '') {
if (isset(i18n::$likely_subtags[$subsite->Language])) {
i18n::set_locale(i18n::$likely_subtags[$subsite->Language]);
}
}
Permission::flush_permission_cache();
}
/**
* Make this subsite the current one
*/
public function activate() {
Subsite::changeSubsite($this);
}
/**
* @todo Possible security issue, don't grant edit permissions to everybody.
*/
function canEdit($member = false) {
return true;
}
/**
* Get a matching subsite for the given host, or for the current HTTP_HOST.
* Supports "fuzzy" matching of domains by placing an asterisk at the start of end of the string,
* for example matching all subdomains on *.example.com with one subsite,
* and all subdomains on *.example.org on another.
*
* @param $host The host to find the subsite for. If not specified, $_SERVER['HTTP_HOST'] is used.
* @return int Subsite ID
*/
static function getSubsiteIDForDomain($host = null, $returnMainIfNotFound = true) {
if($host == null) $host = $_SERVER['HTTP_HOST'];
if(!Subsite::$strict_subdomain_matching) $host = preg_replace('/^www\./', '', $host);
$SQL_host = Convert::raw2sql($host);
$matchingDomains = DataObject::get("SubsiteDomain", "'$SQL_host' LIKE replace(\"SubsiteDomain\".\"Domain\",'*','%')",
"\"IsPrimary\" DESC")->innerJoin('Subsite', "\"Subsite\".\"ID\" = \"SubsiteDomain\".\"SubsiteID\" AND
\"Subsite\".\"IsPublic\"=1");
if($matchingDomains && $matchingDomains->Count()>0) {
$subsiteIDs = array_unique($matchingDomains->map('SubsiteID')->keys());
$subsiteDomains = array_unique($matchingDomains->map('Domain')->keys());
if(sizeof($subsiteIDs) > 1) {
throw new UnexpectedValueException(sprintf(
"Multiple subsites match on '%s': %s",
$host,
implode(',', $subsiteDomains)
));
}
return $subsiteIDs[0];
}
// Check for a 'default' subsite
if ($default = DataObject::get_one('Subsite', "\"DefaultSite\" = 1")) {
return $default->ID;
}
// Default subsite id = 0, the main site
return 0;
}
function getMembersByPermission($permissionCodes = array('ADMIN')){
if(!is_array($permissionCodes))
user_error('Permissions must be passed to Subsite::getMembersByPermission as an array', E_USER_ERROR);
$SQL_permissionCodes = Convert::raw2sql($permissionCodes);
$SQL_permissionCodes = join("','", $SQL_permissionCodes);
return DataObject::get(
'Member',
"\"Group\".\"SubsiteID\" = $this->ID AND \"Permission\".\"Code\" IN ('$SQL_permissionCodes')",
'',
"LEFT JOIN \"Group_Members\" ON \"Member\".\"ID\" = \"Group_Members\".\"MemberID\"
LEFT JOIN \"Group\" ON \"Group\".\"ID\" = \"Group_Members\".\"GroupID\"
LEFT JOIN \"Permission\" ON \"Permission\".\"GroupID\" = \"Group\".\"ID\""
);
}
/**
* Checks if a member can be granted certain permissions, regardless of the subsite context.
* Similar logic to {@link Permission::checkMember()}, but only returns TRUE
* if the member is part of a group with the "AccessAllSubsites" flag set.
* If more than one permission is passed to the method, at least one of them must
* be granted for if to return TRUE.
*
* @todo Allow permission inheritance through group hierarchy.
*
* @param Member Member to check against. Defaults to currently logged in member
* @param Array Permission code strings. Defaults to "ADMIN".
* @return boolean
*/
static function hasMainSitePermission($member = null, $permissionCodes = array('ADMIN')) {
if(!is_array($permissionCodes))
user_error('Permissions must be passed to Subsite::hasMainSitePermission as an array', E_USER_ERROR);
if(!$member && $member !== FALSE) $member = Member::currentUser();
if(!$member) return false;
if(!in_array("ADMIN", $permissionCodes)) $permissionCodes[] = "ADMIN";
$SQLa_perm = Convert::raw2sql($permissionCodes);
$SQL_perms = join("','", $SQLa_perm);
$memberID = (int)$member->ID;
// Count this user's groups which can access the main site
$groupCount = DB::query("
SELECT COUNT(\"Permission\".\"ID\")
FROM \"Permission\"
INNER JOIN \"Group\" ON \"Group\".\"ID\" = \"Permission\".\"GroupID\" AND \"Group\".\"AccessAllSubsites\" = 1
INNER JOIN \"Group_Members\" ON \"Group_Members\".\"GroupID\" = \"Permission\".\"GroupID\"
WHERE \"Permission\".\"Code\" IN ('$SQL_perms')
AND \"MemberID\" = {$memberID}
")->value();
// Count this user's groups which have a role that can access the main site
$roleCount = DB::query("
SELECT COUNT(\"PermissionRoleCode\".\"ID\")
FROM \"Group\"
INNER JOIN \"Group_Members\" ON \"Group_Members\".\"GroupID\" = \"Group\".\"ID\"
INNER JOIN \"Group_Roles\" ON \"Group_Roles\".\"GroupID\"=\"Group\".\"ID\"
INNER JOIN \"PermissionRole\" ON \"Group_Roles\".\"PermissionRoleID\"=\"PermissionRole\".\"ID\"
INNER JOIN \"PermissionRoleCode\" ON \"PermissionRole\".\"ID\"=\"PermissionRoleCode\".\"RoleID\"
WHERE \"PermissionRoleCode\".\"Code\" IN ('$SQL_perms')
AND \"Group\".\"AccessAllSubsites\" = 1
AND \"MemberID\" = {$memberID}
")->value();
// There has to be at least one that allows access.
return ($groupCount + $roleCount > 0);
}
/**
* Duplicate this subsite
*/
function duplicate($doWrite = true) {
$newTemplate = parent::duplicate($doWrite);
$oldSubsiteID = Session::get('SubsiteID');
self::changeSubsite($this->ID);
/*
* Copy data from this template to the given subsite. Does this using an iterative depth-first search.
* This will make sure that the new parents on the new subsite are correct, and there are no funny
* issues with having to check whether or not the new parents have been added to the site tree
* when a page, etc, is duplicated
*/
$stack = array(array(0,0));
while(count($stack) > 0) {
list($sourceParentID, $destParentID) = array_pop($stack);
$children = Versioned::get_by_stage('Page', 'Live', "\"ParentID\" = $sourceParentID", '');
if($children) {
foreach($children as $child) {
$childClone = $child->duplicateToSubsite($newTemplate, false);
$childClone->ParentID = $destParentID;
$childClone->writeToStage('Stage');
$childClone->publish('Stage', 'Live');
array_push($stack, array($child->ID, $childClone->ID));
}
}
}
self::changeSubsite($oldSubsiteID);
return $newTemplate;
}
/**
* Return the subsites that the current user can access.
* Look for one of the given permission codes on the site.
*
* Sites and Templates will only be included if they have a Title
*
* @param $permCode array|string Either a single permission code or an array of permission codes.
* @param $includeMainSite If true, the main site will be included if appropriate.
* @param $mainSiteTitle The label to give to the main site
* @param $member
* @return DataList of {@link Subsite} instances
*/
public static function accessible_sites($permCode, $includeMainSite = true, $mainSiteTitle = "Main site", $member = null) {
// Rationalise member arguments
if(!$member) $member = Member::currentUser();
if(!$member) return new ArrayList();
if(!is_object($member)) $member = DataObject::get_by_id('Member', $member);
// Rationalise permCode argument
if(is_array($permCode)) $SQL_codes = "'" . implode("', '", Convert::raw2sql($permCode)) . "'";
else $SQL_codes = "'" . Convert::raw2sql($permCode) . "'";
// Cache handling
$cacheKey = $SQL_codes . '-' . $member->ID . '-' . $includeMainSite . '-' . $mainSiteTitle;
if(isset(self::$_cache_accessible_sites[$cacheKey])) {
return self::$_cache_accessible_sites[$cacheKey];
}
$templateClassList = "'" . implode("', '", ClassInfo::subclassesFor("Subsite_Template")) . "'";
$subsites = DataList::create('Subsite')
->where("\"Subsite\".\"Title\" != ''")
->leftJoin('Group_Subsites', "\"Group_Subsites\".\"SubsiteID\" = \"Subsite\".\"ID\"")
->innerJoin('Group', "\"Group\".\"ID\" = \"Group_Subsites\".\"GroupID\" OR \"Group\".\"AccessAllSubsites\" = 1")
->innerJoin('Group_Members', "\"Group_Members\".\"GroupID\"=\"Group\".\"ID\" AND \"Group_Members\".\"MemberID\" = $member->ID")
->innerJoin('Permission', "\"Group\".\"ID\"=\"Permission\".\"GroupID\" AND \"Permission\".\"Code\" IN ($SQL_codes, 'ADMIN')");
if(!$subsites) $subsites = new ArrayList();
$rolesSubsites = DataList::create('Subsite')
->where("\"Subsite\".\"Title\" != ''")
->leftJoin('Group_Subsites', "\"Group_Subsites\".\"SubsiteID\" = \"Subsite\".\"ID\"")
->innerJoin('Group', "\"Group\".\"ID\" = \"Group_Subsites\".\"GroupID\" OR \"Group\".\"AccessAllSubsites\" = 1")
->innerJoin('Group_Members', "\"Group_Members\".\"GroupID\"=\"Group\".\"ID\" AND \"Group_Members\".\"MemberID\" = $member->ID")
->innerJoin('Group_Roles', "\"Group_Roles\".\"GroupID\"=\"Group\".\"ID\"")
->innerJoin('PermissionRole', "\"Group_Roles\".\"PermissionRoleID\"=\"PermissionRole\".\"ID\"")
->innerJoin('PermissionRoleCode', "\"PermissionRole\".\"ID\"=\"PermissionRoleCode\".\"RoleID\" AND \"PermissionRoleCode\".\"Code\" IN ($SQL_codes, 'ADMIN')");
if(!$subsites && $rolesSubsites) return $rolesSubsites;
$subsites = new ArrayList($subsites->toArray());
if($rolesSubsites) foreach($rolesSubsites as $subsite) {
if(!$subsites->find('ID', $subsite->ID)) {
$subsites->push($subsite);
}
}
if($includeMainSite) {
if(!is_array($permCode)) $permCode = array($permCode);
if(self::hasMainSitePermission($member, $permCode)) {
$subsites=$subsites->toArray();
$mainSite = new Subsite();
$mainSite->Title = $mainSiteTitle;
array_unshift($subsites, $mainSite);
$subsites=ArrayList::create($subsites);
}
}
self::$_cache_accessible_sites[$cacheKey] = $subsites;
return $subsites;
}
/**
* Write a host->domain map to subsites/host-map.php
*
* This is used primarily when using subsites in conjunction with StaticPublisher
*
* @return void
*/
static function writeHostMap($file = null) {
if (!self::$write_hostmap) return;
if (!$file) $file = Director::baseFolder().'/subsites/host-map.php';
$hostmap = array();
$subsites = DataObject::get('Subsite');
if ($subsites) foreach($subsites as $subsite) {
$domains = $subsite->Domains();
if ($domains) foreach($domains as $domain) {
$domainStr = $domain->Domain;
if(!Subsite::$strict_subdomain_matching) $domainStr = preg_replace('/^www\./', '', $domainStr);
$hostmap[$domainStr] = $subsite->domain();
}
if ($subsite->DefaultSite) $hostmap['default'] = $subsite->domain();
}
$data = "<?php \n";
$data .= "// Generated by Subsite::writeHostMap() on " . date('d/M/y') . "\n";
$data .= '$subsiteHostmap = ' . var_export($hostmap, true) . ';';
if (is_writable(dirname($file)) || is_writable($file)) {
file_put_contents($file, $data);
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CMS ADMINISTRATION HELPERS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function providePermissions() {
return array(
'SUBSITE_ASSETS_CREATE_SUBSITE' => array(
'name' => _t('Subsite.MANAGE_ASSETS', 'Manage assets for subsites'),
'category' => _t('Permissions.PERMISSIONS_CATEGORY', 'Roles and access permissions'),
'help' => _t('Subsite.MANAGE_ASSETS_HELP', 'Ability to select the subsite to which an asset folder belongs. Requires "Access to Files & Images."'),
'sort' => 300
)
);
}
static function get_from_all_subsites($className, $filter = "", $sort = "", $join = "", $limit = "") {
$oldState = self::$disable_subsite_filter;
self::$disable_subsite_filter = true;
$result = DataObject::get($className, $filter, $sort, $join, $limit);
self::$disable_subsite_filter = $oldState;
return $result;
}
/**
* Disable the sub-site filtering; queries will select from all subsites
*/
static function disable_subsite_filter($disabled = true) {
self::$disable_subsite_filter = $disabled;
}
/**
* Flush caches on database reset
*/
static function on_db_reset() {
self::$_cache_accessible_sites = array();
}
}
/**
* An instance of subsite that can be duplicated to provide a quick way to create new subsites.
*
* @package subsites
*/
class Subsite_Template extends Subsite {
/**
* Create an instance of this template, with the given title & domain
*/
function createInstance($title, $domain = null) {
$intranet = Object::create('Subsite');
$intranet->Title = $title;
$intranet->TemplateID = $this->ID;
$intranet->write();
if($domain) {
$intranetDomain = Object::create('SubsiteDomain');
$intranetDomain->SubsiteID = $intranet->ID;
$intranetDomain->Domain = $domain;
$intranetDomain->write();
}
$oldSubsiteID = Session::get('SubsiteID');
self::changeSubsite($this->ID);
/*
* Copy site content from this template to the given subsite. Does this using an iterative depth-first search.
* This will make sure that the new parents on the new subsite are correct, and there are no funny
* issues with having to check whether or not the new parents have been added to the site tree
* when a page, etc, is duplicated
*/
$stack = array(array(0,0));
while(count($stack) > 0) {
list($sourceParentID, $destParentID) = array_pop($stack);
$children = Versioned::get_by_stage('SiteTree', 'Live', "\"ParentID\" = $sourceParentID", '');
if($children) {
foreach($children as $child) {
//Change to destination subsite
self::changeSubsite($intranet->ID);
$childClone = $child->duplicateToSubsite($intranet);
$childClone->ParentID = $destParentID;
$childClone->writeToStage('Stage');
$childClone->publish('Stage', 'Live');
//Change Back to this subsite
self::changeSubsite($this->ID);
array_push($stack, array($child->ID, $childClone->ID));
}
}
}
self::changeSubsite($oldSubsiteID);
return $intranet;
}
}

View File

@ -1,32 +0,0 @@
<?php
class SubsiteDomain extends DataObject {
static $db = array(
"Domain" => "Varchar(255)",
"IsPrimary" => "Boolean",
);
static $has_one = array(
"Subsite" => "Subsite",
);
public static $summary_fields=array(
'Domain'=>'Domain',
'IsPrimary'=>'Is Primary Domain'
);
/**
* Whenever a Subsite Domain is written, rewrite the hostmap
*
* @return void
*/
public function onAfterWrite() {
Subsite::writeHostMap();
}
public function getCMSFields() {
return new FieldList(
new TextField('Domain', _t('SubsiteDomain.DOMAIN', '_Domain'), null, 255),
new CheckboxField('IsPrimary', _t('SubsiteDomain.IS_PRIMARY', '_Is Primary Domain'))
);
}
}

1
codecov.yml Normal file
View File

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

View File

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

View File

@ -1,62 +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-login-status.subsites {
padding-bottom: 7px;
}
#SubsitesSelect,
#SubsitesSelect option {
font-size: 12px;
}
#SubsitesSelect {
width: 171px;
padding: 3px;
}
#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;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

61
docs/en/introduction.md Normal file
View File

@ -0,0 +1,61 @@
# Subsites
[User guide](userguide/index.md)
## FAQ
### How can I restrict a content author to a particular subsite?
To lock down a subsite to certain content authors you will need to create a group in security add the content authors
to this group and then specify that this group only has access to a the relevant subsite.
### Why is the first site I have developed is appearing as the main site?
When you install SilverStripe with Subsites no subsites will exists and you will just have the main site.
If you install Subsites to an existing sites then the current site will be classed at the main site.
### What is the difference between the main site and the sub sites?
The main site will be the your primary focus so if SilverStripe was using Subsites we will have silverstripe.com
as the main site and a subsite would be something like doc.silverstripe.com
### Will I need to make any changes to my domain name?
Potentially if you intend to have something like subsite.mydomain.com as well as www.mydomain.com both running on
your SilverStripe install on the same server then you will need to check that that subsite.mydomain.com points to
the same web server as www.mydomain.com You may also need to speak to your domain registrar and hosting provider
regarding this to confirm if this will work.
You can confirm if your sub domain is set up by pinging it and confirming if the IP address returned is the same as
your main website. However this does not guarantee this will work as your hosting provider may need to set up a
virtual host.
### Will I need to set up a virtual host to use subsites?
Yes if you are running on a Apache server a virtual host will need to be set up to support a subsite, you will need
to speak to your website administrator or hosting provider to facilitate this.
### How can I test a subsite works on my local machine or on a machine without a virtual host being set up?
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.

32
docs/en/technical.md Normal file
View File

@ -0,0 +1,32 @@
## Architecture
Subsites works by creating a Subsites model which stores all the details like Theme and Language for a
subsite that you create.
It also adds a column to SiteTree called SubsiteID which defaults to 0 for the main site but can be used to link a
page to a particular subsite.
The subsite module adds functionality to the admin section of the site to allow you to create new subsites and copy
pages between the main site and any subsites.
Subsites makes use of a DataExtension called SiteTreeSubsites to add support for subsites to the SiteTree,
which extends various methods to add Subsite functionality some of the methods are listed below
### augmentSQL
This methods modifies the SiteTree results returned for a Subsite it does this by using the Subsite ID and filtering the
SiteTree via the SubsiteID column on the SiteTree
### onBeforeWrite
This method is used to update the SubsiteID on a SiteTree object when a page is saved and the the current SubsiteID is null.
### updateCMSFields
This method is used to add Subsite related fields to the CMS form for adding and editing SiteTree pages.
### duplicateToSubsite
This method is called when a pages are being copied between the main site or another subsite.
### alternateAbsoluteLink
This method modifies the absolute link to contain the valid subsite domain
### updatePreviewLink
This method modifies the preview link for the CMS.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

View File

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

108
docs/en/userguide/set_up.md Normal file
View File

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

View File

@ -0,0 +1,31 @@
---
title: Working with subsites
---
# Working with subsites
## Managing content across subsites
To edit a particular subsite, choose the subsite from the dropdown menu in the left-hand menu.
![Subsites dropdown](_images/subsites-dropdown.png)
### Subsites virtual pages
You can pull in the content from a page that resides on another subsite by creating a page of the type **Subsites
Virtual Page**. Pick the subsite from which you want to pull the content, then select the page. As with regular virtual
pages, your **Subsites Virtual Page** will display the content from the original page and get updated automatically if
the original content changes.
![Subsites virtual page](_images/subsites-virtual-page.jpg)
### Duplicating pages from the main site
If you have an existing page on the main site that you would like to copy to a subsite, all you need to do is:
* Visit the page in the site tree and scroll down to the 'Copy page to subsite' dropdown
* Choose the subsite you wish to copy the page to
* Press the 'Copy' button
![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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
images/subsites.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 B

View File

@ -1,52 +0,0 @@
(function($) {
$.entwine('ss', function($) {
$('#SubsitesSelect').live('change', function() {
window.location.search=$.query.set('SubsiteID', $(this).val());
});
// Subsite tab of Group editor
$('#Form_ItemEditForm_AccessAllSubsites').entwine({
/**
* Constructor: onmatch
*/
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({
getChangeTrackerOptions: function() {
this.ChangeTrackerOptions.ignoreFieldSelector+=', input[name=IsSubsite]';
}
});
/**
* 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;
});
}
});
});
})(jQuery);

View File

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

76
lang/ar.yml Normal file
View File

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

64
lang/cs.yml Normal file
View File

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

100
lang/de.yml Normal file
View File

@ -0,0 +1,100 @@
de:
DomainNameField:
INVALID_DOMAIN: 'Ungültige Domain'
SilverStripe\CMS\Model\VirtualPage:
EDITCONTENT: 'Klicken Sie hier, um den Inhalt zu bearbeiten'
SilverStripe\Subsites\Admin\SubsiteAdmin:
MENUTITLE: Subsites
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
SilverStripe\Subsites\Extensions\GroupSubsites:
ACCESSALL: 'Alle Subsites'
ACCESSONLY: 'Nur diese Subsites'
ACCESSRADIOTITLE: 'Dieser Gruppe Zugriff geben auf'
GlobalGroup: 'globale Gruppe'
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
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'
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
PLURALNAME: Subsites
PLURALS:
one: 'Eine Subseite'
other: '{count} Subsites'
PageTypeBlacklistField: 'Seitentypen verbieten?'
SINGULARNAME: Subseite
SiteConfigSubtitle: 'Ihr Websiteslogan'
SiteConfigTitle: 'Name Ihrer Website'
ThemeFieldEmptyString: '-'
ValidateTitle: 'Bitte geben Sie einen Titel an'
belongs_many_many_Groups: Gruppe
db_DefaultSite: 'Standard Seite'
db_Language: Sprache
db_RedirectURL: Weierleitungs-URL
db_Theme: Theme
db_Title: Titel
has_many_Domains: Domains
SilverStripe\Subsites\Model\SubsiteDomain:
DOMAIN: Domäne
DOMAIN_DESCRIPTION: 'Hostname dieser Subsite (ohne Protokol). Joker (*) ist erlaubt.'
IS_PRIMARY: 'Ist Hauptdomäne?'
PLURALNAME: 'Domänen der Subseite'
PLURALS:
one: 'Eine Domäne der Subseite'
other: '{count} Domainen der Subseite'
PROTOCOL_AUTOMATIC: Automatisch
PROTOCOL_DESCRIPTION: 'DIes ist die Standarddomäne für diese Subseite'
PROTOCOL_HTTP: 'http://'
PROTOCOL_HTTPS: 'https://'
Protocol: Protokoll
SINGULARNAME: 'Domäne der Subseite'
db_Domain: Domäne
db_Protocol: Protokoll
has_one_Subsite: Subseite
SilverStripe\Subsites\Pages\SubsitesVirtualPage:
DESCRIPTION: 'Zeigt den Inhalt einer anderen Seite von einer anderen Subseite an'
OverrideNote: 'Überschreibt den geerbten Wert der Quelle'
PLURALNAME: 'Subsites Virtuelle Seiten'
PLURALS:
one: 'Eine Subsites Virtuelle Seite'
other: '{count} Subsites Virtuelle Seiten'
SINGULARNAME: 'Subsites Virtuelle Seite'
SubsiteField: Subseite
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
ReportDropdown: Seiten
ReportDropdownSubsite: Subseite
Subsite:
COPYSTRUCTURE: 'Struktur kopieren von:'
NOTEMPLATE: 'Kein Template'
Subsites:
DefaultSiteFieldLabel: 'Standard Seite'
DomainFieldLabel: Domäne
IsPublicFieldLabel: 'Öffentlich zugänglich'
LanguageFieldLabel: Sprache
MainSiteTitle: Hauptsite
PageTypeBlacklistFieldLabel: 'Ausgeschlossene Seitentypen'
PrimaryDomainFieldLabel: 'Primäre Domain'
RedirectURLFieldLabel: Weierleitungs-URL
ThemeFieldLabel: Theme
TitleFieldLabel: 'Name der Subsite'

113
lang/en.yml Normal file
View File

@ -0,0 +1,113 @@
en:
DomainNameField:
INVALID_DOMAIN: 'Invalid domain name'
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
SilverStripe\Subsites\Extensions\GroupSubsites:
ACCESSALL: 'All subsites'
ACCESSONLY: 'Only these subsites'
ACCESSRADIOTITLE: 'Give this group access to'
GlobalGroup: 'global group'
MANAGE_SUBSITES: 'Manage subsites for groups'
MANAGE_SUBSITES_HELP: 'Ability to limit the permissions for a group to one or more subsites.'
SECURITYTABTITLE: Subsites
db_AccessAllSubsites: 'Access all subsites'
many_many_Subsites: Subsites
SilverStripe\Subsites\Extensions\LeftAndMainSubsites:
SITECONTENTLEFT: 'Site Content'
Saved: 'Saved, please update related pages.'
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'
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
PLURALNAME: Subsites
PLURALS:
one: 'A Subsite'
other: '{count} Subsites'
PageTypeBlacklistField: 'Disallow page types?'
SINGULARNAME: Subsite
SiteConfigSubtitle: 'Your tagline here'
SiteConfigTitle: 'Your Site Name'
ThemeFieldEmptyString: '-'
ValidateTitle: 'Please add a "Title"'
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: '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'
db_Domain: Domain
db_IsPrimary: 'Is primary'
db_Protocol: Protocol
has_one_Subsite: Subsite
SilverStripe\Subsites\Pages\SubsitesVirtualPage:
DESCRIPTION: 'Displays the content of a page on another subsite'
OverrideNote: 'Overrides inherited value from the source'
PLURALNAME: 'Subsites Virtual Pages'
PLURALS:
one: 'A Subsites Virtual Page'
other: '{count} Subsites Virtual Pages'
SINGULARNAME: 'Subsites Virtual Page'
SubsiteField: Subsite
db_CustomExtraMeta: 'Custom extra meta'
db_CustomMetaDescription: 'Custom meta description'
db_CustomMetaKeywords: 'Custom meta keywords'
db_CustomMetaTitle: 'Custom meta title'
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
ReportDropdown: Sites
ReportDropdownAll: All
ReportDropdownSubsite: Subsite
Subsite:
COPYSTRUCTURE: 'Copy structure from:'
NOTEMPLATE: 'No template'
Subsites:
DefaultSiteFieldLabel: 'Default site'
DomainFieldLabel: Domain
IsPublicFieldLabel: 'Enable public access'
LanguageFieldLabel: Language
MainSiteTitle: 'Main site'
PageTypeBlacklistFieldLabel: 'Page Type Blacklist'
PrimaryDomainFieldLabel: 'Primary Domain'
RedirectURLFieldLabel: 'Redirect URL'
ThemeFieldLabel: Theme
TitleFieldLabel: 'Subsite Name'

View File

@ -1,39 +0,0 @@
en_US:
SubsiteAdmin:
MENUTITLE: "Subsites"
GroupSubsites:
ACCESSALL: "All subsites"
ACCESSONLY: "Only these subsites"
ACCESSRADIOTITLE: "Give this group access to"
SECURITYTABTITLE: "Subsites"
ModelAdmin:
LOADEDFOREDITING: "Loaded \'%s\' for editing."
RelatedPageLink:
PLURALNAME: "Related Page Links"
SINGULARNAME: "Related Page Link"
Subsite:
PLURALNAME: "Subsits"
SINGULARNAME: "Subsite"
DOMAINSAVEFIRST: "You can only add domains after saving for the first time"
SubsiteDomain:
PLURALNAME: "Subsite Domains"
SINGULARNAME: "Subsite Domain"
DOMAIN: "Domain"
IS_PRIMARY: "Is Primary Domain"
Subsite_Template:
PLURALNAME: "Subsite Templats"
SINGULARNAME: "Subsite Template"
SubsitesVirtualPage:
PLURALNAME: "Subsites Virtual Pags"
SINGULARNAME: "Subsites Virtual Page"
VirtualPage:
CHOOSE: "Choose a page to link to"
EDITCONTENT: "Click here to edit the content"
GridFieldAddFromTemplateButton:
AddFromTemplate: "Add New from Template"
GridFieldAddFromTemplate:
NewFromTemplate: "New %s from template"
TEMPLATE_NOT_FOUND: "The selected template could not be found"
TEMPLATE: "Template"
LeftAndMainSubsites:
DEFAULT_SITE: "Default Site"

113
lang/eo.yml Normal file
View File

@ -0,0 +1,113 @@
eo:
DomainNameField:
INVALID_DOMAIN: 'Nevalida domajna nomo'
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
SilverStripe\Subsites\Extensions\GroupSubsites:
ACCESSALL: 'Ĉiuj subretejoj'
ACCESSONLY: 'Nur ĉi tiuj subretejoj'
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: 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.'
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'
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 '
CustomMetaDescription: Priskribo
CustomMetaKeywords: Ŝlosilvortoj
CustomMetaTitle: Titolo
PLURALNAME: Subretejoj
PLURALS:
one: 'Unu subretejo'
other: '{count} subretejoj'
PageTypeBlacklistField: 'Ĉu malpermesi paĝajn tipojn?'
SINGULARNAME: Subretejo
SiteConfigSubtitle: 'Jen via slogano'
SiteConfigTitle: 'Nomo de via retejo'
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: 'Kiam generante ligilojn al ĉi tiu subretejo, uzu la elektitan protokolon.'
PROTOCOL_HTTP: 'http://'
PROTOCOL_HTTPS: 'https://'
Protocol: Protokolo
SINGULARNAME: 'Subreteja domajno'
db_Domain: Domajno
db_IsPrimary: 'Estas unuaranga'
db_Protocol: Protokolo
has_one_Subsite: Subretejo
SilverStripe\Subsites\Pages\SubsitesVirtualPage:
DESCRIPTION: 'Vidigas la enhavon de paĝo en alia subretejo'
OverrideNote: 'Anstataŭigas hereditan valoron el la fonto'
PLURALNAME: 'Virtualaj paĝoj de subretejoj'
PLURALS:
one: 'Unu virtuala paĝo de subretejoj'
other: '{count} virtualaj paĝoj de subretejoj'
SINGULARNAME: 'Virtuala paĝo de subretejoj'
SubsiteField: Subretejo
db_CustomExtraMeta: 'Propra kroma meta'
db_CustomMetaDescription: 'Propra metapriskribo'
db_CustomMetaKeywords: 'Propraj meta-ŝlosilvortoj '
db_CustomMetaTitle: 'Propraj meta-titolo'
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
ReportDropdown: Retejoj
ReportDropdownAll: Ĉiuj
ReportDropdownSubsite: Subretejo
Subsite:
COPYSTRUCTURE: 'Kopii strukturon de:'
NOTEMPLATE: 'Mankas ŝablono'
Subsites:
DefaultSiteFieldLabel: 'Apriora retejo'
DomainFieldLabel: Domajno
IsPublicFieldLabel: 'Enŝalti publikan aliron'
LanguageFieldLabel: Lingvo
MainSiteTitle: 'Ĉefa retejo'
PageTypeBlacklistFieldLabel: 'Paĝtipa nigra listo'
PrimaryDomainFieldLabel: 'Unuaranga domajno'
RedirectURLFieldLabel: 'Redirekti je URL'
ThemeFieldLabel: Etoso
TitleFieldLabel: 'Nomo de subretejo'

13
lang/es.yml Normal file
View File

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

13
lang/et_EE.yml Normal file
View File

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

59
lang/fa_IR.yml Normal file
View File

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

92
lang/fi.yml Normal file
View File

@ -0,0 +1,92 @@
fi:
DomainNameField:
INVALID_DOMAIN: 'Virheellinen domain-nimi'
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'
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 ryhmän oikeudet »'
GlobalGroup: Globaaliryhmä
MANAGE_SUBSITES: 'Hallinnoi ryhmien alasivustoja'
MANAGE_SUBSITES_HELP: 'Mahdollisuus rajoittaa ryhmän oikeuksia yhdelle tai useammalle alisivustolle.'
SECURITYTABTITLE: Alasivustot
many_many_Subsites: Alasivut
SilverStripe\Subsites\Extensions\LeftAndMainSubsites:
SITECONTENTLEFT: 'Sivuston sisältö'
Saved: 'Tallennettu, ole hyvä ja päivitä liittyvät sivut.'
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
has_one_Subsite: Alasivusto
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
CopyAction: Kopioi
CopyToSubsite: 'Kopioi sivu alasivustolle'
CopyToSubsiteWithChildren: 'Sisällytä alasivut?'
SubsiteOperations: 'Alasivuston toiminnot'
has_one_Subsite: Alasivusto
SilverStripe\Subsites\Model\Subsite:
ConfigurationTab: Asetukset
CopyMessage: 'Luotiin kopio lähteestä {title}'
CustomExtraMeta: 'Omat meta-tagit'
CustomMetaDescription: Kuvaus
CustomMetaKeywords: Avainsanat
CustomMetaTitle: Otsikko
PLURALNAME: Alasivut
PLURALS:
one: Alasivu
other: '{count} alasivua'
PageTypeBlacklistField: 'Kiellä sivutyyppien käyttö?'
SINGULARNAME: Alasivusto
SiteConfigSubtitle: 'Tähän sloganisi'
SiteConfigTitle: 'Sivuston nimi'
ValidateTitle: 'Lisää "Otsikko"'
belongs_many_many_Groups: Ryhmät
db_DefaultSite: Oletussivusto
db_Language: Kieli
db_RedirectURL: 'Edelleenohjaus URL'
db_Theme: Teema
db_Title: Otsikko
has_many_Domains: Domainit
SilverStripe\Subsites\Model\SubsiteDomain:
DOMAIN: Verkkotunnus
DOMAIN_DESCRIPTION: 'Tämä alisivuston isäntänimi (ilman protokollaa). Sallii wildcard-merkit (*).'
ISPRIMARY_DESCRIPTION: 'Merkitse tämä oletusdomainiksi tälle alisivustolle'
IS_PRIMARY: 'On päädomain?'
PLURALNAME: 'Alisivuston domain-osoitteet'
PROTOCOL_AUTOMATIC: Automaattinen
PROTOCOL_HTTP: 'http://'
PROTOCOL_HTTPS: 'https://'
Protocol: Protokolla
SINGULARNAME: 'Alisivuston domain-osoite'
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
ReportDropdownSubsite: Alasivusto
Subsite:
COPYSTRUCTURE: 'Kopioi rakenne kohteesta:'
NOTEMPLATE: 'Ei sivupohjaa'
Subsites:
DefaultSiteFieldLabel: Oletussivusto
DomainFieldLabel: Domain
IsPublicFieldLabel: 'Aktivoi julkinen pääsy'
LanguageFieldLabel: Kieli
MainSiteTitle: Pääsivusto
PageTypeBlacklistFieldLabel: 'Sivutyyppien mustalista'
PrimaryDomainFieldLabel: Oletusdomain
RedirectURLFieldLabel: 'Edelleenohjaus URL'
ThemeFieldLabel: Teema
TitleFieldLabel: 'Alisivuston nimi'

14
lang/fr.yml Normal file
View File

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

87
lang/hr.yml Normal file
View File

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

52
lang/id.yml Normal file
View File

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

87
lang/it.yml Normal file
View File

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

60
lang/ja.yml Normal file
View File

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

43
lang/lt.yml Normal file
View File

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

76
lang/mi.yml Normal file
View File

@ -0,0 +1,76 @@
mi:
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'
SilverStripe\Subsites\Extensions\GroupSubsites:
ACCESSALL: 'Ngā pae iti katoa'
ACCESSONLY: 'Ko ēnei pae iti anake'
ACCESSRADIOTITLE: 'Tukuna tēnei rōpū kia uru ki'
GlobalGroup: 'Rōpū Hurinoa'
MANAGE_SUBSITES: 'Whakahaere pae iti mō ngā rōpū'
MANAGE_SUBSITES_HELP: 'Te āheinga ki te whakawhāiti whakaaetanga mō tētahi rōpū ki tētahi neke atu rānei o ngā pae iti.'
SECURITYTABTITLE: 'Ngā pae iti'
many_many_Subsites: 'Ngā pae iti'
SilverStripe\Subsites\Extensions\LeftAndMainSubsites:
Saved: 'Kua tiakina, whakahoutia ngā whārangi pāhono.'
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'
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
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'
ValidateTitle: 'Tāurua he "Taitara"'
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'
db_Domain: Rohe
has_one_Subsite: 'Pae iti'
SilverStripe\Subsites\Pages\SubsitesVirtualPage:
DESCRIPTION: 'Ka whakaatu i ngā ihirangi o tētahi whārangi ki tētahi atu pae iti'
PLURALNAME: 'Ngā Whārangi Taketake'
SINGULARNAME: 'Whārangi Mariko Pae Iti'
SubsiteField: 'Pae iti'
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
ReportDropdown: 'Ngā Pae'
ReportDropdownSubsite: 'Pae iti'
Subsite:
COPYSTRUCTURE: 'Tāurutia te hanganga mai i:'
NOTEMPLATE: 'Kāore he tātauira'
Subsites:
DefaultSiteFieldLabel: 'Pae taunoa'
DomainFieldLabel: Rohe
IsPublicFieldLabel: 'Whakahohe urunga tūmatatanui'
LanguageFieldLabel: Reo
MainSiteTitle: 'Pae matua'
PageTypeBlacklistFieldLabel: 'Rārangirāhui Momo Whārangi'
PrimaryDomainFieldLabel: 'Rohe Matua'
RedirectURLFieldLabel: 'Tukua anō te URL'
ThemeFieldLabel: Kaupapa
TitleFieldLabel: 'Ingoa Pae Iti'

View File

@ -1,40 +1,51 @@
nb_NO:
SubsiteAdmin:
MENUTITLE: "Underdomener"
GroupSubsites:
ACCESSALL: "All subsites"
ACCESSONLY: "Only these subsites"
ACCESSRADIOTITLE: "Give this group access to"
SECURITYTABTITLE: "subdomener"
SECURITYACCESS: "Begrens CMS tilgang til Subdomener"
ModelAdmin:
LOADEDFOREDITING: "Loaded \'%s\' for editing."
RelatedPageLink:
PLURALNAME: "Related Page Links"
SINGULARNAME: "Related Page Link"
Subsite:
PLURALNAME: "Subdomener"
SINGULARNAME: "Subdomene"
DOMAINSAVEFIRST: "You can only add domains after saving for the first time"
SubsiteDomain:
PLURALNAME: "Subsite Domains"
SINGULARNAME: "Subsite Domain"
DOMAIN: "Domain"
IS_PRIMARY: "Is Primary Domain"
Subsite_Template:
PLURALNAME: "Subsite Templats"
SINGULARNAME: "Subsite Template"
SubsitesVirtualPage:
PLURALNAME: "Subdomeners Virtuelle Sider"
SINGULARNAME: "Subdomeners Virtuelle Side"
VirtualPage:
CHOOSE: "Velg en side å lenke til"
EDITCONTENT: "klikk her for å endre dette innholdet"
GridFieldAddFromTemplateButton:
AddFromTemplate: "Add New from Template"
GridFieldAddFromTemplate:
NewFromTemplate: "New %s from template"
TEMPLATE_NOT_FOUND: "The selected template could not be found"
TEMPLATE: "Template"
LeftAndMainSubsites:
DEFAULT_SITE: "Default Site"
GridFieldAddFromTemplateButton:
AddFromTemplate: 'Add New from Template'
GroupSubsites:
ACCESSALL: 'All subsites'
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'

103
lang/nl.yml Normal file
View File

@ -0,0 +1,103 @@
nl:
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
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
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'
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'
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

56
lang/pl_PL.yml Normal file
View File

@ -0,0 +1,56 @@
pl_PL:
GridFieldAddFromTemplateButton:
AddFromTemplate: 'Dodaj nową na podstawie szablonu'
GroupSubsites:
ACCESSALL: 'Wszystkie podwitryny'
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'

87
lang/ru.yml Normal file
View File

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

37
lang/sk.yml Normal file
View File

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

35
lang/sl.yml Normal file
View File

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

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

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

14
lang/sv.yml Normal file
View File

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

13
lang/tr.yml Normal file
View File

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

View File

@ -1,40 +1,50 @@
tr_TR:
SubsiteAdmin:
MENUTITLE: "Alt Siteler"
GroupSubsites:
ACCESSALL: "All subsites"
ACCESSONLY: "Only these subsites"
ACCESSRADIOTITLE: "Give this group access to"
SECURITYTABTITLE: "Alt Siteler"
SECURITYACCESS: "Alt sitelere İYS erişimini kısıtla"
ModelAdmin:
LOADEDFOREDITING: "Loaded \'%s\' for editing."
RelatedPageLink:
PLURALNAME: "Related Page Links"
SINGULARNAME: "Related Page Link"
Subsite:
PLURALNAME: "Alt Siteler"
SINGULARNAME: "Alt Site"
DOMAINSAVEFIRST: "You can only add domains after saving for the first time"
SubsiteDomain:
PLURALNAME: "Subsite Domains"
SINGULARNAME: "Subsite Domain"
DOMAIN: "Domain"
IS_PRIMARY: "Is Primary Domain"
Subsite_Template:
PLURALNAME: "Subsite Templats"
SINGULARNAME: "Subsite Template"
SubsitesVirtualPage:
PLURALNAME: "Alt Site Sanal Sayfalar"
SINGULARNAME: "Alt Site Sanal Sayfa"
VirtualPage:
CHOOSE: "İzleyene bağlantı vermek için bir sayfa seçiniz: "
EDITCONTENT: "İçeriği düzenlemek için tıklayınız"
GridFieldAddFromTemplateButton:
AddFromTemplate: "Add New from Template"
GridFieldAddFromTemplate:
NewFromTemplate: "New %s from template"
TEMPLATE_NOT_FOUND: "The selected template could not be found"
TEMPLATE: "Template"
LeftAndMainSubsites:
DEFAULT_SITE: "Default Site"
GridFieldAddFromTemplateButton:
AddFromTemplate: 'Add New from Template'
GroupSubsites:
ACCESSALL: 'All subsites'
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'

76
lang/zh.yml Normal file
View File

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

12
license.md Normal file
View File

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

14
phpcs.xml.dist Normal file
View File

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

17
phpunit.xml.dist Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,65 @@
<?php
namespace SilverStripe\Subsites\Extensions;
use SilverStripe\Assets\FileNameFilter;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Core\Config\Config;
use SilverStripe\ORM\DataExtension;
use SilverStripe\ORM\DataObject;
use SilverStripe\Subsites\Model\Subsite;
class ErrorPageSubsite extends DataExtension
{
/**
* Alter file path to generated a static (static) error page file to handle error page template
* on different sub-sites
*
* @see ErrorPage::get_error_filename()
*
* FIXME since {@link Subsite::currentSubsite()} partly relies on Session, viewing other sub-site (including
* main site) between opening ErrorPage in the CMS and publish ErrorPage causes static error page to get
* generated incorrectly.
*
* @param string $name
* @param int $statusCode
*/
public function updateErrorFilename(&$name, &$statusCode)
{
$static_filepath = Config::inst()->get($this->owner->ClassName, 'static_filepath');
$subdomainPart = '';
// Try to get current subsite from session
$subsite = Subsite::currentSubsite();
// since this function is called from Page class before the controller is created, we have
// to get subsite from domain instead
if (!$subsite) {
$subsiteID = Subsite::getSubsiteIDForDomain();
if ($subsiteID != 0) {
$subsite = DataObject::get_by_id(Subsite::class, $subsiteID);
} else {
$subsite = null;
}
}
if ($subsite) {
$subdomain = $subsite->domain();
$subdomainPart = "-{$subdomain}";
}
// @todo implement Translatable namespace
if (singleton(SiteTree::class)->hasExtension('Translatable')
&& $locale
&& $locale != Translatable::default_locale()
) {
$fileName = "error-{$statusCode}-{$locale}{$subdomainPart}.html";
} else {
$fileName= "error-{$statusCode}{$subdomainPart}.html";
}
$fileName = FileNameFilter::create()->filter($fileName);
$name = implode('/', [$static_filepath, $fileName]);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,23 @@
<?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
* this class to your admin in config. eg:
*
* MyAdmin::add_extension('SubsiteMenuExtension');
*
* Or you can include the subsiteCMSShowInMenu function in your admin class and have it return true
*/
class SubsiteMenuExtension extends Extension
{
public function subsiteCMSShowInMenu()
{
return true;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

@ -1,59 +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="http://www.silverstripe.org/" target="_blank" title="SilverStripe (Version - $CMSVersion)">
SilverStripe <% if CMSVersion %><abbr class="version">$CMSVersion</abbr><% end_if %>
</a>
<span><% if SiteConfig %>$SiteConfig.Title<% else %>$ApplicationName<% end_if %></span>
</div>
<div class="cms-login-status">
<a href="Security/logout" class="logout-link" title="<% _t('LOGOUT','Log out') %>"><% _t('LOGOUT','Log out') %></a>
<% control CurrentMember %>
<span>
<% _t('Hello','Hi') %>
<a href="{$AbsoluteBaseURL}admin/myprofile" class="profile-link ss-ui-dialog-link" data-popupclass="edit-profile-popup">
<% if FirstName && Surname %>$FirstName $Surname<% else_if FirstName %>$FirstName<% else %>$Email<% end_if %>
</a>
</span>
<% end_control %>
</div>
<div class="cms-login-status subsites">
$SubsiteList
</div>
</div>
<div class="cms-panel-content center">
<ul class="cms-menu-list">
<% control MainMenu %>
<li class="$LinkingMode $FirstLast <% if LinkingMode == 'link' %><% else %>opened<% end_if %>" id="Menu-$Code">
<a href="$Link" <% if Code == 'Help' %>target="_blank"<% end_if%>>
<span class="icon icon-16 icon-{$Code.LowerCase}">&nbsp;</span>
<span class="text">$Title</span>
</a>
<% if Code == 'AssetAdmin' %>
<ul>
<li class="first <% if Top.class == 'AssetAdmin' %>current<% end_if %>" id="Menu-AssetAdmin">
<a href="admin/assets/">
<span class="text">Edit &amp; organize</span>
</a>
</li>
<li class="last <% if Top.class == 'CMSFileAddController' %>current<% end_if %>" id="Menu-CMSFileAddController">
<a href="admin/assets/add">
<span class="text">Add files</span>
</a>
</li>
</ul>
<% end_if %>
</li>
<% end_control %>
</ul>
</div>
<div class="cms-panel-toggle south">
<a class="toggle-expand" href="#"><span>&raquo;</span></a>
<a class="toggle-collapse" href="#"><span>&laquo;</span></a>
</div>
</div>

View File

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

View File

@ -0,0 +1,9 @@
<div class="cms-subsites" data-pjax-fragment="SubsiteList">
<div class="field dropdown">
<select id="SubsitesSelect">
<% loop $ListSubsites %>
<option value="$ID" $CurrentState>$Title</option>
<% end_loop %>
</select>
</div>
</div>

View File

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

View File

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

View File

@ -1,29 +0,0 @@
<?php
class LeftAndMainSubsitesTest extends FunctionalTest {
static $fixture_file = 'subsites/tests/SubsiteTest.yml';
function testAlternateAccessCheck() {
$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;
foreach($ids as $id) {
Subsite::changeSubsite($id); //switch to main site (subsite ID zero)
$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.");
}
}
}

View File

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

View File

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

View File

@ -1,43 +0,0 @@
<?php
class SubsiteAdminFunctionalTest extends FunctionalTest {
static $fixture_file = 'subsites/tests/SubsiteTest.yml';
static $use_draft_site = true;
protected $autoFollowRedirection = false;
/**
* Admin should be able to access all subsites and the main site
*/
function testAdminCanAccessAllSubsites() {
$member = $this->objFromFixture('Member', 'admin');
Session::set("loggedInAs", $member->ID);
$this->get('admin/pages?SubsiteID=0&ajax=1');
$this->get('admin');
$this->assertEquals(Subsite::currentSubsiteID(), '0', 'Can access main site');
$mainSubsite = $this->objFromFixture('Subsite_Template', 'main');
$this->get("admin/pages?SubsiteID={$mainSubsite->ID}&ajax=1");
$this->get('admin');
$this->assertEquals(Subsite::currentSubsiteID(), $mainSubsite->ID, 'Can access the subsite');
}
/**
* User which has AccessAllSubsites set to 1 should be able to access all subsites and main site,
* even though he does not have the ADMIN permission.
*/
function testEditorCanAccessAllSubsites() {
$member = $this->objFromFixture('Member', 'editor');
Session::set("loggedInAs", $member->ID);
$this->get('admin/pages?SubsiteID=0&ajax=1');
$this->get('admin');
$this->assertEquals(Subsite::currentSubsiteID(), '0', 'Can access main site');
$mainSubsite = $this->objFromFixture('Subsite_Template', 'main');
$this->get("admin/pages?SubsiteID={$mainSubsite->ID}&ajax=1");
$this->get('admin');
$this->assertEquals(Subsite::currentSubsiteID(), $mainSubsite->ID, 'Can access the subsite');
}
}

View File

@ -1,113 +0,0 @@
<?php
class SubsiteAdminTest extends SapphireTest {
static $fixture_file = 'subsites/tests/SubsiteTest.yml';
function adminLoggedInSession() {
return new Session(array(
'loggedInAs' => $this->idFromFixture('Member', 'admin')
));
}
/**
* Test generation of the view
*/
function testBasicView() {
Subsite::$write_hostmap = false;
$subsite1ID = $this->objFromFixture('Subsite','domaintest1')->ID;
// Open the admin area logged in as admin
$response1 = Director::test('admin/subsites/', null, $this->adminLoggedInSession());
// Confirm that this URL gets you the entire page, with the edit form loaded
$response2 = Director::test("admin/subsites/Subsite/EditForm/field/Subsite/item/$subsite1ID/edit", null, $this->adminLoggedInSession());
$this->assertTrue(strpos($response2->getBody(), 'id="Form_ItemEditForm_ID"') !== false, "Testing Form_ItemEditForm_ID exists");
$this->assertTrue(strpos($response2->getBody(), '<head') !== false, "Testing <head> exists");
}
/**
* Test searching for an intranet
*/
function XXtestIntranetSearch() {
$cont = new SubsiteAdmin();
$cont->pushCurrent();
$cont->setSession($this->adminLoggedInSession());
// Check that the logged-in member has the correct permissions
$this->assertTrue(Permission::check('ADMIN') ? true : false);
$form = $cont->SearchForm();
$searches = array(
array('Name' => 'Other'),
);
foreach($searches as $search) {
$response = $form->testAjaxSubmission('getResults', $search);
$links = $response->getLinks();
foreach($links as $link) {
$this->assertTrue(preg_match('/^admin\/subsites\/show\/[0-9]+$/', $link['href']) == 1, "Search result links bad.");
}
}
$cont->popCurrent();
}
/**
* Test the intranet creation form.
*/
function XXtestIntranetCreation() {
$cont = new SubsiteAdmin();
$cont->pushCurrent();
$cont->setSession($this->adminLoggedInSession());
$form = $cont->AddSubsiteForm();
$source = $form->dataFieldByName('TemplateID')->getSource();
$templateIDs = $this->allFixtureIDs('Subsite_Template');
foreach($templateIDs as $templateID) {
$this->assertArrayHasKey($templateID, $source);
}
$templateObj = $this->objFromFixture('Subsite_Template','main');
$this->assertEquals($templateObj->Title, $source[$templateObj->ID], "Template dropdown isn't listing Title values");
$response = $form->testSubmission('addintranet', array(
'Name' => 'Test Intranet',
'Domain' => 'test.example.com',
'TemplateID' => 1,
'AdminEmail' => '',
'AdminName' => '',
));
$this->assertTrue(true == preg_match('/admin\/subsites\/show\/([0-9]+)/i', $response->getHeader('Location'), $matches), "Intranet creation dowsn't redirect to new view");
$newIntranet = DataObject::get_by_id("Subsite", $matches[1]);
$this->assertEquals('Test Intranet', $newIntranet->Title, "New intranet not created properly.");
$cont->popCurrent();
}
/**
* Test that the main-site user with ADMIN permissions can access all subsites, regardless
* of whether he is in a subsite-specific group or not.
*/
function testMainsiteAdminCanAccessAllSubsites() {
$member = $this->objFromFixture('Member', 'admin');
Session::set("loggedInAs", $member->ID);
$cmsMain = new CMSMain();
foreach($cmsMain->Subsites() as $subsite) {
$ids[$subsite->ID] = true;
}
$this->assertArrayHasKey(0, $ids, "Main site accessible");
$this->assertArrayHasKey($this->idFromFixture('Subsite_Template','main'), $ids, "Site with no groups inaccesible");
$this->assertArrayHasKey($this->idFromFixture('Subsite_Template','subsite1'), $ids, "Subsite1 Template inaccessible");
$this->assertArrayHasKey($this->idFromFixture('Subsite_Template','subsite2'), $ids, "Subsite2 Template inaccessible");
}
}

View File

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

View File

@ -1,183 +0,0 @@
Subsite_Template:
main:
Title: Template
subsite1:
Title: Subsite1 Template
subsite2:
Title: Subsite2 Template
Subsite:
domaintest1:
Title: Test 1
domaintest2:
Title: Test 2
domaintest3:
Title: Test 3
SubsiteDomain:
subsite1:
SubsiteID: =>Subsite_Template.subsite1
Domain: subsite1.*
subsite2:
SubsiteID: =>Subsite_Template.subsite2
Domain: subsite2.*
dt1a:
SubsiteID: =>Subsite.domaintest1
Domain: one.example.org
IsPrimary: 1
dt1b:
SubsiteID: =>Subsite.domaintest1
Domain: one.*
dt2a:
SubsiteID: =>Subsite.domaintest2
Domain: two.mysite.com
IsPrimary: 1
dt2b:
SubsiteID: =>Subsite.domaintest2
Domain: *.mysite.com
dt3:
SubsiteID: =>Subsite.domaintest3
Domain: three.*
IsPrimary: 1
SiteTree:
mainSubsitePage:
Title: MainSubsitePage
SubsiteID: 0
home:
Title: Home
SubsiteID: =>Subsite_Template.main
about:
Title: About
SubsiteID: =>Subsite_Template.main
linky:
Title: Linky
MetaTitle: Linky
SubsiteID: =>Subsite_Template.main
staff:
Title: Staff
ParentID: =>SiteTree.about
SubsiteID: =>Subsite_Template.main
contact:
Title: Contact Us
SubsiteID: =>Subsite_Template.main
importantpage:
Title: Important Page
SubsiteID: =>Subsite_Template.main
subsite1_home:
Title: Home
SubsiteID: =>Subsite_Template.subsite1
subsite1_contactus:
Title: Contact Us
SubsiteID: =>Subsite_Template.subsite1
subsite1_staff:
Title: Staff
SubsiteID: =>Subsite_Template.subsite1
subsite2_home:
Title: Home
SubsiteID: =>Subsite_Template.subsite2
subsite2_contactus:
Title: Contact Us
SubsiteID: =>Subsite_Template.subsite2
PermissionRoleCode:
roleCode1:
Code: CMS_ACCESS_CMSMain
PermissionRole:
role1:
Title: role1
Codes: =>PermissionRoleCode.roleCode1
Group:
admin:
Title: Admin
Code: admin
AccessAllSubsites: 1
editor:
Title: Editor
Code: editor
AccessAllSubsites: 1
subsite1_group:
Title: subsite1_group
Code: subsite1_group
AccessAllSubsites: 0
Subsites: =>Subsite_Template.subsite1
subsite2_group:
Title: subsite2_group
Code: subsite2_group
AccessAllSubsites: 0
Subsites: =>Subsite_Template.subsite2
subsite1admins:
Title: subsite1admins
Code: subsite1admins
AccessAllSubsites: 0
Subsites: =>Subsite_Template.subsite1
allsubsitesauthors:
Title: allsubsitesauthors
Code: allsubsitesauthors
AccessAllSubsites: 1
subsite1_group_via_role:
Title: subsite1_group_via_role
Code: subsite1_group_via_role
AccessAllSubsites: 1
Roles: =>PermissionRole.role1
Permission:
admin:
Code: ADMIN
GroupID: =>Group.admin
editor1:
Code: CMS_ACCESS_CMSMain
GroupID: =>Group.editor
editor2:
Code: SITETREE_VIEW_ALL
GroupID: =>Group.editor
editor3:
Code: VIEW_DRAFT_CONTENT
GroupID: =>Group.editor
accesscmsmain1:
Code: CMS_ACCESS_CMSMain
GroupID: =>Group.subsite1_group
accesscmsmain2:
Code: CMS_ACCESS_CMSMain
GroupID: =>Group.subsite2_group
accesscmsmain3:
Code: CMS_ACCESS_CMSMain
GroupID: =>Group.subsite1admins
accesscmsmain4:
Code: CMS_ACCESS_CMSMain
GroupID: =>Group.allsubsitesauthors
securityaccess1:
Code: CMS_ACCESS_SecurityAdmin
GroupID: =>Group.subsite1_group
securityaccess2:
Code: CMS_ACCESS_SecurityAdmin
GroupID: =>Group.subsite2_group
adminsubsite1:
Code: ADMIN
GroupID: =>Group.subsite1admins
Member:
admin:
FirstName: Admin
Surname: User
Email: admin@test.com
Password: rangi
Groups: =>Group.admin
editor:
FirstName: Editor
Surname: User
Email: editor@test.com
Password: rangi
Groups: =>Group.editor
subsite1member:
Email: subsite1member@test.com
Groups: =>Group.subsite1_group
subsite2member:
Email: subsite2member@test.com
Groups: =>Group.subsite2_group
subsite1admin:
Email: subsite1admin@test.com
Groups: =>Group.subsite1admins
allsubsitesauthor:
Email: allsubsitesauthor@test.com
Groups: =>Group.allsubsitesauthors
subsite1member2:
Email: subsite1member2@test.com
Groups: =>Group.subsite1_group_via_role

View File

@ -1,259 +0,0 @@
<?php
class SubsitesVirtualPageTest extends SapphireTest {
static $fixture_file = array(
'subsites/tests/SubsiteTest.yml',
'subsites/tests/SubsitesVirtualPageTest.yml',
);
function setUp() {
parent::setUp();
$this->logInWithPermission('ADMIN');
$fh = fopen(Director::baseFolder() . '/assets/testscript-test-file.pdf', "w");
fwrite($fh, str_repeat('x',1000000));
fclose($fh);
}
function tearDown() {
parent::tearDown();
$testFiles = array(
'/assets/testscript-test-file.pdf',
'/assets/renamed-test-file.pdf',
'/assets/renamed-test-file-second-time.pdf',
);
foreach($testFiles as $file) {
if(file_exists(Director::baseFolder().$file)) unlink(Director::baseFolder().$file);
}
}
// Attempt to bring main:linky to subsite2:linky
function testVirtualPageFromAnotherSubsite() {
Subsite::$write_hostmap = false;
$subsite = $this->objFromFixture('Subsite_Template', 'subsite2');
Subsite::changeSubsite($subsite->ID);
Subsite::$disable_subsite_filter = false;
$linky = $this->objFromFixture('SiteTree', 'linky');
$svp = new SubsitesVirtualPage();
$svp->CopyContentFromID = $linky->ID;
$svp->SubsiteID = $subsite->ID;
$svp->URLSegment = 'linky';
$svp->write();
$this->assertEquals($svp->SubsiteID, $subsite->ID);
$this->assertEquals($svp->Title, $linky->Title);
}
/**
* Test custom metadata. Reloading Content should not
* obliterate our custom fields
*/
function testCustomMetadata() {
Subsite::$write_hostmap = false;
$subsite = $this->objFromFixture('Subsite_Template', 'main');
Subsite::changeSubsite($subsite->ID);
$orig = $this->objFromFixture('SiteTree', 'linky');
$svp = new SubsitesVirtualPage();
$svp->CopyContentFromID = $orig->ID;
$svp->SubsiteID = $subsite->ID;
$svp->URLSegment = 'linky-'.rand();
$svp->write();
$this->assertEquals($svp->MetaTitle, 'Linky');
$svp->CustomMetaTitle = 'SVPTitle';
$svp->write();
$this->assertEquals($svp->MetaTitle, 'SVPTitle');
$svp->copyFrom($svp->CopyContentFrom());
$svp->write();
$this->assertEquals($svp->MetaTitle, 'SVPTitle');
}
function testFileLinkRewritingOnVirtualPages() {
// File setup
$this->logInWithPermission('ADMIN');
touch(Director::baseFolder() . '/assets/testscript-test-file.pdf');
// Publish the source page
$page = $this->objFromFixture('Page', 'page1');
$this->assertTrue($page->doPublish());
// Create a virtual page from it, and publish that
$svp = new SubsitesVirtualPage();
$svp->CopyContentFromID = $page->ID;
$svp->write();
$svp->doPublish();
// Rename the file
$file = $this->objFromFixture('File', 'file1');
$file->Name = 'renamed-test-file.pdf';
$file->write();
// Verify that the draft and publish virtual pages both have the corrected link
$this->assertContains('<img src="assets/renamed-test-file.pdf"',
DB::query("SELECT \"Content\" FROM \"SiteTree\" WHERE \"ID\" = $svp->ID")->value());
$this->assertContains('<img src="assets/renamed-test-file.pdf"',
DB::query("SELECT \"Content\" FROM \"SiteTree_Live\" WHERE \"ID\" = $svp->ID")->value());
// File teardown
$testFiles = array(
'/assets/testscript-test-file.pdf',
'/assets/renamed-test-file.pdf',
);
foreach($testFiles as $file) {
if(file_exists(Director::baseFolder().$file)) unlink(Director::baseFolder().$file);
}
}
function testSubsiteVirtualPagesArentInappropriatelyPublished() {
// Fixture
$p = new Page();
$p->Content = "test content";
$p->write();
$vp = new SubsitesVirtualPage();
$vp->CopyContentFromID = $p->ID;
$vp->write();
// VP is oragne
$this->assertTrue($vp->IsAddedToStage);
// VP is still orange after we publish
$p->doPublish();
$this->fixVersionNumberCache($vp);
$this->assertTrue($vp->IsAddedToStage);
// A new VP created after P's initial construction
$vp2 = new SubsitesVirtualPage();
$vp2->CopyContentFromID = $p->ID;
$vp2->write();
$this->assertTrue($vp2->IsAddedToStage);
// Also remains orange after a republish
$p->Content = "new content";
$p->write();
$p->doPublish();
$this->fixVersionNumberCache($vp2);
$this->assertTrue($vp2->IsAddedToStage);
// VP is now published
$vp->doPublish();
$this->fixVersionNumberCache($vp);
$this->assertTrue($vp->ExistsOnLive);
$this->assertFalse($vp->IsModifiedOnStage);
// P edited, VP and P both go green
$p->Content = "third content";
$p->write();
$this->fixVersionNumberCache($vp, $p);
$this->assertTrue($p->IsModifiedOnStage);
$this->assertTrue($vp->IsModifiedOnStage);
// Publish, VP goes black
$p->doPublish();
$this->fixVersionNumberCache($vp);
$this->assertTrue($vp->ExistsOnLive);
$this->assertFalse($vp->IsModifiedOnStage);
}
function testUnpublishingParentPageUnpublishesSubsiteVirtualPages() {
StaticPublisher::$disable_realtime = true;
// Go to main site, get parent page
$subsite = $this->objFromFixture('Subsite_Template', 'main');
Subsite::changeSubsite($subsite->ID);
$page = $this->objFromFixture('SiteTree', 'importantpage');
// Create two SVPs on other subsites
$subsite = $this->objFromFixture('Subsite_Template', 'subsite1');
Subsite::changeSubsite($subsite->ID);
$vp1 = new SubsitesVirtualPage();
$vp1->CopyContentFromID = $page->ID;
$vp1->write();
$vp1->doPublish();
$subsite = $this->objFromFixture('Subsite_Template', 'subsite2');
Subsite::changeSubsite($subsite->ID);
$vp2 = new SubsitesVirtualPage();
$vp2->CopyContentFromID = $page->ID;
$vp2->write();
$vp2->doPublish();
// Switch back to main site, unpublish source
$subsite = $this->objFromFixture('Subsite_Template', 'main');
Subsite::changeSubsite($subsite->ID);
$page = $this->objFromFixture('SiteTree', 'importantpage');
$page->doUnpublish();
Subsite::changeSubsite($vp1->SubsiteID);
$onLive = Versioned::get_one_by_stage('SubsitesVirtualPage', 'Live', "\"SiteTree_Live\".\"ID\" = ".$vp1->ID);
$this->assertNull($onLive, 'SVP has been removed from live');
$subsite = $this->objFromFixture('Subsite_Template', 'subsite2');
Subsite::changeSubsite($vp2->SubsiteID);
$onLive = Versioned::get_one_by_stage('SubsitesVirtualPage', 'Live', "\"SiteTree_Live\".\"ID\" = ".$vp2->ID);
$this->assertNull($onLive, 'SVP has been removed from live');
}
/**
* Similar to {@link SiteTreeSubsitesTest->testTwoPagesWithSameURLOnDifferentSubsites()}
* and {@link SiteTreeSubsitesTest->testPagesInDifferentSubsitesCanShareURLSegment()}.
*/
function testSubsiteVirtualPageCanHaveSameUrlsegmentAsOtherSubsite() {
Subsite::$write_hostmap = false;
$subsite1 = $this->objFromFixture('Subsite_Template', 'subsite1');
$subsite2 = $this->objFromFixture('Subsite_Template', 'subsite2');
Subsite::changeSubsite($subsite1->ID);
$subsite1Page = $this->objFromFixture('SiteTree', 'subsite1_staff');
$subsite1Page->URLSegment = 'staff';
$subsite1Page->write();
// saving on subsite1, and linking to subsite1
$subsite1Vp = new SubsitesVirtualPage();
$subsite1Vp->CopyContentFromID = $subsite1Page->ID;
$subsite1Vp->SubsiteID = $subsite1->ID;
$subsite1Vp->write();
$this->assertNotEquals(
$subsite1Vp->URLSegment,
$subsite1Page->URLSegment,
"Doesn't allow explicit URLSegment overrides when already existing in same subsite"
);
//Change to subsite 2
Subsite::changeSubsite($subsite2->ID);
// saving in subsite2 (which already has a page with URLSegment 'contact-us'),
// but linking to a page in subsite1
$subsite2Vp = new SubsitesVirtualPage();
$subsite2Vp->CopyContentFromID = $subsite1Page->ID;
$subsite2Vp->SubsiteID = $subsite2->ID;
$subsite2Vp->write();
$this->assertEquals(
$subsite2Vp->URLSegment,
$subsite1Page->URLSegment,
"Does allow explicit URLSegment overrides when only existing in a different subsite"
);
}
function fixVersionNumberCache($page) {
$pages = func_get_args();
foreach($pages as $p) {
Versioned::prepopulate_versionnumber_cache('SiteTree', 'Stage', array($p->ID));
Versioned::prepopulate_versionnumber_cache('SiteTree', 'Live', array($p->ID));
}
}
}

View File

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

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

@ -0,0 +1,29 @@
Feature: Preview navigation
As a CMS user
I can navigate a subsite in the preview pane
In order to preview my content
Background:
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/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 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, follow a link to return to first one.
And I follow "Goto my page" in preview
And I wait for 1 second
Then the preview contains "My page content"

View File

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,20 @@
<?php
namespace SilverStripe\Subsites\Tests\SiteTreeSubsitesTest;
use SilverStripe\Dev\TestOnly;
use SilverStripe\ErrorPage\ErrorPage;
class TestErrorPage extends ErrorPage implements TestOnly
{
/**
* Helper method to call protected members
*
* @param int $statusCode
* @return string
*/
public static function get_error_filename_spy($statusCode)
{
return self::get_error_filename($statusCode);
}
}

View File

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

View File

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

View File

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

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

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

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

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.