Compare commits
576 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
0a3056267c | ||
|
92363c4ca8 | ||
|
5025c32fc7 | ||
|
dcc39392cb | ||
|
d66e723a51 | ||
|
23b9db798d | ||
|
83600d758a | ||
|
dd3073f41a | ||
|
4e292b8e89 | ||
|
e020766095 | ||
|
4049f7a12d | ||
|
40ee7c76db | ||
|
178b521c54 | ||
|
7d1431df5e | ||
|
39de47167d | ||
|
3ec16bbe56 | ||
|
6ff4f336f7 | ||
|
a052bfd590 | ||
|
d1d1b139d0 | ||
|
b855a555c8 | ||
|
c847f3e1d8 | ||
|
86205a6286 | ||
|
413de7014c | ||
|
b4bf58e132 | ||
|
a249d46dcc | ||
|
701c6cd053 | ||
|
4006884aa7 | ||
|
aa9797a5f1 | ||
|
5e823223d7 | ||
|
04fe468f36 | ||
|
21240a7c83 | ||
|
339b0c855b | ||
|
d11124f576 | ||
|
412b2709d2 | ||
|
7d8909ac6d | ||
|
30b1f09af4 | ||
|
6770dedc2a | ||
|
67a21914eb | ||
|
901dbc8848 | ||
|
4308ac4316 | ||
|
73f3d15bfb | ||
|
a7e9e8dcdc | ||
|
5f489b1df9 | ||
|
f45ccead3c | ||
|
416f55ad03 | ||
|
ceaa915b77 | ||
|
aba286d8a3 | ||
|
a1ee94ce61 | ||
|
8ba7070b02 | ||
|
1e311e8668 | ||
|
5eb7e8a7a3 | ||
|
837ab70a8d | ||
|
800e8dc473 | ||
|
d13d73299a | ||
|
945e2bc370 | ||
|
9ad6c8f97e | ||
|
f566ab2b2a | ||
|
0338d41626 | ||
|
193866ec62 | ||
|
74b6cec374 | ||
|
b16495cf54 | ||
|
cdc37de076 | ||
|
b9d972b7fd | ||
|
6e1b504ff3 | ||
|
2bab73ee35 | ||
|
6969fe06d3 | ||
|
7860a03180 | ||
|
ed4663be9b | ||
|
acf9715c3b | ||
|
5c3d000b9b | ||
|
dfe1ba5c6a | ||
|
07aefb1982 | ||
|
2a16e1eefa | ||
|
cae9bd51ec | ||
|
5049a5e13c | ||
|
13ab072303 | ||
|
0179176b6c | ||
|
3a2ef2b3bc | ||
|
643b8d436a | ||
|
e306264740 | ||
|
f4ec177065 | ||
|
bd690ae62b | ||
|
3fb2e9a1e0 | ||
|
08e5af4f2b | ||
|
b5bd1e69b5 | ||
|
017804dbd4 | ||
|
683e7da208 | ||
|
212790ae29 | ||
|
5a4d613d8e | ||
|
27bae53017 | ||
|
fd10b868ec | ||
|
f1fce6f739 | ||
|
2175d44755 | ||
|
831c3c3cbe | ||
|
67af02c3b2 | ||
|
b32499ef31 | ||
|
5d015c7a96 | ||
|
38b356c256 | ||
|
5cf44c9d02 | ||
|
b1c1931d5d | ||
|
917640699d | ||
|
09abe2b2f2 | ||
|
9a7cdbbe2d | ||
|
ce63a9ed08 | ||
|
58f89801b0 | ||
|
9feef185dc | ||
|
2eb04ffa78 | ||
|
e73d622bdb | ||
|
d0054a1294 | ||
|
c4adf556cf | ||
|
644b9c8b90 | ||
|
81e6d0fe59 | ||
|
9abaca6d48 | ||
|
7d27abf2b1 | ||
|
001f44d73b | ||
|
ca01e2680a | ||
|
e41dc8b018 | ||
|
9655371276 | ||
|
67d10ec0cb | ||
|
4fdf2e24e3 | ||
|
614819a1d3 | ||
|
be3bcab715 | ||
|
135ae961bf | ||
|
eddbc90524 | ||
|
a4e99a2df5 | ||
|
77fafe6450 | ||
|
4249fffc0f | ||
|
b3bd51cb6c | ||
|
0275bb1eca | ||
|
483a867289 | ||
|
46a863557b | ||
|
19edb78756 | ||
|
5c4a655106 | ||
|
f6503822e8 | ||
|
2b26876596 | ||
|
900d04d94a | ||
|
1f51fcd909 | ||
|
33244fb430 | ||
|
b8576744a1 | ||
|
1595079156 | ||
|
9ee451f706 | ||
|
2a9f3ac0f6 | ||
|
fadd42910b | ||
|
c60acb3190 | ||
|
4d7641e16a | ||
|
3b8207d70c | ||
|
68c763da3e | ||
|
1e44e1d4ba | ||
|
2644083a2d | ||
|
ee961594bc | ||
|
e313f2ed5d | ||
|
536420ec68 | ||
|
f8e4804cc1 | ||
|
46653e2b07 | ||
|
fe20bc2907 | ||
|
ec327aee7c | ||
|
d1fc84d15c | ||
|
f003fb5e74 | ||
|
9dbdd992f7 | ||
|
1294671086 | ||
|
fbd98ff402 | ||
|
d9fcaa3319 | ||
|
da2e8fcc8b | ||
|
ce9dd1a856 | ||
|
59f6685e2a | ||
|
e510213c3e | ||
|
a0ecbdf4b6 | ||
|
e4fe534f10 | ||
|
3afdd01d41 | ||
|
2a35a5c70a | ||
|
1fa549886f | ||
|
2bf4812947 | ||
|
810ee63ea6 | ||
|
5e79abdbbc | ||
|
ff9997e0f2 | ||
|
bf7dd9c37b | ||
|
9199d509d6 | ||
|
bbfb93d50d | ||
|
7cc86199e7 | ||
|
1a5666182e | ||
|
2a6f7b5dfb | ||
|
6a8f15a194 | ||
|
e52fe41a23 | ||
|
1e458ef03d | ||
|
4323db52f0 | ||
|
bb226a0652 | ||
|
fa3f1fa767 | ||
|
313d22ffca | ||
|
70dc70f494 | ||
|
7681634cb2 | ||
|
6af985420f | ||
|
039a7a8c84 | ||
|
e8a72e1c33 | ||
|
5b8a0dbf13 | ||
|
8af796fa7a | ||
|
7f28c32427 | ||
|
6747b5ffe8 | ||
|
9ba1275b49 | ||
|
687e013793 | ||
|
f24fd60f14 | ||
|
87485e39f4 | ||
|
6e35807dc7 | ||
|
c177a9f640 | ||
|
d8088edfa9 | ||
|
bf2c81dce6 | ||
|
4b6804eaab | ||
|
7e1c2eb0aa | ||
|
191c63bd9b | ||
|
f635aa9811 | ||
|
c732c0c799 | ||
|
ffbcb9a0c8 | ||
|
d78e3c4662 | ||
|
75b851326d | ||
|
19f0162265 | ||
|
5370bc8af6 | ||
|
c43abb05c2 | ||
|
56bc57a5c5 | ||
|
de0bba0df2 | ||
|
153db12d42 | ||
|
f0a5dcf63c | ||
|
f19b82f029 | ||
|
21f4d80ef2 | ||
|
dc9d6de62d | ||
|
9038a166bd | ||
|
315621892d | ||
|
5285a68404 | ||
|
90bddc7e55 | ||
|
aae3e655c4 | ||
|
875f5519ed | ||
|
e8b292ecc5 | ||
|
b6ba567ee5 | ||
|
8222f619f8 | ||
|
1e5ee559b0 | ||
|
c30c0d94eb | ||
|
af06f803c5 | ||
|
032ea73154 | ||
|
631498fd1d | ||
|
5c801a4392 | ||
|
94e88b5be9 | ||
|
adc0395c25 | ||
|
cc064c43ce | ||
|
531a38e043 | ||
|
6a8da1caaa | ||
|
b4943fb77c | ||
|
7115d060a6 | ||
|
418fa586a3 | ||
|
fe1c0a58c0 | ||
|
c7425ef23e | ||
|
67bb7e0028 | ||
|
0fcde6c0bb | ||
|
2f7dc7a7a8 | ||
|
30454dc04f | ||
|
6feec1de34 | ||
|
856496e09d | ||
|
7f77bb0c17 | ||
|
afd23be64c | ||
|
8398a361cd | ||
|
f8f39c135f | ||
|
24ee5a186e | ||
|
e04d04dc70 | ||
|
742c4a7fa3 | ||
|
e4702dc132 | ||
|
299f12765c | ||
|
5279748931 | ||
|
ab24aa6d9f | ||
|
510d80683a | ||
|
73943afd5b | ||
|
34c0c4946c | ||
|
5404dafac1 | ||
|
e405dff946 | ||
|
809b1d9b85 | ||
|
098660e27d | ||
|
8b5f593999 | ||
|
db69c486a0 | ||
|
bb5f2c07e1 | ||
|
aa370f8df6 | ||
|
4f15894f86 | ||
|
cde41c4409 | ||
|
34b3fbf04d | ||
|
6fb0af0f93 | ||
|
8eee411d5a | ||
|
17427fd251 | ||
|
6bbf988fda | ||
|
73f5bcaecc | ||
|
17e0db47a1 | ||
|
d02b668885 | ||
|
a88ac5d05d | ||
|
ace068b93d | ||
|
4f174ffd6e | ||
|
a0836bed79 | ||
|
1dd1f2ce6c | ||
|
a201e7e953 | ||
|
4e297d920b | ||
|
1cdae5024a | ||
|
157db89dbf | ||
|
727efb0ea0 | ||
|
01d09695a9 | ||
|
33622c440b | ||
|
b529947362 | ||
|
bcab2af5af | ||
|
95a71566f1 | ||
|
0ebf9509bf | ||
|
8fcfe55ba8 | ||
|
7bf430ba83 | ||
|
1dcba49e5c | ||
|
0157bd0c05 | ||
|
4a1f571f1c | ||
|
a2a86c6eb1 | ||
|
7301099241 | ||
|
c2a81f76ae | ||
|
fa6b80624c | ||
|
3996810fa9 | ||
|
62df0b45a5 | ||
|
4328f8aa35 | ||
|
9822807a58 | ||
|
c343542106 | ||
|
961185a6fd | ||
|
95e437d828 | ||
|
d9fb455d3d | ||
|
e83435b2b6 | ||
|
ea00ac3520 | ||
|
ed8efc2144 | ||
|
05953f1758 | ||
|
02a0895f4a | ||
|
0f5c6008e1 | ||
|
20e1c016f1 | ||
|
188b02df6b | ||
|
32385e580d | ||
|
784c3fca5e | ||
|
1704d1d033 | ||
|
9bccfffe4e | ||
|
b8ab1eda7e | ||
|
94755775ba | ||
|
83077ff78f | ||
|
60b259ebb5 | ||
|
1452e3600b | ||
|
774dee91d6 | ||
|
40b8e102d0 | ||
|
bbf4b67e23 | ||
|
827f4f2fe9 | ||
|
f0b762cd0d | ||
|
c1bf1bcc21 | ||
|
2cbdeba69a | ||
|
58b8476ede | ||
|
e84c4f5d7d | ||
|
4884bdde05 | ||
|
4328c20b45 | ||
|
669995d544 | ||
|
20b81fc4b0 | ||
|
9d186d6e7a | ||
|
bd26907abf | ||
|
f7bdc570d1 | ||
|
2da5828e90 | ||
|
de2220754c | ||
|
df4f68be08 | ||
|
aab69e0baa | ||
|
8c4a4e743c | ||
|
e331c51abb | ||
|
a7fee1729e | ||
|
56accd0856 | ||
|
adfa7257d7 | ||
|
19aeb8fd64 | ||
|
65f85faff6 | ||
|
1ac6e78bb3 | ||
|
793b46ede3 | ||
|
314f218306 | ||
|
ffe0d61c4f | ||
|
c3471ece1c | ||
|
b9582167c7 | ||
|
d934fbe08c | ||
|
1a9797c185 | ||
|
46bcffaa86 | ||
|
b0087b9035 | ||
|
c155855100 | ||
|
c620ff02f4 | ||
|
38031887a9 | ||
|
e129cafa94 | ||
|
5cf2d87e59 | ||
|
ce360aa383 | ||
|
c081de1202 | ||
|
7ffaf61aeb | ||
|
8d8ee14cc2 | ||
|
9fdc1d6607 | ||
|
60725e7b5c | ||
|
8be959f747 | ||
|
ddefef11d6 | ||
|
af6f499cac | ||
|
9a289a2e17 | ||
|
428a5f958e | ||
|
ab81117c5e | ||
|
a13bf10184 | ||
|
03e52101bb | ||
|
9b09eb94e0 | ||
|
4d38452435 | ||
|
9b97f8af89 | ||
|
a4f647c9ed | ||
|
43d128d520 | ||
|
6eed988317 | ||
|
5ec1c94410 | ||
|
438836bfcc | ||
|
32c45d0856 | ||
|
8bc9728104 | ||
|
24ebd1c9f9 | ||
|
83b0afe4d4 | ||
|
5be5f1c3b3 | ||
|
2b7c3d1f85 | ||
|
877f4f5f9d | ||
|
1975861aec | ||
|
c2484365cf | ||
|
49fbfcb459 | ||
|
21a8c56217 | ||
|
a4a1ab6a78 | ||
|
55c7240425 | ||
|
9673c881c1 | ||
|
fea1684f5c | ||
|
9d3c4506af | ||
|
849c0061e8 | ||
|
25754e1158 | ||
|
17010f39a3 | ||
|
abe1ac9fe6 | ||
|
4fc13b19f7 | ||
|
e7ad086641 | ||
|
76852594a8 | ||
|
e86cc55ba6 | ||
|
e03e7d9ce9 | ||
|
212e4797b0 | ||
|
6f1e1ab953 | ||
|
709cbfa2c8 | ||
|
f348f5fa97 | ||
|
997459caf3 | ||
|
492f437589 | ||
|
e2bdd5ca41 | ||
|
5d3af16aaf | ||
|
d8e72f3ac9 | ||
|
8586749318 | ||
|
b8f98323ae | ||
|
9862cf5ea6 | ||
|
4126a0a1fa | ||
|
d1e829697f | ||
|
c5f507b3f9 | ||
|
2295501587 | ||
|
ef602abe47 | ||
|
a49189ef58 | ||
|
a7ef6472ee | ||
|
f814534d93 | ||
|
b71e544820 | ||
|
fe6d93eaac | ||
|
8aa6512a49 | ||
|
80885a75d7 | ||
|
ff6d28b067 | ||
|
2c3b5bf5af | ||
|
e33a5b4cae | ||
|
2c84e627db | ||
|
5bb718224c | ||
|
6225b2d680 | ||
|
0fc3490219 | ||
|
8705a46b98 | ||
|
77fb5c8d77 | ||
|
6fb0aab811 | ||
|
10302932a9 | ||
|
2cce5e1606 | ||
|
ae6badf5c0 | ||
|
84551163a6 | ||
|
e8f5f58bb0 | ||
|
81c95c9f3e | ||
|
9f498616fd | ||
|
0e61dfc3f6 | ||
|
7c97a8ae74 | ||
|
6afac5f9af | ||
|
65bf5c732b | ||
|
b8667fb933 | ||
|
3962df53cd | ||
|
c8f6f4a588 | ||
|
871e2bcefd | ||
|
ff28ac1b1e | ||
|
f7bab259b7 | ||
|
98636f8f58 | ||
|
bdecbe9097 | ||
|
9e7142d229 | ||
|
a98958fdf9 | ||
|
e556a57250 | ||
|
8572263cb6 | ||
|
50cc4f857a | ||
|
6e91600507 | ||
|
c7dbf44f11 | ||
|
3587bc666f | ||
|
01f976aa28 | ||
|
07d449fa2d | ||
|
cf642aae2f | ||
|
504342b45e | ||
|
7ee8bdbf72 | ||
|
cef0781c92 | ||
|
20c6cbc851 | ||
|
0a93a42c2c | ||
|
6ea8dc7ca8 | ||
|
8a0afcf225 | ||
|
a1aba25708 | ||
|
69693e77ca | ||
|
8b049fd46c | ||
|
c15491c8c5 | ||
|
598d45aca6 | ||
|
fb670d30d7 | ||
|
540071c449 | ||
|
3f3fa95e2c | ||
|
686bf3bdda | ||
|
d803de205f | ||
|
18f248fadd | ||
|
257d71d946 | ||
|
280562fb7e | ||
|
8458c52429 | ||
|
8afbb6842f | ||
|
145b8e6633 | ||
|
b4024c94bb | ||
|
2803573a9c | ||
|
1876b78204 | ||
|
a4c925b625 | ||
|
ce90c2124b | ||
|
218382b984 | ||
|
4c17537d1d | ||
|
a0ede56c0e | ||
|
e241e70fbf | ||
|
a38c3a6ff3 | ||
|
0d7dc49d6b | ||
|
d45ff44edb | ||
|
2fc67a1e5d | ||
|
7ba4a20b75 | ||
|
933cfc8cfa | ||
|
9000bc47c5 | ||
|
415206e70b | ||
|
4b0440b15b | ||
|
5f02e0c406 | ||
|
1fc2fe5440 | ||
|
a9ee70c5dd | ||
|
984d5d9a59 | ||
|
5e18e8dca3 | ||
|
c22c8dc810 | ||
|
e9678221fc | ||
|
cf534aad31 | ||
|
63aa46aa36 | ||
|
c7077ae749 | ||
|
79625583bd | ||
|
8c9c62f08c | ||
|
0d9d697286 | ||
|
798a1158d6 | ||
|
b979b38694 | ||
|
3bcaa48d67 | ||
|
9c9e0bfa94 | ||
|
ce3f8c9060 | ||
|
0274b9effd | ||
|
80daa54dfc | ||
|
996abfcc58 | ||
|
6f06dd565b | ||
|
601e174b34 | ||
|
750cdbcff9 | ||
|
c97b1140bf | ||
|
5604879146 | ||
|
bfc70f9b06 | ||
|
c18a0a266f | ||
|
999c115961 | ||
|
c389f36eda | ||
|
2d4d35b1a7 | ||
|
d701afcb61 | ||
|
2595d655cb | ||
|
831a918f8d | ||
|
5b9af35566 | ||
|
e6c8dff7be | ||
|
94edb694b7 | ||
|
d99785d773 | ||
|
6d6667aa08 | ||
|
0520b57f84 | ||
|
b6f59741d6 | ||
|
533c7fae8d | ||
|
8acc4a230a | ||
|
ba6cb193b7 | ||
|
a97b0d33eb | ||
|
47df87f62c |
23
.editorconfig
Normal 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
@ -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
@ -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
|
16
.github/workflows/deploy-userhelp-docs.yml
vendored
Normal 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
@ -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
@ -0,0 +1,17 @@
|
|||||||
|
name: Keepalive
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
# The 4th of every month at 10:50am UTC
|
||||||
|
schedule:
|
||||||
|
- cron: '50 10 4 * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
keepalive:
|
||||||
|
name: Keepalive
|
||||||
|
# Only run cron on the silverstripe account
|
||||||
|
if: (github.event_name == 'schedule' && github.repository_owner == 'silverstripe') || (github.event_name != 'schedule')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Keepalive
|
||||||
|
uses: silverstripe/gha-keepalive@v1
|
1
.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
|
host-map.php
|
||||||
|
34
.travis.yml
@ -1,34 +0,0 @@
|
|||||||
# See https://github.com/silverstripe-labs/silverstripe-travis-support for setup details
|
|
||||||
|
|
||||||
language: php
|
|
||||||
|
|
||||||
php:
|
|
||||||
- 5.4
|
|
||||||
|
|
||||||
env:
|
|
||||||
global:
|
|
||||||
- CORE_RELEASE=3.1
|
|
||||||
matrix:
|
|
||||||
- DB=MYSQL
|
|
||||||
- DB=PGSQL
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- php: 5.3
|
|
||||||
env: DB=MYSQL
|
|
||||||
- php: 5.4
|
|
||||||
env: DB=MYSQL BEHAT_TEST=1
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
- composer self-update
|
|
||||||
- phpenv rehash
|
|
||||||
- git clone git://github.com/silverstripe-labs/silverstripe-travis-support.git ~/travis-support
|
|
||||||
- "if [ \"$BEHAT_TEST\" = \"\" ]; then php ~/travis-support/travis_setup.php --source `pwd` --target ~/builds/ss; fi"
|
|
||||||
- "if [ \"$BEHAT_TEST\" = \"1\" ]; then php ~/travis-support/travis_setup.php --source `pwd` --target ~/builds/ss --require silverstripe/behat-extension; fi"
|
|
||||||
- cd ~/builds/ss
|
|
||||||
- php ~/travis-support/travis_setup_selenium.php --if-env BEHAT_TEST
|
|
||||||
- php ~/travis-support/travis_setup_php54_webserver.php --if-env BEHAT_TEST
|
|
||||||
|
|
||||||
script:
|
|
||||||
- "if [ \"$BEHAT_TEST\" = \"\" ]; then phpunit subsites/tests; fi"
|
|
||||||
- "if [ \"$BEHAT_TEST\" = \"1\" ]; then vendor/bin/behat @subsites; fi"
|
|
@ -1,8 +1,9 @@
|
|||||||
[main]
|
[main]
|
||||||
host = https://www.transifex.com
|
host = https://www.transifex.com
|
||||||
|
|
||||||
[silverstripe-subsites.master]
|
[o:silverstripe:p:silverstripe-subsites:r:master]
|
||||||
file_filter = lang/<lang>.yml
|
file_filter = lang/<lang>.yml
|
||||||
source_file = lang/en.yml
|
source_file = lang/en.yml
|
||||||
source_lang = en
|
source_lang = en
|
||||||
type = YML
|
type = YML
|
||||||
|
|
||||||
|
20
.upgrade.yml
Normal 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
@ -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
@ -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.
|
|
87
README.md
@ -1,6 +1,7 @@
|
|||||||
# Subsites Module
|
# Subsites Module
|
||||||
|
|
||||||
[![Build Status](https://secure.travis-ci.org/silverstripe/silverstripe-subsites.png?branch=master)](http://travis-ci.org/silverstripe/silverstripe-subsites)
|
[![CI](https://github.com/silverstripe/silverstripe-subsites/actions/workflows/ci.yml/badge.svg)](https://github.com/silverstripe/silverstripe-subsites/actions/workflows/ci.yml)
|
||||||
|
[![Silverstripe supported module](https://img.shields.io/badge/silverstripe-supported-0071C4.svg)](https://www.silverstripe.org/software/addons/silverstripe-commercially-supported-module-list/)
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
@ -8,7 +9,7 @@ The subsites module provides a convenient way of running multiple websites from
|
|||||||
sharing users, content, and assets between them - the sites will be managed from a single CMS.
|
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
|
A useful way to think of its use is where you have a business with a global headquarters and four branches in various
|
||||||
countries. The subsites module allows the five offices to use a single SilverStripe installation, and have information
|
countries. The subsites module allows the five offices to use a single Silverstripe installation, and have information
|
||||||
from the headquarters flow down into the branches. The branches can hold information that is individual and the website
|
from the headquarters flow down into the branches. The branches can hold information that is individual and the website
|
||||||
templates can also be different.
|
templates can also be different.
|
||||||
|
|
||||||
@ -21,8 +22,8 @@ permissions" will imply that the person will likely be able to escalate his/her
|
|||||||
|
|
||||||
For user documentation please see:
|
For user documentation please see:
|
||||||
|
|
||||||
1. [Setting up subsites](docs/en/set_up.md)
|
1. [Setting up subsites](docs/en/userguide/set_up.md)
|
||||||
1. [Working with subsites](docs/en/working_with.md)
|
1. [Working with subsites](docs/en/userguide/working_with.md)
|
||||||
|
|
||||||
## Features & limitations
|
## Features & limitations
|
||||||
|
|
||||||
@ -39,9 +40,14 @@ For user documentation please see:
|
|||||||
|
|
||||||
### Limitations:
|
### 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.
|
* 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
|
* A subsite cannot use a different codebase as the main site, they are intrinsically tied
|
||||||
* However, you can remove page types from a subsite when creating the subsite - [see the setup documentation for further details](set_up.md)
|
* However, you can remove page types from a subsite when creating the subsite - [see the setup documentation for further details](docs/en/userguide/set_up.md)
|
||||||
* The only code a developer can edit between subsites is the theme
|
* The 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.
|
* The separation between subsites in the CMS needs to be seen as cosmetic, and mostly applicable to the "Pages" and "Files" sections of the CMS.
|
||||||
* All subsites run in the same process space and data set. Therefore if an outage affects one subsite it will affect all subsites, and if bad code or hardware corrupts one subsite's data, it's very likely that it has corrupted all subsite data.
|
* All subsites run in the same process space and data set. Therefore if an outage affects one subsite it will affect all subsites, and if bad code or hardware corrupts one subsite's data, it's very likely that it has corrupted all subsite data.
|
||||||
@ -53,7 +59,7 @@ If more isolation of code, security, or performance is needed, then consider run
|
|||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
* SilverStripe 3.1
|
* Silverstripe 4.x
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@ -63,7 +69,7 @@ If more isolation of code, security, or performance is needed, then consider run
|
|||||||
* Once you've created a subsite, you'll see a "Create Subsite Domain" button, hit that button to enter a domain or subdomain for your subsite. This will determine the URL of your website. For example, if your site is running on `http://localhost/mysite`, and you set the subdomain to "subsite", then your subsite will be accessible on `http://subsite.localhost/mysite`
|
* 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.
|
* 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.
|
* 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
|
## Usage
|
||||||
|
|
||||||
@ -95,7 +101,55 @@ In some Browsers the SubsiteID is visible if you hover over the "Edit" link in t
|
|||||||
|
|
||||||
### Subsite-specific themes
|
### 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
|
### Limit available themes for a subsite
|
||||||
|
|
||||||
@ -123,8 +177,8 @@ Include the current SubsiteID as a hidden field on getCMSFields, or updateCMSFie
|
|||||||
:::php
|
:::php
|
||||||
public function getCMSFields() {
|
public function getCMSFields() {
|
||||||
$fields = parent::getCMSFields();
|
$fields = parent::getCMSFields();
|
||||||
if(class_exists('Subsite')){
|
if(class_exists(Subsite::class)){
|
||||||
$fields->push(new HiddenField('SubsiteID','SubsiteID', Subsite::currentSubsiteID()));
|
$fields->push(new HiddenField('SubsiteID','SubsiteID', SubsiteState::singleton()->getSubsiteId()));
|
||||||
}
|
}
|
||||||
return $fields;
|
return $fields;
|
||||||
}
|
}
|
||||||
@ -138,8 +192,8 @@ To limit your admin gridfields to the current Subsite records, you can do someth
|
|||||||
$form = parent::getEditForm($id, $fields);
|
$form = parent::getEditForm($id, $fields);
|
||||||
|
|
||||||
$gridField = $form->Fields()->fieldByName($this->sanitiseClassName($this->modelClass));
|
$gridField = $form->Fields()->fieldByName($this->sanitiseClassName($this->modelClass));
|
||||||
if(class_exists('Subsite')){
|
if(class_exists(Subsite::class)){
|
||||||
$list = $gridField->getList()->filter(array('SubsiteID'=>Subsite::currentSubsiteID()));
|
$list = $gridField->getList()->filter(['SubsiteID'=>SubsiteState::singleton()->getSubsiteId()]);
|
||||||
$gridField->setList($list);
|
$gridField->setList($list);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,6 +218,14 @@ or by defining the subsiteCMSShowInMenu function in your admin:
|
|||||||
return true;
|
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
|
### Public display of a subsite
|
||||||
|
|
||||||
@ -189,4 +251,3 @@ for all subdomains:
|
|||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
![](docs/en/_images/subsites-module-adminscreenshot.png)
|
![](docs/en/_images/subsites-module-adminscreenshot.png)
|
||||||
|
|
||||||
|
28
_config.php
@ -1,28 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The subsites module modifies the behaviour of the CMS - in the SiteTree and Group databases - to store information
|
|
||||||
* about a number of sub-sites, rather than a single site.
|
|
||||||
*/
|
|
||||||
|
|
||||||
SiteTree::add_extension('SiteTreeSubsites');
|
|
||||||
ContentController::add_extension('ControllerSubsites');
|
|
||||||
CMSPageAddController::add_extension('CMSPageAddControllerExtension');
|
|
||||||
LeftAndMain::add_extension('LeftAndMainSubsites');
|
|
||||||
LeftAndMain::add_extension('ControllerSubsites');
|
|
||||||
|
|
||||||
Group::add_extension('GroupSubsites');
|
|
||||||
File::add_extension('FileSubsites');
|
|
||||||
ErrorPage::add_extension('ErrorPageSubsite');
|
|
||||||
SiteConfig::add_extension('SiteConfigSubsites');
|
|
||||||
|
|
||||||
SS_Report::add_excluded_reports('SubsiteReportWrapper');
|
|
||||||
|
|
||||||
//Display in cms menu
|
|
||||||
AssetAdmin::add_extension('SubsiteMenuExtension');
|
|
||||||
SecurityAdmin::add_extension('SubsiteMenuExtension');
|
|
||||||
CMSMain::add_extension('SubsiteMenuExtension');
|
|
||||||
CMSPagesController::add_extension('SubsiteMenuExtension');
|
|
||||||
SubsiteAdmin::add_extension('SubsiteMenuExtension');
|
|
||||||
CMSSettingsController::add_extension('SubsiteMenuExtension');
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
|||||||
---
|
---
|
||||||
Name: mysiteconfig
|
Name: subsiteconfig
|
||||||
After: 'framework/*','cms/*'
|
After:
|
||||||
|
- 'framework/*'
|
||||||
---
|
---
|
||||||
AssetAdmin:
|
SilverStripe\AssetAdmin\Controller\AssetAdmin:
|
||||||
treats_subsite_0_as_global: true
|
treats_subsite_0_as_global: true
|
||||||
|
|
||||||
|
SilverStripe\Reports\Report:
|
||||||
|
excluded_reports:
|
||||||
|
- SilverStripe\Subsites\Reports\SubsiteReportWrapper
|
||||||
|
75
_config/extensions.yml
Normal 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
@ -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
@ -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
@ -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"
|
82
client/css/LeftAndMain_Subsites.css
Normal 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;
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
/*jslint browser: true, nomen: true*/
|
/*jslint browser: true, nomen: true*/
|
||||||
/*global $, window, jQuery*/
|
/*global $, window, jQuery*/
|
||||||
|
|
||||||
(function($) {
|
(function($) {
|
||||||
'use strict';
|
'use strict';
|
||||||
$.entwine('ss', function($) {
|
$.entwine('ss', function($) {
|
||||||
@ -18,7 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
$('.cms-container .cms-menu-list li a').entwine({
|
$('.cms-container .cms-menu-list li a').entwine({
|
||||||
onclick: function(e) {
|
onclick: function(e) {
|
||||||
$('.cms-container').loadFragment('SubsiteXHRController', 'SubsiteList');
|
$('.cms-container').loadFragment('admin/subsite_xhr', 'SubsiteList');
|
||||||
this._super(e);
|
this._super(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -28,20 +27,17 @@
|
|||||||
*/
|
*/
|
||||||
$('.cms-container .SubsiteAdmin .cms-edit-form fieldset.ss-gridfield').entwine({
|
$('.cms-container .SubsiteAdmin .cms-edit-form fieldset.ss-gridfield').entwine({
|
||||||
onreload: function(e) {
|
onreload: function(e) {
|
||||||
$('.cms-container').loadFragment('SubsiteXHRController', 'SubsiteList');
|
$('.cms-container').loadFragment('admin/subsite_xhr', 'SubsiteList');
|
||||||
this._super(e);
|
this._super(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Reload subsites dropdown when subsites are added or names are modified
|
* Reload subsites dropdown when subsites are added or names are modified
|
||||||
*/
|
*/
|
||||||
$('.cms-container .cms-content-fields .subsite-model').entwine({
|
$('.cms-container .tab.subsite-model').entwine({
|
||||||
onadd: function(e) {
|
onadd: function(e) {
|
||||||
$('.cms-container').loadFragment('SubsiteXHRController', 'SubsiteList');
|
$('.cms-container').loadFragment('admin/subsite_xhr', 'SubsiteList');
|
||||||
this._super(e);
|
this._super(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -90,26 +86,6 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Binding a visibility toggle anchor to a longer list of checkboxes.
|
|
||||||
* Hidden by default, unless either the toggle checkbox, or any of the
|
|
||||||
* actual value checkboxes are selected.
|
|
||||||
*/
|
|
||||||
$('#PageTypeBlacklist').entwine({
|
|
||||||
onmatch: function() {
|
|
||||||
var hasLimits=Boolean($('#PageTypeBlacklist').find('input:checked').length);
|
|
||||||
jQuery('#PageTypeBlacklist').toggle(hasLimits);
|
|
||||||
|
|
||||||
|
|
||||||
//Bind listener
|
|
||||||
$('a#PageTypeBlacklistToggle').click(function(e) {
|
|
||||||
jQuery('#PageTypeBlacklist').toggle();
|
|
||||||
e.stopPropagation();
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.cms-edit-form input[name=action_copytosubsite]').entwine({
|
$('.cms-edit-form input[name=action_copytosubsite]').entwine({
|
||||||
onclick: function(e) {
|
onclick: function(e) {
|
||||||
var form = this.closest('form');
|
var form = this.closest('form');
|
||||||
@ -125,7 +101,7 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Update links and forms with GET/POST SubsiteID param, so we remaing on the current subsite.
|
* Update links and forms with GET/POST SubsiteID param, so we remaing on the current subsite.
|
||||||
* The initial link for the iframe comes from SiteTreeSubsites::alternatePreviewLink.
|
* The initial link for the iframe comes from SiteTreeSubsites::updatePreviewLink.
|
||||||
*
|
*
|
||||||
* This is done so we can use the CMS domain for displaying previews so we prevent single-origin
|
* 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.
|
* violations and SSL cert problems that come up when iframing content from a different URL.
|
34
client/javascript/SubsitesTreeDropdownField.js
Normal 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
@ -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).
|
@ -1,33 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* Admin interface to manage and create {@link Subsite} instances.
|
|
||||||
*
|
|
||||||
* @package subsites
|
|
||||||
*/
|
|
||||||
class SubsiteAdmin extends ModelAdmin {
|
|
||||||
|
|
||||||
private static $managed_models = array('Subsite');
|
|
||||||
|
|
||||||
private static $url_segment = 'subsites';
|
|
||||||
|
|
||||||
private static $menu_title = "Subsites";
|
|
||||||
|
|
||||||
private static $menu_icon = "subsites/images/subsites.png";
|
|
||||||
|
|
||||||
public $showImportForm=false;
|
|
||||||
|
|
||||||
private static $tree_class = 'Subsite';
|
|
||||||
|
|
||||||
public function getEditForm($id = null, $fields = null) {
|
|
||||||
$form = parent::getEditForm($id, $fields);
|
|
||||||
|
|
||||||
$grid=$form->Fields()->dataFieldByName('Subsite');
|
|
||||||
if($grid) {
|
|
||||||
$grid->getConfig()->removeComponentsByType('GridFieldDetailForm');
|
|
||||||
$grid->getConfig()->addComponent(new GridFieldSubsiteDetailForm());
|
|
||||||
}
|
|
||||||
|
|
||||||
return $form;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a subsite-aware version of another report.
|
|
||||||
* Pass another report (or its classname) into the constructor.
|
|
||||||
*/
|
|
||||||
class SubsiteReportWrapper extends SS_ReportWrapper {
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Filtering
|
|
||||||
|
|
||||||
function parameterFields() {
|
|
||||||
$subsites = Subsite::accessible_sites('CMS_ACCESS_CMSMain', true);
|
|
||||||
$options = $subsites->toDropdownMap('ID', 'Title');
|
|
||||||
|
|
||||||
$subsiteField = new TreeMultiselectField(
|
|
||||||
'Subsites',
|
|
||||||
_t('SubsiteReportWrapper.ReportDropdown', 'Sites'),
|
|
||||||
$options
|
|
||||||
);
|
|
||||||
$subsiteField->setValue(array_keys($options));
|
|
||||||
|
|
||||||
// We don't need to make the field editable if only one subsite is available
|
|
||||||
if(sizeof($options) <= 1) {
|
|
||||||
$subsiteField = $subsiteField->performReadonlyTransformation();
|
|
||||||
}
|
|
||||||
|
|
||||||
$fields = parent::parameterFields();
|
|
||||||
if($fields) {
|
|
||||||
$fields->insertBefore($subsiteField, $fields->First()->Name());
|
|
||||||
} else {
|
|
||||||
$fields = new FieldList($subsiteField);
|
|
||||||
}
|
|
||||||
return $fields;
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Columns
|
|
||||||
|
|
||||||
function columns() {
|
|
||||||
$columns = parent::columns();
|
|
||||||
$columns['Subsite.Title'] = "Subsite";
|
|
||||||
return $columns;
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Querying
|
|
||||||
|
|
||||||
function beforeQuery($params) {
|
|
||||||
// The user has select a few specific sites
|
|
||||||
if(!empty($params['Subsites'])) {
|
|
||||||
Subsite::$force_subsite = $params['Subsites'];
|
|
||||||
|
|
||||||
// Default: restrict to all accessible sites
|
|
||||||
} else {
|
|
||||||
$subsites = Subsite::accessible_sites('CMS_ACCESS_CMSMain');
|
|
||||||
$options = $subsites->toDropdownMap('ID', 'Title');
|
|
||||||
Subsite::$force_subsite = join(',', array_keys($options));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function afterQuery() {
|
|
||||||
// Manually manage the subsite filtering
|
|
||||||
Subsite::$force_subsite = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Section-agnostic PJAX controller.
|
|
||||||
*/
|
|
||||||
class SubsiteXHRController extends LeftAndMain {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Relax the access permissions, so anyone who has access to any CMS subsite can access this controller.
|
|
||||||
*/
|
|
||||||
public function canView($member = null) {
|
|
||||||
if (parent::canView()) return true;
|
|
||||||
|
|
||||||
if (Subsite::all_accessible_sites()->count()>0) return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Similar as above, but for the LeftAndMainSubsites - allow access if user allowed into the CMS at all.
|
|
||||||
*/
|
|
||||||
public function canAccess() {
|
|
||||||
if (Subsite::all_accessible_sites()->count()>0) return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getResponseNegotiator() {
|
|
||||||
$negotiator = parent::getResponseNegotiator();
|
|
||||||
$self = $this;
|
|
||||||
|
|
||||||
// Register a new callback
|
|
||||||
$negotiator->setCallback('SubsiteList', function() use(&$self) {
|
|
||||||
return $self->SubsiteList();
|
|
||||||
});
|
|
||||||
|
|
||||||
return $negotiator;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide the list of available subsites as a cms-section-agnostic PJAX handler.
|
|
||||||
*/
|
|
||||||
public function SubsiteList() {
|
|
||||||
return $this->renderWith('SubsiteList');
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,210 +0,0 @@
|
|||||||
<?php
|
|
||||||
class SubsitesVirtualPage extends VirtualPage {
|
|
||||||
|
|
||||||
private static $description = 'Displays the content of a page on another subsite';
|
|
||||||
|
|
||||||
private static $db = array(
|
|
||||||
'CustomMetaTitle' => 'Varchar(255)',
|
|
||||||
'CustomMetaKeywords' => 'Varchar(255)',
|
|
||||||
'CustomMetaDescription' => 'Text',
|
|
||||||
'CustomExtraMeta' => 'HTMLText'
|
|
||||||
);
|
|
||||||
|
|
||||||
public function getCMSFields() {
|
|
||||||
$fields = parent::getCMSFields();
|
|
||||||
|
|
||||||
$subsites = DataObject::get('Subsite');
|
|
||||||
if(!$subsites) {
|
|
||||||
$subsites = new ArrayList();
|
|
||||||
}else {
|
|
||||||
$subsites=ArrayList::create($subsites->toArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
$subsites->push(new ArrayData(array('Title' => 'Main site', 'ID' => 0)));
|
|
||||||
|
|
||||||
$subsiteSelectionField = new DropdownField(
|
|
||||||
"CopyContentFromID_SubsiteID",
|
|
||||||
_t('SubsitesVirtualPage.SubsiteField',"Subsite"),
|
|
||||||
$subsites->map('ID', 'Title'),
|
|
||||||
($this->CopyContentFromID) ? $this->CopyContentFrom()->SubsiteID : Session::get('SubsiteID')
|
|
||||||
);
|
|
||||||
$fields->addFieldToTab(
|
|
||||||
'Root.Main',
|
|
||||||
$subsiteSelectionField,
|
|
||||||
'CopyContentFromID'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Setup the linking to the original page.
|
|
||||||
$pageSelectionField = new SubsitesTreeDropdownField(
|
|
||||||
"CopyContentFromID",
|
|
||||||
_t('VirtualPage.CHOOSE', "Choose a page to link to"),
|
|
||||||
"SiteTree",
|
|
||||||
"ID",
|
|
||||||
"MenuTitle"
|
|
||||||
);
|
|
||||||
|
|
||||||
if(Controller::has_curr() && Controller::curr()->getRequest()) {
|
|
||||||
$subsiteID = Controller::curr()->getRequest()->postVar('CopyContentFromID_SubsiteID');
|
|
||||||
$pageSelectionField->setSubsiteID($subsiteID);
|
|
||||||
}
|
|
||||||
$fields->replaceField('CopyContentFromID', $pageSelectionField);
|
|
||||||
|
|
||||||
// Create links back to the original object in the CMS
|
|
||||||
if($this->CopyContentFromID) {
|
|
||||||
$editLink = "admin/page/edit/show/$this->CopyContentFromID/?SubsiteID=" . $this->CopyContentFrom()->SubsiteID;
|
|
||||||
$linkToContent = "
|
|
||||||
<a class=\"cmsEditlink\" href=\"$editLink\">" .
|
|
||||||
_t('VirtualPage.EDITCONTENT', 'Click here to edit the content') .
|
|
||||||
"</a>";
|
|
||||||
$fields->removeByName("VirtualPageContentLinkLabel");
|
|
||||||
$fields->addFieldToTab(
|
|
||||||
"Root.Main",
|
|
||||||
$linkToContentLabelField = new LabelField('VirtualPageContentLinkLabel', $linkToContent),
|
|
||||||
'Title'
|
|
||||||
);
|
|
||||||
$linkToContentLabelField->setAllowHTML(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$fields->addFieldToTab(
|
|
||||||
'Root.Main',
|
|
||||||
TextField::create(
|
|
||||||
'CustomMetaTitle',
|
|
||||||
$this->fieldLabel('CustomMetaTitle')
|
|
||||||
)->setDescription(_t('SubsitesVirtualPage.OverrideNote', 'Overrides inherited value from the source')),
|
|
||||||
'MetaTitle'
|
|
||||||
);
|
|
||||||
$fields->addFieldToTab(
|
|
||||||
'Root.Main',
|
|
||||||
TextareaField::create(
|
|
||||||
'CustomMetaKeywords',
|
|
||||||
$this->fieldLabel('CustomMetaTitle')
|
|
||||||
)->setDescription(_t('SubsitesVirtualPage.OverrideNote')),
|
|
||||||
'MetaKeywords'
|
|
||||||
);
|
|
||||||
$fields->addFieldToTab(
|
|
||||||
'Root.Main',
|
|
||||||
TextareaField::create(
|
|
||||||
'CustomMetaDescription',
|
|
||||||
$this->fieldLabel('CustomMetaTitle')
|
|
||||||
)->setDescription(_t('SubsitesVirtualPage.OverrideNote')),
|
|
||||||
'MetaDescription'
|
|
||||||
);
|
|
||||||
$fields->addFieldToTab(
|
|
||||||
'Root.Main',
|
|
||||||
TextField::create(
|
|
||||||
'CustomExtraMeta',
|
|
||||||
$this->fieldLabel('CustomMetaTitle')
|
|
||||||
)->setDescription(_t('SubsitesVirtualPage.OverrideNote')),
|
|
||||||
'ExtraMeta'
|
|
||||||
);
|
|
||||||
|
|
||||||
return $fields;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function fieldLabels($includerelations = true) {
|
|
||||||
$labels = parent::fieldLabels($includerelations);
|
|
||||||
$labels['CustomMetaTitle'] = _t('Subsite.CustomMetaTitle','Title');
|
|
||||||
$labels['CustomMetaKeywords'] = _t('Subsite.CustomMetaKeywords','Keywords');
|
|
||||||
$labels['CustomMetaDescription'] = _t('Subsite.CustomMetaDescription','Description');
|
|
||||||
$labels['CustomExtraMeta'] = _t('Subsite.CustomExtraMeta','Custom Meta Tags');
|
|
||||||
|
|
||||||
return $labels;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getVirtualFields() {
|
|
||||||
$fields = parent::getVirtualFields();
|
|
||||||
foreach($fields as $k => $v) {
|
|
||||||
if($v == 'SubsiteID') unset($fields[$k]);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach(self::$db as $field => $type) if (in_array($field, $fields)) unset($fields[array_search($field, $fields)]);
|
|
||||||
|
|
||||||
return $fields;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function syncLinkTracking() {
|
|
||||||
$oldState = Subsite::$disable_subsite_filter;
|
|
||||||
Subsite::$disable_subsite_filter = true;
|
|
||||||
if ($this->CopyContentFromID) $this->HasBrokenLink = DataObject::get_by_id('SiteTree', $this->CopyContentFromID) ? false : true;
|
|
||||||
Subsite::$disable_subsite_filter = $oldState;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onBeforeWrite() {
|
|
||||||
parent::onBeforeWrite();
|
|
||||||
|
|
||||||
if($this->CustomMetaTitle) $this->MetaTitle = $this->CustomMetaTitle;
|
|
||||||
else {
|
|
||||||
$this->MetaTitle = $this->ContentSource()->MetaTitle ? $this->ContentSource()->MetaTitle : $this->MetaTitle;
|
|
||||||
}
|
|
||||||
if($this->CustomMetaKeywords) $this->MetaKeywords = $this->CustomMetaKeywords;
|
|
||||||
else {
|
|
||||||
$this->MetaKeywords = $this->ContentSource()->MetaKeywords ? $this->ContentSource()->MetaKeywords : $this->MetaKeywords;
|
|
||||||
}
|
|
||||||
if($this->CustomMetaDescription) $this->MetaDescription = $this->CustomMetaDescription;
|
|
||||||
else {
|
|
||||||
$this->MetaDescription = $this->ContentSource()->MetaDescription ? $this->ContentSource()->MetaDescription : $this->MetaDescription;
|
|
||||||
}
|
|
||||||
if($this->CustomExtraMeta) $this->ExtraMeta = $this->CustomExtraMeta;
|
|
||||||
else {
|
|
||||||
$this->ExtraMeta = $this->ContentSource()->ExtraMeta ? $this->ContentSource()->ExtraMeta : $this->ExtraMeta;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function validURLSegment() {
|
|
||||||
$isValid = parent::validURLSegment();
|
|
||||||
|
|
||||||
// Veto the validation rules if its false. In this case, some logic
|
|
||||||
// needs to be duplicated from parent to find out the exact reason the validation failed.
|
|
||||||
if(!$isValid) {
|
|
||||||
$IDFilter = ($this->ID) ? "AND \"SiteTree\".\"ID\" <> $this->ID" : null;
|
|
||||||
$parentFilter = null;
|
|
||||||
|
|
||||||
if(Config::inst()->get('SiteTree', 'nested_urls')) {
|
|
||||||
if($this->ParentID) {
|
|
||||||
$parentFilter = " AND \"SiteTree\".\"ParentID\" = $this->ParentID";
|
|
||||||
} else {
|
|
||||||
$parentFilter = ' AND "SiteTree"."ParentID" = 0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$origDisableSubsiteFilter = Subsite::$disable_subsite_filter;
|
|
||||||
Subsite::$disable_subsite_filter = true;
|
|
||||||
$existingPage = DataObject::get_one(
|
|
||||||
'SiteTree',
|
|
||||||
"\"URLSegment\" = '$this->URLSegment' $IDFilter $parentFilter",
|
|
||||||
false // disable cache, it doesn't include subsite status in the key
|
|
||||||
);
|
|
||||||
Subsite::$disable_subsite_filter = $origDisableSubsiteFilter;
|
|
||||||
$existingPageInSubsite = DataObject::get_one(
|
|
||||||
'SiteTree',
|
|
||||||
"\"URLSegment\" = '$this->URLSegment' $IDFilter $parentFilter",
|
|
||||||
false // disable cache, it doesn't include subsite status in the key
|
|
||||||
);
|
|
||||||
|
|
||||||
// If URL has been vetoed because of an existing page,
|
|
||||||
// be more specific and allow same URLSegments in different subsites
|
|
||||||
$isValid = !($existingPage && $existingPageInSubsite);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $isValid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SubsitesVirtualPage_Controller extends VirtualPage_Controller {
|
|
||||||
|
|
||||||
public function reloadContent() {
|
|
||||||
$this->failover->copyFrom($this->failover->CopyContentFrom());
|
|
||||||
$this->failover->write();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function init(){
|
|
||||||
$origDisableSubsiteFilter = Subsite::$disable_subsite_filter;
|
|
||||||
Subsite::$disable_subsite_filter = true;
|
|
||||||
|
|
||||||
parent::init();
|
|
||||||
|
|
||||||
Subsite::$disable_subsite_filter = $origDisableSubsiteFilter;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
<?php
|
|
||||||
class CMSPageAddControllerExtension extends Extension {
|
|
||||||
|
|
||||||
function updatePageOptions(&$fields) {
|
|
||||||
$fields->push(new HiddenField('SubsiteID', 'SubsiteID', Subsite::currentSubsiteID()));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
?>
|
|
@ -1,40 +0,0 @@
|
|||||||
<?php
|
|
||||||
class ErrorPageSubsite extends DataExtension {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alter file path to generated a static (static) error page file to handle error page template on different sub-sites
|
|
||||||
*
|
|
||||||
* @see Error::get_filepath_for_errorcode()
|
|
||||||
*
|
|
||||||
* FIXME since {@link Subsite::currentSubsite()} partly relies on Session, viewing other sub-site (including main site) between
|
|
||||||
* opening ErrorPage in the CMS and publish ErrorPage causes static error page to get generated incorrectly.
|
|
||||||
*/
|
|
||||||
function alternateFilepathForErrorcode($statusCode, $locale = null) {
|
|
||||||
$static_filepath = Config::inst()->get($this->owner->ClassName, 'static_filepath');
|
|
||||||
$subdomainPart = "";
|
|
||||||
|
|
||||||
// Try to get current subsite from session
|
|
||||||
$subsite = Subsite::currentSubsite(false);
|
|
||||||
|
|
||||||
// since this function is called from Page class before the controller is created, we have to get subsite from domain instead
|
|
||||||
if(!$subsite) {
|
|
||||||
$subsiteID = Subsite::getSubsiteIDForDomain();
|
|
||||||
if($subsiteID != 0) $subsite = DataObject::get_by_id("Subsite", $subsiteID);
|
|
||||||
else $subsite = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if($subsite) {
|
|
||||||
$subdomain = $subsite->domain();
|
|
||||||
$subdomainPart = "-{$subdomain}";
|
|
||||||
}
|
|
||||||
|
|
||||||
if(singleton('SiteTree')->hasExtension('Translatable') && $locale && $locale != Translatable::default_locale()) {
|
|
||||||
$filepath = $static_filepath . "/error-{$statusCode}-{$locale}{$subdomainPart}.html";
|
|
||||||
} else {
|
|
||||||
$filepath = $static_filepath . "/error-{$statusCode}{$subdomainPart}.html";
|
|
||||||
}
|
|
||||||
|
|
||||||
return $filepath;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,131 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* Extension for the File object to add subsites support
|
|
||||||
*
|
|
||||||
* @package subsites
|
|
||||||
*/
|
|
||||||
class FileSubsites extends DataExtension {
|
|
||||||
|
|
||||||
// If this is set to true, all folders created will be default be
|
|
||||||
// considered 'global', unless set otherwise
|
|
||||||
static $default_root_folders_global = false;
|
|
||||||
|
|
||||||
private static $has_one=array(
|
|
||||||
'Subsite' => 'Subsite',
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Amends the CMS tree title for folders in the Files & Images section.
|
|
||||||
* Prefixes a '* ' to the folders that are accessible from all subsites.
|
|
||||||
*/
|
|
||||||
function alternateTreeTitle() {
|
|
||||||
if($this->owner->SubsiteID == 0) return " * " . $this->owner->Title;
|
|
||||||
else return $this->owner->Title;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add subsites-specific fields to the folder editor.
|
|
||||||
*/
|
|
||||||
function updateCMSFields(FieldList $fields) {
|
|
||||||
if($this->owner instanceof Folder) {
|
|
||||||
$sites = Subsite::accessible_sites('CMS_ACCESS_AssetAdmin');
|
|
||||||
$values = array();
|
|
||||||
$values[0] = _t('FileSubsites.AllSitesDropdownOpt','All sites');
|
|
||||||
foreach ($sites as $site) {
|
|
||||||
$values[$site->ID] = $site->Title;
|
|
||||||
}
|
|
||||||
ksort($values);
|
|
||||||
if($sites){
|
|
||||||
//Dropdown needed to move folders between subsites
|
|
||||||
$dropdown = new DropdownField(
|
|
||||||
'SubsiteID',
|
|
||||||
_t('FileSubsites.SubsiteFieldLabel','Subsite'),
|
|
||||||
$values
|
|
||||||
);
|
|
||||||
$dropdown->addExtraClass('subsites-move-dropdown');
|
|
||||||
$fields->push($dropdown);
|
|
||||||
$fields->push(new LiteralField(
|
|
||||||
'Message',
|
|
||||||
'<p class="message notice">'.
|
|
||||||
_t('ASSETADMIN.SUBSITENOTICE', 'Folders and files created in the main site are accessible by all subsites.')
|
|
||||||
.'</p>'
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update any requests to limit the results to the current site
|
|
||||||
*/
|
|
||||||
function augmentSQL(SQLQuery &$query) {
|
|
||||||
if(Subsite::$disable_subsite_filter) return;
|
|
||||||
|
|
||||||
// If you're querying by ID, ignore the sub-site - this is a bit ugly... (but it was WAYYYYYYYYY worse)
|
|
||||||
//@TODO I don't think excluding if SiteTree_ImageTracking is a good idea however because of the SS 3.0 api and ManyManyList::removeAll() changing the from table after this function is called there isn't much of a choice
|
|
||||||
|
|
||||||
$from = $query->getFrom();
|
|
||||||
$where = $query->getWhere();
|
|
||||||
|
|
||||||
if(!isset($from['SiteTree_ImageTracking']) && !($where && preg_match('/\.(\'|"|`|)ID(\'|"|`|)/', $where[0]))) {
|
|
||||||
$subsiteID = (int) Subsite::currentSubsiteID();
|
|
||||||
|
|
||||||
// The foreach is an ugly way of getting the first key :-)
|
|
||||||
foreach($query->getFrom() as $tableName => $info) {
|
|
||||||
$where = "\"$tableName\".\"SubsiteID\" IN (0, $subsiteID)";
|
|
||||||
$query->addWhere($where);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$sect=array_values($query->getSelect());
|
|
||||||
$isCounting = strpos($sect[0], 'COUNT') !== false;
|
|
||||||
|
|
||||||
// Ordering when deleting or counting doesn't apply
|
|
||||||
if(!$query->getDelete() && !$isCounting) {
|
|
||||||
$query->addOrderBy("\"SubsiteID\"");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onBeforeWrite() {
|
|
||||||
if (!$this->owner->ID && !$this->owner->SubsiteID) {
|
|
||||||
if (self::$default_root_folders_global) {
|
|
||||||
$this->owner->SubsiteID = 0;
|
|
||||||
} else {
|
|
||||||
$this->owner->SubsiteID = Subsite::currentSubsiteID();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onAfterUpload() {
|
|
||||||
// If we have a parent, use it's subsite as our subsite
|
|
||||||
if ($this->owner->Parent()) {
|
|
||||||
$this->owner->SubsiteID = $this->owner->Parent()->SubsiteID;
|
|
||||||
} else {
|
|
||||||
$this->owner->SubsiteID = Subsite::currentSubsiteID();
|
|
||||||
}
|
|
||||||
$this->owner->write();
|
|
||||||
}
|
|
||||||
|
|
||||||
function canEdit($member = null) {
|
|
||||||
// Check the CMS_ACCESS_SecurityAdmin privileges on the subsite that owns this group
|
|
||||||
$subsiteID = Session::get('SubsiteID');
|
|
||||||
if($subsiteID&&$subsiteID == $this->owner->SubsiteID) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
Session::set('SubsiteID', $this->owner->SubsiteID);
|
|
||||||
$access = Permission::check(array('CMS_ACCESS_AssetAdmin', 'CMS_ACCESS_LeftAndMain'));
|
|
||||||
Session::set('SubsiteID', $subsiteID);
|
|
||||||
|
|
||||||
return $access;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a piece of text to keep DataObject cache keys appropriately specific
|
|
||||||
*/
|
|
||||||
function cacheKeyComponent() {
|
|
||||||
return 'subsite-'.Subsite::currentSubsiteID();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,188 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* Extension for the Group object to add subsites support
|
|
||||||
*
|
|
||||||
* @package subsites
|
|
||||||
*/
|
|
||||||
class GroupSubsites extends DataExtension implements PermissionProvider {
|
|
||||||
|
|
||||||
private static $db = array(
|
|
||||||
'AccessAllSubsites' => 'Boolean'
|
|
||||||
);
|
|
||||||
|
|
||||||
private static $many_many = array(
|
|
||||||
'Subsites' => 'Subsite'
|
|
||||||
);
|
|
||||||
|
|
||||||
private static $defaults = array(
|
|
||||||
'AccessAllSubsites' => true
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Migrations for GroupSubsites data.
|
|
||||||
*/
|
|
||||||
function requireDefaultRecords() {
|
|
||||||
// Migration for Group.SubsiteID data from when Groups only had a single subsite
|
|
||||||
$groupFields = DB::getConn()->fieldList('Group');
|
|
||||||
|
|
||||||
// Detection of SubsiteID field is the trigger for old-style-subsiteID migration
|
|
||||||
if(isset($groupFields['SubsiteID'])) {
|
|
||||||
// Migrate subsite-specific data
|
|
||||||
DB::query('INSERT INTO "Group_Subsites" ("GroupID", "SubsiteID")
|
|
||||||
SELECT "ID", "SubsiteID" FROM "Group" WHERE "SubsiteID" > 0');
|
|
||||||
|
|
||||||
// Migrate global-access data
|
|
||||||
DB::query('UPDATE "Group" SET "AccessAllSubsites" = 1 WHERE "SubsiteID" = 0');
|
|
||||||
|
|
||||||
// Move the field out of the way so that this migration doesn't get executed again
|
|
||||||
DB::getConn()->renameField('Group', 'SubsiteID', '_obsolete_SubsiteID');
|
|
||||||
|
|
||||||
// No subsite access on anything means that we've just installed the subsites module.
|
|
||||||
// Make all previous groups global-access groups
|
|
||||||
} else if(!DB::query('SELECT "Group"."ID" FROM "Group"
|
|
||||||
LEFT JOIN "Group_Subsites" ON "Group_Subsites"."GroupID" = "Group"."ID" AND "Group_Subsites"."SubsiteID" > 0
|
|
||||||
WHERE "AccessAllSubsites" = 1
|
|
||||||
OR "Group_Subsites"."GroupID" IS NOT NULL ')->value()) {
|
|
||||||
|
|
||||||
DB::query('UPDATE "Group" SET "AccessAllSubsites" = 1');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateCMSFields(FieldList $fields) {
|
|
||||||
if($this->owner->canEdit() ){
|
|
||||||
// i18n tab
|
|
||||||
$fields->findOrMakeTab('Root.Subsites',_t('GroupSubsites.SECURITYTABTITLE','Subsites'));
|
|
||||||
|
|
||||||
$subsites = Subsite::accessible_sites(array('ADMIN', 'SECURITY_SUBSITE_GROUP'), true);
|
|
||||||
$subsiteMap = $subsites->map();
|
|
||||||
|
|
||||||
// Prevent XSS injection
|
|
||||||
$subsiteMap = Convert::raw2xml($subsiteMap);
|
|
||||||
|
|
||||||
// Interface is different if you have the rights to modify subsite group values on
|
|
||||||
// all subsites
|
|
||||||
if(isset($subsiteMap[0])) {
|
|
||||||
$fields->addFieldToTab("Root.Subsites", new OptionsetField("AccessAllSubsites",
|
|
||||||
_t('GroupSubsites.ACCESSRADIOTITLE', 'Give this group access to'),
|
|
||||||
array(
|
|
||||||
1 => _t('GroupSubsites.ACCESSALL', "All subsites"),
|
|
||||||
0 => _t('GroupSubsites.ACCESSONLY', "Only these subsites"),
|
|
||||||
)
|
|
||||||
));
|
|
||||||
|
|
||||||
unset($subsiteMap[0]);
|
|
||||||
$fields->addFieldToTab("Root.Subsites", new CheckboxSetField("Subsites", "",
|
|
||||||
$subsiteMap));
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if (sizeof($subsiteMap) <= 1) {
|
|
||||||
$fields->addFieldToTab("Root.Subsites", new ReadonlyField("SubsitesHuman",
|
|
||||||
_t('GroupSubsites.ACCESSRADIOTITLE', 'Give this group access to'),
|
|
||||||
reset($subsiteMap)));
|
|
||||||
} else {
|
|
||||||
$fields->addFieldToTab("Root.Subsites", new CheckboxSetField("Subsites",
|
|
||||||
_t('GroupSubsites.ACCESSRADIOTITLE', 'Give this group access to'),
|
|
||||||
$subsiteMap));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If this group belongs to a subsite,
|
|
||||||
* append the subsites title to the group title
|
|
||||||
* to make it easy to distinguish in the tree-view
|
|
||||||
* of the security admin interface.
|
|
||||||
*/
|
|
||||||
function alternateTreeTitle() {
|
|
||||||
if($this->owner->AccessAllSubsites) {
|
|
||||||
$title = _t('GroupSubsites.GlobalGroup', 'global group');
|
|
||||||
return htmlspecialchars($this->owner->Title, ENT_QUOTES) . ' <i>(' . $title . ')</i>';
|
|
||||||
} else {
|
|
||||||
$subsites = Convert::raw2xml(implode(", ", $this->owner->Subsites()->column('Title')));
|
|
||||||
return htmlspecialchars($this->owner->Title) . " <i>($subsites)</i>";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update any requests to limit the results to the current site
|
|
||||||
*/
|
|
||||||
function augmentSQL(SQLQuery &$query) {
|
|
||||||
if(Subsite::$disable_subsite_filter) return;
|
|
||||||
if(Cookie::get('noSubsiteFilter') == 'true') return;
|
|
||||||
|
|
||||||
// If you're querying by ID, ignore the sub-site - this is a bit ugly...
|
|
||||||
if(!$query->filtersOnID()) {
|
|
||||||
|
|
||||||
/*if($context = DataObject::context_obj()) $subsiteID = (int)$context->SubsiteID;
|
|
||||||
else */$subsiteID = (int)Subsite::currentSubsiteID();
|
|
||||||
|
|
||||||
// Don't filter by Group_Subsites if we've already done that
|
|
||||||
$hasGroupSubsites = false;
|
|
||||||
foreach($query->getFrom() as $item) {
|
|
||||||
if((is_array($item) && strpos($item['table'], 'Group_Subsites')!==false) || (!is_array($item) && strpos($item, 'Group_Subsites')!==false)) {
|
|
||||||
$hasGroupSubsites = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!$hasGroupSubsites) {
|
|
||||||
if($subsiteID) {
|
|
||||||
$query->addLeftJoin("Group_Subsites", "\"Group_Subsites\".\"GroupID\"
|
|
||||||
= \"Group\".\"ID\" AND \"Group_Subsites\".\"SubsiteID\" = $subsiteID");
|
|
||||||
$query->addWhere("(\"Group_Subsites\".\"SubsiteID\" IS NOT NULL OR
|
|
||||||
\"Group\".\"AccessAllSubsites\" = 1)");
|
|
||||||
} else {
|
|
||||||
$query->addWhere("\"Group\".\"AccessAllSubsites\" = 1");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WORKAROUND for databases that complain about an ORDER BY when the column wasn't selected (e.g. SQL Server)
|
|
||||||
$select=$query->getSelect();
|
|
||||||
if(isset($select[0]) && !$select[0] == 'COUNT(*)') {
|
|
||||||
$query->orderby = "\"AccessAllSubsites\" DESC" . ($query->orderby ? ', ' : '') . $query->orderby;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onBeforeWrite() {
|
|
||||||
// New record test approximated by checking whether the ID has changed.
|
|
||||||
// Note also that the after write test is only used when we're *not* on a subsite
|
|
||||||
if($this->owner->isChanged('ID') && !Subsite::currentSubsiteID()) {
|
|
||||||
$this->owner->AccessAllSubsites = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onAfterWrite() {
|
|
||||||
// New record test approximated by checking whether the ID has changed.
|
|
||||||
// Note also that the after write test is only used when we're on a subsite
|
|
||||||
if($this->owner->isChanged('ID') && $currentSubsiteID = Subsite::currentSubsiteID()) {
|
|
||||||
$subsites = $this->owner->Subsites();
|
|
||||||
$subsites->add($currentSubsiteID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function alternateCanEdit() {
|
|
||||||
// Find the sites that this group belongs to and the sites where we have appropriate perm.
|
|
||||||
$accessibleSites = Subsite::accessible_sites('CMS_ACCESS_SecurityAdmin')->column('ID');
|
|
||||||
$linkedSites = $this->owner->Subsites()->column('ID');
|
|
||||||
|
|
||||||
// We are allowed to access this site if at we have CMS_ACCESS_SecurityAdmin permission on
|
|
||||||
// at least one of the sites
|
|
||||||
return (bool)array_intersect($accessibleSites, $linkedSites);
|
|
||||||
}
|
|
||||||
|
|
||||||
function providePermissions() {
|
|
||||||
return array(
|
|
||||||
'SECURITY_SUBSITE_GROUP' => array(
|
|
||||||
'name' => _t('GroupSubsites.MANAGE_SUBSITES', 'Manage subsites for groups'),
|
|
||||||
'category' => _t('Permissions.PERMISSIONS_CATEGORY', 'Roles and access permissions'),
|
|
||||||
'help' => _t('GroupSubsites.MANAGE_SUBSITES_HELP', 'Ability to limit the permissions for a group to one or more subsites.'),
|
|
||||||
'sort' => 200
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
?>
|
|
@ -1,301 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* Decorator designed to add subsites support to LeftAndMain
|
|
||||||
*
|
|
||||||
* @package subsites
|
|
||||||
*/
|
|
||||||
class LeftAndMainSubsites extends Extension {
|
|
||||||
|
|
||||||
private static $allowed_actions = array('CopyToSubsite');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Normally SubsiteID=0 on a DataObject means it is only accessible from the special "main site".
|
|
||||||
* However in some situations SubsiteID=0 will be understood as a "globally accessible" object in which
|
|
||||||
* case this property is set to true (i.e. in AssetAdmin).
|
|
||||||
*/
|
|
||||||
private static $treats_subsite_0_as_global = false;
|
|
||||||
|
|
||||||
function init() {
|
|
||||||
Requirements::css('subsites/css/LeftAndMain_Subsites.css');
|
|
||||||
Requirements::javascript('subsites/javascript/LeftAndMain_Subsites.js');
|
|
||||||
Requirements::javascript('subsites/javascript/VirtualPage_Subsites.js');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the title of the CMS tree
|
|
||||||
*/
|
|
||||||
function getCMSTreeTitle() {
|
|
||||||
$subsite = Subsite::currentSubSite();
|
|
||||||
return $subsite ? Convert::raw2xml($subsite->Title) : _t('LeftAndMain.SITECONTENTLEFT');
|
|
||||||
}
|
|
||||||
|
|
||||||
function updatePageOptions(&$fields) {
|
|
||||||
$fields->push(new HiddenField('SubsiteID', 'SubsiteID', Subsite::currentSubsiteID()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find all subsites accessible for current user on this controller.
|
|
||||||
*
|
|
||||||
* @return ArrayList of {@link Subsite} instances.
|
|
||||||
*/
|
|
||||||
function sectionSites($includeMainSite = true, $mainSiteTitle = "Main site", $member = null) {
|
|
||||||
if($mainSiteTitle == 'Main site') {
|
|
||||||
$mainSiteTitle = _t('Subsites.MainSiteTitle', 'Main site');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rationalise member arguments
|
|
||||||
if(!$member) $member = Member::currentUser();
|
|
||||||
if(!$member) return new ArrayList();
|
|
||||||
if(!is_object($member)) $member = DataObject::get_by_id('Member', $member);
|
|
||||||
|
|
||||||
// Collect permissions - honour the LeftAndMain::required_permission_codes, current model requires
|
|
||||||
// us to check if the user satisfies ALL permissions. Code partly copied from LeftAndMain::canView.
|
|
||||||
$codes = array();
|
|
||||||
$extraCodes = Config::inst()->get($this->owner->class, 'required_permission_codes');
|
|
||||||
if($extraCodes !== false) {
|
|
||||||
if($extraCodes) $codes = array_merge($codes, (array)$extraCodes);
|
|
||||||
else $codes[] = "CMS_ACCESS_{$this->owner->class}";
|
|
||||||
} else {
|
|
||||||
// Check overriden - all subsites accessible.
|
|
||||||
return Subsite::all_sites();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find subsites satisfying all permissions for the Member.
|
|
||||||
$codesPerSite = array();
|
|
||||||
$sitesArray = array();
|
|
||||||
foreach ($codes as $code) {
|
|
||||||
$sites = Subsite::accessible_sites($code, $includeMainSite, $mainSiteTitle, $member);
|
|
||||||
foreach ($sites as $site) {
|
|
||||||
// Build the structure for checking how many codes match.
|
|
||||||
$codesPerSite[$site->ID][$code] = true;
|
|
||||||
|
|
||||||
// Retain Subsite objects for later.
|
|
||||||
$sitesArray[$site->ID] = $site;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find sites that satisfy all codes conjuncitvely.
|
|
||||||
$accessibleSites = new ArrayList();
|
|
||||||
foreach ($codesPerSite as $siteID => $siteCodes) {
|
|
||||||
if (count($siteCodes)==count($codes)) {
|
|
||||||
$accessibleSites->push($sitesArray[$siteID]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $accessibleSites;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Returns a list of the subsites accessible to the current user.
|
|
||||||
* It's enough for any section to be accessible for the section to be included.
|
|
||||||
*/
|
|
||||||
public function Subsites() {
|
|
||||||
return Subsite::all_accessible_sites();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Generates a list of subsites with the data needed to
|
|
||||||
* produce a dropdown site switcher
|
|
||||||
* @return ArrayList
|
|
||||||
*/
|
|
||||||
|
|
||||||
public function ListSubsites(){
|
|
||||||
$list = $this->Subsites();
|
|
||||||
$currentSubsiteID = Subsite::currentSubsiteID();
|
|
||||||
|
|
||||||
if($list == null || $list->Count() == 1 && $list->First()->DefaultSite == true){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Requirements::javascript('subsites/javascript/LeftAndMain_Subsites.js');
|
|
||||||
|
|
||||||
$output = new ArrayList();
|
|
||||||
|
|
||||||
foreach($list as $subsite) {
|
|
||||||
$CurrentState = $subsite->ID == $currentSubsiteID ? 'selected' : '';
|
|
||||||
|
|
||||||
$output->push(new ArrayData(array(
|
|
||||||
'CurrentState' => $CurrentState,
|
|
||||||
'ID' => $subsite->ID,
|
|
||||||
'Title' => Convert::raw2xml($subsite->Title)
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $output;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function alternateMenuDisplayCheck($controllerName) {
|
|
||||||
if(!class_exists($controllerName)){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check subsite support.
|
|
||||||
if(Subsite::currentSubsiteID() == 0){
|
|
||||||
// Main site always supports everything.
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
$controller = singleton($controllerName);
|
|
||||||
if($controller->hasMethod('subsiteCMSShowInMenu') && $controller->subsiteCMSShowInMenu()){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// It's not necessary to check access permissions here. Framework calls canView on the controller,
|
|
||||||
// which in turn uses the Permission API which is augmented by our GroupSubsites.
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function CanAddSubsites() {
|
|
||||||
return Permission::check("ADMIN", "any", null, "all");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper for testing if the subsite should be adjusted.
|
|
||||||
*/
|
|
||||||
public function shouldChangeSubsite($adminClass, $recordSubsiteID, $currentSubsiteID) {
|
|
||||||
if (Config::inst()->get($adminClass, 'treats_subsite_0_as_global') && $recordSubsiteID==0) return false;
|
|
||||||
if ($recordSubsiteID!=$currentSubsiteID) return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the current controller is accessible for this user on this subsite.
|
|
||||||
*/
|
|
||||||
function canAccess() {
|
|
||||||
// Admin can access everything, no point in checking.
|
|
||||||
$member = Member::currentUser();
|
|
||||||
if($member &&
|
|
||||||
(
|
|
||||||
Permission::checkMember($member, 'ADMIN') || // 'Full administrative rights' in SecurityAdmin
|
|
||||||
Permission::checkMember($member, 'CMS_ACCESS_LeftAndMain') // 'Access to all CMS sections' in SecurityAdmin
|
|
||||||
)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we have access to current section on the current subsite.
|
|
||||||
$accessibleSites = $this->owner->sectionSites(true, "Main site", $member);
|
|
||||||
if ($accessibleSites->count() && $accessibleSites->find('ID', Subsite::currentSubsiteID())) {
|
|
||||||
// Current section can be accessed on the current site, all good.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prevent accessing disallowed resources. This happens after onBeforeInit has executed,
|
|
||||||
* so all redirections should've already taken place.
|
|
||||||
*/
|
|
||||||
public function alternateAccessCheck() {
|
|
||||||
return $this->owner->canAccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redirect the user to something accessible if the current section/subsite is forbidden.
|
|
||||||
*
|
|
||||||
* This is done via onBeforeInit as it needs to be done before the LeftAndMain::init has a
|
|
||||||
* chance to forbids access via alternateAccessCheck.
|
|
||||||
*
|
|
||||||
* If we need to change the subsite we force the redirection to /admin/ so the frontend is
|
|
||||||
* fully re-synchronised with the internal session. This is better than risking some panels
|
|
||||||
* showing data from another subsite.
|
|
||||||
*/
|
|
||||||
public function onBeforeInit() {
|
|
||||||
// We are accessing the CMS, so we need to let Subsites know we will be using the session.
|
|
||||||
Subsite::$use_session_subsiteid = true;
|
|
||||||
|
|
||||||
// FIRST, check if we need to change subsites due to the URL.
|
|
||||||
|
|
||||||
// Catch forced subsite changes that need to cause CMS reloads.
|
|
||||||
if(isset($_GET['SubsiteID'])) {
|
|
||||||
// Clear current page when subsite changes (or is set for the first time)
|
|
||||||
if(!Session::get('SubsiteID') || $_GET['SubsiteID'] != Session::get('SubsiteID')) {
|
|
||||||
Session::clear("{$this->owner->class}.currentPage");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update current subsite in session
|
|
||||||
Subsite::changeSubsite($_GET['SubsiteID']);
|
|
||||||
|
|
||||||
if (!$this->owner->canView(Member::currentUser())) {
|
|
||||||
//Redirect to the default CMS section
|
|
||||||
return $this->owner->redirect('admin/');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Automatically redirect the session to appropriate subsite when requesting a record.
|
|
||||||
// This is needed to properly initialise the session in situations where someone opens the CMS via a link.
|
|
||||||
$record = $this->owner->currentPage();
|
|
||||||
if($record && isset($record->SubsiteID) && is_numeric($record->SubsiteID) && isset($this->owner->urlParams['ID'])) {
|
|
||||||
|
|
||||||
if ($this->shouldChangeSubsite($this->owner->class, $record->SubsiteID, Subsite::currentSubsiteID())) {
|
|
||||||
// Update current subsite in session
|
|
||||||
Subsite::changeSubsite($record->SubsiteID);
|
|
||||||
|
|
||||||
if (!$this->owner->canView(Member::currentUser())) {
|
|
||||||
//Redirect to the default CMS section
|
|
||||||
return $this->owner->redirect('admin/');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// SECOND, check if we need to change subsites due to lack of permissions.
|
|
||||||
|
|
||||||
if (!$this->owner->canAccess()) {
|
|
||||||
|
|
||||||
$member = Member::currentUser();
|
|
||||||
|
|
||||||
// Current section is not accessible, try at least to stick to the same subsite.
|
|
||||||
$menu = CMSMenu::get_menu_items();
|
|
||||||
foreach($menu as $candidate) {
|
|
||||||
if($candidate->controller && $candidate->controller!=$this->owner->class) {
|
|
||||||
|
|
||||||
$accessibleSites = singleton($candidate->controller)->sectionSites(true, 'Main site', $member);
|
|
||||||
if ($accessibleSites->count() && $accessibleSites->find('ID', Subsite::currentSubsiteID())) {
|
|
||||||
// Section is accessible, redirect there.
|
|
||||||
return $this->owner->redirect(singleton($candidate->controller)->Link());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no section is available, look for other accessible subsites.
|
|
||||||
foreach($menu as $candidate) {
|
|
||||||
if($candidate->controller) {
|
|
||||||
$accessibleSites = singleton($candidate->controller)->sectionSites(true, 'Main site', $member);
|
|
||||||
if ($accessibleSites->count()) {
|
|
||||||
Subsite::changeSubsite($accessibleSites->First()->ID);
|
|
||||||
return $this->owner->redirect(singleton($candidate->controller)->Link());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have not found any accessible section or subsite. User should be denied access.
|
|
||||||
return Security::permissionFailure($this->owner);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Current site is accessible. Allow through.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function augmentNewSiteTreeItem(&$item) {
|
|
||||||
$item->SubsiteID = isset($_POST['SubsiteID']) ? $_POST['SubsiteID'] : Subsite::currentSubsiteID();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onAfterSave($record) {
|
|
||||||
if($record->hasMethod('NormalRelated') && ($record->NormalRelated() || $record->ReverseRelated())) {
|
|
||||||
$this->owner->response->addHeader('X-Status', rawurlencode(_t('LeftAndMainSubsites.Saved', 'Saved, please update related pages.')));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function copytosubsite($data, $form) {
|
|
||||||
$page = DataObject::get_by_id('SiteTree', $data['ID']);
|
|
||||||
$subsite = DataObject::get_by_id('Subsite', $data['CopyToSubsiteID']);
|
|
||||||
$newPage = $page->duplicateToSubsite($subsite->ID, true);
|
|
||||||
$response = $this->owner->getResponse();
|
|
||||||
$response->addHeader('X-Reload', true);
|
|
||||||
return $this->owner->redirect(Controller::join_links($this->owner->Link('show'), $newPage->ID));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extension for the SiteConfig object to add subsites support
|
|
||||||
*/
|
|
||||||
class SiteConfigSubsites extends DataExtension {
|
|
||||||
|
|
||||||
private static $has_one = array(
|
|
||||||
'Subsite' => 'Subsite', // The subsite that this page belongs to
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update any requests to limit the results to the current site
|
|
||||||
*/
|
|
||||||
function augmentSQL(SQLQuery &$query) {
|
|
||||||
if(Subsite::$disable_subsite_filter) return;
|
|
||||||
|
|
||||||
// If you're querying by ID, ignore the sub-site - this is a bit ugly...
|
|
||||||
if (!$query->where || (!preg_match('/\.(\'|"|`|)ID(\'|"|`|)( ?)=/', $query->where[0]) && !preg_match('/\.?(\'|"|`|)SubsiteID(\'|"|`|)( ?)=/', $query->where[0]))) {
|
|
||||||
/*if($context = DataObject::context_obj()) $subsiteID = (int)$context->SubsiteID;
|
|
||||||
else */$subsiteID = (int)Subsite::currentSubsiteID();
|
|
||||||
|
|
||||||
$froms=$query->getFrom();
|
|
||||||
$froms=array_keys($froms);
|
|
||||||
$tableName = array_shift($froms);
|
|
||||||
if($tableName != 'SiteConfig') return;
|
|
||||||
$query->addWhere("\"$tableName\".\"SubsiteID\" IN ($subsiteID)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onBeforeWrite() {
|
|
||||||
if((!is_numeric($this->owner->ID) || !$this->owner->ID) && !$this->owner->SubsiteID) $this->owner->SubsiteID = Subsite::currentSubsiteID();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a piece of text to keep DataObject cache keys appropriately specific
|
|
||||||
*/
|
|
||||||
function cacheKeyComponent() {
|
|
||||||
return 'subsite-'.Subsite::currentSubsiteID();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateCMSFields(FieldList $fields) {
|
|
||||||
$fields->push(new HiddenField('SubsiteID','SubsiteID', Subsite::currentSubsiteID()));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,301 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extension for the SiteTree object to add subsites support
|
|
||||||
*/
|
|
||||||
class SiteTreeSubsites extends DataExtension {
|
|
||||||
|
|
||||||
private static $has_one = array(
|
|
||||||
'Subsite' => 'Subsite', // The subsite that this page belongs to
|
|
||||||
);
|
|
||||||
|
|
||||||
private static $many_many = array(
|
|
||||||
'CrossSubsiteLinkTracking' => 'SiteTree' // Stored separately, as the logic for URL rewriting is different
|
|
||||||
);
|
|
||||||
|
|
||||||
private static $many_many_extraFields = array(
|
|
||||||
"CrossSubsiteLinkTracking" => array("FieldName" => "Varchar")
|
|
||||||
);
|
|
||||||
|
|
||||||
function isMainSite() {
|
|
||||||
if($this->owner->SubsiteID == 0) return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update any requests to limit the results to the current site
|
|
||||||
*/
|
|
||||||
function augmentSQL(SQLQuery &$query, DataQuery &$dataQuery = null) {
|
|
||||||
if(Subsite::$disable_subsite_filter) return;
|
|
||||||
if($dataQuery->getQueryParam('Subsite.filter') === false) return;
|
|
||||||
|
|
||||||
// Don't run on delete queries, since they are always tied to
|
|
||||||
// a specific ID.
|
|
||||||
if ($query->getDelete()) return;
|
|
||||||
|
|
||||||
// If you're querying by ID, ignore the sub-site - this is a bit ugly...
|
|
||||||
// if(!$query->where || (strpos($query->where[0], ".\"ID\" = ") === false && strpos($query->where[0], ".`ID` = ") === false && strpos($query->where[0], ".ID = ") === false && strpos($query->where[0], "ID = ") !== 0)) {
|
|
||||||
if (!$query->where || (!preg_match('/\.(\'|"|`|)ID(\'|"|`|)( ?)=/', $query->where[0]))) {
|
|
||||||
|
|
||||||
if (Subsite::$force_subsite) $subsiteID = Subsite::$force_subsite;
|
|
||||||
else {
|
|
||||||
/*if($context = DataObject::context_obj()) $subsiteID = (int)$context->SubsiteID;
|
|
||||||
else */$subsiteID = (int)Subsite::currentSubsiteID();
|
|
||||||
}
|
|
||||||
|
|
||||||
// The foreach is an ugly way of getting the first key :-)
|
|
||||||
foreach($query->getFrom() as $tableName => $info) {
|
|
||||||
// The tableName should be SiteTree or SiteTree_Live...
|
|
||||||
if(strpos($tableName,'SiteTree') === false) break;
|
|
||||||
$query->addWhere("\"$tableName\".\"SubsiteID\" IN ($subsiteID)");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onBeforeWrite() {
|
|
||||||
if(!$this->owner->ID && !$this->owner->SubsiteID) $this->owner->SubsiteID = Subsite::currentSubsiteID();
|
|
||||||
|
|
||||||
parent::onBeforeWrite();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateCMSFields(FieldList $fields) {
|
|
||||||
$subsites = Subsite::accessible_sites("CMS_ACCESS_CMSMain");
|
|
||||||
$subsitesMap = array();
|
|
||||||
if($subsites && $subsites->Count()) {
|
|
||||||
$subsitesMap = $subsites->map('ID', 'Title');
|
|
||||||
unset($subsitesMap[$this->owner->SubsiteID]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Master page edit field (only allowed from default subsite to avoid inconsistent relationships)
|
|
||||||
$isDefaultSubsite = $this->owner->SubsiteID == 0 || $this->owner->Subsite()->DefaultSite;
|
|
||||||
if($isDefaultSubsite && $subsitesMap) {
|
|
||||||
$fields->addFieldToTab(
|
|
||||||
'Root.Main',
|
|
||||||
new DropdownField(
|
|
||||||
"CopyToSubsiteID",
|
|
||||||
_t('SiteTreeSubsites.CopyToSubsite', "Copy page to subsite"),
|
|
||||||
$subsitesMap,
|
|
||||||
''
|
|
||||||
)
|
|
||||||
);
|
|
||||||
$fields->addFieldToTab(
|
|
||||||
'Root.Main',
|
|
||||||
$copyAction = new InlineFormAction(
|
|
||||||
"copytosubsite",
|
|
||||||
_t('SiteTreeSubsites.CopyAction', "Copy")
|
|
||||||
)
|
|
||||||
);
|
|
||||||
$copyAction->includeDefaultJS(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// replace readonly link prefix
|
|
||||||
$subsite = $this->owner->Subsite();
|
|
||||||
$nested_urls_enabled = Config::inst()->get('SiteTree', 'nested_urls');
|
|
||||||
if($subsite && $subsite->ID) {
|
|
||||||
$baseUrl = Director::protocol() . $subsite->domain() . '/';
|
|
||||||
$baseLink = Controller::join_links (
|
|
||||||
$baseUrl,
|
|
||||||
($nested_urls_enabled && $this->owner->ParentID ? $this->owner->Parent()->RelativeLink(true) : null)
|
|
||||||
);
|
|
||||||
|
|
||||||
$urlsegment = $fields->dataFieldByName('URLSegment');
|
|
||||||
$urlsegment->setURLPrefix($baseLink);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function alternateSiteConfig() {
|
|
||||||
if(!$this->owner->SubsiteID) return false;
|
|
||||||
$sc = DataObject::get_one('SiteConfig', '"SubsiteID" = ' . $this->owner->SubsiteID);
|
|
||||||
if(!$sc) {
|
|
||||||
$sc = new SiteConfig();
|
|
||||||
$sc->SubsiteID = $this->owner->SubsiteID;
|
|
||||||
$sc->Title = _t('Subsite.SiteConfigTitle','Your Site Name');
|
|
||||||
$sc->Tagline = _t('Subsite.SiteConfigSubtitle','Your tagline here');
|
|
||||||
$sc->write();
|
|
||||||
}
|
|
||||||
return $sc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Only allow editing of a page if the member satisfies one of the following conditions:
|
|
||||||
* - Is in a group which has access to the subsite this page belongs to
|
|
||||||
* - Is in a group with edit permissions on the "main site"
|
|
||||||
*
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
function canEdit($member = null) {
|
|
||||||
|
|
||||||
if(!$member) $member = Member::currentUser();
|
|
||||||
|
|
||||||
// Find the sites that this user has access to
|
|
||||||
$goodSites = Subsite::accessible_sites('CMS_ACCESS_CMSMain',true,'all',$member)->column('ID');
|
|
||||||
|
|
||||||
if (!is_null($this->owner->SubsiteID)) {
|
|
||||||
$subsiteID = $this->owner->SubsiteID;
|
|
||||||
} else {
|
|
||||||
// The relationships might not be available during the record creation when using a GridField.
|
|
||||||
// In this case the related objects will have empty fields, and SubsiteID will not be available.
|
|
||||||
//
|
|
||||||
// We do the second best: fetch the likely SubsiteID from the session. The drawback is this might
|
|
||||||
// make it possible to force relations to point to other (forbidden) subsites.
|
|
||||||
$subsiteID = Subsite::currentSubsiteID();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return true if they have access to this object's site
|
|
||||||
if(!(in_array(0, $goodSites) || in_array($subsiteID, $goodSites))) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
function canDelete($member = null) {
|
|
||||||
if(!$member && $member !== FALSE) $member = Member::currentUser();
|
|
||||||
|
|
||||||
return $this->canEdit($member);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
function canAddChildren($member = null) {
|
|
||||||
if(!$member && $member !== FALSE) $member = Member::currentUser();
|
|
||||||
|
|
||||||
return $this->canEdit($member);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
function canPublish($member = null) {
|
|
||||||
if(!$member && $member !== FALSE) $member = Member::currentUser();
|
|
||||||
|
|
||||||
return $this->canEdit($member);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a duplicate of this page and save it to another subsite
|
|
||||||
* @param $subsiteID int|Subsite The Subsite to copy to, or its ID
|
|
||||||
*/
|
|
||||||
public function duplicateToSubsite($subsiteID = null) {
|
|
||||||
if(is_object($subsiteID)) {
|
|
||||||
$subsite = $subsiteID;
|
|
||||||
$subsiteID = $subsite->ID;
|
|
||||||
} else $subsite = DataObject::get_by_id('Subsite', $subsiteID);
|
|
||||||
|
|
||||||
$oldSubsite=Subsite::currentSubsiteID();
|
|
||||||
if($subsiteID) {
|
|
||||||
Subsite::changeSubsite($subsiteID);
|
|
||||||
}else {
|
|
||||||
$subsiteID=$oldSubsite;
|
|
||||||
}
|
|
||||||
|
|
||||||
$page = $this->owner->duplicate(false);
|
|
||||||
|
|
||||||
$page->CheckedPublicationDifferences = $page->AddedToStage = true;
|
|
||||||
$subsiteID = ($subsiteID ? $subsiteID : $oldSubsite);
|
|
||||||
$page->SubsiteID = $subsiteID;
|
|
||||||
|
|
||||||
// MasterPageID is here for legacy purposes, to satisfy the subsites_relatedpages module
|
|
||||||
$page->MasterPageID = $this->owner->ID;
|
|
||||||
$page->write();
|
|
||||||
|
|
||||||
Subsite::changeSubsite($oldSubsite);
|
|
||||||
|
|
||||||
return $page;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by ContentController::init();
|
|
||||||
*/
|
|
||||||
static function contentcontrollerInit($controller) {
|
|
||||||
$subsite = Subsite::currentSubsite();
|
|
||||||
if($subsite && $subsite->Theme) SSViewer::set_theme(Subsite::currentSubsite()->Theme);
|
|
||||||
}
|
|
||||||
|
|
||||||
function alternateAbsoluteLink() {
|
|
||||||
// Generate the existing absolute URL and replace the domain with the subsite domain.
|
|
||||||
// This helps deal with Link() returning an absolute URL.
|
|
||||||
$url = Director::absoluteURL($this->owner->Link());
|
|
||||||
if($this->owner->SubsiteID) {
|
|
||||||
$url = preg_replace('/\/\/[^\/]+\//', '//' . $this->owner->Subsite()->domain() . '/', $url);
|
|
||||||
}
|
|
||||||
return $url;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use the CMS domain for iframed CMS previews to prevent single-origin violations
|
|
||||||
* and SSL cert problems.
|
|
||||||
*/
|
|
||||||
function alternatePreviewLink($action = null) {
|
|
||||||
$url = Director::absoluteURL($this->owner->Link());
|
|
||||||
if($this->owner->SubsiteID) {
|
|
||||||
$url = HTTP::setGetVar('SubsiteID', $this->owner->SubsiteID, $url);
|
|
||||||
}
|
|
||||||
return $url;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inject the subsite ID into the content so it can be used by frontend scripts.
|
|
||||||
*/
|
|
||||||
function MetaTags(&$tags) {
|
|
||||||
if($this->owner->SubsiteID) {
|
|
||||||
$tags .= "<meta name=\"x-subsite-id\" content=\"" . $this->owner->SubsiteID . "\" />\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
return $tags;
|
|
||||||
}
|
|
||||||
|
|
||||||
function augmentSyncLinkTracking() {
|
|
||||||
// Set LinkTracking appropriately
|
|
||||||
$links = HTTP::getLinksIn($this->owner->Content);
|
|
||||||
$linkedPages = array();
|
|
||||||
|
|
||||||
if($links) foreach($links as $link) {
|
|
||||||
if(substr($link, 0, strlen('http://')) == 'http://') {
|
|
||||||
$withoutHttp = substr($link, strlen('http://'));
|
|
||||||
if(strpos($withoutHttp, '/') && strpos($withoutHttp, '/') < strlen($withoutHttp)) {
|
|
||||||
$domain = substr($withoutHttp, 0, strpos($withoutHttp, '/'));
|
|
||||||
$rest = substr($withoutHttp, strpos($withoutHttp, '/') + 1);
|
|
||||||
|
|
||||||
$subsiteID = Subsite::getSubsiteIDForDomain($domain);
|
|
||||||
if($subsiteID == 0) continue; // We have no idea what the domain for the main site is, so cant track links to it
|
|
||||||
|
|
||||||
$origDisableSubsiteFilter = Subsite::$disable_subsite_filter;
|
|
||||||
Subsite::disable_subsite_filter(true);
|
|
||||||
$candidatePage = DataObject::get_one("SiteTree", "\"URLSegment\" = '" . Convert::raw2sql(urldecode( $rest)) . "' AND \"SubsiteID\" = " . $subsiteID, false);
|
|
||||||
Subsite::disable_subsite_filter($origDisableSubsiteFilter);
|
|
||||||
|
|
||||||
if($candidatePage) {
|
|
||||||
$linkedPages[] = $candidatePage->ID;
|
|
||||||
} else {
|
|
||||||
$this->owner->HasBrokenLink = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->owner->CrossSubsiteLinkTracking()->setByIDList($linkedPages);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a piece of text to keep DataObject cache keys appropriately specific
|
|
||||||
*/
|
|
||||||
function cacheKeyComponent() {
|
|
||||||
return 'subsite-'.Subsite::currentSubsiteID();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Member
|
|
||||||
* @return boolean|null
|
|
||||||
*/
|
|
||||||
function canCreate($member = null) {
|
|
||||||
// Typically called on a singleton, so we're not using the Subsite() relation
|
|
||||||
$subsite = Subsite::currentSubsite();
|
|
||||||
if($subsite && $subsite->exists() && $subsite->PageTypeBlacklist) {
|
|
||||||
$blacklisted = explode(',', $subsite->PageTypeBlacklist);
|
|
||||||
// All subclasses need to be listed explicitly
|
|
||||||
if(in_array($this->owner->class, $blacklisted)) return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
<?php
|
|
||||||
class GridFieldSubsiteDetailForm extends GridFieldDetailForm {
|
|
||||||
protected $itemRequestClass='GridFieldSubsiteDetailForm_ItemRequest';
|
|
||||||
}
|
|
||||||
|
|
||||||
class GridFieldSubsiteDetailForm_ItemRequest extends GridFieldDetailForm_ItemRequest {
|
|
||||||
|
|
||||||
private static $allowed_actions = array(
|
|
||||||
'ItemEditForm',
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds an item edit form. The arguments to getCMSFields() are the popupController and
|
|
||||||
* popupFormName, however this is an experimental API and may change.
|
|
||||||
*
|
|
||||||
* @todo In the future, we will probably need to come up with a tigher object representing a partially
|
|
||||||
* complete controller with gaps for extra functionality. This, for example, would be a better way
|
|
||||||
* of letting Security/login put its log-in form inside a UI specified elsewhere.
|
|
||||||
*
|
|
||||||
* @return Form
|
|
||||||
* @see GridFieldDetailForm_ItemRequest::ItemEditForm()
|
|
||||||
*/
|
|
||||||
function ItemEditForm() {
|
|
||||||
$form=parent::ItemEditForm();
|
|
||||||
|
|
||||||
if($this->record->ID == 0) {
|
|
||||||
$templates = Subsite::get()->sort('Title');
|
|
||||||
$templateArray = array();
|
|
||||||
if($templates) {
|
|
||||||
$templateArray = $templates->map('ID', 'Title');
|
|
||||||
}
|
|
||||||
|
|
||||||
$templateDropdown = new DropdownField('TemplateID', _t('Subsite.COPYSTRUCTURE', 'Copy structure from:'), $templateArray);
|
|
||||||
$templateDropdown->setEmptyString('(' . _t('Subsite.NOTEMPLATE', 'No template') . ')');
|
|
||||||
$form->Fields()->addFieldToTab('Root.Configuration', $templateDropdown);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $form;
|
|
||||||
}
|
|
||||||
|
|
||||||
function doSave($data, $form) {
|
|
||||||
$new_record = $this->record->ID == 0;
|
|
||||||
if($new_record && isset($data['TemplateID']) && !empty($data['TemplateID'])) {
|
|
||||||
$template = Subsite::get()->byID(intval($data['TemplateID']));
|
|
||||||
if($template) {
|
|
||||||
$this->record = $template->duplicate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return parent::doSave($data, $form);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* Wraps around a TreedropdownField to add ability for temporary
|
|
||||||
* switching of subsite sessions.
|
|
||||||
*
|
|
||||||
* @package subsites
|
|
||||||
*/
|
|
||||||
class SubsitesTreeDropdownField extends TreeDropdownField {
|
|
||||||
|
|
||||||
private static $allowed_actions = array(
|
|
||||||
'tree'
|
|
||||||
);
|
|
||||||
|
|
||||||
protected $subsiteID = 0;
|
|
||||||
|
|
||||||
protected $extraClasses = array('SubsitesTreeDropdownField');
|
|
||||||
|
|
||||||
function Field($properties = array()) {
|
|
||||||
$html = parent::Field($properties);
|
|
||||||
|
|
||||||
Requirements::javascript('subsites/javascript/SubsitesTreeDropdownField.js');
|
|
||||||
|
|
||||||
return $html;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setSubsiteID($id) {
|
|
||||||
$this->subsiteID = $id;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSubsiteID() {
|
|
||||||
return $this->subsiteID;
|
|
||||||
}
|
|
||||||
|
|
||||||
function tree(SS_HTTPRequest $request) {
|
|
||||||
$oldSubsiteID = Session::get('SubsiteID');
|
|
||||||
Session::set('SubsiteID', $this->subsiteID);
|
|
||||||
|
|
||||||
$results = parent::tree($request);
|
|
||||||
|
|
||||||
Session::set('SubsiteID', $oldSubsiteID);
|
|
||||||
|
|
||||||
return $results;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,820 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* A dynamically created subsite. SiteTree objects can now belong to a subsite.
|
|
||||||
* You can simulate subsite access without setting up virtual hosts by appending ?SubsiteID=<ID> to the request.
|
|
||||||
*
|
|
||||||
* @package subsites
|
|
||||||
*/
|
|
||||||
class Subsite extends DataObject {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var $use_session_subsiteid Boolean Set to TRUE when using the CMS and FALSE
|
|
||||||
* when browsing the frontend of a website.
|
|
||||||
*
|
|
||||||
* @todo Remove flag once the Subsite CMS works without session state,
|
|
||||||
* similarly to the Translatable module.
|
|
||||||
*/
|
|
||||||
public static $use_session_subsiteid = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var boolean $disable_subsite_filter If enabled, bypasses the query decoration
|
|
||||||
* to limit DataObject::get*() calls to a specific subsite. Useful for debugging.
|
|
||||||
*/
|
|
||||||
public static $disable_subsite_filter = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows you to force a specific subsite ID, or comma separated list of IDs.
|
|
||||||
* Only works for reading. An object cannot be written to more than 1 subsite.
|
|
||||||
*/
|
|
||||||
public static $force_subsite = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @var boolean
|
|
||||||
*/
|
|
||||||
public static $write_hostmap = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Memory cache of accessible sites
|
|
||||||
*
|
|
||||||
* @array
|
|
||||||
*/
|
|
||||||
private static $_cache_accessible_sites = array();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Memory cache of subsite id for domains
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private static $_cache_subsite_for_domain = array();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array $allowed_themes Numeric array of all themes which are allowed to be selected for all subsites.
|
|
||||||
* Corresponds to subfolder names within the /themes folder. By default, all themes contained in this folder
|
|
||||||
* are listed.
|
|
||||||
*/
|
|
||||||
private static $allowed_themes = array();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Boolean If set to TRUE, don't assume 'www.example.com' and 'example.com' are the same.
|
|
||||||
* Doesn't affect wildcard matching, so '*.example.com' will match 'www.example.com' (but not 'example.com')
|
|
||||||
* in both TRUE or FALSE setting.
|
|
||||||
*/
|
|
||||||
public static $strict_subdomain_matching = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var boolean Respects the IsPublic flag when retrieving subsites
|
|
||||||
*/
|
|
||||||
public static $check_is_public = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set allowed themes
|
|
||||||
*
|
|
||||||
* @param array $themes - Numeric array of all themes which are allowed to be selected for all subsites.
|
|
||||||
*/
|
|
||||||
public static function set_allowed_themes($themes) {
|
|
||||||
self::$allowed_themes = $themes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the subsite currently set in the session.
|
|
||||||
*
|
|
||||||
* @uses ControllerSubsites->controllerAugmentInit()
|
|
||||||
* @return Subsite
|
|
||||||
*/
|
|
||||||
public static function currentSubsite() {
|
|
||||||
// get_by_id handles caching so we don't have to
|
|
||||||
return DataObject::get_by_id('Subsite', self::currentSubsiteID());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function gets the current subsite ID from the session. It used in the backend so Ajax requests
|
|
||||||
* use the correct subsite. The frontend handles subsites differently. It calls getSubsiteIDForDomain
|
|
||||||
* directly from ModelAsController::getNestedController. Only gets Subsite instances which have their
|
|
||||||
* {@link IsPublic} flag set to TRUE.
|
|
||||||
*
|
|
||||||
* You can simulate subsite access without creating virtual hosts by appending ?SubsiteID=<ID> to the request.
|
|
||||||
*
|
|
||||||
* @todo Pass $request object from controller so we don't have to rely on $_GET
|
|
||||||
*
|
|
||||||
* @param boolean $cache
|
|
||||||
* @return int ID of the current subsite instance
|
|
||||||
*/
|
|
||||||
public static function currentSubsiteID() {
|
|
||||||
$id = NULL;
|
|
||||||
|
|
||||||
if(isset($_GET['SubsiteID'])) {
|
|
||||||
$id = (int)$_GET['SubsiteID'];
|
|
||||||
} else if (Subsite::$use_session_subsiteid) {
|
|
||||||
$id = Session::get('SubsiteID');
|
|
||||||
}
|
|
||||||
|
|
||||||
if($id === NULL) {
|
|
||||||
$id = self::getSubsiteIDForDomain();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (int)$id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Switch to another subsite through storing the subsite identifier in the current PHP session.
|
|
||||||
* Only takes effect when {@link Subsite::$use_session_subsiteid} is set to TRUE.
|
|
||||||
*
|
|
||||||
* @param int|Subsite $subsite Either the ID of the subsite, or the subsite object itself
|
|
||||||
*/
|
|
||||||
public static function changeSubsite($subsite) {
|
|
||||||
// Session subsite change only meaningful if the session is active.
|
|
||||||
// Otherwise we risk setting it to wrong value, e.g. if we rely on currentSubsiteID.
|
|
||||||
if (!Subsite::$use_session_subsiteid) return;
|
|
||||||
|
|
||||||
if(is_object($subsite)) $subsiteID = $subsite->ID;
|
|
||||||
else $subsiteID = $subsite;
|
|
||||||
|
|
||||||
Session::set('SubsiteID', (int)$subsiteID);
|
|
||||||
|
|
||||||
// Set locale
|
|
||||||
if (is_object($subsite) && $subsite->Language != '') {
|
|
||||||
$locale = i18n::get_locale_from_lang($subsite->Language);
|
|
||||||
if($locale) {
|
|
||||||
i18n::set_locale($locale);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Permission::flush_permission_cache();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a matching subsite for the given host, or for the current HTTP_HOST.
|
|
||||||
* Supports "fuzzy" matching of domains by placing an asterisk at the start of end of the string,
|
|
||||||
* for example matching all subdomains on *.example.com with one subsite,
|
|
||||||
* and all subdomains on *.example.org on another.
|
|
||||||
*
|
|
||||||
* @param $host The host to find the subsite for. If not specified, $_SERVER['HTTP_HOST'] is used.
|
|
||||||
* @return int Subsite ID
|
|
||||||
*/
|
|
||||||
public static function getSubsiteIDForDomain($host = null, $checkPermissions = true) {
|
|
||||||
if($host == null && isset($_SERVER['HTTP_HOST'])) {
|
|
||||||
$host = $_SERVER['HTTP_HOST'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$matchingDomains = null;
|
|
||||||
$cacheKey = null;
|
|
||||||
if ($host) {
|
|
||||||
if(!self::$strict_subdomain_matching) $host = preg_replace('/^www\./', '', $host);
|
|
||||||
|
|
||||||
$cacheKey = implode('_', array($host, Member::currentUserID(), self::$check_is_public));
|
|
||||||
if(isset(self::$_cache_subsite_for_domain[$cacheKey])) return self::$_cache_subsite_for_domain[$cacheKey];
|
|
||||||
|
|
||||||
$SQL_host = Convert::raw2sql($host);
|
|
||||||
$matchingDomains = DataObject::get(
|
|
||||||
"SubsiteDomain",
|
|
||||||
"'$SQL_host' LIKE replace(\"SubsiteDomain\".\"Domain\",'*','%')",
|
|
||||||
"\"IsPrimary\" DESC"
|
|
||||||
)->innerJoin('Subsite', "\"Subsite\".\"ID\" = \"SubsiteDomain\".\"SubsiteID\" AND \"Subsite\".\"IsPublic\"=1");
|
|
||||||
}
|
|
||||||
|
|
||||||
if($matchingDomains && $matchingDomains->Count()) {
|
|
||||||
$subsiteIDs = array_unique($matchingDomains->column('SubsiteID'));
|
|
||||||
$subsiteDomains = array_unique($matchingDomains->column('Domain'));
|
|
||||||
if(sizeof($subsiteIDs) > 1) {
|
|
||||||
throw new UnexpectedValueException(sprintf(
|
|
||||||
"Multiple subsites match on '%s': %s",
|
|
||||||
$host,
|
|
||||||
implode(',', $subsiteDomains)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
$subsiteID = $subsiteIDs[0];
|
|
||||||
} else if($default = DataObject::get_one('Subsite', "\"DefaultSite\" = 1")) {
|
|
||||||
// Check for a 'default' subsite
|
|
||||||
$subsiteID = $default->ID;
|
|
||||||
} else {
|
|
||||||
// Default subsite id = 0, the main site
|
|
||||||
$subsiteID = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($cacheKey) {
|
|
||||||
self::$_cache_subsite_for_domain[$cacheKey] = $subsiteID;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $subsiteID;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param string $className
|
|
||||||
* @param string $filter
|
|
||||||
* @param string $sort
|
|
||||||
* @param string $join
|
|
||||||
* @param string $limit
|
|
||||||
* @return DataList
|
|
||||||
*/
|
|
||||||
public static function get_from_all_subsites($className, $filter = "", $sort = "", $join = "", $limit = "") {
|
|
||||||
$result = DataObject::get($className, $filter, $sort, $join, $limit);
|
|
||||||
$result = $result->setDataQueryParam('Subsite.filter', false);
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disable the sub-site filtering; queries will select from all subsites
|
|
||||||
*/
|
|
||||||
public static function disable_subsite_filter($disabled = true) {
|
|
||||||
self::$disable_subsite_filter = $disabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flush caches on database reset
|
|
||||||
*/
|
|
||||||
public static function on_db_reset() {
|
|
||||||
self::$_cache_accessible_sites = array();
|
|
||||||
self::$_cache_subsite_for_domain = array();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return all subsites, regardless of permissions (augmented with main site).
|
|
||||||
*
|
|
||||||
* @return SS_List List of {@link Subsite} objects (DataList or ArrayList).
|
|
||||||
*/
|
|
||||||
public static function all_sites($includeMainSite = true, $mainSiteTitle = "Main site") {
|
|
||||||
$subsites = Subsite::get();
|
|
||||||
|
|
||||||
if($includeMainSite) {
|
|
||||||
$subsites = $subsites->toArray();
|
|
||||||
|
|
||||||
$mainSite = new Subsite();
|
|
||||||
$mainSite->Title = $mainSiteTitle;
|
|
||||||
array_unshift($subsites, $mainSite);
|
|
||||||
|
|
||||||
$subsites = ArrayList::create($subsites);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $subsites;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Returns an ArrayList of the subsites accessible to the current user.
|
|
||||||
* It's enough for any section to be accessible for the site to be included.
|
|
||||||
*
|
|
||||||
* @return ArrayList of {@link Subsite} instances.
|
|
||||||
*/
|
|
||||||
public static function all_accessible_sites($includeMainSite = true, $mainSiteTitle = "Main site", $member = null) {
|
|
||||||
// Rationalise member arguments
|
|
||||||
if(!$member) $member = Member::currentUser();
|
|
||||||
if(!$member) return new ArrayList();
|
|
||||||
if(!is_object($member)) $member = DataObject::get_by_id('Member', $member);
|
|
||||||
|
|
||||||
$subsites = new ArrayList();
|
|
||||||
|
|
||||||
// Collect subsites for all sections.
|
|
||||||
$menu = CMSMenu::get_viewable_menu_items();
|
|
||||||
foreach($menu as $candidate) {
|
|
||||||
if ($candidate->controller) {
|
|
||||||
$accessibleSites = singleton($candidate->controller)->sectionSites(
|
|
||||||
$includeMainSite,
|
|
||||||
$mainSiteTitle,
|
|
||||||
$member
|
|
||||||
);
|
|
||||||
|
|
||||||
// Replace existing keys so no one site appears twice.
|
|
||||||
$subsites->merge($accessibleSites);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$subsites->removeDuplicates();
|
|
||||||
|
|
||||||
return $subsites;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the subsites that the current user can access by given permission.
|
|
||||||
* Sites will only be included if they have a Title.
|
|
||||||
*
|
|
||||||
* @param $permCode array|string Either a single permission code or an array of permission codes.
|
|
||||||
* @param $includeMainSite If true, the main site will be included if appropriate.
|
|
||||||
* @param $mainSiteTitle The label to give to the main site
|
|
||||||
* @param $member
|
|
||||||
* @return DataList of {@link Subsite} instances
|
|
||||||
*/
|
|
||||||
public static function accessible_sites($permCode, $includeMainSite = true, $mainSiteTitle = "Main site", $member = null) {
|
|
||||||
// Rationalise member arguments
|
|
||||||
if(!$member) $member = Member::currentUser();
|
|
||||||
if(!$member) return new ArrayList();
|
|
||||||
if(!is_object($member)) $member = DataObject::get_by_id('Member', $member);
|
|
||||||
|
|
||||||
// Rationalise permCode argument
|
|
||||||
if(is_array($permCode)) $SQL_codes = "'" . implode("', '", Convert::raw2sql($permCode)) . "'";
|
|
||||||
else $SQL_codes = "'" . Convert::raw2sql($permCode) . "'";
|
|
||||||
|
|
||||||
// Cache handling
|
|
||||||
$cacheKey = $SQL_codes . '-' . $member->ID . '-' . $includeMainSite . '-' . $mainSiteTitle;
|
|
||||||
if(isset(self::$_cache_accessible_sites[$cacheKey])) {
|
|
||||||
return self::$_cache_accessible_sites[$cacheKey];
|
|
||||||
}
|
|
||||||
|
|
||||||
$subsites = DataList::create('Subsite')
|
|
||||||
->where("\"Subsite\".\"Title\" != ''")
|
|
||||||
->leftJoin('Group_Subsites', "\"Group_Subsites\".\"SubsiteID\" = \"Subsite\".\"ID\"")
|
|
||||||
->innerJoin('Group', "\"Group\".\"ID\" = \"Group_Subsites\".\"GroupID\" OR \"Group\".\"AccessAllSubsites\" = 1")
|
|
||||||
->innerJoin('Group_Members', "\"Group_Members\".\"GroupID\"=\"Group\".\"ID\" AND \"Group_Members\".\"MemberID\" = $member->ID")
|
|
||||||
->innerJoin('Permission', "\"Group\".\"ID\"=\"Permission\".\"GroupID\" AND \"Permission\".\"Code\" IN ($SQL_codes, 'CMS_ACCESS_LeftAndMain', 'ADMIN')");
|
|
||||||
|
|
||||||
if(!$subsites) $subsites = new ArrayList();
|
|
||||||
|
|
||||||
$rolesSubsites = DataList::create('Subsite')
|
|
||||||
->where("\"Subsite\".\"Title\" != ''")
|
|
||||||
->leftJoin('Group_Subsites', "\"Group_Subsites\".\"SubsiteID\" = \"Subsite\".\"ID\"")
|
|
||||||
->innerJoin('Group', "\"Group\".\"ID\" = \"Group_Subsites\".\"GroupID\" OR \"Group\".\"AccessAllSubsites\" = 1")
|
|
||||||
->innerJoin('Group_Members', "\"Group_Members\".\"GroupID\"=\"Group\".\"ID\" AND \"Group_Members\".\"MemberID\" = $member->ID")
|
|
||||||
->innerJoin('Group_Roles', "\"Group_Roles\".\"GroupID\"=\"Group\".\"ID\"")
|
|
||||||
->innerJoin('PermissionRole', "\"Group_Roles\".\"PermissionRoleID\"=\"PermissionRole\".\"ID\"")
|
|
||||||
->innerJoin('PermissionRoleCode', "\"PermissionRole\".\"ID\"=\"PermissionRoleCode\".\"RoleID\" AND \"PermissionRoleCode\".\"Code\" IN ($SQL_codes, 'CMS_ACCESS_LeftAndMain', 'ADMIN')");
|
|
||||||
|
|
||||||
if(!$subsites && $rolesSubsites) return $rolesSubsites;
|
|
||||||
|
|
||||||
$subsites = new ArrayList($subsites->toArray());
|
|
||||||
|
|
||||||
if($rolesSubsites) foreach($rolesSubsites as $subsite) {
|
|
||||||
if(!$subsites->find('ID', $subsite->ID)) {
|
|
||||||
$subsites->push($subsite);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if($includeMainSite) {
|
|
||||||
if(!is_array($permCode)) $permCode = array($permCode);
|
|
||||||
if(self::hasMainSitePermission($member, $permCode)) {
|
|
||||||
$subsites=$subsites->toArray();
|
|
||||||
|
|
||||||
$mainSite = new Subsite();
|
|
||||||
$mainSite->Title = $mainSiteTitle;
|
|
||||||
array_unshift($subsites, $mainSite);
|
|
||||||
$subsites=ArrayList::create($subsites);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self::$_cache_accessible_sites[$cacheKey] = $subsites;
|
|
||||||
|
|
||||||
return $subsites;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write a host->domain map to subsites/host-map.php
|
|
||||||
*
|
|
||||||
* This is used primarily when using subsites in conjunction with StaticPublisher
|
|
||||||
*
|
|
||||||
* @param string $file - filepath of the host map to be written
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public static function writeHostMap($file = null) {
|
|
||||||
if (!self::$write_hostmap) return;
|
|
||||||
|
|
||||||
if (!$file) $file = Director::baseFolder().'/subsites/host-map.php';
|
|
||||||
$hostmap = array();
|
|
||||||
|
|
||||||
$subsites = DataObject::get('Subsite');
|
|
||||||
|
|
||||||
if ($subsites) foreach($subsites as $subsite) {
|
|
||||||
$domains = $subsite->Domains();
|
|
||||||
if ($domains) foreach($domains as $domain) {
|
|
||||||
$domainStr = $domain->Domain;
|
|
||||||
if(!self::$strict_subdomain_matching) $domainStr = preg_replace('/^www\./', '', $domainStr);
|
|
||||||
$hostmap[$domainStr] = $subsite->domain();
|
|
||||||
}
|
|
||||||
if ($subsite->DefaultSite) $hostmap['default'] = $subsite->domain();
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = "<?php \n";
|
|
||||||
$data .= "// Generated by Subsite::writeHostMap() on " . date('d/M/y') . "\n";
|
|
||||||
$data .= '$subsiteHostmap = ' . var_export($hostmap, true) . ';';
|
|
||||||
|
|
||||||
if (is_writable(dirname($file)) || is_writable($file)) {
|
|
||||||
file_put_contents($file, $data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a member can be granted certain permissions, regardless of the subsite context.
|
|
||||||
* Similar logic to {@link Permission::checkMember()}, but only returns TRUE
|
|
||||||
* if the member is part of a group with the "AccessAllSubsites" flag set.
|
|
||||||
* If more than one permission is passed to the method, at least one of them must
|
|
||||||
* be granted for if to return TRUE.
|
|
||||||
*
|
|
||||||
* @todo Allow permission inheritance through group hierarchy.
|
|
||||||
*
|
|
||||||
* @param Member Member to check against. Defaults to currently logged in member
|
|
||||||
* @param Array Permission code strings. Defaults to "ADMIN".
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
public static function hasMainSitePermission($member = null, $permissionCodes = array('ADMIN')) {
|
|
||||||
if(!is_array($permissionCodes))
|
|
||||||
user_error('Permissions must be passed to Subsite::hasMainSitePermission as an array', E_USER_ERROR);
|
|
||||||
|
|
||||||
if(!$member && $member !== FALSE) $member = Member::currentUser();
|
|
||||||
|
|
||||||
if(!$member) return false;
|
|
||||||
|
|
||||||
if(!in_array("ADMIN", $permissionCodes)) $permissionCodes[] = "ADMIN";
|
|
||||||
|
|
||||||
$SQLa_perm = Convert::raw2sql($permissionCodes);
|
|
||||||
$SQL_perms = join("','", $SQLa_perm);
|
|
||||||
$memberID = (int)$member->ID;
|
|
||||||
|
|
||||||
// Count this user's groups which can access the main site
|
|
||||||
$groupCount = DB::query("
|
|
||||||
SELECT COUNT(\"Permission\".\"ID\")
|
|
||||||
FROM \"Permission\"
|
|
||||||
INNER JOIN \"Group\" ON \"Group\".\"ID\" = \"Permission\".\"GroupID\" AND \"Group\".\"AccessAllSubsites\" = 1
|
|
||||||
INNER JOIN \"Group_Members\" ON \"Group_Members\".\"GroupID\" = \"Permission\".\"GroupID\"
|
|
||||||
WHERE \"Permission\".\"Code\" IN ('$SQL_perms')
|
|
||||||
AND \"MemberID\" = {$memberID}
|
|
||||||
")->value();
|
|
||||||
|
|
||||||
// Count this user's groups which have a role that can access the main site
|
|
||||||
$roleCount = DB::query("
|
|
||||||
SELECT COUNT(\"PermissionRoleCode\".\"ID\")
|
|
||||||
FROM \"Group\"
|
|
||||||
INNER JOIN \"Group_Members\" ON \"Group_Members\".\"GroupID\" = \"Group\".\"ID\"
|
|
||||||
INNER JOIN \"Group_Roles\" ON \"Group_Roles\".\"GroupID\"=\"Group\".\"ID\"
|
|
||||||
INNER JOIN \"PermissionRole\" ON \"Group_Roles\".\"PermissionRoleID\"=\"PermissionRole\".\"ID\"
|
|
||||||
INNER JOIN \"PermissionRoleCode\" ON \"PermissionRole\".\"ID\"=\"PermissionRoleCode\".\"RoleID\"
|
|
||||||
WHERE \"PermissionRoleCode\".\"Code\" IN ('$SQL_perms')
|
|
||||||
AND \"Group\".\"AccessAllSubsites\" = 1
|
|
||||||
AND \"MemberID\" = {$memberID}
|
|
||||||
")->value();
|
|
||||||
|
|
||||||
// There has to be at least one that allows access.
|
|
||||||
return ($groupCount + $roleCount > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private static $db = array(
|
|
||||||
'Title' => 'Varchar(255)',
|
|
||||||
'RedirectURL' => 'Varchar(255)',
|
|
||||||
'DefaultSite' => 'Boolean',
|
|
||||||
'Theme' => 'Varchar',
|
|
||||||
'Language' => 'Varchar(6)',
|
|
||||||
|
|
||||||
// Used to hide unfinished/private subsites from public view.
|
|
||||||
// If unset, will default to true
|
|
||||||
'IsPublic' => 'Boolean',
|
|
||||||
|
|
||||||
// Comma-separated list of disallowed page types
|
|
||||||
'PageTypeBlacklist' => 'Text',
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private static $has_many = array(
|
|
||||||
'Domains' => 'SubsiteDomain',
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private static $belongs_many_many = array(
|
|
||||||
"Groups" => "Group",
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private static $defaults = array(
|
|
||||||
'IsPublic' => 1
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private static $searchable_fields = array(
|
|
||||||
'Title',
|
|
||||||
'Domains.Domain',
|
|
||||||
'IsPublic',
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private static $default_sort = "\"Title\" ASC";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @todo Possible security issue, don't grant edit permissions to everybody.
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
public function canEdit($member = false) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the configuration fields for each subsite
|
|
||||||
*
|
|
||||||
* @return FieldList
|
|
||||||
*/
|
|
||||||
public function getCMSFields() {
|
|
||||||
if($this->ID!=0) {
|
|
||||||
$domainTable = new GridField(
|
|
||||||
"Domains",
|
|
||||||
_t('Subsite.DomainsListTitle',"Domains"),
|
|
||||||
$this->Domains(),
|
|
||||||
GridFieldConfig_RecordEditor::create(10)
|
|
||||||
);
|
|
||||||
}else {
|
|
||||||
$domainTable = new LiteralField(
|
|
||||||
'Domains',
|
|
||||||
'<p>'._t('Subsite.DOMAINSAVEFIRST', 'You can only add domains after saving for the first time').'</p>'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$languageSelector = new DropdownField(
|
|
||||||
'Language',
|
|
||||||
$this->fieldLabel('Language'),
|
|
||||||
i18n::get_common_locales()
|
|
||||||
);
|
|
||||||
|
|
||||||
$pageTypeMap = array();
|
|
||||||
$pageTypes = SiteTree::page_type_classes();
|
|
||||||
foreach($pageTypes as $pageType) {
|
|
||||||
$pageTypeMap[$pageType] = singleton($pageType)->i18n_singular_name();
|
|
||||||
}
|
|
||||||
asort($pageTypeMap);
|
|
||||||
|
|
||||||
$fields = new FieldList(
|
|
||||||
$subsiteTabs = new TabSet('Root',
|
|
||||||
new Tab(
|
|
||||||
'Configuration',
|
|
||||||
_t('Subsite.TabTitleConfig', 'Configuration'),
|
|
||||||
new HeaderField($this->getClassName() . ' configuration', 2),
|
|
||||||
new TextField('Title', $this->fieldLabel('Title'), $this->Title),
|
|
||||||
|
|
||||||
new HeaderField(
|
|
||||||
_t('Subsite.DomainsHeadline',"Domains for this subsite")
|
|
||||||
),
|
|
||||||
$domainTable,
|
|
||||||
$languageSelector,
|
|
||||||
// new TextField('RedirectURL', 'Redirect to URL', $this->RedirectURL),
|
|
||||||
new CheckboxField('DefaultSite', $this->fieldLabel('DefaultSite'), $this->DefaultSite),
|
|
||||||
new CheckboxField('IsPublic', $this->fieldLabel('IsPublic'), $this->IsPublic),
|
|
||||||
|
|
||||||
new DropdownField('Theme',$this->fieldLabel('Theme'), $this->allowedThemes(), $this->Theme),
|
|
||||||
|
|
||||||
|
|
||||||
new LiteralField(
|
|
||||||
'PageTypeBlacklistToggle',
|
|
||||||
sprintf(
|
|
||||||
'<div class="field"><a href="#" id="PageTypeBlacklistToggle">%s</a></div>',
|
|
||||||
_t('Subsite.PageTypeBlacklistField', 'Disallow page types?')
|
|
||||||
)
|
|
||||||
),
|
|
||||||
new CheckboxSetField(
|
|
||||||
'PageTypeBlacklist',
|
|
||||||
false,
|
|
||||||
$pageTypeMap
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
new HiddenField('ID', '', $this->ID),
|
|
||||||
new HiddenField('IsSubsite', '', 1)
|
|
||||||
);
|
|
||||||
|
|
||||||
$subsiteTabs->addExtraClass('subsite-model');
|
|
||||||
|
|
||||||
$this->extend('updateCMSFields', $fields);
|
|
||||||
return $fields;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param boolean $includerelations
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function fieldLabels($includerelations = true) {
|
|
||||||
$labels = parent::fieldLabels($includerelations);
|
|
||||||
$labels['Title'] = _t('Subsites.TitleFieldLabel', 'Subsite Name');
|
|
||||||
$labels['RedirectURL'] = _t('Subsites.RedirectURLFieldLabel', 'Redirect URL');
|
|
||||||
$labels['DefaultSite'] = _t('Subsites.DefaultSiteFieldLabel', 'Default site');
|
|
||||||
$labels['Theme'] = _t('Subsites.ThemeFieldLabel', 'Theme');
|
|
||||||
$labels['Language'] = _t('Subsites.LanguageFieldLabel', 'Language');
|
|
||||||
$labels['IsPublic'] = _t('Subsites.IsPublicFieldLabel', 'Enable public access');
|
|
||||||
$labels['PageTypeBlacklist'] = _t('Subsites.PageTypeBlacklistFieldLabel', 'Page Type Blacklist');
|
|
||||||
$labels['Domains.Domain'] = _t('Subsites.DomainFieldLabel', 'Domain');
|
|
||||||
$labels['PrimaryDomain'] = _t('Subsites.PrimaryDomainFieldLabel', 'Primary Domain');
|
|
||||||
|
|
||||||
return $labels;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function summaryFields() {
|
|
||||||
return array(
|
|
||||||
'Title' => $this->fieldLabel('Title'),
|
|
||||||
'PrimaryDomain' => $this->fieldLabel('PrimaryDomain'),
|
|
||||||
'IsPublic' => _t('Subsite.IsPublicHeaderField','Active subsite'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the themes that can be used with this subsite, as an array of themecode => description
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function allowedThemes() {
|
|
||||||
if($themes = $this->stat('allowed_themes')) {
|
|
||||||
return ArrayLib::valuekey($themes);
|
|
||||||
} else {
|
|
||||||
$themes = array();
|
|
||||||
if(is_dir('../themes/')) {
|
|
||||||
foreach(scandir('../themes/') as $theme) {
|
|
||||||
if($theme[0] == '.') continue;
|
|
||||||
$theme = strtok($theme,'_');
|
|
||||||
$themes[$theme] = $theme;
|
|
||||||
}
|
|
||||||
ksort($themes);
|
|
||||||
}
|
|
||||||
return $themes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string Current locale of the subsite
|
|
||||||
*/
|
|
||||||
public function getLanguage() {
|
|
||||||
if($this->getField('Language')) {
|
|
||||||
return $this->getField('Language');
|
|
||||||
} else {
|
|
||||||
return i18n::get_locale();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @return ValidationResult
|
|
||||||
*/
|
|
||||||
public function validate() {
|
|
||||||
$result = parent::validate();
|
|
||||||
if(!$this->Title) {
|
|
||||||
$result->error(_t('Subsite.ValidateTitle', 'Please add a "Title"'));
|
|
||||||
}
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whenever a Subsite is written, rewrite the hostmap
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function onAfterWrite() {
|
|
||||||
Subsite::writeHostMap();
|
|
||||||
parent::onAfterWrite();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the primary domain of this site. Tries to "normalize" the domain name,
|
|
||||||
* by replacing potential wildcards.
|
|
||||||
*
|
|
||||||
* @return string The full domain name of this subsite (without protocol prefix)
|
|
||||||
*/
|
|
||||||
public function domain() {
|
|
||||||
if($this->ID) {
|
|
||||||
$domains = DataObject::get("SubsiteDomain", "\"SubsiteID\" = $this->ID", "\"IsPrimary\" DESC","", 1);
|
|
||||||
if($domains && $domains->Count()>0) {
|
|
||||||
$domain = $domains->First()->Domain;
|
|
||||||
// If there are wildcards in the primary domain (not recommended), make some
|
|
||||||
// educated guesses about what to replace them with:
|
|
||||||
$domain = preg_replace('/\.\*$/',".$_SERVER[HTTP_HOST]", $domain);
|
|
||||||
// Default to "subsite." prefix for first wildcard
|
|
||||||
// TODO Whats the significance of "subsite" in this context?!
|
|
||||||
$domain = preg_replace('/^\*\./',"subsite.", $domain);
|
|
||||||
// *Only* removes "intermediate" subdomains, so 'subdomain.www.domain.com' becomes 'subdomain.domain.com'
|
|
||||||
$domain = str_replace('.www.','.', $domain);
|
|
||||||
|
|
||||||
return $domain;
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubsiteID = 0 is often used to refer to the main site, just return $_SERVER['HTTP_HOST']
|
|
||||||
} else {
|
|
||||||
return $_SERVER['HTTP_HOST'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @return string - The full domain name of this subsite (without protocol prefix)
|
|
||||||
*/
|
|
||||||
public function getPrimaryDomain() {
|
|
||||||
return $this->domain();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function absoluteBaseURL() {
|
|
||||||
return "http://" . $this->domain() . Director::baseURL();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @todo getClassName is redundant, already stored as a database field?
|
|
||||||
*/
|
|
||||||
public function getClassName() {
|
|
||||||
return $this->class;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Javascript admin action to duplicate this subsite
|
|
||||||
*
|
|
||||||
* @return string - javascript
|
|
||||||
*/
|
|
||||||
public function adminDuplicate() {
|
|
||||||
$newItem = $this->duplicate();
|
|
||||||
$message = _t(
|
|
||||||
'Subsite.CopyMessage',
|
|
||||||
'Created a copy of {title}',
|
|
||||||
array('title' => Convert::raw2js($this->Title))
|
|
||||||
);
|
|
||||||
|
|
||||||
return <<<JS
|
|
||||||
statusMessage($message, 'good');
|
|
||||||
$('Form_EditForm').loadURLFromServer('admin/subsites/show/$newItem->ID');
|
|
||||||
JS;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make this subsite the current one
|
|
||||||
*/
|
|
||||||
public function activate() {
|
|
||||||
Subsite::changeSubsite($this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param array $permissionCodes
|
|
||||||
* @return DataList
|
|
||||||
*/
|
|
||||||
public function getMembersByPermission($permissionCodes = array('ADMIN')){
|
|
||||||
if(!is_array($permissionCodes))
|
|
||||||
user_error('Permissions must be passed to Subsite::getMembersByPermission as an array', E_USER_ERROR);
|
|
||||||
$SQL_permissionCodes = Convert::raw2sql($permissionCodes);
|
|
||||||
|
|
||||||
$SQL_permissionCodes = join("','", $SQL_permissionCodes);
|
|
||||||
|
|
||||||
return DataObject::get(
|
|
||||||
'Member',
|
|
||||||
"\"Group\".\"SubsiteID\" = $this->ID AND \"Permission\".\"Code\" IN ('$SQL_permissionCodes')",
|
|
||||||
'',
|
|
||||||
"LEFT JOIN \"Group_Members\" ON \"Member\".\"ID\" = \"Group_Members\".\"MemberID\"
|
|
||||||
LEFT JOIN \"Group\" ON \"Group\".\"ID\" = \"Group_Members\".\"GroupID\"
|
|
||||||
LEFT JOIN \"Permission\" ON \"Permission\".\"GroupID\" = \"Group\".\"ID\""
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Duplicate this subsite
|
|
||||||
*/
|
|
||||||
public function duplicate($doWrite = true) {
|
|
||||||
$duplicate = parent::duplicate($doWrite);
|
|
||||||
|
|
||||||
$oldSubsiteID = Session::get('SubsiteID');
|
|
||||||
self::changeSubsite($this->ID);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Copy data from this object to the given subsite. Does this using an iterative depth-first search.
|
|
||||||
* This will make sure that the new parents on the new subsite are correct, and there are no funny
|
|
||||||
* issues with having to check whether or not the new parents have been added to the site tree
|
|
||||||
* when a page, etc, is duplicated
|
|
||||||
*/
|
|
||||||
$stack = array(array(0,0));
|
|
||||||
while(count($stack) > 0) {
|
|
||||||
list($sourceParentID, $destParentID) = array_pop($stack);
|
|
||||||
$children = Versioned::get_by_stage('Page', 'Live', "\"ParentID\" = $sourceParentID", '');
|
|
||||||
|
|
||||||
if($children) {
|
|
||||||
foreach($children as $child) {
|
|
||||||
self::changeSubsite($duplicate->ID); //Change to destination subsite
|
|
||||||
|
|
||||||
$childClone = $child->duplicateToSubsite($duplicate, false);
|
|
||||||
$childClone->ParentID = $destParentID;
|
|
||||||
$childClone->writeToStage('Stage');
|
|
||||||
$childClone->publish('Stage', 'Live');
|
|
||||||
|
|
||||||
self::changeSubsite($this->ID); //Change Back to this subsite
|
|
||||||
|
|
||||||
array_push($stack, array($child->ID, $childClone->ID));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self::changeSubsite($oldSubsiteID);
|
|
||||||
|
|
||||||
return $duplicate;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,81 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @property text Domain domain name of this subsite. Do not include the URL scheme here
|
|
||||||
* @property bool IsPrimary Is this the primary subdomain?
|
|
||||||
*/
|
|
||||||
class SubsiteDomain extends DataObject {
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private static $db = array(
|
|
||||||
"Domain" => "Varchar(255)",
|
|
||||||
"IsPrimary" => "Boolean",
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private static $has_one = array(
|
|
||||||
"Subsite" => "Subsite",
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private static $summary_fields=array(
|
|
||||||
'Domain',
|
|
||||||
'IsPrimary',
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whenever a Subsite Domain is written, rewrite the hostmap
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function onAfterWrite() {
|
|
||||||
Subsite::writeHostMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @return \FieldList
|
|
||||||
*/
|
|
||||||
public function getCMSFields() {
|
|
||||||
$fields = new FieldList(
|
|
||||||
new TextField('Domain', $this->fieldLabel('Domain'), null, 255),
|
|
||||||
new CheckboxField('IsPrimary', $this->fieldLabel('IsPrimary'))
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->extend('updateCMSFields', $fields);
|
|
||||||
return $fields;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param bool $includerelations
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function fieldLabels($includerelations = true) {
|
|
||||||
$labels = parent::fieldLabels($includerelations);
|
|
||||||
$labels['Domain'] = _t('SubsiteDomain.DOMAIN', 'Domain');
|
|
||||||
$labels['IsPrimary'] = _t('SubsiteDomain.IS_PRIMARY', 'Is Primary Domain');
|
|
||||||
|
|
||||||
return $labels;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Before writing the Subsite Domain, strip out any HTML the user has entered.
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function onBeforeWrite() {
|
|
||||||
parent::onBeforeWrite();
|
|
||||||
|
|
||||||
//strip out any HTML to avoid XSS attacks
|
|
||||||
$this->Domain = Convert::html2raw($this->Domain);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* Handy alternative to copying pages when creating a subsite through the UI.
|
|
||||||
* Can be used to batch-add new pages after subsite creation,
|
|
||||||
* or simply to process a large site outside of the UI.
|
|
||||||
*
|
|
||||||
* Example: sake dev/tasks/SubsiteCopyPagesTask from=<subsite-source> to=<subsite-target>
|
|
||||||
*/
|
|
||||||
class SubsiteCopyPagesTask extends BuildTask {
|
|
||||||
|
|
||||||
protected $title = 'Copy pages to different subsite';
|
|
||||||
|
|
||||||
protected $description = '';
|
|
||||||
|
|
||||||
function run($request) {
|
|
||||||
$subsiteFromId = $request->getVar('from');
|
|
||||||
if(!is_numeric($subsiteFromId)) throw new InvalidArgumentException('Missing "from" parameter');
|
|
||||||
$subsiteFrom = DataObject::get_by_id('Subsite', $subsiteFromId);
|
|
||||||
if(!$subsiteFrom) throw new InvalidArgumentException('Subsite not found');
|
|
||||||
|
|
||||||
$subsiteToId = $request->getVar('to');
|
|
||||||
if(!is_numeric($subsiteToId)) throw new InvalidArgumentException('Missing "to" parameter');
|
|
||||||
$subsiteTo = DataObject::get_by_id('Subsite', $subsiteToId);
|
|
||||||
if(!$subsiteTo) throw new InvalidArgumentException('Subsite not found');
|
|
||||||
|
|
||||||
$useVirtualPages = (bool)$request->getVar('virtual');
|
|
||||||
|
|
||||||
Subsite::changeSubsite($subsiteFrom);
|
|
||||||
|
|
||||||
// Copy data from this template to the given subsite. Does this using an iterative depth-first search.
|
|
||||||
// This will make sure that the new parents on the new subsite are correct, and there are no funny
|
|
||||||
// issues with having to check whether or not the new parents have been added to the site tree
|
|
||||||
// when a page, etc, is duplicated
|
|
||||||
$stack = array(array(0,0));
|
|
||||||
while(count($stack) > 0) {
|
|
||||||
list($sourceParentID, $destParentID) = array_pop($stack);
|
|
||||||
|
|
||||||
$children = Versioned::get_by_stage('SiteTree', 'Live', "\"ParentID\" = $sourceParentID", '');
|
|
||||||
|
|
||||||
if($children) {
|
|
||||||
foreach($children as $child) {
|
|
||||||
if($useVirtualPages) {
|
|
||||||
$childClone = new SubsitesVirtualPage();
|
|
||||||
$childClone->writeToStage('Stage');
|
|
||||||
$childClone->CopyContentFromID = $child->ID;
|
|
||||||
$childClone->SubsiteID = $subsiteTo->ID;
|
|
||||||
} else {
|
|
||||||
$childClone = $child->duplicateToSubsite($subsiteTo->ID, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
$childClone->ParentID = $destParentID;
|
|
||||||
$childClone->writeToStage('Stage');
|
|
||||||
$childClone->publish('Stage', 'Live');
|
|
||||||
array_push($stack, array($child->ID, $childClone->ID));
|
|
||||||
|
|
||||||
$this->log(sprintf('Copied "%s" (#%d, %s)', $child->Title, $child->ID, $child->Link()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unset($children);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function log($msg) {
|
|
||||||
echo $msg . "\n";
|
|
||||||
}
|
|
||||||
}
|
|
1
codecov.yml
Normal file
@ -0,0 +1 @@
|
|||||||
|
comment: false
|
@ -1,17 +1,44 @@
|
|||||||
{
|
{
|
||||||
"name": "silverstripe/subsites",
|
"name": "silverstripe/subsites",
|
||||||
"description": "Run multiple sites from a single SilverStripe install.",
|
"description": "Run multiple sites from a single SilverStripe install.",
|
||||||
"type": "silverstripe-module",
|
"license": "BSD-3-Clause",
|
||||||
"keywords": ["silverstripe", "subsites", "multisite"],
|
"type": "silverstripe-vendormodule",
|
||||||
|
"keywords": [
|
||||||
|
"silverstripe",
|
||||||
|
"subsites",
|
||||||
|
"multisite"
|
||||||
|
],
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
"name": "Sam Minnee",
|
"name": "Sam Minnee",
|
||||||
"email": "sam@silverstripe.com"
|
"email": "sam@silverstripe.com"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require":
|
"require": {
|
||||||
{
|
"php": "^7.4 || ^8.0",
|
||||||
"silverstripe/framework": "~3.1.0",
|
"silverstripe/framework": "^4.12",
|
||||||
"silverstripe/cms": "~3.1.0"
|
"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
|
||||||
}
|
}
|
@ -1,95 +0,0 @@
|
|||||||
/**
|
|
||||||
* Styling for the subsite actions section in the CMS
|
|
||||||
*/
|
|
||||||
#SubsiteActions {
|
|
||||||
position: absolute;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 350px;
|
|
||||||
text-align: right;
|
|
||||||
margin-right: 130px;
|
|
||||||
height: 51px;
|
|
||||||
border-bottom: 3px solid #d4d0c8;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
#SubsiteActions fieldset {
|
|
||||||
padding: 3px;
|
|
||||||
border-style: none;
|
|
||||||
margin-top: 1px;
|
|
||||||
background: none;
|
|
||||||
}
|
|
||||||
#SubsiteActions fieldset span {
|
|
||||||
padding: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cms-menu .cms-subsites{
|
|
||||||
padding:3px 0px 15px;
|
|
||||||
}
|
|
||||||
.cms-menu .cms-subsites .field.dropdown{
|
|
||||||
padding-bottom:0;
|
|
||||||
margin-bottom:0;
|
|
||||||
}
|
|
||||||
.cms-menu.collapsed .cms-subsites {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Custom chzn styles for dark blue background */
|
|
||||||
.cms-subsites .chzn-container-single .chzn-single,
|
|
||||||
.cms-subsites .chzn-container-active .chzn-single {
|
|
||||||
filter: none; /* Fix for IE9 */
|
|
||||||
border: 1px solid #152338;
|
|
||||||
background:#213557;
|
|
||||||
-webkit-box-shadow: inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
|
|
||||||
box-shadow: inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.cms-menu .cms-subsites .dropdown span{
|
|
||||||
padding-left:5px;
|
|
||||||
}
|
|
||||||
.cms-subsites .chzn-container-single .chzn-single div b{
|
|
||||||
background: url(../images/chosen-sprite-light.png) 3px 0 no-repeat;
|
|
||||||
}
|
|
||||||
.cms-subsites .chzn-container .chzn-drop{
|
|
||||||
padding-left:5px;
|
|
||||||
background:#213557;
|
|
||||||
border: 1px solid #152338;
|
|
||||||
border-top:0;
|
|
||||||
color:#fff;
|
|
||||||
-webkit-box-shadow: inset 1px 0 0 rgba(255,255,255,.125);
|
|
||||||
box-shadow: inset 1px 0 0 rgba(255,255,255,.125);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#AddSubsiteLink {
|
|
||||||
display: block;
|
|
||||||
font-size: 80%;
|
|
||||||
margin-left: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#Form_AddSubsiteForm .field {
|
|
||||||
margin-left: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#Form_AddSubsiteForm label.left {
|
|
||||||
float: left;
|
|
||||||
width: 100px;
|
|
||||||
margin-left: -100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.SubsiteAdmin .right form #URL .fieldgroup * {
|
|
||||||
font-size: 11px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cms-add-form #PageType li .class-SubsitesVirtualPage, .class-SubsitesVirtualPage a .jstree-pageicon {
|
|
||||||
background-position: 0 -32px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subsites-move-dropdown{
|
|
||||||
display:none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#Root_DetailsView .subsites-move-dropdown{
|
|
||||||
display:block;
|
|
||||||
}
|
|
@ -1,87 +1,9 @@
|
|||||||
## Introduction
|
# Subsites
|
||||||
|
|
||||||
Subsites is a module to allow you manage multiple related sites from a single CMS interface.
|
[User guide](userguide/index.md)
|
||||||
|
|
||||||
When Subsites is installed your existing site is defined as the main site, you will be then be able to create related
|
|
||||||
subsites under the main site.
|
|
||||||
|
|
||||||
|
|
||||||
So for example you may have an international presence and you want to create a subsite for a country where you do
|
|
||||||
business which is geared just for that market.
|
|
||||||
You could create a subsite for this and have all information related to that country kept under this subsite, you can
|
|
||||||
also set up a subdomain for this site.
|
|
||||||
|
|
||||||
|
|
||||||
One of the benefits of subsites is that it is easy to copy pages between the subsites and you have access to all of
|
|
||||||
the assets across all of the subsites.
|
|
||||||
|
|
||||||
Subsites is not for running unrelated websites on a single SilverStripe instance so if 2 sites have different vhosts
|
|
||||||
you will not be able to run them with Subsites on a single SilverStripe instance.
|
|
||||||
|
|
||||||
With Subsites you can set up users to have access to all subsites or just a selection of subsites.
|
|
||||||
|
|
||||||
|
|
||||||
## Common subsite uses
|
|
||||||
Subsites can be used for various different reasons here are some of the common ones
|
|
||||||
|
|
||||||
* Setting up a subsite for a small campaign so for example a clothing company may set up a summer or winter subsite to
|
|
||||||
market just that season of clothing.
|
|
||||||
* Locking down a particular subsite you may create a particular department like recruitment who would have access to
|
|
||||||
create and edit pages for their particular subsite but they would not be able to modify the main website.
|
|
||||||
* Running sub-domains on a single SilverStripe instance, with subsites if a sub-domain is pointing to the same instance
|
|
||||||
and has been setup correctly you can manage this via a single CMS instance.
|
|
||||||
* Subsites can not be used to run multiple websites on a single instance.
|
|
||||||
Subsites does not allow you to run multiple domains/vhosts on a single instance.
|
|
||||||
|
|
||||||
## Access
|
|
||||||
|
|
||||||
Access to certain subsites can be limited to administrators based on the groups they are in.
|
|
||||||
So for example if you had a couple of subsites you could create a group for each subsite and then specify that the
|
|
||||||
group had access to all subsites or just a specific subsites.
|
|
||||||
To access this functionality go to
|
|
||||||
|
|
||||||
|
|
||||||
Security -> Groups
|
|
||||||
|
|
||||||
![alt text](_images/subsite-admin-security-group.png "Groups")
|
|
||||||
|
|
||||||
Select the group you want to modify and then go to the Subsites tab
|
|
||||||
|
|
||||||
You can also limit the page types that are available for a subsite (but not the main site).
|
|
||||||
This can be done via accessing the particular subsite you want to amend via the Subsite admin section, underneath the
|
|
||||||
Subsite theme will be a link called 'Disallow page types?' clicking on that link will display a list of checkboxes for
|
|
||||||
all of the page types which can be selected to disable that page type for the subsite you are editing.
|
|
||||||
This is useful when you create a content editor and you do not want them to be able to add certain page types.
|
|
||||||
|
|
||||||
## Theme
|
|
||||||
A theme is group of templates, images and CSS for the look of a website.
|
|
||||||
When you are using Subsites you may have different themes installed for your site so you could apply different
|
|
||||||
themes for each subsite.
|
|
||||||
|
|
||||||
## Page types
|
|
||||||
Page types refer to the type of pages that can be set up on a site.
|
|
||||||
A page type will have certain features and functionality some examples on SilverStripe would be 'Page', 'HomePage'
|
|
||||||
and 'ErrorPage' these all differ to each other in what they would be used for so you would use Page for any pages
|
|
||||||
underneath the HomePage.
|
|
||||||
|
|
||||||
|
|
||||||
You would only have one HomePage for your site and you may have some logic to only allow you to create one of these
|
|
||||||
pages, ErrorPage would only be used for error pages and would be designed to be very minimal to work in situations
|
|
||||||
where the site is experiencing difficulties like no DB access.
|
|
||||||
|
|
||||||
|
|
||||||
You can set up a Subsite to only work with certain page types so you may have a page type with a contact form for a
|
|
||||||
particular department so you may set up a new subsite and not allow that page type to be used on a particular subsite.
|
|
||||||
You will not be able to filter Page Types for the main site.
|
|
||||||
|
|
||||||
## Assets
|
|
||||||
Assets are files that have been uploaded via the CMS.
|
|
||||||
It is suggested to use a naming convention for files designated to be used on a particular subsite or to create folders
|
|
||||||
for each subsite to help organise them.
|
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
|
|
||||||
### How can I restrict a content author to a particular subsite?
|
### How can I restrict a content author to a particular subsite?
|
||||||
|
|
||||||
|
|
||||||
@ -126,3 +48,14 @@ to speak to your website administrator or hosting provider to facilitate this.
|
|||||||
|
|
||||||
|
|
||||||
You can simulate subsite access without setting up virtual hosts by appending ?SubsiteID=<ID> to the request.
|
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.
|
||||||
|
@ -28,5 +28,5 @@ This method is called when a pages are being copied between the main site or ano
|
|||||||
### alternateAbsoluteLink
|
### alternateAbsoluteLink
|
||||||
This method modifies the absolute link to contain the valid subsite domain
|
This method modifies the absolute link to contain the valid subsite domain
|
||||||
|
|
||||||
### alternatePreviewLink
|
### updatePreviewLink
|
||||||
This method modifies the preview link for the CMS.
|
This method modifies the preview link for the CMS.
|
||||||
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 93 KiB |
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 153 KiB After Width: | Height: | Size: 153 KiB |
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 176 KiB After Width: | Height: | Size: 176 KiB |
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB |
50
docs/en/userguide/index.md
Normal 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)
|
@ -1,45 +1,46 @@
|
|||||||
|
---
|
||||||
|
title: Setting up
|
||||||
|
---
|
||||||
# Setting up
|
# Setting up
|
||||||
|
|
||||||
## Creating subsites
|
## Creating subsites
|
||||||
|
|
||||||
To view, edit and create subsites, go to the *Subsite* section of the CMS. Click *Search* to view a list of all
|
To view, edit and create subsites, go to the *Subsite* section of the CMS. Click *Search* to view a list of all existing subsites.
|
||||||
existing subsites.
|
|
||||||
|
|
||||||
![View subsites](_images/view-subsites.jpg)
|
![View subsites](_images/view-subsites.jpg)
|
||||||
|
|
||||||
Click on a subsite name to edit its details. Click the red X to delete a subsite (you will be asked for confirmation.)
|
Click on a subsite name to edit its details. To create a new subsite, click *Add Subsite*. This opens the *Subsite configuration* subsection.
|
||||||
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)
|
![Subsite configuration](_images/subsite-configuration.jpg)
|
||||||
|
|
||||||
You can configure the following details for a subsite:
|
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)
|
* *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,
|
* *Domains for this subsite:* Lets you add one or more domains for this subsite, e.g., subsite.co.nz, subsite.org.nz, subsite.com
|
||||||
subsite.com
|
|
||||||
* *Language:* Sets the language for the subsite. This affects the spellchecker (not the CMS interface language)
|
* *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.
|
* *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.
|
* *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
|
* *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.
|
||||||
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.
|
||||||
* *Disallow page types:* Lets you mark some page types to prevent being used within this subsite.
|
* *Copy structure from:* Gives you the option to select an existing subsite from which to copy pages and files (see "Copying subsites" for more information about this)
|
||||||
* *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
|
## Roles
|
||||||
|
|
||||||
When creating roles, you can assign the following subsite-specific permissions:
|
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
|
* *Access to 'Subsites' section:* Shows the *Subsite* section in the CMS, allowing you to manage subsites for your site (ie, create, edit, view.)
|
||||||
site (ie, create, edit, view.)
|
|
||||||
* *Manage subsites for groups:* Ability to limit the permissions for a group to one or more subsites.
|
* *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
|
* *Manage assets for subsites:* Ability to select the subsite to which an asset folder belongs (also requires access to *Files* section)
|
||||||
*Files* section)
|
|
||||||
|
|
||||||
## Groups
|
## Groups
|
||||||
|
|
||||||
Groups can have access to all sites, or to one or more specific subsites. If you have different staff in charge of each
|
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.
|
||||||
subsite, you probably want to create a separate group for each subsite.
|
|
||||||
|
|
||||||
The dropdown in the upper left corner of the CMS indicates which subsite you are currently on.
|
The dropdown in the upper left corner of the CMS indicates which subsite you are currently on.
|
||||||
|
|
||||||
@ -50,7 +51,19 @@ these subsites* to reveal a checklist of all available sites.
|
|||||||
|
|
||||||
![Group subsites access](_images/group-subsites-access.png)
|
![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
|
## 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.
|
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
|
To create a new subsite template, create a new subsite described above under "Creating subsites" but don't add any domains. Add a name that will make it easy to see that it is a template. Select the new template from the subsites dropdown in the upper right and create the pages and add the files and images you'd like to become part of the
|
||||||
@ -60,7 +73,14 @@ When you create a new subsite, you can now choose to Copy structure from your te
|
|||||||
|
|
||||||
![Copy subsite structure](_images/copy-structure.jpg)
|
![Copy subsite structure](_images/copy-structure.jpg)
|
||||||
|
|
||||||
## Disable particular page types from a subsite
|
## Page types
|
||||||
|
Page types refer to the type of pages that can be set up on a site. A page type will have certain features and functionality some examples on SilverStripe would be 'Page', 'HomePage' and 'ErrorPage' these all differ to each other in what they would be used for so you would use Page for any pages
|
||||||
|
underneath the HomePage.
|
||||||
|
|
||||||
|
You would only have one HomePage for your site and you may have some logic to only allow you to create one of these pages, ErrorPage would only be used for error pages and would be designed to be very minimal to work in situations where the site is experiencing difficulties (like no database access).
|
||||||
|
|
||||||
|
### Disable particular page types from a subsite
|
||||||
|
|
||||||
Sometimes, you will have two or more websites that are very similar, but have some small differences. For example, a head office and its 3 satellite offices may have 4 subsites, but only the head office site will have a "Company News" section on the site. In this instance, you can still use the subsites module, and use the 'Disallow page types' ability to remove certain page types from being created on subsites.
|
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
|
1. Create a new subsite as you normally would via the Subsites section in the CMS
|
||||||
@ -69,7 +89,7 @@ Sometimes, you will have two or more websites that are very similar, but have so
|
|||||||
4. Select the page types that you wish to forbid from this subsite.
|
4. Select the page types that you wish to forbid from this subsite.
|
||||||
5. Click the Save button at the bottom of the section.
|
5. Click the Save button at the bottom of the section.
|
||||||
|
|
||||||
Note that this process is exactly the same when editing an existing subsite, you just select the subsite you want to remove page types from instead of creating a new subsite.
|
*Note:* This process is exactly the same when editing an existing subsite, you just select the subsite you want to remove page types from instead of creating a new subsite. Also you cannot not filter Page Types for the main site.
|
||||||
|
|
||||||
![Disallow page types screenshot](_images/disallow-page-types.png)
|
![Disallow page types screenshot](_images/disallow-page-types.png)
|
||||||
|
|
||||||
@ -80,3 +100,9 @@ Now, whenever someone wants to create a new page on the subsite (the 'London Bra
|
|||||||
The page type blacklist applies only to future pages, not existing ones. If an administrator blacklists a defined page type on a subsite, existing pages of that type will remain on the subsite. The administrator (or another user) can later change it to a type that is not blacklisted after the page is published; however, once a user does that, that "grandfathered" pagetype will no longer be available to the published page.
|
The page type blacklist applies only to future pages, not existing ones. If an administrator blacklists a defined page type on a subsite, existing pages of that type will remain on the subsite. The administrator (or another user) can later change it to a type that is not blacklisted after the page is published; however, once a user does that, that "grandfathered" pagetype will no longer be available to the published page.
|
||||||
|
|
||||||
For example, say a subsite user publishes a new Company Page before it was forbidden by an administrator. Later, a user with full administrative rights decides to disallow Company Pages from being created on the subsite. This restriction will only apply to all future pages that are published, but not to existing Company Pages. The full administrator (or any user with sufficient publishing rights) will have to manually convert any published Company Pages on the subsite to that of another allowed type.
|
For example, say a subsite user publishes a new Company Page before it was forbidden by an administrator. Later, a user with full administrative rights decides to disallow Company Pages from being created on the subsite. This restriction will only apply to all future pages that are published, but not to existing Company Pages. The full administrator (or any user with sufficient publishing rights) will have to manually convert any published Company Pages on the subsite to that of another allowed type.
|
||||||
|
|
||||||
|
## Themes
|
||||||
|
A theme is group of templates, images and CSS for the look of a website. When you are using Subsites you may have different themes installed for your site so you could apply different themes for each subsite.
|
||||||
|
|
||||||
|
## Assets
|
||||||
|
Assets are files that have been uploaded via the CMS. It is suggested to use a naming convention for files designated to be used on a particular subsite or to create folders for each subsite to help organise them.
|
@ -1,12 +1,15 @@
|
|||||||
|
---
|
||||||
|
title: Working with subsites
|
||||||
|
---
|
||||||
# Working with subsites
|
# Working with subsites
|
||||||
|
|
||||||
## Managing content across subsites
|
## Managing content across subsites
|
||||||
|
|
||||||
Select a subsite from the dropdown in the upper left to display the content for that site in the site tree.
|
To edit a particular subsite, choose the subsite from the dropdown menu in the left-hand menu.
|
||||||
|
|
||||||
![Subsites dropdown](_images/subsites-dropdown.png)
|
![Subsites dropdown](_images/subsites-dropdown.png)
|
||||||
|
|
||||||
## Subsites virtual pages
|
### Subsites virtual pages
|
||||||
|
|
||||||
You can pull in the content from a page that resides on another subsite by creating a page of the type **Subsites
|
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
|
Virtual Page**. Pick the subsite from which you want to pull the content, then select the page. As with regular virtual
|
||||||
@ -15,7 +18,7 @@ the original content changes.
|
|||||||
|
|
||||||
![Subsites virtual page](_images/subsites-virtual-page.jpg)
|
![Subsites virtual page](_images/subsites-virtual-page.jpg)
|
||||||
|
|
||||||
## Duplicating pages from the main site
|
### Duplicating pages from the main site
|
||||||
|
|
||||||
If you have an existing page on the main site that you would like to copy to a subsite, all you need to do is:
|
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:
|
||||||
|
|
@ -1,25 +0,0 @@
|
|||||||
(function($) {
|
|
||||||
$.entwine('ss', function($) {
|
|
||||||
$('.TreeDropdownField').entwine({
|
|
||||||
subsiteID: function() {
|
|
||||||
var subsiteSel = $('#CopyContentFromID_SubsiteID select')[0];
|
|
||||||
if(!subsiteSel) return;
|
|
||||||
|
|
||||||
subsiteSel.onchange = (function() {
|
|
||||||
this.createTreeNode(true);
|
|
||||||
this.ajaxGetTree((function(response) {
|
|
||||||
this.newTreeReady(response, true);
|
|
||||||
this.updateTreeLabel();
|
|
||||||
}).bind(this));
|
|
||||||
}).bind(this);
|
|
||||||
return subsiteSel.options[subsiteSel.selectedIndex].value;
|
|
||||||
},
|
|
||||||
|
|
||||||
getRequestParams: function() {
|
|
||||||
var name = this.find(':input:hidden').attr('name'), obj = {};
|
|
||||||
obj[name + '_SubsiteID'] = parseInt(this.subsiteID());
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})(jQuery);
|
|
79
lang/ar.yml
@ -1,54 +1,68 @@
|
|||||||
ar:
|
ar:
|
||||||
SubsiteAdmin:
|
SilverStripe\CMS\Model\VirtualPage:
|
||||||
MENUTITLE: مواقع فرعية
|
EDITCONTENT: 'انقر هنا لتحرير المحتوى'
|
||||||
SubsiteXHRController:
|
SilverStripe\Subsites\Admin\SubsiteAdmin:
|
||||||
MENUTITLE: المراقب XHR للموقع الفرعي
|
MENUTITLE: 'المواقع الفرعية'
|
||||||
ASSETADMIN:
|
SilverStripe\Subsites\Extensions\FileSubsites:
|
||||||
SUBSITENOTICE: 'يمكن الوصول إلى المجلدات والملفات التي تم إنشاؤها في الموقع الرئيسي من طرف كل المواقع الفرعية.'
|
has_one_Subsite: 'الموقع الفرعي'
|
||||||
FileSubsites:
|
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
|
||||||
AllSitesDropdownOpt: 'كافة المواقع'
|
AllSitesDropdownOpt: 'كافة المواقع'
|
||||||
SubsiteFieldLabel: الموقع الفرعي
|
SUBSITENOTICE: 'يمكن الوصول إلى المجلدات والملفات التي تم إنشاؤها في الموقع الرئيسي من طرف كل المواقع الفرعية.'
|
||||||
GridFieldAddFromTemplateButton:
|
SubsiteFieldLabel: 'الموقع الفرعي'
|
||||||
AddFromTemplate: 'اضف جديد من القالب'
|
SilverStripe\Subsites\Extensions\GroupSubsites:
|
||||||
GroupSubsites:
|
|
||||||
ACCESSALL: 'كل المواقع الفرعية'
|
ACCESSALL: 'كل المواقع الفرعية'
|
||||||
ACCESSONLY: 'فقط هذه المواقع الفرعية'
|
ACCESSONLY: 'فقط هذه المواقع الفرعية'
|
||||||
ACCESSRADIOTITLE: 'امنح هذه المجموعة تصريح الولوج إلى'
|
ACCESSRADIOTITLE: 'امنح هذه المجموعة تصريح الولوج إلى'
|
||||||
GlobalGroup: 'المجموعة العامة'
|
GlobalGroup: 'المجموعة العامة'
|
||||||
MANAGE_SUBSITES: 'إدارة مواقع فرعية للمجموعات'
|
MANAGE_SUBSITES: 'إدارة مواقع فرعية للمجموعات'
|
||||||
MANAGE_SUBSITES_HELP: 'القدرة على الحد من أذونات مجموعة ما على موقع فرعي واحدة أو أكثر.'
|
MANAGE_SUBSITES_HELP: 'القدرة على الحد من أذونات مجموعة ما على موقع فرعي واحدة أو أكثر.'
|
||||||
SECURITYTABTITLE: مواقع فرعية
|
SECURITYTABTITLE: 'المواقع الفرعية'
|
||||||
LeftAndMainSubsites:
|
many_many_Subsites: 'المواقع الفرعية'
|
||||||
|
SilverStripe\Subsites\Extensions\LeftAndMainSubsites:
|
||||||
Saved: 'تمّ الحفظ، يرجى تحديث الصفحات ذات الصلة.'
|
Saved: 'تمّ الحفظ، يرجى تحديث الصفحات ذات الصلة.'
|
||||||
SiteTreeSubsites:
|
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
|
||||||
|
has_one_Subsite: 'الموقع الفرعي'
|
||||||
|
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
|
||||||
CopyAction: نسخ
|
CopyAction: نسخ
|
||||||
CopyToSubsite: 'نسخ الصفحة في موقع فرعي'
|
CopyToSubsite: 'نسخ الصفحة في موقع فرعي'
|
||||||
Subsite:
|
has_one_Subsite: 'الموقع الفرعي'
|
||||||
COPYSTRUCTURE: 'نسخ الهيكل من:'
|
SilverStripe\Subsites\Model\Subsite:
|
||||||
|
ConfigurationTab: المواصفات
|
||||||
CopyMessage: 'إنشاء نسخة من {title}'
|
CopyMessage: 'إنشاء نسخة من {title}'
|
||||||
CustomExtraMeta: 'العلامات الوصفية المخصصة'
|
CustomExtraMeta: 'تخصيص Meta Tags'
|
||||||
CustomMetaDescription: الوصف
|
CustomMetaDescription: الوصف
|
||||||
CustomMetaKeywords: كلمات البحث
|
CustomMetaKeywords: 'كلمات البحث'
|
||||||
CustomMetaTitle: عنوان
|
CustomMetaTitle: عنوان
|
||||||
DOMAINSAVEFIRST: 'لا يمكنك إضافة النطاقات إلا بعد القيام بالحفظ لأول مرة'
|
PLURALNAME: 'المواقع الفرعية'
|
||||||
DomainsHeadline: 'نطاقات هذا الموقع الفرعي'
|
|
||||||
DomainsListTitle: النطاقات
|
|
||||||
IsPublicHeaderField: 'موقع فرعي نشط'
|
|
||||||
NOTEMPLATE: 'بدون قالب'
|
|
||||||
PLURALNAME: المواقع الفرعية
|
|
||||||
PageTypeBlacklistField: 'عدم السماح بأصناف الصفحات؟'
|
PageTypeBlacklistField: 'عدم السماح بأصناف الصفحات؟'
|
||||||
SINGULARNAME: الموقع الفرعي
|
SINGULARNAME: 'الموقع الفرعي'
|
||||||
SiteConfigSubtitle: 'هنا سطر الوصف الخاص بك'
|
SiteConfigSubtitle: 'هنا سطر الوصف الخاص بك'
|
||||||
SiteConfigTitle: 'اسم موقعك'
|
SiteConfigTitle: 'اسم موقعك'
|
||||||
TabTitleConfig: المواصفات
|
|
||||||
ValidateTitle: 'الرجاء إضافة "عنوان"'
|
ValidateTitle: 'الرجاء إضافة "عنوان"'
|
||||||
SubsiteDomain:
|
belongs_many_many_Groups: المجموعات
|
||||||
DOMAIN: نطاق
|
db_DefaultSite: 'الموقع الافتراضي'
|
||||||
IS_PRIMARY: 'هو نطاق أساسي'
|
db_Language: لغة
|
||||||
|
db_RedirectURL: 'إعادة توجيه عنوان موقع الويب'
|
||||||
|
db_Theme: المحور
|
||||||
|
db_Title: عنوان
|
||||||
|
has_many_Domains: النطاقات
|
||||||
|
SilverStripe\Subsites\Model\SubsiteDomain:
|
||||||
|
DOMAIN: النطاق
|
||||||
PLURALNAME: 'نطاقات موقع فرعي'
|
PLURALNAME: 'نطاقات موقع فرعي'
|
||||||
SINGULARNAME: 'نطاق موقع فرعي'
|
SINGULARNAME: 'نطاق موقع فرعي'
|
||||||
SubsiteReportWrapper:
|
db_Domain: النطاق
|
||||||
|
has_one_Subsite: 'الموقع الفرعي'
|
||||||
|
SilverStripe\Subsites\Pages\SubsitesVirtualPage:
|
||||||
|
DESCRIPTION: 'يعرض محتوى صفحة على موقع فرعي آخر'
|
||||||
|
PLURALNAME: 'قاعدة الصفحات'
|
||||||
|
SINGULARNAME: 'الصفحة الإفتراضية للمواقع الفرعية'
|
||||||
|
SubsiteField: 'الموقع الفرعي'
|
||||||
|
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
|
||||||
ReportDropdown: المواقع
|
ReportDropdown: المواقع
|
||||||
|
ReportDropdownSubsite: 'الموقع الفرعي'
|
||||||
|
Subsite:
|
||||||
|
COPYSTRUCTURE: 'نسخ الهيكل من:'
|
||||||
|
NOTEMPLATE: 'بدون قالب'
|
||||||
Subsites:
|
Subsites:
|
||||||
DefaultSiteFieldLabel: 'الموقع الافتراضي'
|
DefaultSiteFieldLabel: 'الموقع الافتراضي'
|
||||||
DomainFieldLabel: النطاق
|
DomainFieldLabel: النطاق
|
||||||
@ -60,8 +74,3 @@ ar:
|
|||||||
RedirectURLFieldLabel: 'إعادة توجيه عنوان موقع الويب'
|
RedirectURLFieldLabel: 'إعادة توجيه عنوان موقع الويب'
|
||||||
ThemeFieldLabel: المحور
|
ThemeFieldLabel: المحور
|
||||||
TitleFieldLabel: 'اسم الموقع الفرعي'
|
TitleFieldLabel: 'اسم الموقع الفرعي'
|
||||||
SubsitesVirtualPage:
|
|
||||||
DESCRIPTION: 'يعرض محتوى صفحة على موقع فرعي آخر'
|
|
||||||
PLURALNAME: 'قاعدة الصفحات'
|
|
||||||
SINGULARNAME: 'الصفحة الإفتراضية للمواقع الفرعية'
|
|
||||||
SubsiteField: الموقع الفرعي
|
|
||||||
|
64
lang/cs.yml
Normal 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'
|
103
lang/de.yml
@ -1,16 +1,17 @@
|
|||||||
de:
|
de:
|
||||||
SubsiteAdmin:
|
DomainNameField:
|
||||||
|
INVALID_DOMAIN: 'Ungültige Domain'
|
||||||
|
SilverStripe\CMS\Model\VirtualPage:
|
||||||
|
EDITCONTENT: 'Klicken Sie hier, um den Inhalt zu bearbeiten'
|
||||||
|
SilverStripe\Subsites\Admin\SubsiteAdmin:
|
||||||
MENUTITLE: Subsites
|
MENUTITLE: Subsites
|
||||||
SubsiteXHRController:
|
SilverStripe\Subsites\Extensions\FileSubsites:
|
||||||
MENUTITLE: SubsiteXHRController
|
has_one_Subsite: Subseite
|
||||||
ASSETADMIN:
|
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
|
||||||
|
AllSitesDropdownOpt: 'Alle Seiten'
|
||||||
SUBSITENOTICE: 'Auf Ordner und Dateien der Hauptseite kann von allen Subsites zugegriffen werden.'
|
SUBSITENOTICE: 'Auf Ordner und Dateien der Hauptseite kann von allen Subsites zugegriffen werden.'
|
||||||
FileSubsites:
|
|
||||||
AllSitesDropdownOpt: 'Alle Subseiten'
|
|
||||||
SubsiteFieldLabel: Subseite
|
SubsiteFieldLabel: Subseite
|
||||||
GridFieldAddFromTemplateButton:
|
SilverStripe\Subsites\Extensions\GroupSubsites:
|
||||||
AddFromTemplate: 'Neu hinzufügen von Template'
|
|
||||||
GroupSubsites:
|
|
||||||
ACCESSALL: 'Alle Subsites'
|
ACCESSALL: 'Alle Subsites'
|
||||||
ACCESSONLY: 'Nur diese Subsites'
|
ACCESSONLY: 'Nur diese Subsites'
|
||||||
ACCESSRADIOTITLE: 'Dieser Gruppe Zugriff geben auf'
|
ACCESSRADIOTITLE: 'Dieser Gruppe Zugriff geben auf'
|
||||||
@ -18,50 +19,82 @@ de:
|
|||||||
MANAGE_SUBSITES: 'Subseiten für jede Gruppe bearbeiten'
|
MANAGE_SUBSITES: 'Subseiten für jede Gruppe bearbeiten'
|
||||||
MANAGE_SUBSITES_HELP: 'Möglichkeit, die Berechtigungen einer Gruppe auf bestimmte Subsites zu beschränken.'
|
MANAGE_SUBSITES_HELP: 'Möglichkeit, die Berechtigungen einer Gruppe auf bestimmte Subsites zu beschränken.'
|
||||||
SECURITYTABTITLE: Subsites
|
SECURITYTABTITLE: Subsites
|
||||||
LeftAndMainSubsites:
|
many_many_Subsites: Subsites
|
||||||
Saved: 'Gespeichert.'
|
SilverStripe\Subsites\Extensions\LeftAndMainSubsites:
|
||||||
SiteTreeSubsites:
|
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
|
CopyAction: Kopieren
|
||||||
CopyToSubsite: 'Seite auf Subseite Kopieren'
|
CopyToSubsite: 'Seite auf Subseite Kopieren'
|
||||||
Subsite:
|
CopyToSubsiteWithChildren: 'Samt Unterseiten?'
|
||||||
COPYSTRUCTURE: 'Struktur kopieren von:'
|
SubsiteOperations: 'Subseiten Operationen'
|
||||||
|
has_one_Subsite: Subseite
|
||||||
|
SilverStripe\Subsites\Model\Subsite:
|
||||||
|
ConfigurationTab: Einstellungen
|
||||||
CopyMessage: 'Kopie von {title} erstellt'
|
CopyMessage: 'Kopie von {title} erstellt'
|
||||||
CustomExtraMeta: 'Benutzerdefinierte Meta-Tags'
|
CustomExtraMeta: 'Benutzerdefinierte Meta-Tags'
|
||||||
CustomMetaDescription: Beschreibung
|
CustomMetaDescription: Beschreibung
|
||||||
CustomMetaKeywords: Schlüsselwörter
|
CustomMetaKeywords: Schlüsselwörter
|
||||||
CustomMetaTitle: Titel
|
CustomMetaTitle: Titel
|
||||||
DOMAINSAVEFIRST: 'Domains können erst nach dem ersten Speichern hinzugefügt werden'
|
|
||||||
DomainsHeadline: 'Domains für diese Subsite'
|
|
||||||
DomainsListTitle: Domains
|
|
||||||
IsPublicHeaderField: 'Aktive Subsite'
|
|
||||||
NOTEMPLATE: 'Kein Template'
|
|
||||||
PLURALNAME: Subsites
|
PLURALNAME: Subsites
|
||||||
PageTypeBlacklistField: 'Seitentyp verbieten?'
|
PLURALS:
|
||||||
SINGULARNAME: Subsite
|
one: 'Eine Subseite'
|
||||||
|
other: '{count} Subsites'
|
||||||
|
PageTypeBlacklistField: 'Seitentypen verbieten?'
|
||||||
|
SINGULARNAME: Subseite
|
||||||
SiteConfigSubtitle: 'Ihr Websiteslogan'
|
SiteConfigSubtitle: 'Ihr Websiteslogan'
|
||||||
SiteConfigTitle: 'Name Ihrer Website'
|
SiteConfigTitle: 'Name Ihrer Website'
|
||||||
TabTitleConfig: Einstellungen
|
ThemeFieldEmptyString: '-'
|
||||||
ValidateTitle: 'Bitte geben Sie einen Titel an'
|
ValidateTitle: 'Bitte geben Sie einen Titel an'
|
||||||
SubsiteDomain:
|
belongs_many_many_Groups: Gruppe
|
||||||
DOMAIN: Domain
|
db_DefaultSite: 'Standard Seite'
|
||||||
IS_PRIMARY: 'Ist primäre Domain'
|
db_Language: Sprache
|
||||||
PLURALNAME: 'Subsite Domains'
|
db_RedirectURL: Weierleitungs-URL
|
||||||
SINGULARNAME: 'Subsite Domain'
|
db_Theme: Theme
|
||||||
SubsiteReportWrapper:
|
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
|
ReportDropdown: Seiten
|
||||||
|
ReportDropdownSubsite: Subseite
|
||||||
|
Subsite:
|
||||||
|
COPYSTRUCTURE: 'Struktur kopieren von:'
|
||||||
|
NOTEMPLATE: 'Kein Template'
|
||||||
Subsites:
|
Subsites:
|
||||||
DefaultSiteFieldLabel: 'Standard Seite'
|
DefaultSiteFieldLabel: 'Standard Seite'
|
||||||
DomainFieldLabel: Domäne
|
DomainFieldLabel: Domäne
|
||||||
IsPublicFieldLabel: 'Öffentlich zugänglich'
|
IsPublicFieldLabel: 'Öffentlich zugänglich'
|
||||||
LanguageFieldLabel: Sprache
|
LanguageFieldLabel: Sprache
|
||||||
MainSiteTitle: 'Hauptsite'
|
MainSiteTitle: Hauptsite
|
||||||
PageTypeBlacklistFieldLabel: 'Ausgeschlossene Seitentypen'
|
PageTypeBlacklistFieldLabel: 'Ausgeschlossene Seitentypen'
|
||||||
PrimaryDomainFieldLabel: 'Primäre Domain'
|
PrimaryDomainFieldLabel: 'Primäre Domain'
|
||||||
RedirectURLFieldLabel: 'Weierleitungs-URL'
|
RedirectURLFieldLabel: Weierleitungs-URL
|
||||||
ThemeFieldLabel: Theme
|
ThemeFieldLabel: Theme
|
||||||
TitleFieldLabel: 'Name der Subsite'
|
TitleFieldLabel: 'Name der Subsite'
|
||||||
SubsitesVirtualPage:
|
|
||||||
DESCRIPTION: 'Zeigt den Inhalt einer anderen Seite von einer anderen Subsite an'
|
|
||||||
PLURALNAME: 'Subsites Virtuelle Seiten'
|
|
||||||
SINGULARNAME: 'Subsites Virtuelle Seite'
|
|
||||||
SubsiteField: Subsite
|
|
||||||
|
100
lang/en.yml
@ -1,16 +1,19 @@
|
|||||||
en:
|
en:
|
||||||
SubsiteAdmin:
|
DomainNameField:
|
||||||
|
INVALID_DOMAIN: 'Invalid domain name'
|
||||||
|
SilverStripe\CMS\Model\VirtualPage:
|
||||||
|
EDITCONTENT: 'Click here to edit the content'
|
||||||
|
SilverStripe\Subsites\Admin\SubsiteAdmin:
|
||||||
MENUTITLE: Subsites
|
MENUTITLE: Subsites
|
||||||
SubsiteXHRController:
|
SilverStripe\Subsites\Controller\SubsiteXHRController:
|
||||||
MENUTITLE: SubsiteXHRController
|
MENUTITLE: SilverStripe\Subsites\Controller\SubsiteXHRController
|
||||||
ASSETADMIN:
|
SilverStripe\Subsites\Extensions\FileSubsites:
|
||||||
SUBSITENOTICE: 'Folders and files created in the main site are accessible by all subsites.'
|
has_one_Subsite: Subsite
|
||||||
FileSubsites:
|
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
|
||||||
AllSitesDropdownOpt: 'All sites'
|
AllSitesDropdownOpt: 'All sites'
|
||||||
|
SUBSITENOTICE: 'Folders and files created in the main site are accessible by all subsites.'
|
||||||
SubsiteFieldLabel: Subsite
|
SubsiteFieldLabel: Subsite
|
||||||
GridFieldAddFromTemplateButton:
|
SilverStripe\Subsites\Extensions\GroupSubsites:
|
||||||
AddFromTemplate: 'Add New from Template'
|
|
||||||
GroupSubsites:
|
|
||||||
ACCESSALL: 'All subsites'
|
ACCESSALL: 'All subsites'
|
||||||
ACCESSONLY: 'Only these subsites'
|
ACCESSONLY: 'Only these subsites'
|
||||||
ACCESSRADIOTITLE: 'Give this group access to'
|
ACCESSRADIOTITLE: 'Give this group access to'
|
||||||
@ -18,37 +21,85 @@ en:
|
|||||||
MANAGE_SUBSITES: 'Manage subsites for groups'
|
MANAGE_SUBSITES: 'Manage subsites for groups'
|
||||||
MANAGE_SUBSITES_HELP: 'Ability to limit the permissions for a group to one or more subsites.'
|
MANAGE_SUBSITES_HELP: 'Ability to limit the permissions for a group to one or more subsites.'
|
||||||
SECURITYTABTITLE: Subsites
|
SECURITYTABTITLE: Subsites
|
||||||
LeftAndMainSubsites:
|
db_AccessAllSubsites: 'Access all subsites'
|
||||||
|
many_many_Subsites: Subsites
|
||||||
|
SilverStripe\Subsites\Extensions\LeftAndMainSubsites:
|
||||||
|
SITECONTENTLEFT: 'Site Content'
|
||||||
Saved: 'Saved, please update related pages.'
|
Saved: 'Saved, please update related pages.'
|
||||||
SiteTreeSubsites:
|
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
|
||||||
|
has_one_Subsite: Subsite
|
||||||
|
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
|
||||||
CopyAction: Copy
|
CopyAction: Copy
|
||||||
CopyToSubsite: 'Copy page to subsite'
|
CopyToSubsite: 'Copy page to subsite'
|
||||||
Subsite:
|
CopyToSubsiteWithChildren: 'Include children pages?'
|
||||||
COPYSTRUCTURE: 'Copy structure from:'
|
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}'
|
CopyMessage: 'Created a copy of {title}'
|
||||||
CustomExtraMeta: 'Custom Meta Tags'
|
CustomExtraMeta: 'Custom Meta Tags'
|
||||||
CustomMetaDescription: Description
|
CustomMetaDescription: Description
|
||||||
CustomMetaKeywords: Keywords
|
CustomMetaKeywords: Keywords
|
||||||
CustomMetaTitle: Title
|
CustomMetaTitle: Title
|
||||||
DOMAINSAVEFIRST: 'You can only add domains after saving for the first time'
|
|
||||||
DomainsHeadline: 'Domains for this subsite'
|
|
||||||
DomainsListTitle: Domains
|
|
||||||
IsPublicHeaderField: 'Active subsite'
|
|
||||||
NOTEMPLATE: 'No template'
|
|
||||||
PLURALNAME: Subsites
|
PLURALNAME: Subsites
|
||||||
|
PLURALS:
|
||||||
|
one: 'A Subsite'
|
||||||
|
other: '{count} Subsites'
|
||||||
PageTypeBlacklistField: 'Disallow page types?'
|
PageTypeBlacklistField: 'Disallow page types?'
|
||||||
SINGULARNAME: Subsite
|
SINGULARNAME: Subsite
|
||||||
SiteConfigSubtitle: 'Your tagline here'
|
SiteConfigSubtitle: 'Your tagline here'
|
||||||
SiteConfigTitle: 'Your Site Name'
|
SiteConfigTitle: 'Your Site Name'
|
||||||
TabTitleConfig: Configuration
|
ThemeFieldEmptyString: '-'
|
||||||
ValidateTitle: 'Please add a "Title"'
|
ValidateTitle: 'Please add a "Title"'
|
||||||
SubsiteDomain:
|
belongs_many_many_Groups: Groups
|
||||||
|
db_DefaultSite: 'Default site'
|
||||||
|
db_IsPublic: 'Is public'
|
||||||
|
db_Language: Language
|
||||||
|
db_PageTypeBlacklist: 'Page type blacklist'
|
||||||
|
db_RedirectURL: 'Redirect URL'
|
||||||
|
db_Theme: Theme
|
||||||
|
db_Title: Title
|
||||||
|
has_many_Domains: Domains
|
||||||
|
SilverStripe\Subsites\Model\SubsiteDomain:
|
||||||
DOMAIN: Domain
|
DOMAIN: Domain
|
||||||
IS_PRIMARY: 'Is Primary Domain'
|
DOMAIN_DESCRIPTION: 'Hostname of this subsite (exclude protocol). Allows wildcards (*).'
|
||||||
|
ISPRIMARY_DESCRIPTION: 'Mark this as the default domain for this subsite'
|
||||||
|
IS_PRIMARY: 'Is Primary Domain?'
|
||||||
PLURALNAME: 'Subsite Domains'
|
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'
|
SINGULARNAME: 'Subsite Domain'
|
||||||
SubsiteReportWrapper:
|
db_Domain: Domain
|
||||||
|
db_IsPrimary: 'Is primary'
|
||||||
|
db_Protocol: Protocol
|
||||||
|
has_one_Subsite: Subsite
|
||||||
|
SilverStripe\Subsites\Pages\SubsitesVirtualPage:
|
||||||
|
DESCRIPTION: 'Displays the content of a page on another subsite'
|
||||||
|
OverrideNote: 'Overrides inherited value from the source'
|
||||||
|
PLURALNAME: 'Subsites Virtual Pages'
|
||||||
|
PLURALS:
|
||||||
|
one: 'A Subsites Virtual Page'
|
||||||
|
other: '{count} Subsites Virtual Pages'
|
||||||
|
SINGULARNAME: 'Subsites Virtual Page'
|
||||||
|
SubsiteField: Subsite
|
||||||
|
db_CustomExtraMeta: 'Custom extra meta'
|
||||||
|
db_CustomMetaDescription: 'Custom meta description'
|
||||||
|
db_CustomMetaKeywords: 'Custom meta keywords'
|
||||||
|
db_CustomMetaTitle: 'Custom meta title'
|
||||||
|
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
|
||||||
ReportDropdown: Sites
|
ReportDropdown: Sites
|
||||||
|
ReportDropdownAll: All
|
||||||
|
ReportDropdownSubsite: Subsite
|
||||||
|
Subsite:
|
||||||
|
COPYSTRUCTURE: 'Copy structure from:'
|
||||||
|
NOTEMPLATE: 'No template'
|
||||||
Subsites:
|
Subsites:
|
||||||
DefaultSiteFieldLabel: 'Default site'
|
DefaultSiteFieldLabel: 'Default site'
|
||||||
DomainFieldLabel: Domain
|
DomainFieldLabel: Domain
|
||||||
@ -60,8 +111,3 @@ en:
|
|||||||
RedirectURLFieldLabel: 'Redirect URL'
|
RedirectURLFieldLabel: 'Redirect URL'
|
||||||
ThemeFieldLabel: Theme
|
ThemeFieldLabel: Theme
|
||||||
TitleFieldLabel: 'Subsite Name'
|
TitleFieldLabel: 'Subsite Name'
|
||||||
SubsitesVirtualPage:
|
|
||||||
DESCRIPTION: 'Displays the content of a page on another subsite'
|
|
||||||
PLURALNAME: 'Subsites Virtual Pages'
|
|
||||||
SINGULARNAME: 'Subsites Virtual Page'
|
|
||||||
SubsiteField: Subsite
|
|
||||||
|
108
lang/eo.yml
@ -1,54 +1,105 @@
|
|||||||
eo:
|
eo:
|
||||||
SubsiteAdmin:
|
DomainNameField:
|
||||||
|
INVALID_DOMAIN: 'Nevalida domajna nomo'
|
||||||
|
SilverStripe\CMS\Model\VirtualPage:
|
||||||
|
EDITCONTENT: 'Alklaku ĉi tie por redakti la enhavon'
|
||||||
|
SilverStripe\Subsites\Admin\SubsiteAdmin:
|
||||||
MENUTITLE: Subretejoj
|
MENUTITLE: Subretejoj
|
||||||
SubsiteXHRController:
|
SilverStripe\Subsites\Controller\SubsiteXHRController:
|
||||||
MENUTITLE: SubsiteXHRController
|
MENUTITLE: SilverStripe\Subretejoj\Reganto\SubretejaXHRReganto
|
||||||
ASSETADMIN:
|
SilverStripe\Subsites\Extensions\FileSubsites:
|
||||||
SUBSITENOTICE: 'Dosierujoj kaj dosieroj kreitaj en la ĉefa retejo estas alireblaj de ĉiuj retejoj'
|
has_one_Subsite: Subretejo
|
||||||
FileSubsites:
|
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
|
||||||
AllSitesDropdownOpt: 'Ĉiuj retejoj'
|
AllSitesDropdownOpt: 'Ĉiuj retejoj'
|
||||||
|
SUBSITENOTICE: 'Dosierujoj kaj dosieroj kreitaj en la ĉefa retejo estas alireblaj de ĉiuj retejoj'
|
||||||
SubsiteFieldLabel: Subretejo
|
SubsiteFieldLabel: Subretejo
|
||||||
GridFieldAddFromTemplateButton:
|
SilverStripe\Subsites\Extensions\GroupSubsites:
|
||||||
AddFromTemplate: 'Aldoni novan el ŝablono'
|
|
||||||
GroupSubsites:
|
|
||||||
ACCESSALL: 'Ĉiuj subretejoj'
|
ACCESSALL: 'Ĉiuj subretejoj'
|
||||||
ACCESSONLY: 'Nur ĉi tiuj subretejoj'
|
ACCESSONLY: 'Nur ĉi tiuj subretejoj'
|
||||||
ACCESSRADIOTITLE: 'Doni ĉi tiun grupon aliron al'
|
ACCESSRADIOTITLE: 'Doni al ĉi tiu grupo aliron al'
|
||||||
GlobalGroup: 'ĉiea grupo'
|
GlobalGroup: 'ĉiea grupo'
|
||||||
MANAGE_SUBSITES: 'Administri subretejojn por grupoj'
|
MANAGE_SUBSITES: 'Administri subretejojn por grupoj'
|
||||||
MANAGE_SUBSITES_HELP: 'Eblo limigi la permesojn por grupo al unu aŭ pluaj subretejoj.'
|
MANAGE_SUBSITES_HELP: 'Eblo limigi la permesojn por grupo al unu aŭ pluaj subretejoj.'
|
||||||
SECURITYTABTITLE: Subsites
|
SECURITYTABTITLE: Subretejoj
|
||||||
LeftAndMainSubsites:
|
db_AccessAllSubsites: 'Aliri ĉiujn subretejojn'
|
||||||
|
many_many_Subsites: Subretejoj
|
||||||
|
SilverStripe\Subsites\Extensions\LeftAndMainSubsites:
|
||||||
|
SITECONTENTLEFT: 'Enhavo de retejo'
|
||||||
Saved: 'Konservita, bonvole ĝisdatigi rilatajn paĝojn.'
|
Saved: 'Konservita, bonvole ĝisdatigi rilatajn paĝojn.'
|
||||||
SiteTreeSubsites:
|
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
|
||||||
|
has_one_Subsite: Subretejo
|
||||||
|
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
|
||||||
CopyAction: Kopio
|
CopyAction: Kopio
|
||||||
CopyToSubsite: 'Kopii paĝon al subretejo'
|
CopyToSubsite: 'Kopii paĝon al subretejo'
|
||||||
Subsite:
|
CopyToSubsiteWithChildren: 'Ĉu inkluzivi paĝidojn?'
|
||||||
COPYSTRUCTURE: 'Kopii strukturon de:'
|
SubsiteOperations: 'Subretejaj operacioj'
|
||||||
|
has_one_Subsite: Subretejo
|
||||||
|
many_many_CrossSubsiteLinkTracking: 'Trans-subreteja ligspurado'
|
||||||
|
SilverStripe\Subsites\Model\Subsite:
|
||||||
|
ConfigurationTab: Agordaro
|
||||||
CopyMessage: 'Kreis kopion de {title}'
|
CopyMessage: 'Kreis kopion de {title}'
|
||||||
CustomExtraMeta: 'Propraj meta-etikedoj '
|
CustomExtraMeta: 'Propraj meta-etikedoj '
|
||||||
CustomMetaDescription: Priskribo
|
CustomMetaDescription: Priskribo
|
||||||
CustomMetaKeywords: Ŝlosilvortoj
|
CustomMetaKeywords: Ŝlosilvortoj
|
||||||
CustomMetaTitle: Titolo
|
CustomMetaTitle: Titolo
|
||||||
DOMAINSAVEFIRST: 'Vi povas aldoni domajnojn post konservante unuafoje'
|
|
||||||
DomainsHeadline: 'Domajnoj por ĉi tiu subretejo'
|
|
||||||
DomainsListTitle: Domajnoj
|
|
||||||
IsPublicHeaderField: 'Aktiva subretejo'
|
|
||||||
NOTEMPLATE: 'Mankas ŝablono'
|
|
||||||
PLURALNAME: Subretejoj
|
PLURALNAME: Subretejoj
|
||||||
PageTypeBlacklistField: 'Ĉu malpermesu paĝajn tipojn?'
|
PLURALS:
|
||||||
|
one: 'Unu subretejo'
|
||||||
|
other: '{count} subretejoj'
|
||||||
|
PageTypeBlacklistField: 'Ĉu malpermesi paĝajn tipojn?'
|
||||||
SINGULARNAME: Subretejo
|
SINGULARNAME: Subretejo
|
||||||
SiteConfigSubtitle: 'Jen via slogano'
|
SiteConfigSubtitle: 'Jen via slogano'
|
||||||
SiteConfigTitle: 'Nomo de via retejo'
|
SiteConfigTitle: 'Nomo de via retejo'
|
||||||
TabTitleConfig: Agordaro
|
ThemeFieldEmptyString: '-'
|
||||||
ValidateTitle: 'Bonvole aldonu "Titolon"'
|
ValidateTitle: 'Bonvole aldoni "Titolon"'
|
||||||
SubsiteDomain:
|
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: Domajno
|
||||||
IS_PRIMARY: 'Estas unuaranga domajno'
|
DOMAIN_DESCRIPTION: 'Gastiga nomo de ĉi tiu subretejo (ellasu protokolon). Permesas ĵokerojn (*).'
|
||||||
|
ISPRIMARY_DESCRIPTION: 'Marki ĉi tion kiel la aprioran domajnon por ĉi tiu subretejo'
|
||||||
|
IS_PRIMARY: 'Ĉu unuaranga domajno?'
|
||||||
PLURALNAME: 'Subretejaj domajnoj'
|
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'
|
SINGULARNAME: 'Subreteja domajno'
|
||||||
SubsiteReportWrapper:
|
db_Domain: Domajno
|
||||||
|
db_IsPrimary: 'Estas unuaranga'
|
||||||
|
db_Protocol: Protokolo
|
||||||
|
has_one_Subsite: Subretejo
|
||||||
|
SilverStripe\Subsites\Pages\SubsitesVirtualPage:
|
||||||
|
DESCRIPTION: 'Vidigas la enhavon de paĝo en alia subretejo'
|
||||||
|
OverrideNote: 'Anstataŭigas hereditan valoron el la fonto'
|
||||||
|
PLURALNAME: 'Virtualaj paĝoj de subretejoj'
|
||||||
|
PLURALS:
|
||||||
|
one: 'Unu virtuala paĝo de subretejoj'
|
||||||
|
other: '{count} virtualaj paĝoj de subretejoj'
|
||||||
|
SINGULARNAME: 'Virtuala paĝo de subretejoj'
|
||||||
|
SubsiteField: Subretejo
|
||||||
|
db_CustomExtraMeta: 'Propra kroma meta'
|
||||||
|
db_CustomMetaDescription: 'Propra metapriskribo'
|
||||||
|
db_CustomMetaKeywords: 'Propraj meta-ŝlosilvortoj '
|
||||||
|
db_CustomMetaTitle: 'Propraj meta-titolo'
|
||||||
|
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
|
||||||
ReportDropdown: Retejoj
|
ReportDropdown: Retejoj
|
||||||
|
ReportDropdownAll: Ĉiuj
|
||||||
|
ReportDropdownSubsite: Subretejo
|
||||||
|
Subsite:
|
||||||
|
COPYSTRUCTURE: 'Kopii strukturon de:'
|
||||||
|
NOTEMPLATE: 'Mankas ŝablono'
|
||||||
Subsites:
|
Subsites:
|
||||||
DefaultSiteFieldLabel: 'Apriora retejo'
|
DefaultSiteFieldLabel: 'Apriora retejo'
|
||||||
DomainFieldLabel: Domajno
|
DomainFieldLabel: Domajno
|
||||||
@ -60,8 +111,3 @@ eo:
|
|||||||
RedirectURLFieldLabel: 'Redirekti je URL'
|
RedirectURLFieldLabel: 'Redirekti je URL'
|
||||||
ThemeFieldLabel: Etoso
|
ThemeFieldLabel: Etoso
|
||||||
TitleFieldLabel: 'Nomo de subretejo'
|
TitleFieldLabel: 'Nomo de subretejo'
|
||||||
SubsitesVirtualPage:
|
|
||||||
DESCRIPTION: 'Vidigas la enhavon de paĝo en alia subretejo'
|
|
||||||
PLURALNAME: 'Virtualaj paĝoj de subretejoj'
|
|
||||||
SINGULARNAME: 'Virtuala paĝo de subretejoj'
|
|
||||||
SubsiteField: Subretejo
|
|
||||||
|
13
lang/es.yml
Normal file
@ -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
@ -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
@ -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: 'نام زیر سایت'
|
119
lang/fi.yml
@ -1,67 +1,92 @@
|
|||||||
fi:
|
fi:
|
||||||
SubsiteAdmin:
|
DomainNameField:
|
||||||
|
INVALID_DOMAIN: 'Virheellinen domain-nimi'
|
||||||
|
SilverStripe\CMS\Model\VirtualPage:
|
||||||
|
EDITCONTENT: 'Napsauta tässä muokataksesi sisältöä'
|
||||||
|
SilverStripe\Subsites\Admin\SubsiteAdmin:
|
||||||
MENUTITLE: Alasivustot
|
MENUTITLE: Alasivustot
|
||||||
SubsiteXHRController:
|
SilverStripe\Subsites\Extensions\FileSubsites:
|
||||||
MENUTITLE: SubsiteXHRController
|
has_one_Subsite: Alasivusto
|
||||||
ASSETADMIN:
|
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
|
||||||
SUBSITENOTICE: 'Kansiot ja tiedostot, jotka on luotu pääsivustolla, ovat käytettävissä kaikissa alisivustoissa.'
|
|
||||||
FileSubsites:
|
|
||||||
AllSitesDropdownOpt: 'Kaikki sivustot'
|
AllSitesDropdownOpt: 'Kaikki sivustot'
|
||||||
SubsiteFieldLabel: Alisivusto
|
SUBSITENOTICE: 'Kansiot ja tiedostot, jotka on luotu pääsivustolla, ovat käytettävissä kaikissa alisivustoissa.'
|
||||||
GridFieldAddFromTemplateButton:
|
SubsiteFieldLabel: Alasivusto
|
||||||
AddFromTemplate: 'Lisää uusi sivupohjasta'
|
SilverStripe\Subsites\Extensions\GroupSubsites:
|
||||||
GroupSubsites:
|
|
||||||
ACCESSALL: 'Kaikki alasivustot'
|
ACCESSALL: 'Kaikki alasivustot'
|
||||||
ACCESSONLY: 'Vain nämä alasivustot'
|
ACCESSONLY: 'Vain nämä alasivustot'
|
||||||
ACCESSRADIOTITLE: 'Anna tälle ryhmälle pääsy kohteeseen'
|
ACCESSRADIOTITLE: 'Anna ryhmän oikeudet »'
|
||||||
GlobalGroup: 'Globaaliryhmä'
|
GlobalGroup: Globaaliryhmä
|
||||||
MANAGE_SUBSITES: 'Hallinnoi ryhmien alisivustoja'
|
MANAGE_SUBSITES: 'Hallinnoi ryhmien alasivustoja'
|
||||||
MANAGE_SUBSITES_HELP: 'Mahdollisuus rajoittaa ryhmän oikeuksia yhdelle tai useammalle alisivustolle.'
|
MANAGE_SUBSITES_HELP: 'Mahdollisuus rajoittaa ryhmän oikeuksia yhdelle tai useammalle alisivustolle.'
|
||||||
SECURITYTABTITLE: Alisivustot
|
SECURITYTABTITLE: Alasivustot
|
||||||
LeftAndMainSubsites:
|
many_many_Subsites: Alasivut
|
||||||
|
SilverStripe\Subsites\Extensions\LeftAndMainSubsites:
|
||||||
|
SITECONTENTLEFT: 'Sivuston sisältö'
|
||||||
Saved: 'Tallennettu, ole hyvä ja päivitä liittyvät sivut.'
|
Saved: 'Tallennettu, ole hyvä ja päivitä liittyvät sivut.'
|
||||||
SiteTreeSubsites:
|
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
|
||||||
|
has_one_Subsite: Alasivusto
|
||||||
|
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
|
||||||
CopyAction: Kopioi
|
CopyAction: Kopioi
|
||||||
CopyToSubsite: 'Kopioi sivu alisivustolle'
|
CopyToSubsite: 'Kopioi sivu alasivustolle'
|
||||||
Subsite:
|
CopyToSubsiteWithChildren: 'Sisällytä alasivut?'
|
||||||
COPYSTRUCTURE: 'Kopioi rakenne kohteesta:'
|
SubsiteOperations: 'Alasivuston toiminnot'
|
||||||
|
has_one_Subsite: Alasivusto
|
||||||
|
SilverStripe\Subsites\Model\Subsite:
|
||||||
|
ConfigurationTab: Asetukset
|
||||||
CopyMessage: 'Luotiin kopio lähteestä {title}'
|
CopyMessage: 'Luotiin kopio lähteestä {title}'
|
||||||
CustomExtraMeta: 'Omat Meta-tagit'
|
CustomExtraMeta: 'Omat meta-tagit'
|
||||||
CustomMetaDescription: Kuvaus
|
CustomMetaDescription: Kuvaus
|
||||||
CustomMetaKeywords: Avainsanat
|
CustomMetaKeywords: Avainsanat
|
||||||
CustomMetaTitle: Otsikko
|
CustomMetaTitle: Otsikko
|
||||||
DOMAINSAVEFIRST: 'Voit lisätä domain-osoitteita vasta tallentamisen jälkeen'
|
PLURALNAME: Alasivut
|
||||||
DomainsHeadline: 'Tämän alisivuston domainit'
|
PLURALS:
|
||||||
DomainsListTitle: Domainit
|
one: Alasivu
|
||||||
IsPublicHeaderField: 'Aktiivinen alisivusto'
|
other: '{count} alasivua'
|
||||||
NOTEMPLATE: 'Ei sivupohjaa'
|
|
||||||
PLURALNAME: Alasivustot
|
|
||||||
PageTypeBlacklistField: 'Kiellä sivutyyppien käyttö?'
|
PageTypeBlacklistField: 'Kiellä sivutyyppien käyttö?'
|
||||||
SINGULARNAME: Alisivusto
|
SINGULARNAME: Alasivusto
|
||||||
SiteConfigSubtitle: 'Iskulauseesi'
|
SiteConfigSubtitle: 'Tähän sloganisi'
|
||||||
SiteConfigTitle: 'Sivuston nimi'
|
SiteConfigTitle: 'Sivuston nimi'
|
||||||
TabTitleConfig: Asetukset
|
|
||||||
ValidateTitle: 'Lisää "Otsikko"'
|
ValidateTitle: 'Lisää "Otsikko"'
|
||||||
SubsiteDomain:
|
belongs_many_many_Groups: Ryhmät
|
||||||
DOMAIN: Domain
|
db_DefaultSite: Oletussivusto
|
||||||
IS_PRIMARY: 'On päädomain'
|
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'
|
PLURALNAME: 'Alisivuston domain-osoitteet'
|
||||||
|
PROTOCOL_AUTOMATIC: Automaattinen
|
||||||
|
PROTOCOL_HTTP: 'http://'
|
||||||
|
PROTOCOL_HTTPS: 'https://'
|
||||||
|
Protocol: Protokolla
|
||||||
SINGULARNAME: 'Alisivuston domain-osoite'
|
SINGULARNAME: 'Alisivuston domain-osoite'
|
||||||
SubsiteReportWrapper:
|
db_Domain: Verkkotunnus
|
||||||
ReportDropdown: Sivustot
|
db_Protocol: Protokolla
|
||||||
Subsites:
|
has_one_Subsite: Alasivusto
|
||||||
DefaultSiteFieldLabel: 'Oletussivusto'
|
SilverStripe\Subsites\Pages\SubsitesVirtualPage:
|
||||||
DomainFieldLabel: Domain
|
|
||||||
IsPublicFieldLabel: 'Aktivoi julkinen pääsy'
|
|
||||||
LanguageFieldLabel: Kieli
|
|
||||||
MainSiteTitle: 'Pääsivusto'
|
|
||||||
PageTypeBlacklistFieldLabel: 'Sivutyyppien mustalista'
|
|
||||||
PrimaryDomainFieldLabel: 'Oletusdomain'
|
|
||||||
RedirectURLFieldLabel: 'Edelleenohjaus URL'
|
|
||||||
ThemeFieldLabel: Teema
|
|
||||||
TitleFieldLabel: 'Alisivuston nimi'
|
|
||||||
SubsitesVirtualPage:
|
|
||||||
DESCRIPTION: 'Näyttää sisällön toisen alisivuston sivulta'
|
DESCRIPTION: 'Näyttää sisällön toisen alisivuston sivulta'
|
||||||
PLURALNAME: 'Alisivustojen virtuaaliset sivut'
|
PLURALNAME: 'Alisivustojen virtuaaliset sivut'
|
||||||
SINGULARNAME: 'Alisivuston Virtuaalisivu'
|
SINGULARNAME: 'Alisivuston Virtuaalisivu'
|
||||||
SubsiteField: Alisivu
|
SubsiteField: Alasivusto
|
||||||
|
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
|
||||||
|
ReportDropdown: Sivustot
|
||||||
|
ReportDropdownSubsite: Alasivusto
|
||||||
|
Subsite:
|
||||||
|
COPYSTRUCTURE: 'Kopioi rakenne kohteesta:'
|
||||||
|
NOTEMPLATE: 'Ei sivupohjaa'
|
||||||
|
Subsites:
|
||||||
|
DefaultSiteFieldLabel: Oletussivusto
|
||||||
|
DomainFieldLabel: Domain
|
||||||
|
IsPublicFieldLabel: 'Aktivoi julkinen pääsy'
|
||||||
|
LanguageFieldLabel: Kieli
|
||||||
|
MainSiteTitle: Pääsivusto
|
||||||
|
PageTypeBlacklistFieldLabel: 'Sivutyyppien mustalista'
|
||||||
|
PrimaryDomainFieldLabel: Oletusdomain
|
||||||
|
RedirectURLFieldLabel: 'Edelleenohjaus URL'
|
||||||
|
ThemeFieldLabel: Teema
|
||||||
|
TitleFieldLabel: 'Alisivuston nimi'
|
||||||
|
14
lang/fr.yml
Normal file
@ -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
@ -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
@ -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
@ -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'
|
65
lang/ja.yml
@ -1,17 +1,60 @@
|
|||||||
ja:
|
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:
|
SubsiteAdmin:
|
||||||
MENUTITLE: サブサイト
|
MENUTITLE: サブサイト
|
||||||
GridFieldAddFromTemplateButton:
|
|
||||||
AddFromTemplate: 'テンプレートから新しく追加'
|
|
||||||
GroupSubsites:
|
|
||||||
ACCESSALL: '全てのサブサイト'
|
|
||||||
ACCESSONLY: 'これらのサブサイトのみ'
|
|
||||||
ACCESSRADIOTITLE: 'このグループに選択先へのアクセス権を与える'
|
|
||||||
SECURITYTABTITLE: サブサイト
|
|
||||||
SubsiteDomain:
|
SubsiteDomain:
|
||||||
DOMAIN: ドメイン
|
DOMAIN: ドメイン
|
||||||
IS_PRIMARY: 'プライマリドメイン'
|
PLURALNAME: サブサイトのドメイン
|
||||||
PLURALNAME: 'サブサイトのドメイン'
|
SINGULARNAME: サブサイトのドメイン
|
||||||
SINGULARNAME: 'サブサイトのドメイン'
|
Subsites:
|
||||||
|
DomainFieldLabel: ドメイン
|
||||||
|
LanguageFieldLabel: 言語
|
||||||
|
ThemeFieldLabel: テーマ
|
||||||
SubsitesVirtualPage:
|
SubsitesVirtualPage:
|
||||||
SINGULARNAME: 'サブサイトの仮想ページ'
|
SINGULARNAME: サブサイトの仮想ページ
|
||||||
|
43
lang/lt.yml
Normal 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
|
77
lang/mi.yml
@ -1,54 +1,68 @@
|
|||||||
mi:
|
mi:
|
||||||
SubsiteAdmin:
|
SilverStripe\CMS\Model\VirtualPage:
|
||||||
MENUTITLE: Ngā pae iti
|
EDITCONTENT: 'Pāwhiri ki konei hei whakatika i ngā ihirangi'
|
||||||
SubsiteXHRController:
|
SilverStripe\Subsites\Admin\SubsiteAdmin:
|
||||||
MENUTITLE: ManaXHRPaeiti
|
MENUTITLE: 'Ngā pae iti'
|
||||||
ASSETADMIN:
|
SilverStripe\Subsites\Extensions\FileSubsites:
|
||||||
SUBSITENOTICE: 'Ka taea ngā kōpaki me ngā kōnae kua hangaia i te pae matua te uru mā ngā pae iti katoa.'
|
has_one_Subsite: 'Pae iti'
|
||||||
FileSubsites:
|
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
|
||||||
AllSitesDropdownOpt: 'Ngā pae katoa'
|
AllSitesDropdownOpt: 'Ngā pae katoa'
|
||||||
SubsiteFieldLabel: Pae iti
|
SUBSITENOTICE: 'Ka taea ngā kōpaki me ngā kōnae kua hangaia i te pae matua te uru mā ngā pae iti katoa.'
|
||||||
GridFieldAddFromTemplateButton:
|
SubsiteFieldLabel: 'Pae iti'
|
||||||
AddFromTemplate: 'Tāpiri Hōu mai i te Tātauira'
|
SilverStripe\Subsites\Extensions\GroupSubsites:
|
||||||
GroupSubsites:
|
|
||||||
ACCESSALL: 'Ngā pae iti katoa'
|
ACCESSALL: 'Ngā pae iti katoa'
|
||||||
ACCESSONLY: 'Ko ēnei pae iti anake'
|
ACCESSONLY: 'Ko ēnei pae iti anake'
|
||||||
ACCESSRADIOTITLE: 'Tukuna tēnei rōpū kia uru ki'
|
ACCESSRADIOTITLE: 'Tukuna tēnei rōpū kia uru ki'
|
||||||
GlobalGroup: 'Rōpū Hurinoa'
|
GlobalGroup: 'Rōpū Hurinoa'
|
||||||
MANAGE_SUBSITES: 'Whakahaere pae iti mō ngā rōpū'
|
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.'
|
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
|
SECURITYTABTITLE: 'Ngā pae iti'
|
||||||
LeftAndMainSubsites:
|
many_many_Subsites: 'Ngā pae iti'
|
||||||
|
SilverStripe\Subsites\Extensions\LeftAndMainSubsites:
|
||||||
Saved: 'Kua tiakina, whakahoutia ngā whārangi pāhono.'
|
Saved: 'Kua tiakina, whakahoutia ngā whārangi pāhono.'
|
||||||
SiteTreeSubsites:
|
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
|
||||||
|
has_one_Subsite: 'Pae iti'
|
||||||
|
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
|
||||||
CopyAction: Tārua
|
CopyAction: Tārua
|
||||||
CopyToSubsite: 'Tāruatia te whārangi ki te pae iti'
|
CopyToSubsite: 'Tāruatia te whārangi ki te pae iti'
|
||||||
Subsite:
|
has_one_Subsite: 'Pae iti'
|
||||||
COPYSTRUCTURE: 'Tāurutia te hanganga mai i:'
|
SilverStripe\Subsites\Model\Subsite:
|
||||||
|
ConfigurationTab: Whirihoranga
|
||||||
CopyMessage: 'I hangaia he tārua o {title}'
|
CopyMessage: 'I hangaia he tārua o {title}'
|
||||||
CustomExtraMeta: 'Ngā Tūtohu Meta Ritenga'
|
CustomExtraMeta: 'Ngā Tūtohu Meta Ritenga'
|
||||||
CustomMetaDescription: Whakaahuatanga
|
CustomMetaDescription: Whakaahuatanga
|
||||||
CustomMetaKeywords: Ngā kupumatua
|
CustomMetaKeywords: 'Ngā kupumatua'
|
||||||
CustomMetaTitle: Taitara
|
CustomMetaTitle: Taitara
|
||||||
DOMAINSAVEFIRST: 'Ka taea noa iho te tāpiri rohe i muri i te tiakinga tuatahitanga'
|
PLURALNAME: 'Ngā pae iti'
|
||||||
DomainsHeadline: 'Ngā rohe mō tēnei pae iti'
|
|
||||||
DomainsListTitle: Ngā Rohe
|
|
||||||
IsPublicHeaderField: 'Pae iti hohe'
|
|
||||||
NOTEMPLATE: 'Kāore he tātauira'
|
|
||||||
PLURALNAME: Ngā pae iti
|
|
||||||
PageTypeBlacklistField: 'Me whakakāhore ngā momo whārangi?'
|
PageTypeBlacklistField: 'Me whakakāhore ngā momo whārangi?'
|
||||||
SINGULARNAME: Pae iti
|
SINGULARNAME: 'Pae iti'
|
||||||
SiteConfigSubtitle: 'Tō rārangi tautuhinga ki konei'
|
SiteConfigSubtitle: 'Tō rārangi tautuhinga ki konei'
|
||||||
SiteConfigTitle: 'Tō Ingoa Pae'
|
SiteConfigTitle: 'Tō Ingoa Pae'
|
||||||
TabTitleConfig: Whirihoranga
|
|
||||||
ValidateTitle: 'Tāurua he "Taitara"'
|
ValidateTitle: 'Tāurua he "Taitara"'
|
||||||
SubsiteDomain:
|
belongs_many_many_Groups: 'Ngā Rōpū'
|
||||||
|
db_DefaultSite: 'Pae taunoa'
|
||||||
|
db_Language: Reo
|
||||||
|
db_RedirectURL: 'Tukua anō te URL'
|
||||||
|
db_Theme: Kaupapa
|
||||||
|
db_Title: Taitara
|
||||||
|
has_many_Domains: 'Ngā Rohe'
|
||||||
|
SilverStripe\Subsites\Model\SubsiteDomain:
|
||||||
DOMAIN: Rohe
|
DOMAIN: Rohe
|
||||||
IS_PRIMARY: 'Ko Te Rohe Matua'
|
|
||||||
PLURALNAME: 'Ngā Rohe Pae Iti'
|
PLURALNAME: 'Ngā Rohe Pae Iti'
|
||||||
SINGULARNAME: 'Rohe Pae Iti'
|
SINGULARNAME: 'Rohe Pae Iti'
|
||||||
SubsiteReportWrapper:
|
db_Domain: Rohe
|
||||||
ReportDropdown: Ngā Pae
|
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:
|
Subsites:
|
||||||
DefaultSiteFieldLabel: 'Pae taunoa'
|
DefaultSiteFieldLabel: 'Pae taunoa'
|
||||||
DomainFieldLabel: Rohe
|
DomainFieldLabel: Rohe
|
||||||
@ -60,8 +74,3 @@ mi:
|
|||||||
RedirectURLFieldLabel: 'Tukua anō te URL'
|
RedirectURLFieldLabel: 'Tukua anō te URL'
|
||||||
ThemeFieldLabel: Kaupapa
|
ThemeFieldLabel: Kaupapa
|
||||||
TitleFieldLabel: 'Ingoa Pae Iti'
|
TitleFieldLabel: 'Ingoa Pae Iti'
|
||||||
SubsitesVirtualPage:
|
|
||||||
DESCRIPTION: 'Ka whakaatu i ngā ihirangi o tētahi whārangi ki tētahi atu pae iti'
|
|
||||||
PLURALNAME: 'Ngā Whārangi Taketake'
|
|
||||||
SINGULARNAME: 'Whārangi Mariko Pae Iti'
|
|
||||||
SubsiteField: Pae iti
|
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
nb_NO:
|
nb_NO:
|
||||||
SubsiteAdmin:
|
|
||||||
MENUTITLE: Underdomener
|
|
||||||
GridFieldAddFromTemplateButton:
|
GridFieldAddFromTemplateButton:
|
||||||
AddFromTemplate: 'Add New from Template'
|
AddFromTemplate: 'Add New from Template'
|
||||||
GroupSubsites:
|
GroupSubsites:
|
||||||
@ -8,10 +6,46 @@ nb_NO:
|
|||||||
ACCESSONLY: 'Only these subsites'
|
ACCESSONLY: 'Only these subsites'
|
||||||
ACCESSRADIOTITLE: 'Give this group access to'
|
ACCESSRADIOTITLE: 'Give this group access to'
|
||||||
SECURITYTABTITLE: subdomener
|
SECURITYTABTITLE: subdomener
|
||||||
SubsiteDomain:
|
SilverStripe\CMS\Model\VirtualPage:
|
||||||
|
EDITCONTENT: 'klikk her for å endre dette innholdet'
|
||||||
|
SilverStripe\Subsites\Admin\SubsiteAdmin:
|
||||||
|
MENUTITLE: subdomener
|
||||||
|
SilverStripe\Subsites\Extensions\FileSubsites:
|
||||||
|
has_one_Subsite: Subdomene
|
||||||
|
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
|
||||||
|
SubsiteFieldLabel: Subdomene
|
||||||
|
SilverStripe\Subsites\Extensions\GroupSubsites:
|
||||||
|
ACCESSALL: 'All subsites'
|
||||||
|
ACCESSONLY: 'Only these subsites'
|
||||||
|
ACCESSRADIOTITLE: 'Give this group access to'
|
||||||
|
SECURITYTABTITLE: subdomener
|
||||||
|
many_many_Subsites: subdomener
|
||||||
|
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
|
||||||
|
has_one_Subsite: Subdomene
|
||||||
|
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
|
||||||
|
has_one_Subsite: Subdomene
|
||||||
|
SilverStripe\Subsites\Model\Subsite:
|
||||||
|
ConfigurationTab: Konfigurasjon
|
||||||
|
PLURALNAME: subdomener
|
||||||
|
SINGULARNAME: Subdomene
|
||||||
|
SilverStripe\Subsites\Model\SubsiteDomain:
|
||||||
DOMAIN: Domain
|
DOMAIN: Domain
|
||||||
IS_PRIMARY: 'Is Primary Domain'
|
|
||||||
PLURALNAME: 'Subsite Domains'
|
PLURALNAME: 'Subsite Domains'
|
||||||
SINGULARNAME: 'Subsite Domain'
|
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:
|
SubsitesVirtualPage:
|
||||||
SINGULARNAME: 'Subdomeners Virtuelle Side'
|
SINGULARNAME: 'Subdomeners Virtuelle Side'
|
||||||
|
101
lang/nl.yml
@ -1,34 +1,103 @@
|
|||||||
nl:
|
nl:
|
||||||
SubsiteAdmin:
|
DomainNameField:
|
||||||
|
INVALID_DOMAIN: 'Ongeldige domein naam'
|
||||||
|
SilverStripe\CMS\Model\VirtualPage:
|
||||||
|
EDITCONTENT: 'Klik hier om inhoud aan te passen'
|
||||||
|
SilverStripe\Subsites\Admin\SubsiteAdmin:
|
||||||
MENUTITLE: Subsites
|
MENUTITLE: Subsites
|
||||||
FileSubsites:
|
SilverStripe\Subsites\Controller\SubsiteXHRController:
|
||||||
|
MENUTITLE: Subsites
|
||||||
|
SilverStripe\Subsites\Extensions\FileSubsites:
|
||||||
|
has_one_Subsite: Subsite
|
||||||
|
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
|
||||||
|
AllSitesDropdownOpt: 'Alle sites'
|
||||||
|
SUBSITENOTICE: 'Mappen en bestanden van de Hoofdsite zijn toegankelijk voor alle subsites.'
|
||||||
SubsiteFieldLabel: Subsite
|
SubsiteFieldLabel: Subsite
|
||||||
GridFieldAddFromTemplateButton:
|
SilverStripe\Subsites\Extensions\GroupSubsites:
|
||||||
AddFromTemplate: 'Nieuwe toevegen vanaf template'
|
|
||||||
GroupSubsites:
|
|
||||||
ACCESSALL: 'Alle subsites'
|
ACCESSALL: 'Alle subsites'
|
||||||
ACCESSONLY: 'Alleen deze subsites'
|
ACCESSONLY: 'Alleen deze subsites'
|
||||||
ACCESSRADIOTITLE: 'Geef deze groep rechten aan'
|
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
|
SECURITYTABTITLE: Subsites
|
||||||
Subsite:
|
db_AccessAllSubsites: 'Toegang tot alle subsites'
|
||||||
|
many_many_Subsites: Subsites
|
||||||
|
SilverStripe\Subsites\Extensions\LeftAndMainSubsites:
|
||||||
|
SITECONTENTLEFT: 'Site inhoud'
|
||||||
|
Saved: 'Opgeslagen, pas onderliggende pagina''s aan.'
|
||||||
|
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
|
||||||
|
has_one_Subsite: Subsite
|
||||||
|
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
|
||||||
|
CopyAction: Kopieer
|
||||||
|
CopyToSubsite: 'Kopieer pagina''s naar subsite'
|
||||||
|
CopyToSubsiteWithChildren: 'Inclusief onderliggende pagina''s?'
|
||||||
|
SubsiteOperations: 'Subsite Acties'
|
||||||
|
has_one_Subsite: Subsite
|
||||||
|
SilverStripe\Subsites\Model\Subsite:
|
||||||
|
ConfigurationTab: Configuratie
|
||||||
|
CopyMessage: 'Kopie gemaakt van {title}'
|
||||||
|
CustomExtraMeta: 'Andere meta tags'
|
||||||
CustomMetaDescription: Omschrijving
|
CustomMetaDescription: Omschrijving
|
||||||
|
CustomMetaKeywords: Sleutelwoorden
|
||||||
CustomMetaTitle: Titel
|
CustomMetaTitle: Titel
|
||||||
PLURALNAME: Subsites
|
PLURALNAME: Subsites
|
||||||
|
PLURALS:
|
||||||
|
one: 'Een Subsite'
|
||||||
|
other: '{count} Subsites'
|
||||||
|
PageTypeBlacklistField: 'Niet toegestane paginatypes?'
|
||||||
SINGULARNAME: Subsite
|
SINGULARNAME: Subsite
|
||||||
|
SiteConfigSubtitle: 'Jouw slagzin hier'
|
||||||
SiteConfigTitle: 'Jouw Site Naam'
|
SiteConfigTitle: 'Jouw Site Naam'
|
||||||
TabTitleConfig: Configuratie
|
ThemeFieldEmptyString: '-'
|
||||||
SubsiteDomain:
|
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: 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'
|
IS_PRIMARY: 'Is Primaire domein'
|
||||||
PLURALNAME: 'Subsite Domeinen'
|
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'
|
SINGULARNAME: 'Subsite Domein'
|
||||||
Subsites:
|
db_Domain: Domein
|
||||||
DefaultSiteFieldLabel: 'Standaard Site'
|
db_Protocol: Protocol
|
||||||
LanguageFieldLabel: Taal
|
has_one_Subsite: Subsite
|
||||||
PrimaryDomainFieldLabel: 'Primaire domein'
|
SilverStripe\Subsites\Pages\SubsitesVirtualPage:
|
||||||
ThemeFieldLabel: Thema
|
DESCRIPTION: 'Toon de inhoud van een pagina op een andere subsite'
|
||||||
TitleFieldLabel: 'Subsite naam'
|
OverrideNote: 'Overschrijft de overgenomen tekst van de gelinkte pagina'
|
||||||
SubsitesVirtualPage:
|
PLURALNAME: 'Subsites Virtuele paginas'
|
||||||
PLURALNAME: 'Subsites Virtuele pagina'
|
PLURALS:
|
||||||
|
one: 'Subsites Virtuele pagina'
|
||||||
|
other: '{count} Subsites Virtuele paginas'
|
||||||
SINGULARNAME: 'Subsites Virtuele pagina'
|
SINGULARNAME: 'Subsites Virtuele pagina'
|
||||||
SubsiteField: Subsite
|
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
@ -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
|
@ -1,6 +1,4 @@
|
|||||||
pl_PL:
|
pl_PL:
|
||||||
SubsiteAdmin:
|
|
||||||
MENUTITLE: Podwitryny
|
|
||||||
GridFieldAddFromTemplateButton:
|
GridFieldAddFromTemplateButton:
|
||||||
AddFromTemplate: 'Dodaj nową na podstawie szablonu'
|
AddFromTemplate: 'Dodaj nową na podstawie szablonu'
|
||||||
GroupSubsites:
|
GroupSubsites:
|
||||||
@ -8,10 +6,51 @@ pl_PL:
|
|||||||
ACCESSONLY: 'Tylko te podwitryny'
|
ACCESSONLY: 'Tylko te podwitryny'
|
||||||
ACCESSRADIOTITLE: 'Daj tej grupie dostęp do'
|
ACCESSRADIOTITLE: 'Daj tej grupie dostęp do'
|
||||||
SECURITYTABTITLE: Podwitryny
|
SECURITYTABTITLE: Podwitryny
|
||||||
SubsiteDomain:
|
SilverStripe\CMS\Model\VirtualPage:
|
||||||
|
EDITCONTENT: 'Kliknij tutaj aby edytować zawartość'
|
||||||
|
SilverStripe\Subsites\Admin\SubsiteAdmin:
|
||||||
|
MENUTITLE: Podwitryny
|
||||||
|
SilverStripe\Subsites\Extensions\FileSubsites:
|
||||||
|
has_one_Subsite: Podwitryna
|
||||||
|
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
|
||||||
|
SubsiteFieldLabel: Podwitryna
|
||||||
|
SilverStripe\Subsites\Extensions\GroupSubsites:
|
||||||
|
ACCESSALL: 'Wszystkie podwitryny'
|
||||||
|
ACCESSONLY: 'Tylko te podwitryny'
|
||||||
|
ACCESSRADIOTITLE: 'Daj tej grupie dostęp do'
|
||||||
|
SECURITYTABTITLE: Podwitryny
|
||||||
|
many_many_Subsites: Podwitryny
|
||||||
|
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
|
||||||
|
has_one_Subsite: Podwitryna
|
||||||
|
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
|
||||||
|
has_one_Subsite: Podwitryna
|
||||||
|
SilverStripe\Subsites\Model\Subsite:
|
||||||
|
ConfigurationTab: Konfiguracja
|
||||||
|
CustomExtraMeta: 'Własne meta tagi'
|
||||||
|
CustomMetaTitle: Tytuł
|
||||||
|
PLURALNAME: Podwitryny
|
||||||
|
SINGULARNAME: Podwitryna
|
||||||
|
db_Language: Język
|
||||||
|
db_Title: Tytuł
|
||||||
|
SilverStripe\Subsites\Model\SubsiteDomain:
|
||||||
DOMAIN: Domena
|
DOMAIN: Domena
|
||||||
IS_PRIMARY: 'Ma priorytet?'
|
|
||||||
PLURALNAME: 'Domeny podwitryny'
|
PLURALNAME: 'Domeny podwitryny'
|
||||||
SINGULARNAME: 'Domena 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:
|
SubsitesVirtualPage:
|
||||||
SINGULARNAME: 'Wirtualna strona dla podwitryn'
|
SINGULARNAME: 'Wirtualna strona dla podwitryn'
|
||||||
|
87
lang/ru.yml
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -0,0 +1,13 @@
|
|||||||
|
tr:
|
||||||
|
SilverStripe\Subsites\Model\Subsite:
|
||||||
|
CustomExtraMeta: 'Kişisel Meta Etiketleri'
|
||||||
|
CustomMetaDescription: Açı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
|
@ -1,17 +1,50 @@
|
|||||||
tr_TR:
|
tr_TR:
|
||||||
SubsiteAdmin:
|
|
||||||
MENUTITLE: Alt Siteler
|
|
||||||
GridFieldAddFromTemplateButton:
|
GridFieldAddFromTemplateButton:
|
||||||
AddFromTemplate: 'Add New from Template'
|
AddFromTemplate: 'Add New from Template'
|
||||||
GroupSubsites:
|
GroupSubsites:
|
||||||
ACCESSALL: 'All subsites'
|
ACCESSALL: 'All subsites'
|
||||||
ACCESSONLY: 'Only these subsites'
|
ACCESSONLY: 'Only these subsites'
|
||||||
ACCESSRADIOTITLE: 'Give this group access to'
|
ACCESSRADIOTITLE: 'Give this group access to'
|
||||||
SECURITYTABTITLE: Alt Siteler
|
SECURITYTABTITLE: 'Alt Siteler'
|
||||||
SubsiteDomain:
|
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
|
DOMAIN: Domain
|
||||||
IS_PRIMARY: 'Is Primary Domain'
|
|
||||||
PLURALNAME: 'Subsite Domains'
|
PLURALNAME: 'Subsite Domains'
|
||||||
SINGULARNAME: 'Subsite Domain'
|
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:
|
SubsitesVirtualPage:
|
||||||
SINGULARNAME: 'Alt Site Sanal Sayfa'
|
SINGULARNAME: 'Alt Site Sanal Sayfa'
|
||||||
|
111
lang/zh.yml
@ -1,67 +1,76 @@
|
|||||||
zh:
|
zh:
|
||||||
SubsiteAdmin:
|
SilverStripe\CMS\Model\VirtualPage:
|
||||||
|
EDITCONTENT: 点击这里来编辑内容
|
||||||
|
SilverStripe\Subsites\Admin\SubsiteAdmin:
|
||||||
MENUTITLE: 多个子网站
|
MENUTITLE: 多个子网站
|
||||||
SubsiteXHRController:
|
SilverStripe\Subsites\Extensions\FileSubsites:
|
||||||
MENUTITLE: 子网站 XHR 控制器
|
has_one_Subsite: 子网站
|
||||||
ASSETADMIN:
|
SilverStripe\Subsites\Extensions\FolderFormFactoryExtension:
|
||||||
SUBSITENOTICE: '主网站上创建的文件夹和文件可以被所有子网站访问。'
|
AllSitesDropdownOpt: 所有网站
|
||||||
FileSubsites:
|
SUBSITENOTICE: 主网站上创建的文件夹和文件可以被所有子网站访问。
|
||||||
AllSitesDropdownOpt: '所有网站'
|
|
||||||
SubsiteFieldLabel: 子网站
|
SubsiteFieldLabel: 子网站
|
||||||
GridFieldAddFromTemplateButton:
|
SilverStripe\Subsites\Extensions\GroupSubsites:
|
||||||
AddFromTemplate: '从模板中取出新加入'
|
ACCESSALL: 所有子网站
|
||||||
GroupSubsites:
|
ACCESSONLY: 仅这些子网站
|
||||||
ACCESSALL: '所有子网站'
|
ACCESSRADIOTITLE: 准许该群进入
|
||||||
ACCESSONLY: '仅这些子网站'
|
GlobalGroup: 全局小组
|
||||||
ACCESSRADIOTITLE: '准许该群进入'
|
MANAGE_SUBSITES: 管理小组的子网站
|
||||||
GlobalGroup: '全局小组'
|
MANAGE_SUBSITES_HELP: 能够将权限限制在一个小组、一个或多个子网站。
|
||||||
MANAGE_SUBSITES: '管理小组的子网站'
|
|
||||||
MANAGE_SUBSITES_HELP: '能够将权限限制在一个小组、一个或多个子网站。'
|
|
||||||
SECURITYTABTITLE: 多个子网站
|
SECURITYTABTITLE: 多个子网站
|
||||||
LeftAndMainSubsites:
|
many_many_Subsites: 多个子网站
|
||||||
Saved: '已保存,请更新相关的页面。'
|
SilverStripe\Subsites\Extensions\LeftAndMainSubsites:
|
||||||
SiteTreeSubsites:
|
Saved: 已保存,请更新相关的页面。
|
||||||
|
SilverStripe\Subsites\Extensions\SiteConfigSubsites:
|
||||||
|
has_one_Subsite: 子网站
|
||||||
|
SilverStripe\Subsites\Extensions\SiteTreeSubsites:
|
||||||
CopyAction: 复制
|
CopyAction: 复制
|
||||||
CopyToSubsite: '将页面复制到子网站'
|
CopyToSubsite: 将页面复制到子网站
|
||||||
Subsite:
|
has_one_Subsite: 子网站
|
||||||
COPYSTRUCTURE: '复制结构来自:'
|
SilverStripe\Subsites\Model\Subsite:
|
||||||
|
ConfigurationTab: 配置
|
||||||
CopyMessage: '已创建一个 {title} 的副本'
|
CopyMessage: '已创建一个 {title} 的副本'
|
||||||
CustomExtraMeta: '自定义 Meta 标签'
|
CustomExtraMeta: 自定义Meta标签
|
||||||
CustomMetaDescription: 说明
|
CustomMetaDescription: 描述
|
||||||
CustomMetaKeywords: 关键词
|
CustomMetaKeywords: 关键词
|
||||||
CustomMetaTitle: 标题
|
CustomMetaTitle: 题目
|
||||||
DOMAINSAVEFIRST: '只有当您第一次保存后,才能添加域名'
|
|
||||||
DomainsHeadline: '这个子网站的域名'
|
|
||||||
DomainsListTitle: 域名
|
|
||||||
IsPublicHeaderField: '活跃的子网站'
|
|
||||||
NOTEMPLATE: '没有模板'
|
|
||||||
PLURALNAME: 多个子网站
|
PLURALNAME: 多个子网站
|
||||||
PageTypeBlacklistField: '禁止页面类型?'
|
PageTypeBlacklistField: 禁止页面类型?
|
||||||
SINGULARNAME: 子网站
|
SINGULARNAME: 子网站
|
||||||
SiteConfigSubtitle: '您的标语在这里'
|
SiteConfigSubtitle: 您的标语在这里
|
||||||
SiteConfigTitle: '您的网站名称'
|
SiteConfigTitle: 您的网站名称
|
||||||
TabTitleConfig: 配置
|
ValidateTitle: 请添加一个“标题”
|
||||||
ValidateTitle: '请添加一个“标题”'
|
belongs_many_many_Groups: 群组
|
||||||
SubsiteDomain:
|
db_DefaultSite: 默认网站
|
||||||
|
db_Language: 语言
|
||||||
|
db_RedirectURL: '重定向 URL'
|
||||||
|
db_Theme: 主题
|
||||||
|
db_Title: 题目
|
||||||
|
has_many_Domains: 域名
|
||||||
|
SilverStripe\Subsites\Model\SubsiteDomain:
|
||||||
DOMAIN: 域名
|
DOMAIN: 域名
|
||||||
IS_PRIMARY: '是主域名'
|
PLURALNAME: 多个子网站域名
|
||||||
PLURALNAME: '多个子网站域名'
|
SINGULARNAME: 子网站域名
|
||||||
SINGULARNAME: '子网站域名'
|
db_Domain: 域名
|
||||||
SubsiteReportWrapper:
|
has_one_Subsite: 子网站
|
||||||
|
SilverStripe\Subsites\Pages\SubsitesVirtualPage:
|
||||||
|
DESCRIPTION: 显示另一个子网站上一个页面的内容
|
||||||
|
PLURALNAME: 基本页面
|
||||||
|
SINGULARNAME: 子网站虚拟页面
|
||||||
|
SubsiteField: 子网站
|
||||||
|
SilverStripe\Subsites\Reports\SubsiteReportWrapper:
|
||||||
ReportDropdown: 网站
|
ReportDropdown: 网站
|
||||||
|
ReportDropdownSubsite: 子网站
|
||||||
|
Subsite:
|
||||||
|
COPYSTRUCTURE: 复制结构来自:
|
||||||
|
NOTEMPLATE: 没有模板
|
||||||
Subsites:
|
Subsites:
|
||||||
DefaultSiteFieldLabel: '默认网站'
|
DefaultSiteFieldLabel: 默认网站
|
||||||
DomainFieldLabel: 域名
|
DomainFieldLabel: 域名
|
||||||
IsPublicFieldLabel: '启用公共访问权'
|
IsPublicFieldLabel: 启用公共访问权
|
||||||
LanguageFieldLabel: 语言
|
LanguageFieldLabel: 语言
|
||||||
MainSiteTitle: '主网站'
|
MainSiteTitle: 主网站
|
||||||
PageTypeBlacklistFieldLabel: '页面类型黑名单'
|
PageTypeBlacklistFieldLabel: 页面类型黑名单
|
||||||
PrimaryDomainFieldLabel: '主域名'
|
PrimaryDomainFieldLabel: 主域名
|
||||||
RedirectURLFieldLabel: '重定向 URL'
|
RedirectURLFieldLabel: '重定向 URL'
|
||||||
ThemeFieldLabel: 主题
|
ThemeFieldLabel: 主题
|
||||||
TitleFieldLabel: '子网站名称'
|
TitleFieldLabel: 子网站名称
|
||||||
SubsitesVirtualPage:
|
|
||||||
DESCRIPTION: '显示另一个子网站上一个页面的内容'
|
|
||||||
PLURALNAME: '基本页面'
|
|
||||||
SINGULARNAME: '子网站虚拟页面'
|
|
||||||
SubsiteField: 子网站
|
|
||||||
|
12
license.md
Normal 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
@ -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
@ -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>
|
43
src/Admin/SubsiteAdmin.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
68
src/Controller/SubsiteXHRController.php
Normal 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']);
|
||||||
|
}
|
||||||
|
}
|
33
src/Extensions/BaseElementSubsites.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
16
src/Extensions/CMSPageAddControllerExtension.php
Normal 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()));
|
||||||
|
}
|
||||||
|
}
|
29
src/Extensions/ControllerSubsites.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
65
src/Extensions/ErrorPageSubsite.php
Normal 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]);
|
||||||
|
}
|
||||||
|
}
|
140
src/Extensions/FileSubsites.php
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
47
src/Extensions/FolderFormFactoryExtension.php
Normal 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>'
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
250
src/Extensions/GroupSubsites.php
Normal 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
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
22
src/Extensions/HintsCacheKeyExtension.php
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
414
src/Extensions/LeftAndMainSubsites.php
Normal 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
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
78
src/Extensions/SiteConfigSubsites.php
Normal 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()));
|
||||||
|
}
|
||||||
|
}
|
577
src/Extensions/SiteTreeSubsites.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\Subsites\Extensions;
|
||||||
|
|
||||||
|
use SilverStripe\Core\Extension;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Simple extension to show admins in the menu of subsites.
|
* Simple extension to show admins in the menu of subsites.
|
||||||
* If an admin area should be available to a subsite, you can attach
|
* If an admin area should be available to a subsite, you can attach
|
||||||
@ -10,10 +14,10 @@
|
|||||||
* Or you can include the subsiteCMSShowInMenu function in your admin class and have it return true
|
* Or you can include the subsiteCMSShowInMenu function in your admin class and have it return true
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class SubsiteMenuExtension extends Extension{
|
class SubsiteMenuExtension extends Extension
|
||||||
|
{
|
||||||
public function subsiteCMSShowInMenu(){
|
public function subsiteCMSShowInMenu()
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
10
src/Forms/GridFieldSubsiteDetailForm.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\Subsites\Forms;
|
||||||
|
|
||||||
|
use SilverStripe\Forms\GridField\GridFieldDetailForm;
|
||||||
|
|
||||||
|
class GridFieldSubsiteDetailForm extends GridFieldDetailForm
|
||||||
|
{
|
||||||
|
protected $itemRequestClass = GridFieldSubsiteDetailFormItemRequest::class;
|
||||||
|
}
|
63
src/Forms/GridFieldSubsiteDetailFormItemRequest.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
87
src/Forms/SubsitesTreeDropdownField.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
46
src/Forms/WildcardDomainField.php
Normal 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';
|
||||||
|
}
|
||||||
|
}
|
105
src/Middleware/InitStateMiddleware.php
Normal 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
@ -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
@ -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()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
250
src/Pages/SubsitesVirtualPage.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
85
src/Reports/SubsiteReportWrapper.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
99
src/Service/ThemeResolver.php
Normal 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
@ -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;
|
||||||
|
}
|
||||||
|
}
|
92
src/Tasks/SubsiteCopyPagesTask.php
Normal 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";
|
||||||
|
}
|
||||||
|
}
|
35
src/Tasks/SubsiteMigrateFileTask.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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>
|
|
@ -1,43 +0,0 @@
|
|||||||
<div class="cms-menu cms-panel cms-panel-layout west" id="cms-menu" data-layout-type="border">
|
|
||||||
<div class="cms-logo-header north">
|
|
||||||
<div class="cms-logo">
|
|
||||||
<a href="$ApplicationLink" target="_blank" title="$ApplicationName (Version - $CMSVersion)">
|
|
||||||
$ApplicationName <% if $CMSVersion %><abbr class="version">$CMSVersion</abbr><% end_if %>
|
|
||||||
</a>
|
|
||||||
<span><% if $SiteConfig %>$SiteConfig.Title<% else %>$ApplicationName<% end_if %></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="cms-login-status">
|
|
||||||
<a href="Security/logout" class="logout-link" title="<% _t('LeftAndMain_Menu.LOGOUT','Log out') %>"><% _t('LeftAndMain_Menu.LOGOUT','Log out') %></a>
|
|
||||||
<% with $CurrentMember %>
|
|
||||||
<span>
|
|
||||||
<% _t('LeftAndMain_Menu.Hello','Hi') %>
|
|
||||||
<a href="{$AbsoluteBaseURL}admin/myprofile" class="profile-link">
|
|
||||||
<% if $FirstName && $Surname %>$FirstName $Surname<% else_if $FirstName %>$FirstName<% else %>$Email<% end_if %>
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
<% end_with %>
|
|
||||||
</div>
|
|
||||||
<% if $ListSubsites %>
|
|
||||||
<% include SubsiteList %>
|
|
||||||
<% end_if %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="cms-panel-content center">
|
|
||||||
<ul class="cms-menu-list">
|
|
||||||
<% loop $MainMenu %>
|
|
||||||
<li class="$LinkingMode $FirstLast <% if $LinkingMode == 'link' %><% else %>opened<% end_if %>" id="Menu-$Code" title="$Title.ATT">
|
|
||||||
<a href="$Link" <% if $Code == 'Help' %>target="_blank"<% end_if %>>
|
|
||||||
<span class="icon icon-16 icon-{$Code.LowerCase}"> </span>
|
|
||||||
<span class="text">$Title</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<% end_loop %>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="cms-panel-toggle south">
|
|
||||||
<a class="toggle-expand" href="#"><span>»</span></a>
|
|
||||||
<a class="toggle-collapse" href="#"><span>«</span></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
22
templates/SilverStripe/Admin/LeftAndMain_Menu.ss
Normal 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>
|
@ -1,28 +0,0 @@
|
|||||||
<?php
|
|
||||||
class BaseSubsiteTest extends SapphireTest {
|
|
||||||
|
|
||||||
function setUp() {
|
|
||||||
parent::setUp();
|
|
||||||
|
|
||||||
Subsite::$use_session_subsiteid = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Avoid subsites filtering on fixture fetching.
|
|
||||||
*/
|
|
||||||
function objFromFixture($class, $id) {
|
|
||||||
Subsite::disable_subsite_filter(true);
|
|
||||||
$obj = parent::objFromFixture($class, $id);
|
|
||||||
Subsite::disable_subsite_filter(false);
|
|
||||||
|
|
||||||
return $obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests the initial state of disable_subsite_filter
|
|
||||||
*/
|
|
||||||
function testDisableSubsiteFilter() {
|
|
||||||
$this->assertFalse(Subsite::$disable_subsite_filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,76 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class FileSubsitesTest extends BaseSubsiteTest {
|
|
||||||
static $fixture_file = 'subsites/tests/SubsiteTest.yml';
|
|
||||||
|
|
||||||
function testTrivialFeatures() {
|
|
||||||
$this->assertTrue(is_array(singleton('FileSubsites')->extraStatics()));
|
|
||||||
$file = new File();
|
|
||||||
$file->Name = 'FileTitle';
|
|
||||||
$file->Title = 'FileTitle';
|
|
||||||
$this->assertEquals(' * FileTitle', $file->alternateTreeTitle());
|
|
||||||
$file->SubsiteID = $this->objFromFixture('Subsite', 'domaintest1')->ID;
|
|
||||||
$this->assertEquals('FileTitle', $file->getTreeTitle());
|
|
||||||
$this->assertTrue(singleton('Folder')->getCMSFields() instanceof FieldList);
|
|
||||||
Subsite::changeSubsite(1);
|
|
||||||
$this->assertEquals($file->cacheKeyComponent(), 'subsite-1');
|
|
||||||
}
|
|
||||||
|
|
||||||
function testWritingSubsiteID() {
|
|
||||||
$this->objFromFixture('Member', 'admin')->logIn();
|
|
||||||
|
|
||||||
$subsite = $this->objFromFixture('Subsite', 'domaintest1');
|
|
||||||
FileSubsites::$default_root_folders_global = true;
|
|
||||||
|
|
||||||
Subsite::changeSubsite(0);
|
|
||||||
$file = new File();
|
|
||||||
$file->write();
|
|
||||||
$file->onAfterUpload();
|
|
||||||
$this->assertEquals((int)$file->SubsiteID, 0);
|
|
||||||
|
|
||||||
Subsite::changeSubsite($subsite->ID);
|
|
||||||
$this->assertTrue($file->canEdit());
|
|
||||||
|
|
||||||
$file = new File();
|
|
||||||
$file->write();
|
|
||||||
$this->assertEquals((int)$file->SubsiteID, 0);
|
|
||||||
$this->assertTrue($file->canEdit());
|
|
||||||
|
|
||||||
FileSubsites::$default_root_folders_global = false;
|
|
||||||
|
|
||||||
Subsite::changeSubsite($subsite->ID);
|
|
||||||
$file = new File();
|
|
||||||
$file->write();
|
|
||||||
$this->assertEquals($file->SubsiteID, $subsite->ID);
|
|
||||||
|
|
||||||
// Test inheriting from parent folder
|
|
||||||
$folder = new Folder();
|
|
||||||
$folder->write();
|
|
||||||
$this->assertEquals($folder->SubsiteID, $subsite->ID);
|
|
||||||
FileSubsites::$default_root_folders_global = true;
|
|
||||||
$file = new File();
|
|
||||||
$file->ParentID = $folder->ID;
|
|
||||||
$file->onAfterUpload();
|
|
||||||
$this->assertEquals($folder->SubsiteID, $file->SubsiteID);
|
|
||||||
}
|
|
||||||
|
|
||||||
function testSubsitesFolderDropdown() {
|
|
||||||
$this->objFromFixture('Member', 'admin')->logIn();
|
|
||||||
|
|
||||||
$file = new Folder();
|
|
||||||
|
|
||||||
$source = array_values($file->getCMSFields()->dataFieldByName('SubsiteID')->getSource());
|
|
||||||
asort($source);
|
|
||||||
|
|
||||||
$this->assertEquals(array(
|
|
||||||
'Main site',
|
|
||||||
'Template',
|
|
||||||
'Subsite1 Template',
|
|
||||||
'Subsite2 Template',
|
|
||||||
'Test 1',
|
|
||||||
'Test 2',
|
|
||||||
'Test 3'
|
|
||||||
), $source);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class GroupSubsitesTest extends BaseSubsiteTest {
|
|
||||||
static $fixture_file = 'subsites/tests/SubsiteTest.yml';
|
|
||||||
|
|
||||||
protected $requireDefaultRecordsFrom = array('GroupSubsites');
|
|
||||||
|
|
||||||
function testTrivialFeatures() {
|
|
||||||
$this->assertTrue(is_array(singleton('GroupSubsites')->extraStatics()));
|
|
||||||
$this->assertTrue(is_array(singleton('GroupSubsites')->providePermissions()));
|
|
||||||
$this->assertTrue(singleton('Group')->getCMSFields() instanceof FieldList);
|
|
||||||
}
|
|
||||||
|
|
||||||
function testAlternateTreeTitle() {
|
|
||||||
$group = new Group();
|
|
||||||
$group->Title = 'The A Team';
|
|
||||||
$group->AccessAllSubsites = true;
|
|
||||||
$this->assertEquals($group->getTreeTitle(), 'The A Team <i>(global group)</i>');
|
|
||||||
$group->AccessAllSubsites = false;
|
|
||||||
$group->write();
|
|
||||||
$group->Subsites()->add($this->objFromFixture('Subsite', 'domaintest1'));
|
|
||||||
$group->Subsites()->add($this->objFromFixture('Subsite', 'domaintest2'));
|
|
||||||
$this->assertEquals($group->getTreeTitle(), 'The A Team <i>(Test 1, Test 2)</i>');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,88 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class LeftAndMainSubsitesTest extends FunctionalTest {
|
|
||||||
|
|
||||||
static $fixture_file = 'subsites/tests/SubsiteTest.yml';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Avoid subsites filtering on fixture fetching.
|
|
||||||
*/
|
|
||||||
function objFromFixture($class, $id) {
|
|
||||||
Subsite::disable_subsite_filter(true);
|
|
||||||
$obj = parent::objFromFixture($class, $id);
|
|
||||||
Subsite::disable_subsite_filter(false);
|
|
||||||
|
|
||||||
return $obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
function testSectionSites() {
|
|
||||||
$member = $this->objFromFixture('Member', 'subsite1member');
|
|
||||||
|
|
||||||
$cmsmain = singleton('CMSMain');
|
|
||||||
$subsites = $cmsmain->sectionSites(true, "Main site", $member);
|
|
||||||
$this->assertDOSEquals(array(
|
|
||||||
array('Title' =>'Subsite1 Template')
|
|
||||||
), $subsites, 'Lists member-accessible sites for the accessible controller.');
|
|
||||||
|
|
||||||
$assetadmin = singleton('AssetAdmin');
|
|
||||||
$subsites = $assetadmin->sectionSites(true, "Main site", $member);
|
|
||||||
$this->assertDOSEquals(array(), $subsites, 'Does not list any sites for forbidden controller.');
|
|
||||||
|
|
||||||
$member = $this->objFromFixture('Member', 'editor');
|
|
||||||
|
|
||||||
$cmsmain = singleton('CMSMain');
|
|
||||||
$subsites = $cmsmain->sectionSites(true, "Main site", $member);
|
|
||||||
$this->assertDOSContains(array(
|
|
||||||
array('Title' =>'Main site')
|
|
||||||
), $subsites, 'Includes the main site for members who can access all sites.');
|
|
||||||
}
|
|
||||||
|
|
||||||
function testAccessChecksDontChangeCurrentSubsite() {
|
|
||||||
$admin = $this->objFromFixture("Member","admin");
|
|
||||||
$this->loginAs($admin);
|
|
||||||
$ids = array();
|
|
||||||
|
|
||||||
$subsite1 = $this->objFromFixture('Subsite', 'domaintest1');
|
|
||||||
$subsite2 = $this->objFromFixture('Subsite', 'domaintest2');
|
|
||||||
$subsite3 = $this->objFromFixture('Subsite', 'domaintest3');
|
|
||||||
|
|
||||||
$ids[] = $subsite1->ID;
|
|
||||||
$ids[] = $subsite2->ID;
|
|
||||||
$ids[] = $subsite3->ID;
|
|
||||||
$ids[] = 0;
|
|
||||||
|
|
||||||
// Enable session-based subsite tracking.
|
|
||||||
Subsite::$use_session_subsiteid = true;
|
|
||||||
|
|
||||||
foreach($ids as $id) {
|
|
||||||
Subsite::changeSubsite($id);
|
|
||||||
$this->assertEquals($id, Subsite::currentSubsiteID());
|
|
||||||
|
|
||||||
$left = new LeftAndMain();
|
|
||||||
$this->assertTrue($left->canView(), "Admin user can view subsites LeftAndMain with id = '$id'");
|
|
||||||
$this->assertEquals($id, Subsite::currentSubsiteID(),
|
|
||||||
"The current subsite has not been changed in the process of checking permissions for admin user.");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function testShouldChangeSubsite() {
|
|
||||||
$l = new LeftAndMain();
|
|
||||||
Config::inst()->nest();
|
|
||||||
|
|
||||||
Config::inst()->update('CMSPageEditController', 'treats_subsite_0_as_global', false);
|
|
||||||
$this->assertTrue($l->shouldChangeSubsite('CMSPageEditController', 0, 5));
|
|
||||||
$this->assertFalse($l->shouldChangeSubsite('CMSPageEditController', 0, 0));
|
|
||||||
$this->assertTrue($l->shouldChangeSubsite('CMSPageEditController', 1, 5));
|
|
||||||
$this->assertFalse($l->shouldChangeSubsite('CMSPageEditController', 1, 1));
|
|
||||||
|
|
||||||
Config::inst()->update('CMSPageEditController', 'treats_subsite_0_as_global', true);
|
|
||||||
$this->assertFalse($l->shouldChangeSubsite('CMSPageEditController', 0, 5));
|
|
||||||
$this->assertFalse($l->shouldChangeSubsite('CMSPageEditController', 0, 0));
|
|
||||||
$this->assertTrue($l->shouldChangeSubsite('CMSPageEditController', 1, 5));
|
|
||||||
$this->assertFalse($l->shouldChangeSubsite('CMSPageEditController', 1, 1));
|
|
||||||
|
|
||||||
Config::inst()->unnest();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class SiteConfigSubsitesTest extends BaseSubsiteTest {
|
|
||||||
static $fixture_file = 'subsites/tests/SubsiteTest.yml';
|
|
||||||
|
|
||||||
function testEachSubsiteHasAUniqueSiteConfig() {
|
|
||||||
$subsite1 = $this->objFromFixture('Subsite', 'domaintest1');
|
|
||||||
$subsite2 = $this->objFromFixture('Subsite', 'domaintest2');
|
|
||||||
|
|
||||||
$this->assertTrue(is_array(singleton('SiteConfigSubsites')->extraStatics()));
|
|
||||||
|
|
||||||
Subsite::changeSubsite(0);
|
|
||||||
$sc = SiteConfig::current_site_config();
|
|
||||||
$sc->Title = 'RootSite';
|
|
||||||
$sc->write();
|
|
||||||
|
|
||||||
Subsite::changeSubsite($subsite1->ID);
|
|
||||||
$sc = SiteConfig::current_site_config();
|
|
||||||
$sc->Title = 'Subsite1';
|
|
||||||
$sc->write();
|
|
||||||
|
|
||||||
Subsite::changeSubsite($subsite2->ID);
|
|
||||||
$sc = SiteConfig::current_site_config();
|
|
||||||
$sc->Title = 'Subsite2';
|
|
||||||
$sc->write();
|
|
||||||
|
|
||||||
Subsite::changeSubsite(0);
|
|
||||||
$this->assertEquals(SiteConfig::current_site_config()->Title, 'RootSite');
|
|
||||||
Subsite::changeSubsite($subsite1->ID);
|
|
||||||
$this->assertEquals(SiteConfig::current_site_config()->Title, 'Subsite1');
|
|
||||||
Subsite::changeSubsite($subsite2->ID);
|
|
||||||
$this->assertEquals(SiteConfig::current_site_config()->Title, 'Subsite2');
|
|
||||||
|
|
||||||
$keys = SiteConfig::current_site_config()->extend('cacheKeyComponent');
|
|
||||||
$this->assertContains('subsite-' . $subsite2->ID, $keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,194 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class SiteTreeSubsitesTest extends BaseSubsiteTest {
|
|
||||||
|
|
||||||
static $fixture_file = 'subsites/tests/SubsiteTest.yml';
|
|
||||||
|
|
||||||
protected $extraDataObjects = array(
|
|
||||||
'SiteTreeSubsitesTest_ClassA',
|
|
||||||
'SiteTreeSubsitesTest_ClassB'
|
|
||||||
);
|
|
||||||
|
|
||||||
function testPagesInDifferentSubsitesCanShareURLSegment() {
|
|
||||||
$subsiteMain = $this->objFromFixture('Subsite', 'main');
|
|
||||||
$subsite1 = $this->objFromFixture('Subsite', 'subsite1');
|
|
||||||
|
|
||||||
$pageMain = new SiteTree();
|
|
||||||
$pageMain->URLSegment = 'testpage';
|
|
||||||
$pageMain->write();
|
|
||||||
$pageMain->publish('Stage', 'Live');
|
|
||||||
|
|
||||||
$pageMainOther = new SiteTree();
|
|
||||||
$pageMainOther->URLSegment = 'testpage';
|
|
||||||
$pageMainOther->write();
|
|
||||||
$pageMainOther->publish('Stage', 'Live');
|
|
||||||
|
|
||||||
$this->assertNotEquals($pageMain->URLSegment, $pageMainOther->URLSegment,
|
|
||||||
'Pages in same subsite cant share the same URL'
|
|
||||||
);
|
|
||||||
|
|
||||||
Subsite::changeSubsite($subsite1->ID);
|
|
||||||
|
|
||||||
$pageSubsite1 = new SiteTree();
|
|
||||||
$pageSubsite1->URLSegment = 'testpage';
|
|
||||||
$pageSubsite1->write();
|
|
||||||
$pageSubsite1->publish('Stage', 'Live');
|
|
||||||
|
|
||||||
$this->assertEquals($pageMain->URLSegment, $pageSubsite1->URLSegment,
|
|
||||||
'Pages in different subsites can share the same URL'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function testBasicSanity() {
|
|
||||||
$this->assertTrue(singleton('SiteTree')->getSiteConfig() instanceof SiteConfig);
|
|
||||||
// The following assert is breaking in Translatable.
|
|
||||||
$this->assertTrue(singleton('SiteTree')->getCMSFields() instanceof FieldList);
|
|
||||||
$this->assertTrue(singleton('SubsitesVirtualPage')->getCMSFields() instanceof FieldList);
|
|
||||||
$this->assertTrue(is_array(singleton('SiteTreeSubsites')->extraStatics()));
|
|
||||||
}
|
|
||||||
|
|
||||||
function testErrorPageLocations() {
|
|
||||||
$subsite1 = $this->objFromFixture('Subsite', 'domaintest1');
|
|
||||||
|
|
||||||
Subsite::changeSubsite($subsite1->ID);
|
|
||||||
$path = ErrorPage::get_filepath_for_errorcode(500);
|
|
||||||
|
|
||||||
$static_path = Config::inst()->get('ErrorPage', 'static_filepath');
|
|
||||||
$expected_path = $static_path . '/error-500-'.$subsite1->domain().'.html';
|
|
||||||
$this->assertEquals($expected_path, $path);
|
|
||||||
}
|
|
||||||
|
|
||||||
function testCanEditSiteTree() {
|
|
||||||
$admin = $this->objFromFixture('Member', 'admin');
|
|
||||||
$subsite1member = $this->objFromFixture('Member', 'subsite1member');
|
|
||||||
$subsite2member = $this->objFromFixture('Member', 'subsite2member');
|
|
||||||
$mainpage = $this->objFromFixture('Page', 'home');
|
|
||||||
$subsite1page = $this->objFromFixture('Page', 'subsite1_home');
|
|
||||||
$subsite2page = $this->objFromFixture('Page', 'subsite2_home');
|
|
||||||
$subsite1 = $this->objFromFixture('Subsite', 'subsite1');
|
|
||||||
$subsite2 = $this->objFromFixture('Subsite', 'subsite2');
|
|
||||||
|
|
||||||
// Cant pass member as arguments to canEdit() because of GroupSubsites
|
|
||||||
Session::set("loggedInAs", $admin->ID);
|
|
||||||
$this->assertTrue(
|
|
||||||
(bool)$subsite1page->canEdit(),
|
|
||||||
'Administrators can edit all subsites'
|
|
||||||
);
|
|
||||||
|
|
||||||
// @todo: Workaround because GroupSubsites->augmentSQL() is relying on session state
|
|
||||||
Subsite::changeSubsite($subsite1);
|
|
||||||
|
|
||||||
Session::set("loggedInAs", $subsite1member->ID);
|
|
||||||
$this->assertTrue(
|
|
||||||
(bool)$subsite1page->canEdit(),
|
|
||||||
'Members can edit pages on a subsite if they are in a group belonging to this subsite'
|
|
||||||
);
|
|
||||||
|
|
||||||
Session::set("loggedInAs", $subsite2member->ID);
|
|
||||||
$this->assertFalse(
|
|
||||||
(bool)$subsite1page->canEdit(),
|
|
||||||
'Members cant edit pages on a subsite if they are not in a group belonging to this subsite'
|
|
||||||
);
|
|
||||||
|
|
||||||
// @todo: Workaround because GroupSubsites->augmentSQL() is relying on session state
|
|
||||||
Subsite::changeSubsite(0);
|
|
||||||
$this->assertFalse(
|
|
||||||
$mainpage->canEdit(),
|
|
||||||
'Members cant edit pages on the main site if they are not in a group allowing this'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Similar to {@link SubsitesVirtualPageTest->testSubsiteVirtualPageCanHaveSameUrlsegmentAsOtherSubsite()}.
|
|
||||||
*/
|
|
||||||
function testTwoPagesWithSameURLOnDifferentSubsites() {
|
|
||||||
// Set up a couple of pages with the same URL on different subsites
|
|
||||||
$s1 = $this->objFromFixture('Subsite','domaintest1');
|
|
||||||
$s2 = $this->objFromFixture('Subsite','domaintest2');
|
|
||||||
|
|
||||||
$p1 = new SiteTree();
|
|
||||||
$p1->Title = $p1->URLSegment = "test-page";
|
|
||||||
$p1->SubsiteID = $s1->ID;
|
|
||||||
$p1->write();
|
|
||||||
|
|
||||||
$p2 = new SiteTree();
|
|
||||||
$p2->Title = $p1->URLSegment = "test-page";
|
|
||||||
$p2->SubsiteID = $s2->ID;
|
|
||||||
$p2->write();
|
|
||||||
|
|
||||||
// Check that the URLs weren't modified in our set-up
|
|
||||||
$this->assertEquals($p1->URLSegment, 'test-page');
|
|
||||||
$this->assertEquals($p2->URLSegment, 'test-page');
|
|
||||||
|
|
||||||
// Check that if we switch between the different subsites, we receive the correct pages
|
|
||||||
Subsite::changeSubsite($s1);
|
|
||||||
$this->assertEquals($p1->ID, SiteTree::get_by_link('test-page')->ID);
|
|
||||||
|
|
||||||
Subsite::changeSubsite($s2);
|
|
||||||
$this->assertEquals($p2->ID, SiteTree::get_by_link('test-page')->ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
function testPageTypesBlacklistInClassDropdown() {
|
|
||||||
Session::set("loggedInAs", null);
|
|
||||||
|
|
||||||
$s1 = $this->objFromFixture('Subsite','domaintest1');
|
|
||||||
$s2 = $this->objFromFixture('Subsite','domaintest2');
|
|
||||||
$page = singleton('SiteTree');
|
|
||||||
|
|
||||||
$s1->PageTypeBlacklist = 'SiteTreeSubsitesTest_ClassA,ErrorPage';
|
|
||||||
$s1->write();
|
|
||||||
|
|
||||||
Subsite::changeSubsite($s1);
|
|
||||||
$settingsFields = $page->getSettingsFields()->dataFieldByName('ClassName')->getSource();
|
|
||||||
$this->assertArrayNotHasKey('ErrorPage',
|
|
||||||
$settingsFields
|
|
||||||
);
|
|
||||||
$this->assertArrayNotHasKey('SiteTreeSubsitesTest_ClassA',
|
|
||||||
$settingsFields
|
|
||||||
);
|
|
||||||
$this->assertArrayHasKey('SiteTreeSubsitesTest_ClassB',
|
|
||||||
$settingsFields
|
|
||||||
);
|
|
||||||
|
|
||||||
Subsite::changeSubsite($s2);
|
|
||||||
$settingsFields = $page->getSettingsFields()->dataFieldByName('ClassName')->getSource();
|
|
||||||
$this->assertArrayHasKey('ErrorPage',
|
|
||||||
$settingsFields
|
|
||||||
);
|
|
||||||
$this->assertArrayHasKey('SiteTreeSubsitesTest_ClassA',
|
|
||||||
$settingsFields
|
|
||||||
);
|
|
||||||
$this->assertArrayHasKey('SiteTreeSubsitesTest_ClassB',
|
|
||||||
$settingsFields
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function testPageTypesBlacklistInCMSMain() {
|
|
||||||
Session::set("loggedInAs", null);
|
|
||||||
|
|
||||||
$cmsmain = new CMSMain();
|
|
||||||
|
|
||||||
$s1 = $this->objFromFixture('Subsite','domaintest1');
|
|
||||||
$s2 = $this->objFromFixture('Subsite','domaintest2');
|
|
||||||
|
|
||||||
$s1->PageTypeBlacklist = 'SiteTreeSubsitesTest_ClassA,ErrorPage';
|
|
||||||
$s1->write();
|
|
||||||
|
|
||||||
Subsite::changeSubsite($s1);
|
|
||||||
$classes = $cmsmain->PageTypes()->column('ClassName');
|
|
||||||
$this->assertNotContains('ErrorPage', $classes);
|
|
||||||
$this->assertNotContains('SiteTreeSubsitesTest_ClassA', $classes);
|
|
||||||
$this->assertContains('SiteTreeSubsitesTest_ClassB', $classes);
|
|
||||||
|
|
||||||
Subsite::changeSubsite($s2);
|
|
||||||
$classes = $cmsmain->PageTypes()->column("ClassName");
|
|
||||||
$this->assertContains('ErrorPage', $classes);
|
|
||||||
$this->assertContains('SiteTreeSubsitesTest_ClassA', $classes);
|
|
||||||
$this->assertContains('SiteTreeSubsitesTest_ClassB', $classes);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class SiteTreeSubsitesTest_ClassA extends SiteTree implements TestOnly {}
|
|
||||||
|
|
||||||
class SiteTreeSubsitesTest_ClassB extends SiteTree implements TestOnly {}
|
|
@ -1,143 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class SubsiteAdminFunctionalTest extends FunctionalTest {
|
|
||||||
static $fixture_file = 'subsites/tests/SubsiteTest.yml';
|
|
||||||
static $use_draft_site = true;
|
|
||||||
|
|
||||||
protected $autoFollowRedirection = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper: FunctionalTest is only able to follow redirection once, we want to go all the way.
|
|
||||||
*/
|
|
||||||
function getAndFollowAll($url) {
|
|
||||||
$response = $this->get($url);
|
|
||||||
while ($location = $response->getHeader('Location')) {
|
|
||||||
$response = $this->mainSession->followRedirection();
|
|
||||||
}
|
|
||||||
echo $response->getHeader('Location');
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Anonymous user cannot access anything.
|
|
||||||
*/
|
|
||||||
function testAnonymousIsForbiddenAdminAccess() {
|
|
||||||
$response = $this->getAndFollowAll('admin/pages/?SubsiteID=0');
|
|
||||||
$this->assertRegExp('#^Security/login.*#', $this->mainSession->lastUrl(), 'Admin is disallowed');
|
|
||||||
|
|
||||||
$subsite1 = $this->objFromFixture('Subsite', 'subsite1');
|
|
||||||
$response = $this->getAndFollowAll("admin/pages/?SubsiteID={$subsite1->ID}");
|
|
||||||
$this->assertRegExp('#^Security/login.*#', $this->mainSession->lastUrl(), 'Admin is disallowed');
|
|
||||||
|
|
||||||
$response = $this->getAndFollowAll('SubsiteXHRController');
|
|
||||||
$this->assertRegExp('#^Security/login.*#', $this->mainSession->lastUrl(),
|
|
||||||
'SubsiteXHRController is disallowed');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Admin should be able to access all subsites and the main site
|
|
||||||
*/
|
|
||||||
function testAdminCanAccessAllSubsites() {
|
|
||||||
$member = $this->objFromFixture('Member', 'admin');
|
|
||||||
Session::set("loggedInAs", $member->ID);
|
|
||||||
|
|
||||||
$this->getAndFollowAll('admin/pages/?SubsiteID=0');
|
|
||||||
$this->assertEquals(Subsite::currentSubsiteID(), '0', 'Can access main site.');
|
|
||||||
$this->assertRegExp('#^admin/pages.*#', $this->mainSession->lastUrl(), 'Lands on the correct section');
|
|
||||||
|
|
||||||
$subsite1 = $this->objFromFixture('Subsite', 'subsite1');
|
|
||||||
$this->getAndFollowAll("admin/pages/?SubsiteID={$subsite1->ID}");
|
|
||||||
$this->assertEquals(Subsite::currentSubsiteID(), $subsite1->ID, 'Can access other subsite.');
|
|
||||||
$this->assertRegExp('#^admin/pages.*#', $this->mainSession->lastUrl(), 'Lands on the correct section');
|
|
||||||
|
|
||||||
$response = $this->getAndFollowAll('SubsiteXHRController');
|
|
||||||
$this->assertNotRegExp('#^Security/login.*#', $this->mainSession->lastUrl(),
|
|
||||||
'SubsiteXHRController is reachable');
|
|
||||||
}
|
|
||||||
|
|
||||||
function testAdminIsRedirectedToObjectsSubsite() {
|
|
||||||
$member = $this->objFromFixture('Member', 'admin');
|
|
||||||
Session::set("loggedInAs", $member->ID);
|
|
||||||
|
|
||||||
$mainSubsitePage = $this->objFromFixture('Page', 'mainSubsitePage');
|
|
||||||
$subsite1Home = $this->objFromFixture('Page', 'subsite1_home');
|
|
||||||
|
|
||||||
Config::inst()->nest();
|
|
||||||
|
|
||||||
Config::inst()->update('CMSPageEditController', 'treats_subsite_0_as_global', false);
|
|
||||||
Subsite::changeSubsite(0);
|
|
||||||
$this->getAndFollowAll("admin/pages/edit/show/$subsite1Home->ID");
|
|
||||||
$this->assertEquals(Subsite::currentSubsiteID(), $subsite1Home->SubsiteID, 'Loading an object switches the subsite');
|
|
||||||
$this->assertRegExp("#^admin/pages.*#", $this->mainSession->lastUrl(), 'Lands on the correct section');
|
|
||||||
|
|
||||||
Config::inst()->update('CMSPageEditController', 'treats_subsite_0_as_global', true);
|
|
||||||
Subsite::changeSubsite(0);
|
|
||||||
$this->getAndFollowAll("admin/pages/edit/show/$subsite1Home->ID");
|
|
||||||
$this->assertEquals(Subsite::currentSubsiteID(), $subsite1Home->SubsiteID, 'Loading a non-main-site object still switches the subsite if configured with treats_subsite_0_as_global');
|
|
||||||
$this->assertRegExp("#^admin/pages.*#", $this->mainSession->lastUrl(), 'Lands on the correct section');
|
|
||||||
|
|
||||||
$this->getAndFollowAll("admin/pages/edit/show/$mainSubsitePage->ID");
|
|
||||||
$this->assertNotEquals(Subsite::currentSubsiteID(), $mainSubsitePage->SubsiteID, 'Loading a main-site object does not change the subsite if configured with treats_subsite_0_as_global');
|
|
||||||
$this->assertRegExp("#^admin/pages.*#", $this->mainSession->lastUrl(), 'Lands on the correct section');
|
|
||||||
|
|
||||||
Config::inst()->unnest();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User which has AccessAllSubsites set to 1 should be able to access all subsites and main site,
|
|
||||||
* even though he does not have the ADMIN permission.
|
|
||||||
*/
|
|
||||||
function testEditorCanAccessAllSubsites() {
|
|
||||||
$member = $this->objFromFixture('Member', 'editor');
|
|
||||||
Session::set("loggedInAs", $member->ID);
|
|
||||||
|
|
||||||
$this->getAndFollowAll('admin/pages/?SubsiteID=0');
|
|
||||||
$this->assertEquals(Subsite::currentSubsiteID(), '0', 'Can access main site.');
|
|
||||||
$this->assertRegExp('#^admin/pages.*#', $this->mainSession->lastUrl(), 'Lands on the correct section');
|
|
||||||
|
|
||||||
$subsite1 = $this->objFromFixture('Subsite', 'subsite1');
|
|
||||||
$this->getAndFollowAll("admin/pages/?SubsiteID={$subsite1->ID}");
|
|
||||||
$this->assertEquals(Subsite::currentSubsiteID(), $subsite1->ID, 'Can access other subsite.');
|
|
||||||
$this->assertRegExp('#^admin/pages.*#', $this->mainSession->lastUrl(), 'Lands on the correct section');
|
|
||||||
|
|
||||||
$response = $this->getAndFollowAll('SubsiteXHRController');
|
|
||||||
$this->assertNotRegExp('#^Security/login.*#', $this->mainSession->lastUrl(),
|
|
||||||
'SubsiteXHRController is reachable');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test a member who only has access to one subsite (subsite1) and only some sections (pages and security).
|
|
||||||
*/
|
|
||||||
function testSubsiteAdmin() {
|
|
||||||
$member = $this->objFromFixture('Member', 'subsite1member');
|
|
||||||
Session::set("loggedInAs", $member->ID);
|
|
||||||
|
|
||||||
$subsite1 = $this->objFromFixture('Subsite', 'subsite1');
|
|
||||||
|
|
||||||
// Check allowed URL.
|
|
||||||
$this->getAndFollowAll("admin/pages/?SubsiteID={$subsite1->ID}");
|
|
||||||
$this->assertEquals(Subsite::currentSubsiteID(), $subsite1->ID, 'Can access own subsite.');
|
|
||||||
$this->assertRegExp('#^admin/pages.*#', $this->mainSession->lastUrl(), 'Can access permitted section.');
|
|
||||||
|
|
||||||
// Check forbidden section in allowed subsite.
|
|
||||||
$this->getAndFollowAll("admin/assets/?SubsiteID={$subsite1->ID}");
|
|
||||||
$this->assertEquals(Subsite::currentSubsiteID(), $subsite1->ID, 'Is redirected within subsite.');
|
|
||||||
$this->assertNotRegExp('#^admin/assets/.*#', $this->mainSession->lastUrl(),
|
|
||||||
'Is redirected away from forbidden section');
|
|
||||||
|
|
||||||
// Check forbidden site, on a section that's allowed on another subsite
|
|
||||||
$this->getAndFollowAll("admin/pages/?SubsiteID=0");
|
|
||||||
$this->assertEquals(Subsite::currentSubsiteID(), $subsite1->ID, 'Is redirected to permitted subsite.');
|
|
||||||
|
|
||||||
// Check forbidden site, on a section that's not allowed on any other subsite
|
|
||||||
$this->getAndFollowAll("admin/assets/?SubsiteID=0");
|
|
||||||
$this->assertEquals(Subsite::currentSubsiteID(), $subsite1->ID, 'Is redirected to first permitted subsite.');
|
|
||||||
$this->assertNotRegExp('#^Security/login.*#', $this->mainSession->lastUrl(), 'Is not denied access');
|
|
||||||
|
|
||||||
// Check the standalone XHR controller.
|
|
||||||
$response = $this->getAndFollowAll('SubsiteXHRController');
|
|
||||||
$this->assertNotRegExp('#^Security/login.*#', $this->mainSession->lastUrl(),
|
|
||||||
'SubsiteXHRController is reachable');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class SubsiteAdminTest extends BaseSubsiteTest {
|
|
||||||
static $fixture_file = 'subsites/tests/SubsiteTest.yml';
|
|
||||||
|
|
||||||
function adminLoggedInSession() {
|
|
||||||
return new Session(array(
|
|
||||||
'loggedInAs' => $this->idFromFixture('Member', 'admin')
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test generation of the view
|
|
||||||
*/
|
|
||||||
function testBasicView() {
|
|
||||||
Subsite::$write_hostmap = false;
|
|
||||||
$subsite1ID = $this->objFromFixture('Subsite','domaintest1')->ID;
|
|
||||||
|
|
||||||
// Open the admin area logged in as admin
|
|
||||||
$response1 = Director::test('admin/subsites/', null, $this->adminLoggedInSession());
|
|
||||||
|
|
||||||
// Confirm that this URL gets you the entire page, with the edit form loaded
|
|
||||||
$response2 = Director::test("admin/subsites/Subsite/EditForm/field/Subsite/item/$subsite1ID/edit", null, $this->adminLoggedInSession());
|
|
||||||
$this->assertTrue(strpos($response2->getBody(), 'id="Form_ItemEditForm_ID"') !== false, "Testing Form_ItemEditForm_ID exists");
|
|
||||||
$this->assertTrue(strpos($response2->getBody(), '<head') !== false, "Testing <head> exists");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test that the main-site user with ADMIN permissions can access all subsites, regardless
|
|
||||||
* of whether he is in a subsite-specific group or not.
|
|
||||||
*/
|
|
||||||
function testMainsiteAdminCanAccessAllSubsites() {
|
|
||||||
$member = $this->objFromFixture('Member', 'admin');
|
|
||||||
Session::set("loggedInAs", $member->ID);
|
|
||||||
|
|
||||||
$cmsMain = new CMSMain();
|
|
||||||
foreach($cmsMain->Subsites() as $subsite) {
|
|
||||||
$ids[$subsite->ID] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->assertArrayHasKey(0, $ids, "Main site accessible");
|
|
||||||
$this->assertArrayHasKey($this->idFromFixture('Subsite','main'), $ids, "Site with no groups inaccesible");
|
|
||||||
$this->assertArrayHasKey($this->idFromFixture('Subsite','subsite1'), $ids, "Subsite1 Template inaccessible");
|
|
||||||
$this->assertArrayHasKey($this->idFromFixture('Subsite','subsite2'), $ids, "Subsite2 Template inaccessible");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,362 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class SubsiteTest extends BaseSubsiteTest {
|
|
||||||
|
|
||||||
static $fixture_file = 'subsites/tests/SubsiteTest.yml';
|
|
||||||
|
|
||||||
function setUp() {
|
|
||||||
parent::setUp();
|
|
||||||
|
|
||||||
$this->origStrictSubdomainMatching = Subsite::$strict_subdomain_matching;
|
|
||||||
Subsite::$strict_subdomain_matching = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function tearDown() {
|
|
||||||
parent::tearDown();
|
|
||||||
|
|
||||||
Subsite::$strict_subdomain_matching = $this->origStrictSubdomainMatching;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new subsite from the template and verify that all the template's pages are copied
|
|
||||||
*/
|
|
||||||
function testSubsiteCreation() {
|
|
||||||
Subsite::$write_hostmap = false;
|
|
||||||
|
|
||||||
// Create the instance
|
|
||||||
$template = $this->objFromFixture('Subsite', 'main');
|
|
||||||
|
|
||||||
// Test that changeSubsite is working
|
|
||||||
Subsite::changeSubsite($template->ID);
|
|
||||||
$tmplStaff = $this->objFromFixture('Page','staff');
|
|
||||||
$tmplHome = DataObject::get_one('Page', "\"URLSegment\" = 'home'");
|
|
||||||
|
|
||||||
// Publish all the pages in the template, testing that DataObject::get only returns pages from the chosen subsite
|
|
||||||
$pages = DataObject::get("SiteTree");
|
|
||||||
$totalPages = $pages->Count();
|
|
||||||
foreach($pages as $page) {
|
|
||||||
$this->assertEquals($template->ID, $page->SubsiteID);
|
|
||||||
$page->publish('Stage', 'Live');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new site
|
|
||||||
$subsite = $template->duplicate();
|
|
||||||
|
|
||||||
// Check title
|
|
||||||
$this->assertEquals($subsite->Title, $template->Title);
|
|
||||||
|
|
||||||
// Another test that changeSubsite is working
|
|
||||||
$subsite->activate();
|
|
||||||
|
|
||||||
$siteHome = DataObject::get_one('Page', "\"URLSegment\" = 'home'");
|
|
||||||
$this->assertNotEquals($siteHome, false, 'Home Page for subsite not found');
|
|
||||||
$this->assertEquals($subsite->ID, $siteHome->SubsiteID,
|
|
||||||
'createInstance() copies existing pages retaining the same URLSegment'
|
|
||||||
);
|
|
||||||
|
|
||||||
Subsite::changeSubsite(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Confirm that domain lookup is working
|
|
||||||
*/
|
|
||||||
function testDomainLookup() {
|
|
||||||
// Clear existing fixtures
|
|
||||||
foreach(DataObject::get('Subsite') as $subsite) $subsite->delete();
|
|
||||||
foreach(DataObject::get('SubsiteDomain') as $domain) $domain->delete();
|
|
||||||
|
|
||||||
// Much more expressive than YML in this case
|
|
||||||
$subsite1 = $this->createSubsiteWithDomains(array(
|
|
||||||
'one.example.org' => true,
|
|
||||||
'one.*' => false,
|
|
||||||
));
|
|
||||||
$subsite2 = $this->createSubsiteWithDomains(array(
|
|
||||||
'two.mysite.com' => true,
|
|
||||||
'*.mysite.com' => false,
|
|
||||||
'subdomain.onmultiplesubsites.com' => false,
|
|
||||||
));
|
|
||||||
$subsite3 = $this->createSubsiteWithDomains(array(
|
|
||||||
'three.*' => true, // wildcards in primary domain are not recommended
|
|
||||||
'subdomain.unique.com' => false,
|
|
||||||
'*.onmultiplesubsites.com' => false,
|
|
||||||
));
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
$subsite3->ID,
|
|
||||||
Subsite::getSubsiteIDForDomain('subdomain.unique.com'),
|
|
||||||
'Full unique match'
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
$subsite1->ID,
|
|
||||||
Subsite::getSubsiteIDForDomain('one.example.org'),
|
|
||||||
'Full match, doesn\'t complain about multiple matches within a single subsite'
|
|
||||||
);
|
|
||||||
|
|
||||||
$failed = false;
|
|
||||||
try {
|
|
||||||
Subsite::getSubsiteIDForDomain('subdomain.onmultiplesubsites.com');
|
|
||||||
} catch(UnexpectedValueException $e) {
|
|
||||||
$failed = true;
|
|
||||||
}
|
|
||||||
$this->assertTrue(
|
|
||||||
$failed,
|
|
||||||
'Fails on multiple matches with wildcard vs. www across multiple subsites'
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
$subsite1->ID,
|
|
||||||
Subsite::getSubsiteIDForDomain('one.unique.com'),
|
|
||||||
'Fuzzy match suffixed with wildcard (rule "one.*")'
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
$subsite2->ID,
|
|
||||||
Subsite::getSubsiteIDForDomain('two.mysite.com'),
|
|
||||||
'Matches correct subsite for rule'
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
$subsite2->ID,
|
|
||||||
Subsite::getSubsiteIDForDomain('other.mysite.com'),
|
|
||||||
'Fuzzy match prefixed with wildcard (rule "*.mysite.com")'
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
0,
|
|
||||||
Subsite::getSubsiteIDForDomain('unknown.madeup.com'),
|
|
||||||
"Doesn't match unknown subsite"
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function testStrictSubdomainMatching() {
|
|
||||||
// Clear existing fixtures
|
|
||||||
foreach(DataObject::get('Subsite') as $subsite) $subsite->delete();
|
|
||||||
foreach(DataObject::get('SubsiteDomain') as $domain) $domain->delete();
|
|
||||||
|
|
||||||
// Much more expressive than YML in this case
|
|
||||||
$subsite1 = $this->createSubsiteWithDomains(array(
|
|
||||||
'example.org' => true,
|
|
||||||
'example.com' => false,
|
|
||||||
'*.wildcard.com' => false,
|
|
||||||
));
|
|
||||||
$subsite2 = $this->createSubsiteWithDomains(array(
|
|
||||||
'www.example.org' => true,
|
|
||||||
'www.wildcard.com' => false,
|
|
||||||
));
|
|
||||||
|
|
||||||
Subsite::$strict_subdomain_matching = false;
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
$subsite1->ID,
|
|
||||||
Subsite::getSubsiteIDForDomain('example.org'),
|
|
||||||
'Exact matches without strict checking when not using www prefix'
|
|
||||||
);
|
|
||||||
$this->assertEquals(
|
|
||||||
$subsite1->ID,
|
|
||||||
Subsite::getSubsiteIDForDomain('www.example.org'),
|
|
||||||
'Matches without strict checking when using www prefix, still matching first domain regardless of www prefix (falling back to subsite primary key ordering)'
|
|
||||||
);
|
|
||||||
$this->assertEquals(
|
|
||||||
$subsite1->ID,
|
|
||||||
Subsite::getSubsiteIDForDomain('www.example.com'),
|
|
||||||
'Fuzzy matches without strict checking with www prefix'
|
|
||||||
);
|
|
||||||
$this->assertEquals(
|
|
||||||
0,
|
|
||||||
Subsite::getSubsiteIDForDomain('www.wildcard.com'),
|
|
||||||
'Doesn\'t match www prefix without strict check, even if a wildcard subdomain is in place'
|
|
||||||
);
|
|
||||||
|
|
||||||
Subsite::$strict_subdomain_matching = true;
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
$subsite1->ID,
|
|
||||||
Subsite::getSubsiteIDForDomain('example.org'),
|
|
||||||
'Matches with strict checking when not using www prefix'
|
|
||||||
);
|
|
||||||
$this->assertEquals(
|
|
||||||
$subsite2->ID, // not 1
|
|
||||||
Subsite::getSubsiteIDForDomain('www.example.org'),
|
|
||||||
'Matches with strict checking when using www prefix'
|
|
||||||
);
|
|
||||||
$this->assertEquals(
|
|
||||||
0,
|
|
||||||
Subsite::getSubsiteIDForDomain('www.example.com'),
|
|
||||||
'Doesn\'t fuzzy match with strict checking when using www prefix'
|
|
||||||
);
|
|
||||||
$failed = false;
|
|
||||||
try {
|
|
||||||
Subsite::getSubsiteIDForDomain('www.wildcard.com');
|
|
||||||
} catch(UnexpectedValueException $e) {
|
|
||||||
$failed = true;
|
|
||||||
}
|
|
||||||
$this->assertTrue(
|
|
||||||
$failed,
|
|
||||||
'Fails on multiple matches with strict checking and wildcard vs. www'
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function createSubsiteWithDomains($domains) {
|
|
||||||
$subsite = new Subsite(array(
|
|
||||||
'Title' => 'My Subsite'
|
|
||||||
));
|
|
||||||
$subsite->write();
|
|
||||||
foreach($domains as $domainStr => $isPrimary) {
|
|
||||||
$domain = new SubsiteDomain(array(
|
|
||||||
'Domain' => $domainStr,
|
|
||||||
'IsPrimary' => $isPrimary,
|
|
||||||
'SubsiteID' => $subsite->ID
|
|
||||||
));
|
|
||||||
$domain->write();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $subsite;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test the Subsite->domain() method
|
|
||||||
*/
|
|
||||||
function testDefaultDomain() {
|
|
||||||
$this->assertEquals('one.example.org',
|
|
||||||
$this->objFromFixture('Subsite','domaintest1')->domain());
|
|
||||||
|
|
||||||
$this->assertEquals('two.mysite.com',
|
|
||||||
$this->objFromFixture('Subsite','domaintest2')->domain());
|
|
||||||
|
|
||||||
$originalHTTPHost = $_SERVER['HTTP_HOST'];
|
|
||||||
|
|
||||||
$_SERVER['HTTP_HOST'] = "www.example.org";
|
|
||||||
$this->assertEquals('three.example.org',
|
|
||||||
$this->objFromFixture('Subsite','domaintest3')->domain());
|
|
||||||
|
|
||||||
$_SERVER['HTTP_HOST'] = "mysite.example.org";
|
|
||||||
$this->assertEquals('three.mysite.example.org',
|
|
||||||
$this->objFromFixture('Subsite','domaintest3')->domain());
|
|
||||||
|
|
||||||
$this->assertEquals($_SERVER['HTTP_HOST'], singleton('Subsite')->PrimaryDomain);
|
|
||||||
$this->assertEquals('http://'.$_SERVER['HTTP_HOST'].Director::baseURL(), singleton('Subsite')->absoluteBaseURL());
|
|
||||||
|
|
||||||
$_SERVER['HTTP_HOST'] = $originalHTTPHost;
|
|
||||||
}
|
|
||||||
|
|
||||||
function testAllSites() {
|
|
||||||
$subsites = Subsite::all_sites();
|
|
||||||
$this->assertDOSEquals(array(
|
|
||||||
array('Title' =>'Main site'),
|
|
||||||
array('Title' =>'Template'),
|
|
||||||
array('Title' =>'Subsite1 Template'),
|
|
||||||
array('Title' =>'Subsite2 Template'),
|
|
||||||
array('Title' =>'Test 1'),
|
|
||||||
array('Title' =>'Test 2'),
|
|
||||||
array('Title' =>'Test 3')
|
|
||||||
), $subsites, 'Lists all subsites');
|
|
||||||
}
|
|
||||||
|
|
||||||
function testAllAccessibleSites() {
|
|
||||||
$member = $this->objFromFixture('Member', 'subsite1member');
|
|
||||||
|
|
||||||
$subsites = Subsite::all_accessible_sites(true, 'Main site', $member);
|
|
||||||
$this->assertDOSEquals(array(
|
|
||||||
array('Title' =>'Subsite1 Template')
|
|
||||||
), $subsites, 'Lists member-accessible sites.');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test Subsite::accessible_sites()
|
|
||||||
*/
|
|
||||||
function testAccessibleSites() {
|
|
||||||
$member1Sites = Subsite::accessible_sites("CMS_ACCESS_CMSMain", false, null,
|
|
||||||
$this->objFromFixture('Member', 'subsite1member'));
|
|
||||||
$member1SiteTitles = $member1Sites->column("Title");
|
|
||||||
sort($member1SiteTitles);
|
|
||||||
$this->assertEquals('Subsite1 Template', $member1SiteTitles[0], 'Member can get to a subsite via a group');
|
|
||||||
|
|
||||||
$adminSites = Subsite::accessible_sites("CMS_ACCESS_CMSMain", false, null,
|
|
||||||
$this->objFromFixture('Member', 'admin'));
|
|
||||||
$adminSiteTitles = $adminSites->column("Title");
|
|
||||||
sort($adminSiteTitles);
|
|
||||||
$this->assertEquals(array(
|
|
||||||
'Subsite1 Template',
|
|
||||||
'Subsite2 Template',
|
|
||||||
'Template',
|
|
||||||
'Test 1',
|
|
||||||
'Test 2',
|
|
||||||
'Test 3',
|
|
||||||
), $adminSiteTitles);
|
|
||||||
|
|
||||||
$member2Sites = Subsite::accessible_sites("CMS_ACCESS_CMSMain", false, null,
|
|
||||||
$this->objFromFixture('Member', 'subsite1member2'));
|
|
||||||
$member2SiteTitles = $member2Sites->column("Title");
|
|
||||||
sort($member2SiteTitles);
|
|
||||||
$this->assertEquals('Subsite1 Template', $member2SiteTitles[0], 'Member can get to subsite via a group role');
|
|
||||||
}
|
|
||||||
|
|
||||||
function testhasMainSitePermission() {
|
|
||||||
$admin = $this->objFromFixture('Member', 'admin');
|
|
||||||
$subsite1member = $this->objFromFixture('Member', 'subsite1member');
|
|
||||||
$subsite1admin = $this->objFromFixture('Member', 'subsite1admin');
|
|
||||||
$allsubsitesauthor = $this->objFromFixture('Member', 'allsubsitesauthor');
|
|
||||||
|
|
||||||
$this->assertTrue(
|
|
||||||
Subsite::hasMainSitePermission($admin),
|
|
||||||
'Default permissions granted for super-admin'
|
|
||||||
);
|
|
||||||
$this->assertTrue(
|
|
||||||
Subsite::hasMainSitePermission($admin, array("ADMIN")),
|
|
||||||
'ADMIN permissions granted for super-admin'
|
|
||||||
);
|
|
||||||
$this->assertFalse(
|
|
||||||
Subsite::hasMainSitePermission($subsite1admin, array("ADMIN")),
|
|
||||||
'ADMIN permissions (on main site) denied for subsite1 admin'
|
|
||||||
);
|
|
||||||
$this->assertFalse(
|
|
||||||
Subsite::hasMainSitePermission($subsite1admin, array("CMS_ACCESS_CMSMain")),
|
|
||||||
'CMS_ACCESS_CMSMain (on main site) denied for subsite1 admin'
|
|
||||||
);
|
|
||||||
$this->assertFalse(
|
|
||||||
Subsite::hasMainSitePermission($allsubsitesauthor, array("ADMIN")),
|
|
||||||
'ADMIN permissions (on main site) denied for CMS author with edit rights on all subsites'
|
|
||||||
);
|
|
||||||
$this->assertTrue(
|
|
||||||
Subsite::hasMainSitePermission($allsubsitesauthor, array("CMS_ACCESS_CMSMain")),
|
|
||||||
'CMS_ACCESS_CMSMain (on main site) granted for CMS author with edit rights on all subsites'
|
|
||||||
);
|
|
||||||
$this->assertFalse(
|
|
||||||
Subsite::hasMainSitePermission($subsite1member, array("ADMIN")),
|
|
||||||
'ADMIN (on main site) denied for subsite1 subsite1 cms author'
|
|
||||||
);
|
|
||||||
$this->assertFalse(
|
|
||||||
Subsite::hasMainSitePermission($subsite1member, array("CMS_ACCESS_CMSMain")),
|
|
||||||
'CMS_ACCESS_CMSMain (on main site) denied for subsite1 cms author'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function testDuplicateSubsite() {
|
|
||||||
// get subsite1 & create page
|
|
||||||
$subsite1 = $this->objFromFixture('Subsite','domaintest1');
|
|
||||||
$subsite1->activate();
|
|
||||||
$page1 = new Page();
|
|
||||||
$page1->Title = 'MyAwesomePage';
|
|
||||||
$page1->write();
|
|
||||||
$page1->doPublish();
|
|
||||||
$this->assertEquals($page1->SubsiteID, $subsite1->ID);
|
|
||||||
|
|
||||||
// duplicate
|
|
||||||
$subsite2 = $subsite1->duplicate();
|
|
||||||
$subsite2->activate();
|
|
||||||
// change content on dupe
|
|
||||||
$page2 = DataObject::get_one('Page', "\"Title\" = 'MyAwesomePage'");
|
|
||||||
$page2->Title = 'MyNewAwesomePage';
|
|
||||||
$page2->write();
|
|
||||||
$page2->doPublish();
|
|
||||||
|
|
||||||
// check change & check change has not affected subiste1
|
|
||||||
$subsite1->activate();
|
|
||||||
$this->assertEquals('MyAwesomePage', DataObject::get_by_id('Page', $page1->ID)->Title);
|
|
||||||
$subsite2->activate();
|
|
||||||
$this->assertEquals('MyNewAwesomePage', DataObject::get_by_id('Page', $page2->ID)->Title);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,180 +0,0 @@
|
|||||||
Subsite:
|
|
||||||
main:
|
|
||||||
Title: Template
|
|
||||||
subsite1:
|
|
||||||
Title: Subsite1 Template
|
|
||||||
subsite2:
|
|
||||||
Title: Subsite2 Template
|
|
||||||
domaintest1:
|
|
||||||
Title: Test 1
|
|
||||||
domaintest2:
|
|
||||||
Title: Test 2
|
|
||||||
domaintest3:
|
|
||||||
Title: Test 3
|
|
||||||
SubsiteDomain:
|
|
||||||
subsite1:
|
|
||||||
SubsiteID: =>Subsite.subsite1
|
|
||||||
Domain: subsite1.*
|
|
||||||
subsite2:
|
|
||||||
SubsiteID: =>Subsite.subsite2
|
|
||||||
Domain: subsite2.*
|
|
||||||
dt1a:
|
|
||||||
SubsiteID: =>Subsite.domaintest1
|
|
||||||
Domain: one.example.org
|
|
||||||
IsPrimary: 1
|
|
||||||
dt1b:
|
|
||||||
SubsiteID: =>Subsite.domaintest1
|
|
||||||
Domain: one.*
|
|
||||||
dt2a:
|
|
||||||
SubsiteID: =>Subsite.domaintest2
|
|
||||||
Domain: two.mysite.com
|
|
||||||
IsPrimary: 1
|
|
||||||
dt2b:
|
|
||||||
SubsiteID: =>Subsite.domaintest2
|
|
||||||
Domain: *.mysite.com
|
|
||||||
dt3:
|
|
||||||
SubsiteID: =>Subsite.domaintest3
|
|
||||||
Domain: three.*
|
|
||||||
IsPrimary: 1
|
|
||||||
Page:
|
|
||||||
mainSubsitePage:
|
|
||||||
Title: MainSubsitePage
|
|
||||||
SubsiteID: 0
|
|
||||||
home:
|
|
||||||
Title: Home
|
|
||||||
SubsiteID: =>Subsite.main
|
|
||||||
about:
|
|
||||||
Title: About
|
|
||||||
SubsiteID: =>Subsite.main
|
|
||||||
linky:
|
|
||||||
Title: Linky
|
|
||||||
SubsiteID: =>Subsite.main
|
|
||||||
staff:
|
|
||||||
Title: Staff
|
|
||||||
ParentID: =>Page.about
|
|
||||||
SubsiteID: =>Subsite.main
|
|
||||||
contact:
|
|
||||||
Title: Contact Us
|
|
||||||
SubsiteID: =>Subsite.main
|
|
||||||
importantpage:
|
|
||||||
Title: Important Page
|
|
||||||
SubsiteID: =>Subsite.main
|
|
||||||
subsite1_home:
|
|
||||||
Title: Home (Subsite 1)
|
|
||||||
SubsiteID: =>Subsite.subsite1
|
|
||||||
subsite1_contactus:
|
|
||||||
Title: Contact Us (Subsite 1)
|
|
||||||
SubsiteID: =>Subsite.subsite1
|
|
||||||
subsite1_staff:
|
|
||||||
Title: Staff
|
|
||||||
SubsiteID: =>Subsite.subsite1
|
|
||||||
subsite2_home:
|
|
||||||
Title: Home (Subsite 2)
|
|
||||||
SubsiteID: =>Subsite.subsite2
|
|
||||||
subsite2_contactus:
|
|
||||||
Title: Contact Us (Subsite 2)
|
|
||||||
SubsiteID: =>Subsite.subsite2
|
|
||||||
|
|
||||||
PermissionRoleCode:
|
|
||||||
roleCode1:
|
|
||||||
Code: CMS_ACCESS_CMSMain
|
|
||||||
PermissionRole:
|
|
||||||
role1:
|
|
||||||
Title: role1
|
|
||||||
Codes: =>PermissionRoleCode.roleCode1
|
|
||||||
Group:
|
|
||||||
admin:
|
|
||||||
Title: Admin
|
|
||||||
Code: admin
|
|
||||||
AccessAllSubsites: 1
|
|
||||||
editor:
|
|
||||||
Title: Editor
|
|
||||||
Code: editor
|
|
||||||
AccessAllSubsites: 1
|
|
||||||
subsite1_group:
|
|
||||||
Title: subsite1_group
|
|
||||||
Code: subsite1_group
|
|
||||||
AccessAllSubsites: 0
|
|
||||||
Subsites: =>Subsite.subsite1
|
|
||||||
subsite2_group:
|
|
||||||
Title: subsite2_group
|
|
||||||
Code: subsite2_group
|
|
||||||
AccessAllSubsites: 0
|
|
||||||
Subsites: =>Subsite.subsite2
|
|
||||||
subsite1admins:
|
|
||||||
Title: subsite1admins
|
|
||||||
Code: subsite1admins
|
|
||||||
AccessAllSubsites: 0
|
|
||||||
Subsites: =>Subsite.subsite1
|
|
||||||
allsubsitesauthors:
|
|
||||||
Title: allsubsitesauthors
|
|
||||||
Code: allsubsitesauthors
|
|
||||||
AccessAllSubsites: 1
|
|
||||||
subsite1_group_via_role:
|
|
||||||
Title: subsite1_group_via_role
|
|
||||||
Code: subsite1_group_via_role
|
|
||||||
AccessAllSubsites: 1
|
|
||||||
Roles: =>PermissionRole.role1
|
|
||||||
Permission:
|
|
||||||
admin:
|
|
||||||
Code: ADMIN
|
|
||||||
GroupID: =>Group.admin
|
|
||||||
editor1:
|
|
||||||
Code: CMS_ACCESS_CMSMain
|
|
||||||
GroupID: =>Group.editor
|
|
||||||
editor2:
|
|
||||||
Code: SITETREE_VIEW_ALL
|
|
||||||
GroupID: =>Group.editor
|
|
||||||
editor3:
|
|
||||||
Code: VIEW_DRAFT_CONTENT
|
|
||||||
GroupID: =>Group.editor
|
|
||||||
accesscmsmain1:
|
|
||||||
Code: CMS_ACCESS_CMSMain
|
|
||||||
GroupID: =>Group.subsite1_group
|
|
||||||
accesscmsmain2:
|
|
||||||
Code: CMS_ACCESS_CMSMain
|
|
||||||
GroupID: =>Group.subsite2_group
|
|
||||||
accesscmsmain3:
|
|
||||||
Code: CMS_ACCESS_CMSMain
|
|
||||||
GroupID: =>Group.subsite1admins
|
|
||||||
accesscmsmain4:
|
|
||||||
Code: CMS_ACCESS_CMSMain
|
|
||||||
GroupID: =>Group.allsubsitesauthors
|
|
||||||
securityaccess1:
|
|
||||||
Code: CMS_ACCESS_SecurityAdmin
|
|
||||||
GroupID: =>Group.subsite1_group
|
|
||||||
securityaccess2:
|
|
||||||
Code: CMS_ACCESS_SecurityAdmin
|
|
||||||
GroupID: =>Group.subsite2_group
|
|
||||||
adminsubsite1:
|
|
||||||
Code: ADMIN
|
|
||||||
GroupID: =>Group.subsite1admins
|
|
||||||
|
|
||||||
Member:
|
|
||||||
admin:
|
|
||||||
FirstName: Admin
|
|
||||||
Surname: User
|
|
||||||
Email: admin@test.com
|
|
||||||
Password: rangi
|
|
||||||
Groups: =>Group.admin
|
|
||||||
editor:
|
|
||||||
FirstName: Editor
|
|
||||||
Surname: User
|
|
||||||
Email: editor@test.com
|
|
||||||
Password: rangi
|
|
||||||
Groups: =>Group.editor
|
|
||||||
subsite1member:
|
|
||||||
Email: subsite1member@test.com
|
|
||||||
Groups: =>Group.subsite1_group
|
|
||||||
subsite2member:
|
|
||||||
Email: subsite2member@test.com
|
|
||||||
Groups: =>Group.subsite2_group
|
|
||||||
subsite1admin:
|
|
||||||
Email: subsite1admin@test.com
|
|
||||||
Groups: =>Group.subsite1admins
|
|
||||||
allsubsitesauthor:
|
|
||||||
Email: allsubsitesauthor@test.com
|
|
||||||
Groups: =>Group.allsubsitesauthors
|
|
||||||
subsite1member2:
|
|
||||||
Email: subsite1member2@test.com
|
|
||||||
Groups: =>Group.subsite1_group_via_role
|
|
@ -1,275 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class SubsitesVirtualPageTest extends BaseSubsiteTest {
|
|
||||||
static $fixture_file = array(
|
|
||||||
'subsites/tests/SubsiteTest.yml',
|
|
||||||
'subsites/tests/SubsitesVirtualPageTest.yml',
|
|
||||||
);
|
|
||||||
|
|
||||||
function setUp() {
|
|
||||||
parent::setUp();
|
|
||||||
$this->logInWithPermission('ADMIN');
|
|
||||||
|
|
||||||
$fh = fopen(Director::baseFolder() . '/assets/testscript-test-file.pdf', "w");
|
|
||||||
fwrite($fh, str_repeat('x',1000000));
|
|
||||||
fclose($fh);
|
|
||||||
}
|
|
||||||
|
|
||||||
function tearDown() {
|
|
||||||
parent::tearDown();
|
|
||||||
$testFiles = array(
|
|
||||||
'/assets/testscript-test-file.pdf',
|
|
||||||
'/assets/renamed-test-file.pdf',
|
|
||||||
'/assets/renamed-test-file-second-time.pdf',
|
|
||||||
);
|
|
||||||
foreach($testFiles as $file) {
|
|
||||||
if(file_exists(Director::baseFolder().$file)) unlink(Director::baseFolder().$file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to bring main:linky to subsite2:linky
|
|
||||||
function testVirtualPageFromAnotherSubsite() {
|
|
||||||
Subsite::$write_hostmap = false;
|
|
||||||
|
|
||||||
$subsite = $this->objFromFixture('Subsite', 'subsite2');
|
|
||||||
|
|
||||||
Subsite::changeSubsite($subsite->ID);
|
|
||||||
Subsite::$disable_subsite_filter = false;
|
|
||||||
|
|
||||||
$linky = $this->objFromFixture('Page', 'linky');
|
|
||||||
|
|
||||||
$svp = new SubsitesVirtualPage();
|
|
||||||
$svp->CopyContentFromID = $linky->ID;
|
|
||||||
$svp->SubsiteID = $subsite->ID;
|
|
||||||
$svp->URLSegment = 'linky';
|
|
||||||
|
|
||||||
$svp->write();
|
|
||||||
|
|
||||||
$this->assertEquals($svp->SubsiteID, $subsite->ID);
|
|
||||||
$this->assertEquals($svp->Title, $linky->Title);
|
|
||||||
}
|
|
||||||
|
|
||||||
function testFileLinkRewritingOnVirtualPages() {
|
|
||||||
// File setup
|
|
||||||
$this->logInWithPermission('ADMIN');
|
|
||||||
touch(Director::baseFolder() . '/assets/testscript-test-file.pdf');
|
|
||||||
|
|
||||||
// Publish the source page
|
|
||||||
$page = $this->objFromFixture('SiteTree', 'page1');
|
|
||||||
$this->assertTrue($page->doPublish());
|
|
||||||
|
|
||||||
// Create a virtual page from it, and publish that
|
|
||||||
$svp = new SubsitesVirtualPage();
|
|
||||||
$svp->CopyContentFromID = $page->ID;
|
|
||||||
$svp->write();
|
|
||||||
$svp->doPublish();
|
|
||||||
|
|
||||||
// Rename the file
|
|
||||||
$file = $this->objFromFixture('File', 'file1');
|
|
||||||
$file->Name = 'renamed-test-file.pdf';
|
|
||||||
$file->write();
|
|
||||||
|
|
||||||
// Verify that the draft and publish virtual pages both have the corrected link
|
|
||||||
$this->assertContains('<img src="assets/renamed-test-file.pdf"',
|
|
||||||
DB::query("SELECT \"Content\" FROM \"SiteTree\" WHERE \"ID\" = $svp->ID")->value());
|
|
||||||
$this->assertContains('<img src="assets/renamed-test-file.pdf"',
|
|
||||||
DB::query("SELECT \"Content\" FROM \"SiteTree_Live\" WHERE \"ID\" = $svp->ID")->value());
|
|
||||||
|
|
||||||
// File teardown
|
|
||||||
$testFiles = array(
|
|
||||||
'/assets/testscript-test-file.pdf',
|
|
||||||
'/assets/renamed-test-file.pdf',
|
|
||||||
);
|
|
||||||
foreach($testFiles as $file) {
|
|
||||||
if(file_exists(Director::baseFolder().$file)) unlink(Director::baseFolder().$file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function testSubsiteVirtualPagesArentInappropriatelyPublished() {
|
|
||||||
// Fixture
|
|
||||||
$p = new Page();
|
|
||||||
$p->Content = "test content";
|
|
||||||
$p->write();
|
|
||||||
$vp = new SubsitesVirtualPage();
|
|
||||||
$vp->CopyContentFromID = $p->ID;
|
|
||||||
$vp->write();
|
|
||||||
|
|
||||||
// VP is oragne
|
|
||||||
$this->assertTrue($vp->IsAddedToStage);
|
|
||||||
|
|
||||||
// VP is still orange after we publish
|
|
||||||
$p->doPublish();
|
|
||||||
$this->fixVersionNumberCache($vp);
|
|
||||||
$this->assertTrue($vp->IsAddedToStage);
|
|
||||||
|
|
||||||
// A new VP created after P's initial construction
|
|
||||||
$vp2 = new SubsitesVirtualPage();
|
|
||||||
$vp2->CopyContentFromID = $p->ID;
|
|
||||||
$vp2->write();
|
|
||||||
$this->assertTrue($vp2->IsAddedToStage);
|
|
||||||
|
|
||||||
// Also remains orange after a republish
|
|
||||||
$p->Content = "new content";
|
|
||||||
$p->write();
|
|
||||||
$p->doPublish();
|
|
||||||
$this->fixVersionNumberCache($vp2);
|
|
||||||
$this->assertTrue($vp2->IsAddedToStage);
|
|
||||||
|
|
||||||
// VP is now published
|
|
||||||
$vp->doPublish();
|
|
||||||
|
|
||||||
$this->fixVersionNumberCache($vp);
|
|
||||||
$this->assertTrue($vp->ExistsOnLive);
|
|
||||||
$this->assertFalse($vp->IsModifiedOnStage);
|
|
||||||
|
|
||||||
// P edited, VP and P both go green
|
|
||||||
$p->Content = "third content";
|
|
||||||
$p->write();
|
|
||||||
|
|
||||||
$this->fixVersionNumberCache($vp, $p);
|
|
||||||
$this->assertTrue($p->IsModifiedOnStage);
|
|
||||||
$this->assertTrue($vp->IsModifiedOnStage);
|
|
||||||
|
|
||||||
// Publish, VP goes black
|
|
||||||
$p->doPublish();
|
|
||||||
$this->fixVersionNumberCache($vp);
|
|
||||||
$this->assertTrue($vp->ExistsOnLive);
|
|
||||||
$this->assertFalse($vp->IsModifiedOnStage);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This test ensures published Subsites Virtual Pages immediately reflect updates
|
|
||||||
* to their published target pages. Note - this has to happen when the virtual page
|
|
||||||
* is in a different subsite to the page you are editing and republishing,
|
|
||||||
* otherwise the test will pass falsely due to current subsite ID being the same.
|
|
||||||
*/
|
|
||||||
function testPublishedSubsiteVirtualPagesUpdateIfTargetPageUpdates()
|
|
||||||
{
|
|
||||||
// create page
|
|
||||||
$p = new Page();
|
|
||||||
$p->Content = 'Content';
|
|
||||||
$p->Title = 'Title';
|
|
||||||
$p->writeToStage('Stage');
|
|
||||||
$p->publish('Stage', 'Live');
|
|
||||||
$this->assertTrue($p->ExistsOnLive);
|
|
||||||
|
|
||||||
// change to subsite
|
|
||||||
$subsite = $this->objFromFixture('Subsite', 'subsite2');
|
|
||||||
Subsite::changeSubsite($subsite->ID);
|
|
||||||
Subsite::$disable_subsite_filter = false;
|
|
||||||
|
|
||||||
// create svp in subsite
|
|
||||||
$svp = new SubsitesVirtualPage();
|
|
||||||
$svp->CopyContentFromID = $p->ID;
|
|
||||||
$svp->writeToStage('Stage');
|
|
||||||
$svp->publish('Stage', 'Live');
|
|
||||||
$this->assertEquals($svp->SubsiteID, $subsite->ID);
|
|
||||||
$this->assertTrue($svp->ExistsOnLive);
|
|
||||||
|
|
||||||
// change back to original subsite ("Main site")
|
|
||||||
Subsite::changeSubsite(0);
|
|
||||||
|
|
||||||
// update original page
|
|
||||||
$p->Title = 'New Title';
|
|
||||||
// "save & publish"
|
|
||||||
$p->writeToStage('Stage');
|
|
||||||
$p->publish('Stage', 'Live');
|
|
||||||
$this->assertNotEquals($p->SubsiteID, $subsite->ID);
|
|
||||||
|
|
||||||
// reload SVP from database
|
|
||||||
// can't use DO::get by id because caches.
|
|
||||||
$svpdb = $svp->get()->byID($svp->ID);
|
|
||||||
|
|
||||||
// ensure title changed
|
|
||||||
$this->assertEquals($svpdb->Title, $p->Title);
|
|
||||||
}
|
|
||||||
|
|
||||||
function testUnpublishingParentPageUnpublishesSubsiteVirtualPages() {
|
|
||||||
Config::inst()->update('StaticPublisher', 'disable_realtime', true);
|
|
||||||
|
|
||||||
// Go to main site, get parent page
|
|
||||||
$subsite = $this->objFromFixture('Subsite', 'main');
|
|
||||||
Subsite::changeSubsite($subsite->ID);
|
|
||||||
$page = $this->objFromFixture('Page', 'importantpage');
|
|
||||||
|
|
||||||
// Create two SVPs on other subsites
|
|
||||||
$subsite = $this->objFromFixture('Subsite', 'subsite1');
|
|
||||||
Subsite::changeSubsite($subsite->ID);
|
|
||||||
$vp1 = new SubsitesVirtualPage();
|
|
||||||
$vp1->CopyContentFromID = $page->ID;
|
|
||||||
$vp1->write();
|
|
||||||
$vp1->doPublish();
|
|
||||||
|
|
||||||
$subsite = $this->objFromFixture('Subsite', 'subsite2');
|
|
||||||
Subsite::changeSubsite($subsite->ID);
|
|
||||||
$vp2 = new SubsitesVirtualPage();
|
|
||||||
$vp2->CopyContentFromID = $page->ID;
|
|
||||||
$vp2->write();
|
|
||||||
$vp2->doPublish();
|
|
||||||
|
|
||||||
// Switch back to main site, unpublish source
|
|
||||||
$subsite = $this->objFromFixture('Subsite', 'main');
|
|
||||||
Subsite::changeSubsite($subsite->ID);
|
|
||||||
$page = $this->objFromFixture('Page', 'importantpage');
|
|
||||||
$page->doUnpublish();
|
|
||||||
|
|
||||||
Subsite::changeSubsite($vp1->SubsiteID);
|
|
||||||
$onLive = Versioned::get_one_by_stage('SubsitesVirtualPage', 'Live', "\"SiteTree_Live\".\"ID\" = ".$vp1->ID);
|
|
||||||
$this->assertNull($onLive, 'SVP has been removed from live');
|
|
||||||
|
|
||||||
$subsite = $this->objFromFixture('Subsite', 'subsite2');
|
|
||||||
Subsite::changeSubsite($vp2->SubsiteID);
|
|
||||||
$onLive = Versioned::get_one_by_stage('SubsitesVirtualPage', 'Live', "\"SiteTree_Live\".\"ID\" = ".$vp2->ID);
|
|
||||||
$this->assertNull($onLive, 'SVP has been removed from live');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Similar to {@link SiteTreeSubsitesTest->testTwoPagesWithSameURLOnDifferentSubsites()}
|
|
||||||
* and {@link SiteTreeSubsitesTest->testPagesInDifferentSubsitesCanShareURLSegment()}.
|
|
||||||
*/
|
|
||||||
function testSubsiteVirtualPageCanHaveSameUrlsegmentAsOtherSubsite() {
|
|
||||||
Subsite::$write_hostmap = false;
|
|
||||||
$subsite1 = $this->objFromFixture('Subsite', 'subsite1');
|
|
||||||
$subsite2 = $this->objFromFixture('Subsite', 'subsite2');
|
|
||||||
Subsite::changeSubsite($subsite1->ID);
|
|
||||||
|
|
||||||
$subsite1Page = $this->objFromFixture('Page', 'subsite1_staff');
|
|
||||||
$subsite1Page->URLSegment = 'staff';
|
|
||||||
$subsite1Page->write();
|
|
||||||
|
|
||||||
// saving on subsite1, and linking to subsite1
|
|
||||||
$subsite1Vp = new SubsitesVirtualPage();
|
|
||||||
$subsite1Vp->CopyContentFromID = $subsite1Page->ID;
|
|
||||||
$subsite1Vp->SubsiteID = $subsite1->ID;
|
|
||||||
$subsite1Vp->write();
|
|
||||||
$this->assertNotEquals(
|
|
||||||
$subsite1Vp->URLSegment,
|
|
||||||
$subsite1Page->URLSegment,
|
|
||||||
"Doesn't allow explicit URLSegment overrides when already existing in same subsite"
|
|
||||||
);
|
|
||||||
|
|
||||||
//Change to subsite 2
|
|
||||||
Subsite::changeSubsite($subsite2->ID);
|
|
||||||
|
|
||||||
// saving in subsite2 (which already has a page with URLSegment 'contact-us'),
|
|
||||||
// but linking to a page in subsite1
|
|
||||||
$subsite2Vp = new SubsitesVirtualPage();
|
|
||||||
$subsite2Vp->CopyContentFromID = $subsite1Page->ID;
|
|
||||||
$subsite2Vp->SubsiteID = $subsite2->ID;
|
|
||||||
$subsite2Vp->write();
|
|
||||||
$this->assertEquals(
|
|
||||||
$subsite2Vp->URLSegment,
|
|
||||||
$subsite1Page->URLSegment,
|
|
||||||
"Does allow explicit URLSegment overrides when only existing in a different subsite"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function fixVersionNumberCache($page) {
|
|
||||||
$pages = func_get_args();
|
|
||||||
foreach($pages as $p) {
|
|
||||||
Versioned::prepopulate_versionnumber_cache('SiteTree', 'Stage', array($p->ID));
|
|
||||||
Versioned::prepopulate_versionnumber_cache('SiteTree', 'Live', array($p->ID));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
# These need to come first so that SiteTree has the link meta-data written.
|
|
||||||
File:
|
|
||||||
file1:
|
|
||||||
Filename: assets/testscript-test-file.pdf
|
|
||||||
|
|
||||||
SiteTree:
|
|
||||||
page1:
|
|
||||||
Title: page1
|
|
||||||
URLSegment: page1
|
|
||||||
Content: <p><img src="assets/testscript-test-file.pdf" /></p>
|
|
@ -1,100 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Subsites\Test\Behaviour;
|
|
||||||
|
|
||||||
use SilverStripe\BehatExtension\Context\SilverStripeContext,
|
|
||||||
SilverStripe\BehatExtension\Context\BasicContext,
|
|
||||||
SilverStripe\BehatExtension\Context\LoginContext,
|
|
||||||
SilverStripe\BehatExtension\Context\FixtureContext,
|
|
||||||
SilverStripe\Framework\Test\Behaviour\CmsFormsContext,
|
|
||||||
SilverStripe\Framework\Test\Behaviour\CmsUiContext,
|
|
||||||
SilverStripe\Cms\Test\Behaviour;
|
|
||||||
|
|
||||||
// PHPUnit
|
|
||||||
require_once 'PHPUnit/Autoload.php';
|
|
||||||
require_once 'PHPUnit/Framework/Assert/Functions.php';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Features context
|
|
||||||
*
|
|
||||||
* Context automatically loaded by Behat.
|
|
||||||
* Uses subcontexts to extend functionality.
|
|
||||||
*/
|
|
||||||
class FeatureContext extends SilverStripeContext {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var FixtureFactory
|
|
||||||
*/
|
|
||||||
protected $fixtureFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes context.
|
|
||||||
* Every scenario gets it's own context object.
|
|
||||||
*
|
|
||||||
* @param array $parameters context parameters (set them up through behat.yml)
|
|
||||||
*/
|
|
||||||
public function __construct(array $parameters) {
|
|
||||||
parent::__construct($parameters);
|
|
||||||
|
|
||||||
$this->useContext('BasicContext', new BasicContext($parameters));
|
|
||||||
$this->useContext('LoginContext', new LoginContext($parameters));
|
|
||||||
$this->useContext('CmsFormsContext', new CmsFormsContext($parameters));
|
|
||||||
$this->useContext('CmsUiContext', new CmsUiContext($parameters));
|
|
||||||
|
|
||||||
$fixtureContext = new FixtureContext($parameters);
|
|
||||||
$fixtureContext->setFixtureFactory($this->getFixtureFactory());
|
|
||||||
$this->useContext('FixtureContext', $fixtureContext);
|
|
||||||
|
|
||||||
// Use blueprints to set user name from identifier
|
|
||||||
$factory = $fixtureContext->getFixtureFactory();
|
|
||||||
$blueprint = \Injector::inst()->create('FixtureBlueprint', 'Member');
|
|
||||||
$blueprint->addCallback('beforeCreate', function($identifier, &$data, &$fixtures) {
|
|
||||||
if(!isset($data['FirstName'])) $data['FirstName'] = $identifier;
|
|
||||||
});
|
|
||||||
$factory->define('Member', $blueprint);
|
|
||||||
|
|
||||||
// Auto-publish pages
|
|
||||||
foreach(\ClassInfo::subclassesFor('SiteTree') as $id => $class) {
|
|
||||||
$blueprint = \Injector::inst()->create('FixtureBlueprint', $class);
|
|
||||||
$blueprint->addCallback('afterCreate', function($obj, $identifier, &$data, &$fixtures) {
|
|
||||||
$obj->publish('Stage', 'Live');
|
|
||||||
});
|
|
||||||
$factory->define($class, $blueprint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setMinkParameters(array $parameters) {
|
|
||||||
parent::setMinkParameters($parameters);
|
|
||||||
|
|
||||||
if(isset($parameters['files_path'])) {
|
|
||||||
$this->getSubcontext('FixtureContext')->setFilesPath($parameters['files_path']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return FixtureFactory
|
|
||||||
*/
|
|
||||||
public function getFixtureFactory() {
|
|
||||||
if(!$this->fixtureFactory) {
|
|
||||||
$this->fixtureFactory = \Injector::inst()->create('BehatFixtureFactory');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->fixtureFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setFixtureFactory(FixtureFactory $factory) {
|
|
||||||
$this->fixtureFactory = $factory;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Place your definition and hook methods here:
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * @Given /^I have done something with "([^"]*)"$/
|
|
||||||
// */
|
|
||||||
// public function iHaveDoneSomethingWith($argument) {
|
|
||||||
// $container = $this->kernel->getContainer();
|
|
||||||
// $container->get('some_service')->doSomethingWith($argument);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
}
|
|
29
tests/behat/features/insert-content-link.feature
Normal 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
|
81
tests/behat/features/operate-subsites.feature
Normal 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"
|
@ -4,23 +4,26 @@ Feature: Preview navigation
|
|||||||
In order to preview my content
|
In order to preview my content
|
||||||
|
|
||||||
Background:
|
Background:
|
||||||
Given a "subsite" "My subsite"
|
Given a "subsite" "MySubsite"
|
||||||
And a "page" "My page" with "URLSegment"="my-page", "Content"="My page content <a name='aname'>aname</a><a href='other-page'>ahref</a>" and "Subsite"="=>Subsite.My subsite"
|
And a "page" "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 <form action='my-page'><input type='submit' value='Submit my form'></form>" and "Subsite"="=>Subsite.My subsite"
|
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"
|
||||||
Given a "member" "Joe" belonging to "Admin Group" with "Email"="joe@test.com" and "Password"="Password1"
|
And the "group" "EDITOR" has permissions "Access to 'Pages' section" and "Access to 'Subsites' section"
|
||||||
And the "group" "Admin Group" has permissions "Full administrative rights"
|
And I am logged in as a member of "EDITOR" group
|
||||||
And I log in with "joe@test.com" and "Password1"
|
|
||||||
|
|
||||||
|
@javascript
|
||||||
Scenario: I can navigate the subsite preview
|
Scenario: I can navigate the subsite preview
|
||||||
When I go to "admin"
|
When I go to "/admin/pages"
|
||||||
And I select "My subsite" from "SubsitesSelect"
|
And I select "MySubsite" from "SubsitesSelect"
|
||||||
And I go to "admin/pages"
|
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 click on "My page" in the tree
|
||||||
And I wait for 3 seconds
|
|
||||||
And I set the CMS mode to "Preview mode"
|
And I set the CMS mode to "Preview mode"
|
||||||
And I follow "ahref" in preview
|
And I follow "ahref" in preview
|
||||||
|
And I wait for 1 second
|
||||||
Then the preview contains "Other page content"
|
Then the preview contains "Other page content"
|
||||||
# We are already on the second page, submit the form to return to first one.
|
# We are already on the second page, follow a link to return to first one.
|
||||||
When I wait for 3 seconds
|
And I follow "Goto my page" in preview
|
||||||
And I press "Submit my form" in preview
|
And I wait for 1 second
|
||||||
Then the preview contains "My page content"
|
Then the preview contains "My page content"
|
||||||
|
26
tests/behat/features/publish-a-page.feature
Normal 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
After Width: | Height: | Size: 2.2 KiB |
BIN
tests/behat/files/file2.jpg
Normal file
After Width: | Height: | Size: 3.8 KiB |
43
tests/php/BaseSubsiteTest.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
34
tests/php/Extensions/FolderFormFactoryExtensionTest.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
11
tests/php/Extensions/FolderFormFactoryExtensionTest.yml
Normal 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
|
129
tests/php/FileSubsitesTest.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
37
tests/php/GroupSubsitesTest.php
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
80
tests/php/InitStateMiddlewareTest.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
114
tests/php/LeftAndMainSubsitesTest.php
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
143
tests/php/Service/ThemeResolverTest.php
Normal 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)
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
45
tests/php/SiteConfigSubsitesTest.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
494
tests/php/SiteTreeSubsitesTest.php
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
10
tests/php/SiteTreeSubsitesTest/TestClassA.php
Normal 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
|
||||||
|
{
|
||||||
|
}
|
10
tests/php/SiteTreeSubsitesTest/TestClassB.php
Normal 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
|
||||||
|
{
|
||||||
|
}
|
20
tests/php/SiteTreeSubsitesTest/TestErrorPage.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
50
tests/php/State/SubsiteStateTest.php
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
201
tests/php/SubsiteAdminFunctionalTest.php
Normal 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'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
68
tests/php/SubsiteAdminTest.php
Normal 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
@ -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
@ -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
|
47
tests/php/SubsiteXHRControllerTest.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
351
tests/php/SubsitesVirtualPageTest.php
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
12
tests/php/SubsitesVirtualPageTest.yml
Normal 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>'
|
89
tests/php/WildcardDomainFieldTest.php
Normal 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.*'],
|
||||||
|
['*']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|