forked from somatonic/Multisite
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathMultisite.module
More file actions
312 lines (270 loc) · 13.4 KB
/
Multisite.module
File metadata and controls
312 lines (270 loc) · 13.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
<?php
/**
*
* Multisite module for Processwire
* Inspired by Multisite module made by authors: Ryan, Antti Peisa, Avoine Oy, Philipp "Soma" Urlich
*
* complete rewrite of the original and forked modules
*
* @author Christoph Thelen aka @kixe 2017/03/25
* @copyright © 2017 Christoph Thelen
* @license Licensed under MIT, see LICENSE.txt
* @link https://processwire.com/talk/topic/...
* @version 2.0.2
* @since 1.0.0 init - 2017/05/09
* @since 1.0.1 default rootpage, domain related access control for MultisitePageTree, fixed path bug - 2017/05/10
* @since 1.0.2 fixed root page path issue in single language mode FORCE $page->template->slashUrls for rootPage - 2017/05/10
* @since 1.0.3 fixed issue other language rootPageName, let hook modify Page view urls in admin too, take in account slashUrl and useHomeSegment settings - 2017/05/13
* @since 1.0.4 show warning if hooked methods are not hookable - 2017/06/22
* @since 2.0.0 major upgrade, root page of any MultisitePageTree can be placed elsewhere in the pagetree - 2018/01/26
* @since 2.0.1 bugfixes - 2018/01/28
* @since 2.0.2 support of redirects if defined in config, bugfixes - 2018/02/07
* @todo
* - modify ProcessPageList::executeNavJSON() Change $parentID from hardcoded 1 to $config->rootPageID to get proper results Pages > Tree
* - make hookable core function LanguageSupportPageNames::getPagePath() for proper language support
* - make hookable core function AdminRestrictBranch::getBranchRootParentId() to get it working together
*
* FEATURES
* - multilanguage support
* - LanguageSupportPageName::useHomeSegment support
* - slashUrl support
* - domain related homepage and custom 404 is set by id
* - no restriction about domain and page naming, even multilanguage
* - path modification is done by hooks
* - auto modification of view links in backend
* - domain related access control of MultiSiteTree (shows 404 if doesn't match)
* - root page of a Multisite page tree can be placed elsewhere in the ProcessWire page tree
* - Support of redirects
*
* REQUIREMENTS MULTILANGUAGE
* LanguageSupportPageNames installed
* LanguageSupportPageNames::getPagePath() MUST BE MODIFIED hookable in core
* a page name for the default language and any other language must be set for the superRoot page ($page->id = 1)
*
* SETUP
* in config.php
* $config->MultisiteDomains = array(
* "example.com" => array( // domain name is used to map to root page allow subdomains
* "root" => 1026, // int $page->id of the root page or redirect url starting with http
* "http404" => 27 // int page->id of custom 404 error page (optionally)
* ),
* );
*
*
* $config->httpHosts = array_unique(array_merge($config->httpHosts, array_keys($config->MultisiteDomains)));
*/
class Multisite extends WireData implements Module {
// set by constructor
protected $domains = array(); // settings array $config->MultisiteDomains
protected $domain = null; // null/string $config->httpHost if item key of $config->MultisiteDomains
public $rootPageID = 1; // page id of root page related to domain
protected $languageSupport = false; // runtime true if LanguageSupportPageNames is installed
protected $useHomeSegment = "0"; // string "0" or "1" runtime LanguageSupportPageNames::useHomeSegment
protected $it = ''; // string sanitized $_GET['it'] if set
// set by init()
protected $rootPage = null; // page object, instance of root page related to domain
protected $rootPagePath = ''; // language sensitive page path of root page (related to domain)
protected $language = null; // request is default language
protected $homeSegment = ''; //
protected $page = null; // page object, page to be rendered
protected $redirectPage = null;
protected $isAdmin = false;
public static function getModuleInfo() {
return array(
'title' => 'Multisite',
'version' => 202,
'summary' => 'Allows multiple sites with different domains run from single PW-site and database.',
'href' => 'https://github.com/kixe/Multisite',
'singular' => true,
'autoload' => true
);
}
public function __construct() {
// read config settings
if(!empty($this->wire("config")->MultisiteDomains)) $this->domains = $this->wire("config")->MultisiteDomains;
if (array_key_exists($this->wire('config')->httpHost, $this->domains)) {
$this->domain = $this->wire('config')->httpHost;
if (!array_key_exists('root', $this->domains[$this->domain])) throw new WireException("\$config->MultisiteDomains[$this->domain]['root'] not set");
$root = $this->domains[$this->domain]['root'];
if (strpos($root, 'http://') === 0 || strpos($root, 'https://') === 0) $this->redirectPage = $root;
else if (!is_numeric($root)) throw new WireException("Wrong type for \$config->MultisiteDomains[$this->domain]['root']. Use int.");
$this->rootPageID = (int) $root;
// language support
if ($this->wire('modules')->isInstalled('LanguageSupportPageNames')) {
$this->languageSupport = true;
$namespace = (ProcessWire::versionMajor == 3)? 'ProcessWire\\':'';
if (!method_exists("{$namespace}LanguageSupportPageNames", '___getPagePath')) $this->warning('LanguageSupportPageNames::getPagePath() is not hookable');
}
// This module works properly together with AdminRestrictBranch if getBranchRootParentId() is modified and hookable
if ($this->wire('modules')->isInstalled('AdminRestrictBranch')) {
if (method_exists('AdminRestrictBranch', '___getBranchRootParentId')) {
$this->addHookAfter('AdminRestrictBranch::getBranchRootParentId', function($e) {
$e->return = $this->rootPageID;
});
} else $this->warning('AdminRestrictBranch::getBranchRootParentId() is not hookable');
}
// sanitize $_GET['it']
if (isset($_GET['it'])) $this->it = $this->wire('sanitizer')->path($_GET['it']);
}
}
public function init() {
// quick exit
if(empty($this->domains) || $this->domain == null || $this->rootPageID == 1) return;
// redirect if set
if ($this->redirectPage) $this->wire('session')->redirect($this->redirectPage);
// settings ok?
$this->configCheck($this->domain);
// set custom http 404 page
if (isset($this->domains[$this->domain]['http404'])) $this->wire("config")->http404PageID = $this->domains[$this->domain]['http404'];
$superRootURL = $this->wire('config')->urls->root; // could be '/' or '/subfolder/'
// if it's /site/ folder exit
if(strpos($superRootURL . $this->it, $this->wire('config')->urls->site) === 0) return;
// if it's /wire/ folder exit
if(strpos($superRootURL . $this->it, $this->wire('config')->urls->wire) === 0) return;
// if it's form-builder exit
if(strpos($superRootURL . $this->it, "/form-builder/") !== false) return;
// if called from PW front-end editing exit
if($this->wire("input")->post->action == "PageFrontEditSave") return;
// set root page
$this->rootPage = $this->wire('pages')->get($this->rootPageID);
// modify $_GET['it'] if not admin
if(strpos($superRootURL . trim($this->it, '/') . '/', $this->wire('config')->urls->admin) !== 0) {
if ($this->languageSupport) {
$this->useHomeSegment = $this->wire('modules')->get('LanguageSupportPageNames')->useHomeSegment;
$this->language = $this->wire('languages')->getDefault();
$it = array_filter(explode('/',$this->it));
$homeSegment = (empty($it))? $this->getHomeSegment() : $this->getHomeSegment($it[0]);
}
// get rootPagePath in requested language
$this->rootPagePath = $this->getPagePathSegment($this->rootPage, $this->wire('pages')->get(1), $this->language);
if ($this->languageSupport) {
if ($homeSegment && empty($it)) array_unshift($it, $homeSegment);
array_splice($it, 1, 0, $this->rootPagePath); // multi language - splice in at position 1
$it = implode('/',$it);
}
else $it = "$this->rootPagePath/$this->it"; // case single language - just prepend
// get requested page and update slash
$this->page = $this->wire('pages')->get("path=/".trim($it,'/').'/');
$slash = ($this->page->id && $this->page->template->slashUrls)? '/' : '';
// redirect if $_GET['it'] doesn't match LanguageSupportPageNames::useHomeSegment setting
if ($this->languageSupport && $this->language->isDefault() && $this->rootPage == $this->page) {
if ($this->useHomeSegment && $this->it != $homeSegment.$slash) $this->wire('session')->redirect("/$homeSegment$slash");
if (!$this->useHomeSegment && $this->it == $homeSegment.$slash) $this->wire('session')->redirect("/");
}
// redirect if $_GET['it'] doesn't match $page->template->slashUrls setting
if ($this->it) {
if ($slash && substr($this->it, -1) != '/') $this->wire('session')->redirect("/$this->it/");
if (!$slash && $this->page->id && substr($this->it, -1) == '/') $this->wire('session')->redirect("/".rtrim($this->it,'/'));
}
$_GET['it'] = trim($it,'/').$slash;
}
// add path hook
$this->addHookBefore('Page::render', $this, 'hookPageRender');
}
/**
* Disallow access of pages residing in a MultisitePageTree for domains other than the corresponding root page
* @todo obsolete?
*
public function ready() {
if ($this->wire('user')->isSuperuser()) return;
$pageTreeIDs = $this->wire('page')->parents()->each('id');
$pageTreeIDs[] = $this->wire('page')->id;
$multisiteRootIDs = array_map(function($i) { return $i = $i['root']; }, $this->domains);
$matches = array_intersect($multisiteRootIDs, $pageTreeIDs);
if ($this->wire('page')->id == $this->wire("config")->http404PageID || empty($matches)) return;
reset($matches);
if (in_array($this->domain, array_keys($matches))) return;
$view = new ProcessPageView();
$view->pageNotFound($this->wire('page'), $this->it, true, 'access not allowed');
}
*/
/**
* Hook Page::render hooks Page::path and LanguageSupportPageNames::getPagePath
*
* @param HookEvent $e
* @see hookPagePath()
* @todo make hookable LanguageSupportPageNames::getPagePath
*/
protected function hookPageRender(HookEvent $e) {
if ($this->languageSupport) $this->addHookAfter('LanguageSupportPageNames::getPagePath', $this, 'hookPagePath');
else $this->addHookAfter('Page::path', $this, 'hookPagePath');
}
/**
* Hook is called from hookPageRender()
* modifies $page->url, $page->httpUrl, $page->path, $page->localPath() $page->localUrl(), $page->localHttpUrl()
* modifies page view urls ProcessPageListActions::getActions ProcessPageEdit::buildFormView
* @param HookEvent $e
* @see hookPageRender(),
*/
protected function hookPagePath(HookEvent $e) {
$page = isset($e->arguments[0])? $e->arguments[0] : $e->object;
if ($page->id == 1) return;
$language = isset($e->arguments[1])? $e->arguments[1] : '';
$cut = $this->getPagePathSegment($this->rootPage, $this->wire('pages')->get(1), $language);
if ($cut === false) return;
// special case multilanguage + homepage
if ($language && $page == $this->rootPage) {
$homeSegment = $this->getHomeSegment($language);
$useHomeSegment = $this->wire('modules')->get('LanguageSupportPageNames')->useHomeSegment;
if ($language instanceof Language && $language->isDefault() && $useHomeSegment == '0') $e->return = '/';
else $e->return = $page->template->slashUrls? "/$homeSegment/" : "/$homeSegment";
}
else if (strlen($cut) && strpos($e->return, "$cut/")) $e->return = str_replace("$cut/", '', $e->return);
}
/**
* get or verify homeSegment by name or language id or object
* compares given string argument with name of root page (homeSegment)
* modifies property language on success
* @param string|int|object $langIndicator
* @return bool|string $homeSegment
*/
protected function getHomeSegment($langIndicator = '') {
if (!$this->languageSupport) return '';
if (is_string($langIndicator) && strlen($langIndicator)) {
foreach($this->wire('languages') as $language) {
$pageName = $this->wire('pages')->get(1)->get("name$language|name");
if ($pageName === $langIndicator) {
// verified!
$this->language = $language;
return $pageName;
}
}
}
$pageName = $this->wire('pages')->get(1)->get("name$langIndicator|name");
if ($pageName == Pages::defaultRootName) return '';
return $pageName;
}
/**
* get a language sensitive path segment between two pages lacking homeSegment, leading and trailing slashes
* @param Page object $to
* @param Page object $from
* @param null|Language object $language
* @return string e.g. name/name/name
*/
protected function getPagePathSegment(Page $to, Page $from, $language = '') {
if($to == $from) return '';
if (!$to->parents()->has($from)) return false;
// default language or single language environment
$isDefault = true;
if ($this->languageSupport && $language instanceof Language) $isDefault = $language->isDefault();
$path = array();
$pages = $to->parentsUntil($from)->append($to);
foreach($pages as $page) {
$name = $isDefault ? $page->name : $page->get("name$language|name");
if (strlen($name)) $path[] = $name;
}
return implode($path,'/');
}
/**
* check existence and accessibility of pages defined in config settings
* @param string $domain
* @throws Exception
*/
protected function configCheck($domain) {
foreach ($this->domains[$domain] as $key => $id) {
if (!is_int($id)) throw new WireException(sprintf("Expecting integer value (\$page->id). %s given. Check \$config->MultisiteDomains['$domain']['$key'].", $id));
$page = $this->wire('pages')->get($id);
if ($page instanceof NullPage) throw new WireException(sprintf("The page with ID = %s doesn't exist. Check \$config->MultisiteDomains['$domain']['$key'].", $id));
}
}
}