Commit | Line | Data |
---|---|---|
158bd3ca TD |
1 | <?php |
2 | namespace wcf\system; | |
3 | use wcf\data\language\LanguageEditor; | |
4 | use wcf\data\language\SetupLanguage; | |
89142da9 | 5 | use wcf\data\package\installation\queue\PackageInstallationQueueEditor; |
a2cf0cdb | 6 | use wcf\data\package\Package; |
158bd3ca TD |
7 | use wcf\data\user\User; |
8 | use wcf\data\user\UserAction; | |
b401cd0d | 9 | use wcf\system\cache\builder\LanguageCacheBuilder; |
1ca3de4d | 10 | use wcf\system\database\exception\DatabaseException; |
158bd3ca | 11 | use wcf\system\database\util\SQLParser; |
157054c9 | 12 | use wcf\system\database\MySQLDatabase; |
f8d85495 | 13 | use wcf\system\devtools\DevtoolsSetup; |
158bd3ca | 14 | use wcf\system\exception\SystemException; |
8a81a7bd | 15 | use wcf\system\exception\UserInputException; |
158bd3ca TD |
16 | use wcf\system\io\File; |
17 | use wcf\system\io\Tar; | |
18 | use wcf\system\language\LanguageFactory; | |
19 | use wcf\system\package\PackageArchive; | |
20 | use wcf\system\session\ACPSessionFactory; | |
21 | use wcf\system\session\SessionHandler; | |
22 | use wcf\system\setup\Installer; | |
46188698 | 23 | use wcf\system\setup\SetupFileHandler; |
158bd3ca | 24 | use wcf\system\template\SetupTemplateEngine; |
364a2e49 | 25 | use wcf\util\DirectoryUtil; |
158bd3ca | 26 | use wcf\util\FileUtil; |
d3dba552 | 27 | use wcf\util\HeaderUtil; |
158bd3ca TD |
28 | use wcf\util\StringUtil; |
29 | use wcf\util\UserUtil; | |
30 | use wcf\util\XML; | |
31 | ||
32 | // define | |
6840f856 | 33 | define('PACKAGE_ID', 0); |
158bd3ca TD |
34 | define('HTTP_ENABLE_GZIP', 0); |
35 | define('HTTP_GZIP_LEVEL', 0); | |
b23a245a | 36 | define('HTTP_SEND_X_FRAME_OPTIONS', 0); |
158bd3ca | 37 | define('CACHE_SOURCE_TYPE', 'disk'); |
158bd3ca | 38 | define('MODULE_MASTER_PASSWORD', 1); |
0c07c50c | 39 | define('ENABLE_DEBUG_MODE', 1); |
8a12b687 | 40 | define('ENABLE_BENCHMARK', 0); |
158bd3ca TD |
41 | |
42 | /** | |
a17de04e MS |
43 | * Executes the installation of the basic WCF systems. |
44 | * | |
9f959ced | 45 | * @author Marcel Werk |
c839bd49 | 46 | * @copyright 2001-2018 WoltLab GmbH |
158bd3ca | 47 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> |
e71525e4 | 48 | * @package WoltLabSuite\Core\System |
158bd3ca TD |
49 | */ |
50 | class WCFSetup extends WCF { | |
9f959ced MS |
51 | /** |
52 | * list of available languages | |
a2cf0cdb | 53 | * @var string[] |
9f959ced | 54 | */ |
a2cf0cdb AE |
55 | protected static $availableLanguages = []; |
56 | ||
57 | /** | |
58 | * installation directories | |
893aace3 | 59 | * @var string[] |
a2cf0cdb AE |
60 | */ |
61 | protected static $directories = []; | |
9f959ced MS |
62 | |
63 | /** | |
64 | * language code of selected installation language | |
65 | * @var string | |
66 | */ | |
158bd3ca | 67 | protected static $selectedLanguageCode = 'en'; |
9f959ced MS |
68 | |
69 | /** | |
70 | * selected languages to be installed | |
a2cf0cdb | 71 | * @var string[] |
9f959ced | 72 | */ |
a2cf0cdb | 73 | protected static $selectedLanguages = []; |
9f959ced MS |
74 | |
75 | /** | |
76 | * list of installed files | |
893aace3 | 77 | * @var string[] |
9f959ced | 78 | */ |
a2cf0cdb | 79 | protected static $installedFiles = []; |
9f959ced | 80 | |
9f959ced MS |
81 | /** |
82 | * indicates if developer mode is used to install | |
83 | * @var boolean | |
84 | */ | |
158bd3ca | 85 | protected static $developerMode = 0; |
9f959ced | 86 | |
e4bda351 | 87 | /** @noinspection PhpMissingParentConstructorInspection */ |
158bd3ca TD |
88 | /** |
89 | * Calls all init functions of the WCFSetup class and starts the setup process. | |
90 | */ | |
91 | public function __construct() { | |
92 | @set_time_limit(0); | |
2fb1c79d | 93 | |
b2b2c26b MS |
94 | static::getDeveloperMode(); |
95 | static::getLanguageSelection(); | |
96 | static::getInstallationDirectories(); | |
158bd3ca TD |
97 | $this->initLanguage(); |
98 | $this->initTPL(); | |
e4499881 | 99 | /** @noinspection PhpUndefinedMethodInspection */ |
158bd3ca | 100 | self::getLanguage()->loadLanguage(); |
b2b2c26b | 101 | static::getPackageNames(); |
158bd3ca TD |
102 | |
103 | // start setup | |
104 | $this->setup(); | |
105 | } | |
106 | ||
107 | /** | |
ec1b3610 | 108 | * Sets the status of the developer mode. |
158bd3ca TD |
109 | */ |
110 | protected static function getDeveloperMode() { | |
111 | if (isset($_GET['dev'])) self::$developerMode = intval($_GET['dev']); | |
112 | else if (isset($_POST['dev'])) self::$developerMode = intval($_POST['dev']); | |
113 | } | |
114 | ||
115 | /** | |
ec1b3610 | 116 | * Sets the selected language. |
158bd3ca TD |
117 | */ |
118 | protected static function getLanguageSelection() { | |
1157f8be | 119 | self::$availableLanguages = self::getAvailableLanguages(); |
158bd3ca | 120 | |
1157f8be | 121 | if (isset($_REQUEST['languageCode']) && isset(self::$availableLanguages[$_REQUEST['languageCode']])) { |
158bd3ca TD |
122 | self::$selectedLanguageCode = $_REQUEST['languageCode']; |
123 | } | |
124 | else { | |
1157f8be | 125 | self::$selectedLanguageCode = LanguageFactory::getPreferredLanguage(array_keys(self::$availableLanguages), self::$selectedLanguageCode); |
158bd3ca TD |
126 | } |
127 | ||
128 | if (isset($_POST['selectedLanguages']) && is_array($_POST['selectedLanguages'])) { | |
129 | self::$selectedLanguages = $_POST['selectedLanguages']; | |
130 | } | |
131 | } | |
132 | ||
158bd3ca | 133 | /** |
ec1b3610 | 134 | * Sets the selected wcf dir from request. |
2f53b086 | 135 | * |
e71525e4 | 136 | * @since 3.0 |
158bd3ca | 137 | */ |
a2cf0cdb | 138 | protected static function getInstallationDirectories() { |
a2cf0cdb AE |
139 | if (!empty($_REQUEST['directories']) && is_array($_REQUEST['directories'])) { |
140 | foreach ($_REQUEST['directories'] as $application => $directory) { | |
141 | self::$directories[$application] = $directory; | |
142 | ||
143 | if ($application === 'wcf' && @file_exists(self::$directories['wcf'])) { | |
893aace3 | 144 | define('RELATIVE_WCF_DIR', FileUtil::getRelativePath(INSTALL_SCRIPT_DIR, self::$directories['wcf'])); |
a2cf0cdb | 145 | } |
158bd3ca TD |
146 | } |
147 | } | |
148 | ||
a2cf0cdb | 149 | define('WCF_DIR', (isset(self::$directories['wcf']) ? self::$directories['wcf'] : '')); |
158bd3ca | 150 | } |
9f959ced | 151 | |
158bd3ca TD |
152 | /** |
153 | * Initialises the language engine. | |
154 | */ | |
155 | protected function initLanguage() { | |
156 | // set mb settings | |
157 | mb_internal_encoding('UTF-8'); | |
158 | if (function_exists('mb_regex_encoding')) mb_regex_encoding('UTF-8'); | |
159 | mb_language('uni'); | |
160 | ||
161 | // init setup language | |
a2cf0cdb | 162 | self::$languageObj = new SetupLanguage(null, ['languageCode' => self::$selectedLanguageCode]); |
158bd3ca TD |
163 | } |
164 | ||
165 | /** | |
166 | * Initialises the template engine. | |
167 | */ | |
168 | protected function initTPL() { | |
169 | self::$tplObj = SetupTemplateEngine::getInstance(); | |
1157f8be | 170 | self::getTPL()->setLanguageID((self::$selectedLanguageCode == 'en' ? 0 : 1)); |
158bd3ca | 171 | self::getTPL()->setCompileDir(TMP_DIR); |
a2cf0cdb AE |
172 | self::getTPL()->addApplication('wcf', TMP_DIR); |
173 | self::getTPL()->registerPrefilter(['lang']); | |
174 | self::getTPL()->assign([ | |
158bd3ca TD |
175 | '__wcf' => $this, |
176 | 'tmpFilePrefix' => TMP_FILE_PREFIX, | |
177 | 'languageCode' => self::$selectedLanguageCode, | |
178 | 'selectedLanguages' => self::$selectedLanguages, | |
a2cf0cdb | 179 | 'directories' => self::$directories, |
158bd3ca | 180 | 'developerMode' => self::$developerMode |
a2cf0cdb | 181 | ]); |
158bd3ca TD |
182 | } |
183 | ||
184 | /** | |
185 | * Returns all languages from WCFSetup.tar.gz. | |
9f959ced | 186 | * |
a2cf0cdb | 187 | * @return string[] |
158bd3ca | 188 | */ |
1157f8be | 189 | protected static function getAvailableLanguages() { |
db8aa273 AE |
190 | $languages = $match = []; |
191 | foreach (glob(TMP_DIR.'setup/lang/*.xml') as $file) { | |
192 | $xml = new XML(); | |
193 | $xml->load($file); | |
194 | $languageCode = LanguageEditor::readLanguageCodeFromXML($xml); | |
195 | $languageName = LanguageEditor::readLanguageNameFromXML($xml); | |
196 | ||
197 | $languages[$languageCode] = $languageName; | |
158bd3ca | 198 | } |
9f959ced | 199 | |
1157f8be MW |
200 | // sort languages by language name |
201 | asort($languages); | |
9f959ced | 202 | |
158bd3ca TD |
203 | return $languages; |
204 | } | |
205 | ||
206 | /** | |
207 | * Calculates the current state of the progress bar. | |
9f959ced | 208 | * |
158bd3ca TD |
209 | * @param integer $currentStep |
210 | */ | |
211 | protected function calcProgress($currentStep) { | |
158bd3ca | 212 | // calculate progress |
87e78836 | 213 | $progress = round((100 / 25) * ++$currentStep, 0); |
a2cf0cdb | 214 | self::getTPL()->assign(['progress' => $progress]); |
158bd3ca TD |
215 | } |
216 | ||
217 | /** | |
218 | * Executes the setup steps. | |
219 | */ | |
220 | protected function setup() { | |
221 | // get current step | |
222 | if (isset($_REQUEST['step'])) $step = $_REQUEST['step']; | |
223 | else $step = 'selectSetupLanguage'; | |
9f959ced | 224 | |
158bd3ca TD |
225 | // execute current step |
226 | switch ($step) { | |
a2cf0cdb | 227 | /** @noinspection PhpMissingBreakStatementInspection */ |
158bd3ca TD |
228 | case 'selectSetupLanguage': |
229 | if (!self::$developerMode) { | |
230 | $this->calcProgress(0); | |
231 | $this->selectSetupLanguage(); | |
232 | break; | |
233 | } | |
9f959ced | 234 | |
a2cf0cdb | 235 | /** @noinspection PhpMissingBreakStatementInspection */ |
158bd3ca TD |
236 | case 'showLicense': |
237 | if (!self::$developerMode) { | |
238 | $this->calcProgress(1); | |
239 | $this->showLicense(); | |
240 | break; | |
241 | } | |
9f959ced | 242 | |
a2cf0cdb | 243 | /** @noinspection PhpMissingBreakStatementInspection */ |
158bd3ca TD |
244 | case 'showSystemRequirements': |
245 | if (!self::$developerMode) { | |
246 | $this->calcProgress(2); | |
247 | $this->showSystemRequirements(); | |
248 | break; | |
249 | } | |
9f959ced | 250 | |
a2cf0cdb | 251 | case 'configureDirectories': |
4058d032 TD |
252 | $this->calcProgress(3); |
253 | $this->configureDirectories(); | |
254 | break; | |
9f959ced | 255 | |
158bd3ca TD |
256 | case 'unzipFiles': |
257 | $this->calcProgress(4); | |
258 | $this->unzipFiles(); | |
9f959ced MS |
259 | break; |
260 | ||
158bd3ca TD |
261 | case 'selectLanguages': |
262 | $this->calcProgress(5); | |
263 | $this->selectLanguages(); | |
9f959ced MS |
264 | break; |
265 | ||
158bd3ca TD |
266 | case 'configureDB': |
267 | $this->calcProgress(6); | |
268 | $this->configureDB(); | |
9f959ced MS |
269 | break; |
270 | ||
158bd3ca | 271 | case 'createDB': |
038076d1 AE |
272 | $currentStep = 7; |
273 | if (isset($_POST['offset'])) { | |
274 | $currentStep += intval($_POST['offset']); | |
275 | } | |
276 | ||
277 | $this->calcProgress($currentStep); | |
158bd3ca | 278 | $this->createDB(); |
9f959ced MS |
279 | break; |
280 | ||
158bd3ca | 281 | case 'logFiles': |
9c5fbe65 | 282 | $this->calcProgress(21); |
158bd3ca | 283 | $this->logFiles(); |
9f959ced MS |
284 | break; |
285 | ||
158bd3ca | 286 | case 'installLanguage': |
9c5fbe65 | 287 | $this->calcProgress(22); |
158bd3ca | 288 | $this->installLanguage(); |
9f959ced MS |
289 | break; |
290 | ||
158bd3ca | 291 | case 'createUser': |
9c5fbe65 | 292 | $this->calcProgress(23); |
158bd3ca | 293 | $this->createUser(); |
9f959ced MS |
294 | break; |
295 | ||
158bd3ca | 296 | case 'installPackages': |
9c5fbe65 | 297 | $this->calcProgress(24); |
158bd3ca | 298 | $this->installPackages(); |
9f959ced | 299 | break; |
158bd3ca TD |
300 | } |
301 | } | |
302 | ||
303 | /** | |
304 | * Shows the first setup page. | |
305 | */ | |
306 | protected function selectSetupLanguage() { | |
a2cf0cdb | 307 | WCF::getTPL()->assign([ |
1157f8be | 308 | 'availableLanguages' => self::$availableLanguages, |
158bd3ca | 309 | 'nextStep' => 'showLicense' |
a2cf0cdb | 310 | ]); |
158bd3ca TD |
311 | WCF::getTPL()->display('stepSelectSetupLanguage'); |
312 | } | |
313 | ||
314 | /** | |
315 | * Shows the license agreement. | |
316 | */ | |
317 | protected function showLicense() { | |
318 | if (isset($_POST['send'])) { | |
319 | if (isset($_POST['accepted'])) { | |
320 | $this->gotoNextStep('showSystemRequirements'); | |
321 | exit; | |
322 | } | |
323 | else { | |
a2cf0cdb | 324 | WCF::getTPL()->assign(['missingAcception' => true]); |
158bd3ca TD |
325 | } |
326 | ||
327 | } | |
328 | ||
329 | if (file_exists(TMP_DIR.'setup/license/license_'.self::$selectedLanguageCode.'.txt')) { | |
330 | $license = file_get_contents(TMP_DIR.'setup/license/license_'.self::$selectedLanguageCode.'.txt'); | |
331 | } | |
332 | else { | |
333 | $license = file_get_contents(TMP_DIR.'setup/license/license_en.txt'); | |
334 | } | |
335 | ||
a2cf0cdb | 336 | WCF::getTPL()->assign([ |
158bd3ca TD |
337 | 'license' => $license, |
338 | 'nextStep' => 'showLicense' | |
a2cf0cdb | 339 | ]); |
158bd3ca TD |
340 | WCF::getTPL()->display('stepShowLicense'); |
341 | } | |
342 | ||
343 | /** | |
344 | * Shows the system requirements. | |
345 | */ | |
346 | protected function showSystemRequirements() { | |
a2cf0cdb | 347 | $system = []; |
158bd3ca TD |
348 | |
349 | // php version | |
350 | $system['phpVersion']['value'] = phpversion(); | |
351 | $comparePhpVersion = preg_replace('/^(\d+\.\d+\.\d+).*$/', '\\1', $system['phpVersion']['value']); | |
f7d7f59a | 352 | $system['phpVersion']['result'] = (version_compare($comparePhpVersion, '5.5.4') >= 0); |
158bd3ca TD |
353 | |
354 | // sql | |
a2d822ad | 355 | $system['sql']['result'] = MySQLDatabase::isSupported(); |
158bd3ca TD |
356 | |
357 | // upload_max_filesize | |
f774c315 | 358 | $system['uploadMaxFilesize']['value'] = min(ini_get('upload_max_filesize'), ini_get('post_max_size')); |
158bd3ca TD |
359 | $system['uploadMaxFilesize']['result'] = (intval($system['uploadMaxFilesize']['value']) > 0); |
360 | ||
361 | // gdlib version | |
362 | $system['gdLib']['value'] = '0.0.0'; | |
363 | if (function_exists('gd_info')) { | |
364 | $temp = gd_info(); | |
a2cf0cdb | 365 | $match = []; |
158bd3ca TD |
366 | if (preg_match('!([0-9]+\.[0-9]+(?:\.[0-9]+)?)!', $temp['GD Version'], $match)) { |
367 | if (preg_match('/^[0-9]+\.[0-9]+$/', $match[1])) $match[1] .= '.0'; | |
368 | $system['gdLib']['value'] = $match[1]; | |
369 | } | |
370 | } | |
371 | $system['gdLib']['result'] = (version_compare($system['gdLib']['value'], '2.0.0') >= 0); | |
372 | ||
b4f1ea02 AE |
373 | // memory limit |
374 | $system['memoryLimit']['value'] = ini_get('memory_limit'); | |
375 | $system['memoryLimit']['result'] = $this->compareMemoryLimit(); | |
376 | ||
535db603 MW |
377 | // openssl extension |
378 | $system['openssl']['result'] = @extension_loaded('openssl'); | |
379 | ||
a2cf0cdb | 380 | WCF::getTPL()->assign([ |
158bd3ca | 381 | 'system' => $system, |
a2cf0cdb AE |
382 | 'nextStep' => 'configureDirectories' |
383 | ]); | |
158bd3ca TD |
384 | WCF::getTPL()->display('stepShowSystemRequirements'); |
385 | } | |
386 | ||
387 | /** | |
c21e84ea | 388 | * Returns true if memory_limit is set to at least 128 MB |
b4f1ea02 AE |
389 | * |
390 | * @return boolean | |
391 | */ | |
392 | protected function compareMemoryLimit() { | |
393 | $memoryLimit = ini_get('memory_limit'); | |
394 | ||
395 | // no limit | |
396 | if ($memoryLimit == -1) { | |
397 | return true; | |
398 | } | |
399 | ||
400 | // completely numeric, PHP assumes byte | |
401 | if (is_numeric($memoryLimit)) { | |
c21e84ea TD |
402 | $memoryLimit = $memoryLimit / 1024 / 1024; |
403 | return ($memoryLimit >= 128); | |
b4f1ea02 AE |
404 | } |
405 | ||
406 | // PHP supports 'K', 'M' and 'G' shorthand notation | |
53e6372f | 407 | if (preg_match('~^(\d+)([KMG])$~', $memoryLimit, $matches)) { |
b4f1ea02 AE |
408 | switch ($matches[2]) { |
409 | case 'K': | |
410 | $memoryLimit = $matches[1] * 1024; | |
c21e84ea | 411 | return ($memoryLimit >= 128); |
b4f1ea02 AE |
412 | break; |
413 | ||
414 | case 'M': | |
c21e84ea | 415 | return ($matches[1] >= 128); |
b4f1ea02 AE |
416 | break; |
417 | ||
418 | case 'G': | |
419 | return ($matches[1] >= 1); | |
420 | break; | |
421 | } | |
422 | } | |
423 | ||
424 | return false; | |
425 | } | |
426 | ||
427 | /** | |
158bd3ca | 428 | * Searches the wcf dir. |
2f53b086 | 429 | * |
e71525e4 | 430 | * @since 3.0 |
158bd3ca | 431 | */ |
a2cf0cdb AE |
432 | protected function configureDirectories() { |
433 | // get available packages | |
e54191ea | 434 | $packages = []; |
a2cf0cdb AE |
435 | foreach (glob(TMP_DIR . 'install/packages/*') as $file) { |
436 | $filename = basename($file); | |
437 | if (preg_match('~\.(?:tar|tar\.gz|tgz)$~', $filename)) { | |
438 | $package = new PackageArchive($file); | |
439 | $package->openArchive(); | |
440 | ||
441 | $application = Package::getAbbreviation($package->getPackageInfo('name')); | |
442 | ||
a2cf0cdb | 443 | $packages[$application] = [ |
63b9817b | 444 | 'directory' => $package->getPackageInfo('applicationDirectory') ?: $application, |
a2cf0cdb AE |
445 | 'packageDescription' => $package->getLocalizedPackageInfo('packageDescription'), |
446 | 'packageName' => $package->getLocalizedPackageInfo('packageName') | |
447 | ]; | |
a2cf0cdb | 448 | } |
158bd3ca | 449 | } |
a2cf0cdb AE |
450 | |
451 | uasort($packages, function($a, $b) { | |
452 | return strcmp($a['packageName'], $b['packageName']); | |
453 | }); | |
454 | ||
455 | // force cms being shown first | |
456 | $showOrder = ['wcf']; | |
457 | foreach (array_keys($packages) as $application) { | |
458 | if ($application !== 'wcf') $showOrder[] = $application; | |
c5e9262b AE |
459 | } |
460 | ||
67f1b484 | 461 | $documentRoot = FileUtil::unifyDirSeparator(realpath($_SERVER['DOCUMENT_ROOT'])); |
f8d85495 | 462 | if (self::$developerMode && (isset($_ENV['WCFSETUP_USEDEFAULTWCFDIR']) || DevtoolsSetup::getInstance()->useDefaultInstallPath())) { |
4058d032 | 463 | // resolve path relative to document root |
67f1b484 | 464 | $relativePath = FileUtil::getRelativePath($documentRoot, INSTALL_SCRIPT_DIR); |
4058d032 TD |
465 | foreach ($packages as $application => $packageData) { |
466 | self::$directories[$application] = $relativePath . ($application === 'wcf' ? '' : $packageData['directory'] . '/'); | |
467 | } | |
468 | } | |
469 | ||
a2cf0cdb AE |
470 | $errors = []; |
471 | if (!empty(self::$directories)) { | |
472 | $applicationPaths = $knownPaths = []; | |
473 | ||
e54191ea MS |
474 | // use $showOrder to ensure that the error message for duplicate directories |
475 | // will trigger in display order rather than the random sort order returned | |
476 | // by glob() above | |
a2cf0cdb AE |
477 | foreach ($showOrder as $application) { |
478 | $path = FileUtil::getRealPath($documentRoot . '/' . FileUtil::addTrailingSlash(FileUtil::removeLeadingSlash(self::$directories[$application]))); | |
e238d5ba | 479 | if (!empty($documentRoot) && strpos($path, $documentRoot) !== 0) { |
a2cf0cdb AE |
480 | // verify that given path is still within the current document root |
481 | $errors[$application] = 'outsideDocumentRoot'; | |
482 | } | |
483 | else if (in_array($path, $knownPaths)) { | |
484 | // prevent the same path for two or more applications | |
485 | $errors[$application] = 'duplicate'; | |
486 | } | |
487 | else if (@is_file($path . 'global.php')) { | |
488 | // check if directory is empty (dotfiles are okay) | |
489 | $errors[$application] = 'notEmpty'; | |
490 | } | |
491 | else { | |
492 | // try to create directory if it does not exist | |
493 | if (!is_dir($path) && !FileUtil::makePath($path)) { | |
494 | $errors[$application] = 'makePath'; | |
495 | } | |
496 | ||
497 | try { | |
498 | FileUtil::makeWritable($path); | |
499 | } | |
500 | catch (SystemException $e) { | |
501 | $errors[$application] = 'makeWritable'; | |
502 | } | |
503 | } | |
504 | ||
505 | $applicationPaths[$application] = $path; | |
506 | $knownPaths[] = $path; | |
507 | } | |
508 | ||
509 | if (empty($errors)) { | |
510 | // copy over the actual paths | |
511 | self::$directories = array_merge(self::$directories, $applicationPaths); | |
512 | WCF::getTPL()->assign(['directories' => self::$directories]); | |
513 | ||
514 | $this->unzipFiles(); | |
515 | return; | |
516 | } | |
517 | } | |
518 | else { | |
519 | // resolve path relative to document root | |
67f1b484 | 520 | $relativePath = FileUtil::getRelativePath($documentRoot, INSTALL_SCRIPT_DIR); |
a2cf0cdb | 521 | foreach ($packages as $application => $packageData) { |
f5aa0bfd AE |
522 | $dir = $relativePath . ($application === 'wcf' ? '' : $packageData['directory'] . '/'); |
523 | if (mb_strpos($dir, './') === 0) $dir = mb_substr($dir, 1); | |
524 | ||
525 | self::$directories[$application] = $dir; | |
a2cf0cdb | 526 | } |
158bd3ca TD |
527 | } |
528 | ||
a2cf0cdb AE |
529 | WCF::getTPL()->assign([ |
530 | 'directories' => self::$directories, | |
531 | 'documentRoot' => $documentRoot, | |
532 | 'errors' => $errors, | |
533 | 'installScriptDir' => FileUtil::unifyDirSeparator(INSTALL_SCRIPT_DIR), | |
534 | 'nextStep' => 'configureDirectories', // call this step again to validate paths | |
535 | 'packages' => $packages, | |
536 | 'showOrder' => $showOrder | |
537 | ]); | |
538 | ||
539 | WCF::getTPL()->display('stepConfigureDirectories'); | |
158bd3ca TD |
540 | } |
541 | ||
542 | /** | |
543 | * Unzips the files of the wcfsetup tar archive. | |
544 | */ | |
545 | protected function unzipFiles() { | |
c5e9262b | 546 | // WCF seems to be installed, abort |
a2cf0cdb | 547 | if (@is_file(self::$directories['wcf'].'lib/system/WCF.class.php')) { |
c5e9262b | 548 | throw new SystemException('Target directory seems to be an existing installation of WCF, unable to continue.'); |
158bd3ca TD |
549 | } |
550 | // WCF not yet installed, install files first | |
551 | else { | |
b2b2c26b | 552 | static::installFiles(); |
158bd3ca TD |
553 | |
554 | $this->gotoNextStep('selectLanguages'); | |
555 | } | |
556 | } | |
557 | ||
558 | /** | |
559 | * Shows the page for choosing the installed languages. | |
560 | */ | |
561 | protected function selectLanguages() { | |
562 | $errorField = $errorType = ''; | |
158bd3ca | 563 | |
158bd3ca TD |
564 | // skip step in developer mode |
565 | // select all available languages automatically | |
566 | if (self::$developerMode) { | |
a2cf0cdb | 567 | self::$selectedLanguages = []; |
f2f5a09c | 568 | foreach (self::$availableLanguages as $languageCode => $language) { |
158bd3ca TD |
569 | self::$selectedLanguages[] = $languageCode; |
570 | } | |
571 | ||
a2cf0cdb | 572 | self::getTPL()->assign(['selectedLanguages' => self::$selectedLanguages]); |
158bd3ca TD |
573 | $this->gotoNextStep('configureDB'); |
574 | exit; | |
575 | } | |
576 | ||
158bd3ca TD |
577 | // start error handling |
578 | if (isset($_POST['send'])) { | |
579 | try { | |
580 | // no languages selected | |
15fa2802 | 581 | if (empty(self::$selectedLanguages)) { |
158bd3ca TD |
582 | throw new UserInputException('selectedLanguages'); |
583 | } | |
584 | ||
585 | // illegal selection | |
586 | foreach (self::$selectedLanguages as $language) { | |
1157f8be | 587 | if (!isset(self::$availableLanguages[$language])) { |
158bd3ca TD |
588 | throw new UserInputException('selectedLanguages'); |
589 | } | |
590 | } | |
591 | ||
592 | // no errors | |
593 | // go to next step | |
594 | $this->gotoNextStep('configureDB'); | |
595 | exit; | |
596 | } | |
597 | catch (UserInputException $e) { | |
598 | $errorField = $e->getField(); | |
599 | $errorType = $e->getType(); | |
600 | } | |
601 | } | |
602 | else { | |
603 | self::$selectedLanguages[] = self::$selectedLanguageCode; | |
a2cf0cdb | 604 | WCF::getTPL()->assign(['selectedLanguages' => self::$selectedLanguages]); |
158bd3ca TD |
605 | } |
606 | ||
a2cf0cdb | 607 | WCF::getTPL()->assign([ |
158bd3ca TD |
608 | 'errorField' => $errorField, |
609 | 'errorType' => $errorType, | |
1157f8be | 610 | 'availableLanguages' => self::$availableLanguages, |
158bd3ca | 611 | 'nextStep' => 'selectLanguages' |
a2cf0cdb | 612 | ]); |
158bd3ca TD |
613 | WCF::getTPL()->display('stepSelectLanguages'); |
614 | } | |
615 | ||
616 | /** | |
b2aa772d | 617 | * Shows the page for configuring the database connection. |
158bd3ca TD |
618 | */ |
619 | protected function configureDB() { | |
f8d85495 AE |
620 | $attemptConnection = isset($_POST['send']); |
621 | ||
025e9036 | 622 | if (self::$developerMode && isset($_ENV['WCFSETUP_DBHOST'])) { |
c1eb067d TD |
623 | $dbHost = $_ENV['WCFSETUP_DBHOST']; |
624 | $dbUser = $_ENV['WCFSETUP_DBUSER']; | |
625 | $dbPassword = $_ENV['WCFSETUP_DBPASSWORD']; | |
626 | $dbName = $_ENV['WCFSETUP_DBNAME']; | |
627 | $dbNumber = 1; | |
f8d85495 AE |
628 | |
629 | $attemptConnection = true; | |
630 | } | |
631 | else if (self::$developerMode && ($config = DevtoolsSetup::getInstance()->getDatabaseConfig()) !== null) { | |
632 | $dbHost = $config['host']; | |
633 | $dbUser = $config['username']; | |
634 | $dbPassword = $config['password']; | |
635 | $dbName = $config['dbName']; | |
636 | $dbNumber = $config['dbNumber']; | |
637 | ||
638 | if ($config['auto']) $attemptConnection = true; | |
c1eb067d TD |
639 | } |
640 | else { | |
641 | $dbHost = 'localhost'; | |
642 | $dbUser = 'root'; | |
643 | $dbPassword = ''; | |
644 | $dbName = 'wcf'; | |
645 | $dbNumber = 1; | |
c1eb067d TD |
646 | } |
647 | ||
f8d85495 | 648 | if ($attemptConnection) { |
158bd3ca TD |
649 | if (isset($_POST['dbHost'])) $dbHost = $_POST['dbHost']; |
650 | if (isset($_POST['dbUser'])) $dbUser = $_POST['dbUser']; | |
651 | if (isset($_POST['dbPassword'])) $dbPassword = $_POST['dbPassword']; | |
652 | if (isset($_POST['dbName'])) $dbName = $_POST['dbName']; | |
f9600494 TD |
653 | |
654 | // ensure that $dbNumber is zero or a positive integer | |
655 | if (isset($_POST['dbNumber'])) $dbNumber = max(0, intval($_POST['dbNumber'])); | |
158bd3ca TD |
656 | |
657 | // get port | |
0ae85994 | 658 | $dbHostWithoutPort = $dbHost; |
158bd3ca TD |
659 | $dbPort = 0; |
660 | if (preg_match('/^(.+?):(\d+)$/', $dbHost, $match)) { | |
0ae85994 | 661 | $dbHostWithoutPort = $match[1]; |
158bd3ca TD |
662 | $dbPort = intval($match[2]); |
663 | } | |
664 | ||
665 | // test connection | |
666 | try { | |
158bd3ca | 667 | // check connection data |
a2cf0cdb | 668 | /** @var \wcf\system\database\Database $db */ |
1ca3de4d | 669 | try { |
0ae85994 | 670 | $db = new MySQLDatabase($dbHostWithoutPort, $dbUser, $dbPassword, $dbName, $dbPort, true, !!(self::$developerMode)); |
1ca3de4d MW |
671 | } |
672 | catch (DatabaseException $e) { | |
fc664e00 JR |
673 | // work-around for older MySQL versions that don't know utf8mb4 |
674 | if ($e->getPrevious()->getCode() == 1115) { | |
1ca3de4d MW |
675 | throw new SystemException("Insufficient MySQL version. Version '5.5.35' or greater is needed."); |
676 | } | |
677 | ||
678 | throw $e; | |
679 | } | |
158bd3ca TD |
680 | |
681 | // check sql version | |
a2d822ad MW |
682 | $sqlVersion = $db->getVersion(); |
683 | $compareSQLVersion = preg_replace('/^(\d+\.\d+\.\d+).*$/', '\\1', $sqlVersion); | |
684 | if (stripos($sqlVersion, 'MariaDB')) { | |
643cb983 AE |
685 | // MariaDB 5.5.47+ or 10.0.22+ are required |
686 | // https://jira.mariadb.org/browse/MDEV-8756 | |
687 | if ($compareSQLVersion[0] === '5') { | |
688 | // MariaDB 5.5.47+ | |
689 | if (!(version_compare($compareSQLVersion, '5.5.47') >= 0)) { | |
690 | throw new SystemException("Insufficient MariaDB version '".$compareSQLVersion."'. Version '5.5.47' or greater, or version '10.0.22' or greater is needed."); | |
691 | } | |
692 | } | |
693 | else if (!(version_compare($compareSQLVersion, '10.0.22') >= 0)) { | |
694 | // MariaDB 10.0.22+ | |
695 | throw new SystemException("Insufficient MariaDB version '".$compareSQLVersion."'. Version '5.5.47' or greater, or version '10.0.22' or greater is needed."); | |
25d078eb MW |
696 | } |
697 | } | |
a2d822ad MW |
698 | else { |
699 | // MySQL 5.5.35+ | |
700 | if (!(version_compare($compareSQLVersion, '5.5.35') >= 0)) { | |
701 | throw new SystemException("Insufficient MySQL version '".$compareSQLVersion."'. Version '5.5.35' or greater is needed."); | |
308e32b7 | 702 | } |
a2d822ad MW |
703 | } |
704 | ||
705 | // check innodb support | |
706 | $sql = "SHOW ENGINES"; | |
707 | $statement = $db->prepareStatement($sql); | |
708 | $statement->execute(); | |
709 | $hasInnoDB = false; | |
710 | while ($row = $statement->fetchArray()) { | |
711 | if ($row['Engine'] == 'InnoDB' && in_array($row['Support'], ['DEFAULT', 'YES'])) { | |
712 | $hasInnoDB = true; | |
713 | break; | |
158bd3ca TD |
714 | } |
715 | } | |
716 | ||
a2d822ad MW |
717 | if (!$hasInnoDB) { |
718 | throw new SystemException("Support for InnoDB is missing."); | |
719 | } | |
720 | ||
b972d436 | 721 | // check for PHP's MySQL native driver |
109e9d15 | 722 | /* |
b972d436 TD |
723 | $sql = "SELECT 1"; |
724 | $statement = $db->prepareStatement($sql); | |
725 | $statement->execute(); | |
726 | // MySQL native driver understands data types, libmysqlclient does not | |
727 | if ($statement->fetchSingleColumn() !== 1) { | |
728 | throw new SystemException("MySQLnd is not being used for database communication."); | |
729 | } | |
109e9d15 | 730 | */ |
b972d436 | 731 | |
158bd3ca TD |
732 | // check for table conflicts |
733 | $conflictedTables = $this->getConflictedTables($db, $dbNumber); | |
734 | ||
735 | // write config.inc | |
840ba5e8 | 736 | if (empty($conflictedTables)) { |
158bd3ca TD |
737 | // connection successfully established |
738 | // write configuration to config.inc.php | |
739 | $file = new File(WCF_DIR.'config.inc.php'); | |
740 | $file->write("<?php\n"); | |
0ae85994 | 741 | $file->write("\$dbHost = '".str_replace("'", "\\'", $dbHostWithoutPort)."';\n"); |
158bd3ca | 742 | $file->write("\$dbPort = ".$dbPort.";\n"); |
838e315b SG |
743 | $file->write("\$dbUser = '".str_replace("'", "\\'", $dbUser)."';\n"); |
744 | $file->write("\$dbPassword = '".str_replace("'", "\\'", $dbPassword)."';\n"); | |
745 | $file->write("\$dbName = '".str_replace("'", "\\'", $dbName)."';\n"); | |
3d8ccc22 | 746 | $file->write("if (!defined('WCF_N')) define('WCF_N', $dbNumber);\n"); |
158bd3ca | 747 | $file->close(); |
a17de04e | 748 | |
158bd3ca TD |
749 | // go to next step |
750 | $this->gotoNextStep('createDB'); | |
751 | exit; | |
752 | } | |
18dea1d3 | 753 | // show configure template again |
158bd3ca | 754 | else { |
a2cf0cdb | 755 | WCF::getTPL()->assign(['conflictedTables' => $conflictedTables]); |
158bd3ca TD |
756 | } |
757 | } | |
758 | catch (SystemException $e) { | |
a2cf0cdb | 759 | WCF::getTPL()->assign(['exception' => $e]); |
158bd3ca TD |
760 | } |
761 | } | |
a2cf0cdb | 762 | WCF::getTPL()->assign([ |
158bd3ca TD |
763 | 'dbHost' => $dbHost, |
764 | 'dbUser' => $dbUser, | |
765 | 'dbPassword' => $dbPassword, | |
766 | 'dbName' => $dbName, | |
767 | 'dbNumber' => $dbNumber, | |
158bd3ca | 768 | 'nextStep' => 'configureDB' |
a2cf0cdb | 769 | ]); |
158bd3ca TD |
770 | WCF::getTPL()->display('stepConfigureDB'); |
771 | } | |
772 | ||
158bd3ca TD |
773 | /** |
774 | * Checks if in the chosen database are tables in conflict with the wcf tables | |
775 | * which will be created in the next step. | |
9f959ced | 776 | * |
0ad90fc3 | 777 | * @param \wcf\system\database\Database $db |
9f959ced | 778 | * @param integer $dbNumber |
893aace3 | 779 | * @return string[] list of already existing tables |
158bd3ca TD |
780 | */ |
781 | protected function getConflictedTables($db, $dbNumber) { | |
782 | // get content of the sql structure file | |
783 | $sql = file_get_contents(TMP_DIR.'setup/db/install.sql'); | |
784 | ||
785 | // installation number value 'n' (WCF_N) must be reflected in the executed sql queries | |
838e315b | 786 | $sql = str_replace('wcf1_', 'wcf'.$dbNumber.'_', $sql); |
158bd3ca TD |
787 | |
788 | // get all tablenames which should be created | |
789 | preg_match_all("%CREATE\s+TABLE\s+(\w+)%", $sql, $matches); | |
790 | ||
791 | // get all installed tables from chosen database | |
792 | $existingTables = $db->getEditor()->getTableNames(); | |
793 | ||
794 | // check if existing tables are in conflict with wcf tables | |
a2cf0cdb | 795 | $conflictedTables = []; |
158bd3ca TD |
796 | foreach ($existingTables as $existingTableName) { |
797 | foreach ($matches[1] as $wcfTableName) { | |
798 | if ($existingTableName == $wcfTableName) { | |
799 | $conflictedTables[] = $wcfTableName; | |
800 | } | |
801 | } | |
802 | } | |
803 | return $conflictedTables; | |
804 | } | |
805 | ||
806 | /** | |
807 | * Creates the database structure of the wcf. | |
808 | */ | |
809 | protected function createDB() { | |
810 | $this->initDB(); | |
811 | ||
812 | // get content of the sql structure file | |
813 | $sql = file_get_contents(TMP_DIR.'setup/db/install.sql'); | |
814 | ||
2fbf5046 AE |
815 | // split by offsets |
816 | $sqlData = explode('/* SQL_PARSER_OFFSET */', $sql); | |
63b9817b | 817 | $offset = isset($_POST['offset']) ? intval($_POST['offset']) : 0; |
2fbf5046 AE |
818 | if (!isset($sqlData[$offset])) { |
819 | throw new SystemException("Offset for SQL parser is out of bounds, ".$offset." was requested, but there are only ".count($sqlData)." sections"); | |
820 | } | |
821 | $sql = $sqlData[$offset]; | |
822 | ||
158bd3ca | 823 | // installation number value 'n' (WCF_N) must be reflected in the executed sql queries |
838e315b | 824 | $sql = str_replace('wcf1_', 'wcf'.WCF_N.'_', $sql); |
158bd3ca TD |
825 | |
826 | // execute sql queries | |
827 | $parser = new SQLParser($sql); | |
828 | $parser->execute(); | |
829 | ||
830 | // log sql queries | |
831 | preg_match_all("~CREATE\s+TABLE\s+(\w+)~i", $sql, $matches); | |
832 | ||
15fa2802 | 833 | if (!empty($matches[1])) { |
158bd3ca TD |
834 | $sql = "INSERT INTO wcf".WCF_N."_package_installation_sql_log |
835 | (sqlTable) | |
836 | VALUES (?)"; | |
837 | $statement = self::getDB()->prepareStatement($sql); | |
838 | foreach ($matches[1] as $tableName) { | |
a2cf0cdb | 839 | $statement->execute([$tableName]); |
158bd3ca TD |
840 | } |
841 | } | |
842 | ||
2fbf5046 | 843 | if ($offset < (count($sqlData) - 1)) { |
a2cf0cdb AE |
844 | WCF::getTPL()->assign([ |
845 | '__additionalParameters' => [ | |
038076d1 | 846 | 'offset' => $offset + 1 |
a2cf0cdb AE |
847 | ] |
848 | ]); | |
2fbf5046 AE |
849 | |
850 | $this->gotoNextStep('createDB'); | |
851 | } | |
852 | else { | |
853 | /* | |
854 | * Manually install PIPPackageInstallationPlugin since install.sql content is not escaped resulting | |
855 | * in different behaviour in MySQL and MSSQL. You SHOULD NOT move this into install.sql! | |
856 | */ | |
857 | $sql = "INSERT INTO wcf".WCF_N."_package_installation_plugin | |
858 | (pluginName, priority, className) | |
859 | VALUES (?, ?, ?)"; | |
860 | $statement = self::getDB()->prepareStatement($sql); | |
a2cf0cdb | 861 | $statement->execute([ |
2fbf5046 AE |
862 | 'packageInstallationPlugin', |
863 | 1, | |
864 | 'wcf\system\package\plugin\PIPPackageInstallationPlugin' | |
a2cf0cdb | 865 | ]); |
2fbf5046 AE |
866 | |
867 | $this->gotoNextStep('logFiles'); | |
868 | } | |
158bd3ca TD |
869 | } |
870 | ||
871 | /** | |
872 | * Logs the unzipped files. | |
873 | */ | |
874 | protected function logFiles() { | |
875 | $this->initDB(); | |
876 | ||
877 | $this->getInstalledFiles(WCF_DIR); | |
a2cf0cdb | 878 | $acpTemplateInserts = $fileInserts = []; |
158bd3ca | 879 | foreach (self::$installedFiles as $file) { |
a2cf0cdb | 880 | $match = []; |
46188698 | 881 | if (preg_match('~^acp/templates/([^/]+)\.tpl$~', $file, $match)) { |
158bd3ca TD |
882 | // acp template |
883 | $acpTemplateInserts[] = $match[1]; | |
884 | } | |
885 | else { | |
886 | // regular file | |
46188698 | 887 | $fileInserts[] = $file; |
158bd3ca TD |
888 | } |
889 | } | |
b7d92c45 | 890 | |
158bd3ca TD |
891 | // save acp template log |
892 | if (!empty($acpTemplateInserts)) { | |
893 | $sql = "INSERT INTO wcf".WCF_N."_acp_template | |
04727c8b MS |
894 | (templateName, application) |
895 | VALUES (?, ?)"; | |
158bd3ca TD |
896 | $statement = self::getDB()->prepareStatement($sql); |
897 | ||
dcd38d37 | 898 | self::getDB()->beginTransaction(); |
158bd3ca | 899 | foreach ($acpTemplateInserts as $acpTemplate) { |
a2cf0cdb | 900 | $statement->execute([$acpTemplate, 'wcf']); |
158bd3ca | 901 | } |
dcd38d37 | 902 | self::getDB()->commitTransaction(); |
158bd3ca | 903 | } |
b7d92c45 | 904 | |
158bd3ca TD |
905 | // save file log |
906 | if (!empty($fileInserts)) { | |
907 | $sql = "INSERT INTO wcf".WCF_N."_package_installation_file_log | |
04727c8b MS |
908 | (filename, application) |
909 | VALUES (?, ?)"; | |
158bd3ca TD |
910 | $statement = self::getDB()->prepareStatement($sql); |
911 | ||
dcd38d37 | 912 | self::getDB()->beginTransaction(); |
158bd3ca | 913 | foreach ($fileInserts as $file) { |
a2cf0cdb | 914 | $statement->execute([$file, 'wcf']); |
158bd3ca | 915 | } |
dcd38d37 | 916 | self::getDB()->commitTransaction(); |
158bd3ca | 917 | } |
b7d92c45 | 918 | |
158bd3ca TD |
919 | $this->gotoNextStep('installLanguage'); |
920 | } | |
921 | ||
922 | /** | |
923 | * Scans the given dir for installed files. | |
9f959ced MS |
924 | * |
925 | * @param string $dir | |
46188698 | 926 | * @throws SystemException |
158bd3ca TD |
927 | */ |
928 | protected function getInstalledFiles($dir) { | |
46188698 AE |
929 | $logFile = $dir . 'files.log'; |
930 | if (!file_exists($logFile)) { | |
931 | throw new SystemException("Expected a valid file log at '".$logFile."'."); | |
158bd3ca | 932 | } |
46188698 AE |
933 | |
934 | self::$installedFiles = explode("\n", file_get_contents($logFile)); | |
935 | ||
936 | @unlink($logFile); | |
158bd3ca TD |
937 | } |
938 | ||
939 | /** | |
940 | * Installs the selected languages. | |
941 | */ | |
942 | protected function installLanguage() { | |
943 | $this->initDB(); | |
944 | ||
945 | foreach (self::$selectedLanguages as $language) { | |
946 | // get language.xml file name | |
947 | $filename = TMP_DIR.'install/lang/'.$language.'.xml'; | |
948 | ||
949 | // check the file | |
950 | if (!file_exists($filename)) { | |
4fe0b42b | 951 | throw new SystemException("unable to find language file '".$filename."'"); |
158bd3ca TD |
952 | } |
953 | ||
954 | // open the file | |
955 | $xml = new XML(); | |
956 | $xml->load($filename); | |
957 | ||
958 | // import xml | |
959 | LanguageEditor::importFromXML($xml, 0); | |
960 | } | |
b7d92c45 | 961 | |
158bd3ca | 962 | // set default language |
61022658 AE |
963 | $language = LanguageFactory::getInstance()->getLanguageByCode(in_array(self::$selectedLanguageCode, self::$selectedLanguages) ? self::$selectedLanguageCode : self::$selectedLanguages[0]); |
964 | LanguageFactory::getInstance()->makeDefault($language->languageID); | |
158bd3ca | 965 | |
158bd3ca | 966 | // rebuild language cache |
b401cd0d | 967 | LanguageCacheBuilder::getInstance()->reset(); |
158bd3ca TD |
968 | |
969 | // go to next step | |
970 | $this->gotoNextStep('createUser'); | |
971 | } | |
972 | ||
973 | /** | |
974 | * Shows the page for creating the admin account. | |
975 | */ | |
976 | protected function createUser() { | |
977 | $errorType = $errorField = $username = $email = $confirmEmail = $password = $confirmPassword = ''; | |
978 | ||
979 | $username = ''; | |
980 | $email = $confirmEmail = ''; | |
981 | $password = $confirmPassword = ''; | |
982 | ||
983 | if (isset($_POST['send']) || self::$developerMode) { | |
984 | if (isset($_POST['send'])) { | |
39bea7dd MS |
985 | if (isset($_POST['username'])) $username = StringUtil::trim($_POST['username']); |
986 | if (isset($_POST['email'])) $email = StringUtil::trim($_POST['email']); | |
987 | if (isset($_POST['confirmEmail'])) $confirmEmail = StringUtil::trim($_POST['confirmEmail']); | |
988 | if (isset($_POST['password'])) $password = $_POST['password']; | |
989 | if (isset($_POST['confirmPassword'])) $confirmPassword = $_POST['confirmPassword']; | |
158bd3ca TD |
990 | } |
991 | else { | |
992 | $username = $password = $confirmPassword = 'root'; | |
25e094b6 | 993 | $email = $confirmEmail = 'wsc-developer-mode@example.com'; |
158bd3ca TD |
994 | } |
995 | ||
996 | // error handling | |
997 | try { | |
998 | // username | |
999 | if (empty($username)) { | |
1000 | throw new UserInputException('username'); | |
1001 | } | |
1002 | if (!UserUtil::isValidUsername($username)) { | |
063bbf46 | 1003 | throw new UserInputException('username', 'invalid'); |
158bd3ca TD |
1004 | } |
1005 | ||
1006 | // e-mail address | |
1007 | if (empty($email)) { | |
1008 | throw new UserInputException('email'); | |
1009 | } | |
1010 | if (!UserUtil::isValidEmail($email)) { | |
063bbf46 | 1011 | throw new UserInputException('email', 'invalid'); |
158bd3ca TD |
1012 | } |
1013 | ||
1014 | // confirm e-mail address | |
1015 | if ($email != $confirmEmail) { | |
1016 | throw new UserInputException('confirmEmail', 'notEqual'); | |
1017 | } | |
1018 | ||
1019 | // password | |
1020 | if (empty($password)) { | |
1021 | throw new UserInputException('password'); | |
1022 | } | |
1023 | ||
1024 | // confirm e-mail address | |
1025 | if ($password != $confirmPassword) { | |
1026 | throw new UserInputException('confirmPassword', 'notEqual'); | |
1027 | } | |
1028 | ||
1029 | // no errors | |
1030 | // init database connection | |
1031 | $this->initDB(); | |
1032 | ||
1033 | // get language id | |
1034 | $languageID = 0; | |
1035 | $sql = "SELECT languageID | |
1036 | FROM wcf".WCF_N."_language | |
1037 | WHERE languageCode = ?"; | |
1038 | $statement = self::getDB()->prepareStatement($sql); | |
a2cf0cdb | 1039 | $statement->execute([self::$selectedLanguageCode]); |
158bd3ca TD |
1040 | $row = $statement->fetchArray(); |
1041 | if (isset($row['languageID'])) $languageID = $row['languageID']; | |
1042 | ||
ba1a3ccf AE |
1043 | if (!$languageID) { |
1044 | $languageID = LanguageFactory::getInstance()->getDefaultLanguageID(); | |
1045 | } | |
1046 | ||
158bd3ca | 1047 | // create user |
a2cf0cdb AE |
1048 | $data = [ |
1049 | 'data' => [ | |
158bd3ca TD |
1050 | 'email' => $email, |
1051 | 'languageID' => $languageID, | |
1052 | 'password' => $password, | |
9e960e5f MW |
1053 | 'username' => $username, |
1054 | 'signatureEnableHtml' => 1 | |
a2cf0cdb AE |
1055 | ], |
1056 | 'groups' => [ | |
158bd3ca TD |
1057 | 1, |
1058 | 3, | |
1059 | 4 | |
a2cf0cdb AE |
1060 | ], |
1061 | 'languages' => [ | |
158bd3ca | 1062 | $languageID |
a2cf0cdb AE |
1063 | ] |
1064 | ]; | |
158bd3ca | 1065 | |
a2cf0cdb | 1066 | $userAction = new UserAction([], 'create', $data); |
158bd3ca TD |
1067 | $userAction->executeAction(); |
1068 | ||
1069 | // go to next step | |
1070 | $this->gotoNextStep('installPackages'); | |
1071 | exit; | |
1072 | } | |
1073 | catch (UserInputException $e) { | |
1074 | $errorField = $e->getField(); | |
1075 | $errorType = $e->getType(); | |
1076 | } | |
1077 | } | |
1078 | ||
a2cf0cdb | 1079 | WCF::getTPL()->assign([ |
158bd3ca TD |
1080 | 'errorField' => $errorField, |
1081 | 'errorType' => $errorType, | |
1082 | 'username' => $username, | |
1083 | 'email' => $email, | |
1084 | 'confirmEmail' => $confirmEmail, | |
1085 | 'password' => $password, | |
1086 | 'confirmPassword' => $confirmPassword, | |
1087 | 'nextStep' => 'createUser' | |
a2cf0cdb | 1088 | ]); |
158bd3ca TD |
1089 | WCF::getTPL()->display('stepCreateUser'); |
1090 | } | |
1091 | ||
1092 | /** | |
1093 | * Registers with wcf setup delivered packages in the package installation queue. | |
1094 | */ | |
1095 | protected function installPackages() { | |
1096 | // init database connection | |
1097 | $this->initDB(); | |
1098 | ||
1099 | // get admin account | |
1100 | $admin = new User(1); | |
1101 | ||
1102 | // get delivered packages | |
1103 | $wcfPackageFile = ''; | |
a2cf0cdb | 1104 | $otherPackages = []; |
158bd3ca TD |
1105 | $tar = new Tar(SETUP_FILE); |
1106 | foreach ($tar->getContentList() as $file) { | |
838e315b | 1107 | if ($file['type'] != 'folder' && mb_strpos($file['filename'], 'install/packages/') === 0) { |
158bd3ca | 1108 | $packageFile = basename($file['filename']); |
158bd3ca | 1109 | |
1d9fd26a AE |
1110 | // ignore any files which aren't an archive |
1111 | if (preg_match('~\.(tar\.gz|tgz|tar)$~', $packageFile)) { | |
1112 | $packageName = preg_replace('!\.(tar\.gz|tgz|tar)$!', '', $packageFile); | |
1113 | ||
1114 | if ($packageName == 'com.woltlab.wcf') { | |
1115 | $wcfPackageFile = $packageFile; | |
1116 | } | |
1117 | else { | |
1118 | $isStrato = (!empty($_SERVER['DOCUMENT_ROOT']) && (strpos($_SERVER['DOCUMENT_ROOT'], 'strato') !== false)); | |
1119 | if (!$isStrato && preg_match('!\.(tar\.gz|tgz)$!', $packageFile)) { | |
1120 | // try to unzip zipped package files | |
1121 | if (FileUtil::uncompressFile(TMP_DIR.'install/packages/'.$packageFile, TMP_DIR.'install/packages/'.$packageName.'.tar')) { | |
1122 | @unlink(TMP_DIR.'install/packages/'.$packageFile); | |
1123 | $packageFile = $packageName.'.tar'; | |
1124 | } | |
158bd3ca | 1125 | } |
1d9fd26a AE |
1126 | |
1127 | $otherPackages[$packageName] = $packageFile; | |
158bd3ca | 1128 | } |
158bd3ca TD |
1129 | } |
1130 | } | |
1131 | } | |
1132 | $tar->close(); | |
1133 | ||
d3dba552 MW |
1134 | // delete install files |
1135 | $installPhpDeleted = @unlink('./install.php'); | |
1136 | @unlink('./test.php'); | |
1137 | $wcfSetupTarDeleted = @unlink('./WCFSetup.tar.gz'); | |
1138 | ||
1139 | // render page | |
1140 | WCF::getTPL()->assign([ | |
1141 | 'installPhpDeleted' => $installPhpDeleted, | |
1142 | 'wcfSetupTarDeleted' => $wcfSetupTarDeleted | |
1143 | ]); | |
1144 | $output = WCF::getTPL()->fetch('stepInstallPackages'); | |
1145 | ||
158bd3ca TD |
1146 | // register packages in queue |
1147 | // get new process id | |
1148 | $sql = "SELECT MAX(processNo) AS processNo | |
1149 | FROM wcf".WCF_N."_package_installation_queue"; | |
1150 | $statement = self::getDB()->prepareStatement($sql); | |
1151 | $statement->execute(); | |
1152 | $result = $statement->fetchArray(); | |
1153 | $processNo = intval($result['processNo']) + 1; | |
158bd3ca TD |
1154 | |
1155 | // search existing wcf package | |
1156 | $sql = "SELECT COUNT(*) AS count | |
1157 | FROM wcf".WCF_N."_package | |
1158 | WHERE package = 'com.woltlab.wcf'"; | |
1159 | $statement = self::getDB()->prepareStatement($sql); | |
1160 | $statement->execute(); | |
5c6ddd85 | 1161 | if (!$statement->fetchSingleColumn()) { |
158bd3ca | 1162 | if (empty($wcfPackageFile)) { |
4fe0b42b | 1163 | throw new SystemException('the essential package com.woltlab.wcf is missing.'); |
158bd3ca TD |
1164 | } |
1165 | ||
1166 | // register essential wcf package | |
a2cf0cdb | 1167 | $queue = PackageInstallationQueueEditor::create([ |
f287c18d AE |
1168 | 'processNo' => $processNo, |
1169 | 'userID' => $admin->userID, | |
d7281d46 | 1170 | 'package' => 'com.woltlab.wcf', |
e71525e4 | 1171 | 'packageName' => 'WoltLab Suite Core', |
aa00db68 AE |
1172 | 'archive' => TMP_DIR.'install/packages/'.$wcfPackageFile, |
1173 | 'isApplication' => 1 | |
a2cf0cdb | 1174 | ]); |
158bd3ca TD |
1175 | } |
1176 | ||
1177 | // register all other delivered packages | |
1178 | asort($otherPackages); | |
1179 | foreach ($otherPackages as $packageName => $packageFile) { | |
d7281d46 AE |
1180 | // extract packageName from archive's package.xml |
1181 | $archive = new PackageArchive(TMP_DIR.'install/packages/'.$packageFile); | |
1182 | try { | |
1183 | $archive->openArchive(); | |
1184 | } | |
1185 | catch (\Exception $e) { | |
8e2c12f5 AE |
1186 | // we've encountered a broken archive, revert everything and then fail |
1187 | $sql = "SELECT queueID, parentQueueID | |
1188 | FROM wcf".WCF_N."_package_installation_queue"; | |
1189 | $statement = WCF::getDB()->prepareStatement($sql); | |
1190 | $statement->execute(); | |
0557bb04 | 1191 | $queues = $statement->fetchMap('queueID', 'parentQueueID'); |
8e2c12f5 | 1192 | |
a2cf0cdb | 1193 | $queueIDs = []; |
e0d32362 | 1194 | /** @noinspection PhpUndefinedVariableInspection */ |
8e2c12f5 AE |
1195 | $queueID = $queue->queueID; |
1196 | while ($queueID) { | |
1197 | $queueIDs[] = $queueID; | |
1198 | ||
63b9817b | 1199 | $queueID = isset($queues[$queueID]) ? $queues[$queueID] : 0; |
8e2c12f5 AE |
1200 | } |
1201 | ||
1202 | // remove previously created queues | |
1203 | if (!empty($queueIDs)) { | |
1204 | $sql = "DELETE FROM wcf".WCF_N."_package_installation_queue | |
1205 | WHERE queueID = ?"; | |
1206 | $statement = WCF::getDB()->prepareStatement($sql); | |
1207 | WCF::getDB()->beginTransaction(); | |
1208 | foreach ($queueIDs as $queueID) { | |
a2cf0cdb | 1209 | $statement->execute([$queueID]); |
8e2c12f5 AE |
1210 | } |
1211 | WCF::getDB()->commitTransaction(); | |
1212 | } | |
1213 | ||
1214 | // remove package files | |
1215 | @unlink(TMP_DIR.'install/packages/'.$wcfPackageFile); | |
62e40a61 MS |
1216 | foreach ($otherPackages as $otherPackageFile) { |
1217 | @unlink(TMP_DIR.'install/packages/'.$otherPackageFile); | |
8e2c12f5 AE |
1218 | } |
1219 | ||
1220 | // throw exception again | |
1221 | throw new SystemException('', 0, '', $e); | |
d7281d46 AE |
1222 | } |
1223 | ||
e0d32362 | 1224 | /** @noinspection PhpUndefinedVariableInspection */ |
a2cf0cdb | 1225 | $queue = PackageInstallationQueueEditor::create([ |
89142da9 | 1226 | 'parentQueueID' => $queue->queueID, |
f287c18d AE |
1227 | 'processNo' => $processNo, |
1228 | 'userID' => $admin->userID, | |
d7281d46 | 1229 | 'package' => $packageName, |
a2ad7897 | 1230 | 'packageName' => $archive->getLocalizedPackageInfo('packageName'), |
aa00db68 AE |
1231 | 'archive' => TMP_DIR.'install/packages/'.$packageFile, |
1232 | 'isApplication' => 1 | |
a2cf0cdb | 1233 | ]); |
158bd3ca TD |
1234 | } |
1235 | ||
6df5f8b8 AE |
1236 | // determine the (randomized) cookie prefix |
1237 | $useRandomCookiePrefix = true; | |
1238 | if (self::$developerMode && DevtoolsSetup::getInstance()->forceStaticCookiePrefix()) { | |
1239 | $useRandomCookiePrefix = false; | |
1240 | } | |
1241 | ||
58decc14 | 1242 | $prefix = 'wsc31_'; |
6df5f8b8 | 1243 | if ($useRandomCookiePrefix) { |
ccdc9a24 AE |
1244 | $cookieNames = array_keys($_COOKIE); |
1245 | while (true) { | |
1246 | $prefix = 'wsc_' . substr(sha1(mt_rand()), 0, 6) . '_'; | |
1247 | $isValid = true; | |
1248 | foreach ($cookieNames as $cookieName) { | |
1249 | if (strpos($cookieName, $prefix) === 0) { | |
1250 | $isValid = false; | |
1251 | break; | |
1252 | } | |
1253 | } | |
1254 | ||
1255 | if ($isValid) { | |
1256 | break; | |
1257 | } | |
1258 | } | |
1259 | ||
1260 | // the options have not been imported yet | |
1261 | file_put_contents(WCF_DIR . 'cookiePrefix.txt', $prefix); | |
1262 | } | |
1263 | ||
158bd3ca | 1264 | // login as admin |
ccdc9a24 | 1265 | define('COOKIE_PREFIX', $prefix); |
2114900f | 1266 | |
f341086b | 1267 | $factory = new ACPSessionFactory(); |
158bd3ca TD |
1268 | $factory->load(); |
1269 | ||
1270 | SessionHandler::getInstance()->changeUser($admin); | |
1271 | SessionHandler::getInstance()->register('masterPassword', 1); | |
5d6ddb2e | 1272 | SessionHandler::getInstance()->register('__wcfSetup_developerMode', self::$developerMode); |
a2cf0cdb | 1273 | SessionHandler::getInstance()->register('__wcfSetup_directories', self::$directories); |
3c93f269 | 1274 | SessionHandler::getInstance()->unregister('__changeSessionID'); |
158bd3ca TD |
1275 | SessionHandler::getInstance()->update(); |
1276 | ||
158bd3ca | 1277 | // print page |
d3dba552 MW |
1278 | HeaderUtil::sendHeaders(); |
1279 | echo $output; | |
158bd3ca TD |
1280 | |
1281 | // delete tmp files | |
364a2e49 | 1282 | $directory = TMP_DIR.'/'; |
ac9b0f6e | 1283 | DirectoryUtil::getInstance($directory)->removePattern(new Regex('\.tar(\.gz)?$'), true); |
158bd3ca TD |
1284 | } |
1285 | ||
1286 | /** | |
1287 | * Goes to the next step. | |
9f959ced | 1288 | * |
158bd3ca TD |
1289 | * @param string $nextStep |
1290 | */ | |
1291 | protected function gotoNextStep($nextStep) { | |
a2cf0cdb | 1292 | WCF::getTPL()->assign(['nextStep' => $nextStep]); |
158bd3ca TD |
1293 | WCF::getTPL()->display('stepNext'); |
1294 | } | |
1295 | ||
1296 | /** | |
1297 | * Installs the files of the tar archive. | |
1298 | */ | |
1299 | protected static function installFiles() { | |
46188698 AE |
1300 | $fileHandler = new SetupFileHandler(); |
1301 | new Installer(self::$directories['wcf'], SETUP_FILE, $fileHandler, 'install/files/'); | |
1302 | $fileHandler->dumpToFile(self::$directories['wcf'] . 'files.log'); | |
158bd3ca TD |
1303 | } |
1304 | ||
1305 | /** | |
ec1b3610 | 1306 | * Reads the package names of the bundled applications in WCFSetup.tar.gz. |
158bd3ca | 1307 | */ |
359841c3 | 1308 | protected static function getPackageNames() { |
158bd3ca | 1309 | // get package name |
359841c3 | 1310 | $packageNames = []; |
158bd3ca TD |
1311 | $tar = new Tar(SETUP_FILE); |
1312 | foreach ($tar->getContentList() as $file) { | |
838e315b | 1313 | if ($file['type'] != 'folder' && mb_strpos($file['filename'], 'install/packages/') === 0) { |
158bd3ca | 1314 | $packageFile = basename($file['filename']); |
158bd3ca | 1315 | |
359841c3 MW |
1316 | try { |
1317 | $archive = new PackageArchive(TMP_DIR.'install/packages/'.$packageFile); | |
1318 | $archive->openArchive(); | |
1319 | $packageNames[] = $archive->getLocalizedPackageInfo('packageName'); | |
1320 | $archive->getTar()->close(); | |
158bd3ca | 1321 | } |
359841c3 | 1322 | catch (SystemException $e) {} |
158bd3ca TD |
1323 | } |
1324 | } | |
1325 | $tar->close(); | |
1326 | ||
359841c3 MW |
1327 | sort($packageNames); |
1328 | ||
158bd3ca | 1329 | // assign package name |
359841c3 | 1330 | WCF::getTPL()->assign(['setupPackageNames' => $packageNames]); |
158bd3ca TD |
1331 | } |
1332 | } |