Merge branch '3.0'
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / WCFSetup.class.php
CommitLineData
158bd3ca
TD
1<?php
2namespace wcf\system;
3use wcf\data\language\LanguageEditor;
4use wcf\data\language\SetupLanguage;
89142da9 5use wcf\data\package\installation\queue\PackageInstallationQueueEditor;
a2cf0cdb 6use wcf\data\package\Package;
158bd3ca
TD
7use wcf\data\user\User;
8use wcf\data\user\UserAction;
b401cd0d 9use wcf\system\cache\builder\LanguageCacheBuilder;
1ca3de4d 10use wcf\system\database\exception\DatabaseException;
158bd3ca 11use wcf\system\database\util\SQLParser;
157054c9 12use wcf\system\database\MySQLDatabase;
f8d85495 13use wcf\system\devtools\DevtoolsSetup;
158bd3ca 14use wcf\system\exception\SystemException;
8a81a7bd 15use wcf\system\exception\UserInputException;
158bd3ca
TD
16use wcf\system\io\File;
17use wcf\system\io\Tar;
18use wcf\system\language\LanguageFactory;
19use wcf\system\package\PackageArchive;
20use wcf\system\session\ACPSessionFactory;
21use wcf\system\session\SessionHandler;
22use wcf\system\setup\Installer;
46188698 23use wcf\system\setup\SetupFileHandler;
158bd3ca 24use wcf\system\template\SetupTemplateEngine;
364a2e49 25use wcf\util\DirectoryUtil;
158bd3ca 26use wcf\util\FileUtil;
d3dba552 27use wcf\util\HeaderUtil;
158bd3ca
TD
28use wcf\util\StringUtil;
29use wcf\util\UserUtil;
30use wcf\util\XML;
31
32// define
6840f856 33define('PACKAGE_ID', 0);
158bd3ca
TD
34define('HTTP_ENABLE_GZIP', 0);
35define('HTTP_GZIP_LEVEL', 0);
b23a245a 36define('HTTP_SEND_X_FRAME_OPTIONS', 0);
158bd3ca 37define('CACHE_SOURCE_TYPE', 'disk');
158bd3ca 38define('MODULE_MASTER_PASSWORD', 1);
0c07c50c 39define('ENABLE_DEBUG_MODE', 1);
8a12b687 40define('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 */
50class 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}