3 use wcf\data\language\LanguageEditor
;
4 use wcf\data\language\SetupLanguage
;
5 use wcf\data\package\installation\queue\PackageInstallationQueueEditor
;
6 use wcf\data\package\Package
;
7 use wcf\data\user\User
;
8 use wcf\data\user\UserAction
;
9 use wcf\system\cache\builder\LanguageCacheBuilder
;
10 use wcf\system\database\util\SQLParser
;
11 use wcf\system\database\MySQLDatabase
;
12 use wcf\system\exception\SystemException
;
13 use wcf\system\exception\UserInputException
;
14 use wcf\system\io\File
;
15 use wcf\system\io\Tar
;
16 use wcf\system\language\LanguageFactory
;
17 use wcf\system\package\PackageArchive
;
18 use wcf\system\request\RouteHandler
;
19 use wcf\system\session\ACPSessionFactory
;
20 use wcf\system\session\SessionHandler
;
21 use wcf\system\setup\Installer
;
22 use wcf\system\template\SetupTemplateEngine
;
23 use wcf\util\DirectoryUtil
;
24 use wcf\util\FileUtil
;
25 use wcf\util\StringUtil
;
26 use wcf\util\UserUtil
;
30 define('PACKAGE_ID', 0);
31 define('HTTP_ENABLE_NO_CACHE_HEADERS', 0);
32 define('HTTP_ENABLE_GZIP', 0);
33 define('HTTP_GZIP_LEVEL', 0);
34 define('HTTP_SEND_X_FRAME_OPTIONS', 0);
35 define('CACHE_SOURCE_TYPE', 'disk');
36 define('MODULE_MASTER_PASSWORD', 1);
37 define('ENABLE_DEBUG_MODE', 1);
38 define('ENABLE_BENCHMARK', 0);
41 * Executes the installation of the basic WCF systems.
44 * @copyright 2001-2016 WoltLab GmbH
45 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
46 * @package com.woltlab.wcf
48 * @category Community Framework
50 class WCFSetup
extends WCF
{
52 * list of available languages
55 protected static $availableLanguages = [];
58 * installation directories
61 protected static $directories = [];
64 * language code of selected installation language
67 protected static $selectedLanguageCode = 'en';
70 * selected languages to be installed
73 protected static $selectedLanguages = [];
76 * list of installed files
79 protected static $installedFiles = [];
82 * name of installed primary application
85 protected static $setupPackageName = 'WoltLab Community Framework';
88 * indicates if developer mode is used to install
91 protected static $developerMode = 0;
93 /** @noinspection PhpMissingParentConstructorInspection */
95 * Calls all init functions of the WCFSetup class and starts the setup process.
97 public function __construct() {
100 $this->getDeveloperMode();
101 $this->getLanguageSelection();
102 $this->getInstallationDirectories();
103 $this->initLanguage();
105 /** @noinspection PhpUndefinedMethodInspection */
106 self
::getLanguage()->loadLanguage();
107 $this->getPackageName();
114 * Gets the status of the developer mode.
116 protected static function getDeveloperMode() {
117 if (isset($_GET['dev'])) self
::$developerMode = intval($_GET['dev']);
118 else if (isset($_POST['dev'])) self
::$developerMode = intval($_POST['dev']);
122 * Gets the selected language.
124 protected static function getLanguageSelection() {
125 self
::$availableLanguages = self
::getAvailableLanguages();
127 if (isset($_REQUEST['languageCode']) && isset(self
::$availableLanguages[$_REQUEST['languageCode']])) {
128 self
::$selectedLanguageCode = $_REQUEST['languageCode'];
131 self
::$selectedLanguageCode = LanguageFactory
::getPreferredLanguage(array_keys(self
::$availableLanguages), self
::$selectedLanguageCode);
134 if (isset($_POST['selectedLanguages']) && is_array($_POST['selectedLanguages'])) {
135 self
::$selectedLanguages = $_POST['selectedLanguages'];
140 * Gets the selected wcf dir from request.
144 protected static function getInstallationDirectories() {
145 if (self
::$developerMode && isset($_ENV['WCFSETUP_USEDEFAULTWCFDIR'])) {
146 if (!isset($_REQUEST['directories']) ||
!is_array($_REQUEST['directories'])) $_REQUEST['directories'] = [];
147 $_REQUEST['directories']['wcf'] = FileUtil
::unifyDirSeparator(INSTALL_SCRIPT_DIR
).'wcf/';
150 if (!empty($_REQUEST['directories']) && is_array($_REQUEST['directories'])) {
151 foreach ($_REQUEST['directories'] as $application => $directory) {
152 self
::$directories[$application] = $directory;
154 if ($application === 'wcf' && @file_exists
(self
::$directories['wcf'])) {
155 define('RELATIVE_WCF_DIR', FileUtil
::getRelativePath(INSTALL_SCRIPT_DIR
, self
::$directories['wcf']));
160 define('WCF_DIR', (isset(self
::$directories['wcf']) ? self
::$directories['wcf'] : ''));
164 * Initialises the language engine.
166 protected function initLanguage() {
168 mb_internal_encoding('UTF-8');
169 if (function_exists('mb_regex_encoding')) mb_regex_encoding('UTF-8');
172 // init setup language
173 self
::$languageObj = new SetupLanguage(null, ['languageCode' => self
::$selectedLanguageCode]);
177 * Initialises the template engine.
179 protected function initTPL() {
180 self
::$tplObj = SetupTemplateEngine
::getInstance();
181 self
::getTPL()->setLanguageID((self
::$selectedLanguageCode == 'en' ?
0 : 1));
182 self
::getTPL()->setCompileDir(TMP_DIR
);
183 self
::getTPL()->addApplication('wcf', TMP_DIR
);
184 self
::getTPL()->registerPrefilter(['lang']);
185 self
::getTPL()->assign([
187 'tmpFilePrefix' => TMP_FILE_PREFIX
,
188 'languageCode' => self
::$selectedLanguageCode,
189 'selectedLanguages' => self
::$selectedLanguages,
190 'directories' => self
::$directories,
191 'developerMode' => self
::$developerMode
196 * Returns all languages from WCFSetup.tar.gz.
200 protected static function getAvailableLanguages() {
201 $languages = $match = [];
202 foreach (glob(TMP_DIR
.'setup/lang/*.xml') as $file) {
205 $languageCode = LanguageEditor
::readLanguageCodeFromXML($xml);
206 $languageName = LanguageEditor
::readLanguageNameFromXML($xml);
208 $languages[$languageCode] = $languageName;
211 // sort languages by language name
218 * Calculates the current state of the progress bar.
220 * @param integer $currentStep
222 protected function calcProgress($currentStep) {
223 // calculate progress
224 $progress = round((100 / 18) * ++
$currentStep, 0);
225 self
::getTPL()->assign(['progress' => $progress]);
229 * Executes the setup steps.
231 protected function setup() {
233 if (isset($_REQUEST['step'])) $step = $_REQUEST['step'];
234 else $step = 'selectSetupLanguage';
236 // execute current step
238 /** @noinspection PhpMissingBreakStatementInspection */
239 case 'selectSetupLanguage':
240 if (!self
::$developerMode) {
241 $this->calcProgress(0);
242 $this->selectSetupLanguage();
246 /** @noinspection PhpMissingBreakStatementInspection */
248 if (!self
::$developerMode) {
249 $this->calcProgress(1);
250 $this->showLicense();
254 /** @noinspection PhpMissingBreakStatementInspection */
255 case 'showSystemRequirements':
256 if (!self
::$developerMode) {
257 $this->calcProgress(2);
258 $this->showSystemRequirements();
262 /** @noinspection PhpMissingBreakStatementInspection */
263 case 'configureDirectories':
264 if (!self
::$developerMode ||
!isset($_ENV['WCFSETUP_USEDEFAULTWCFDIR'])) {
265 $this->calcProgress(3);
266 $this->configureDirectories();
271 $this->calcProgress(4);
275 case 'selectLanguages':
276 $this->calcProgress(5);
277 $this->selectLanguages();
281 $this->calcProgress(6);
282 $this->configureDB();
287 if (isset($_POST['offset'])) {
288 $currentStep +
= intval($_POST['offset']);
291 $this->calcProgress($currentStep);
296 $this->calcProgress(14);
300 case 'installLanguage':
301 $this->calcProgress(15);
302 $this->installLanguage();
306 $this->calcProgress(16);
310 case 'installPackages':
311 $this->calcProgress(17);
312 $this->installPackages();
318 * Shows the first setup page.
320 protected function selectSetupLanguage() {
321 WCF
::getTPL()->assign([
322 'availableLanguages' => self
::$availableLanguages,
323 'nextStep' => 'showLicense'
325 WCF
::getTPL()->display('stepSelectSetupLanguage');
329 * Shows the license agreement.
331 protected function showLicense() {
332 if (isset($_POST['send'])) {
333 if (isset($_POST['accepted'])) {
334 $this->gotoNextStep('showSystemRequirements');
338 WCF
::getTPL()->assign(['missingAcception' => true]);
343 if (file_exists(TMP_DIR
.'setup/license/license_'.self
::$selectedLanguageCode.'.txt')) {
344 $license = file_get_contents(TMP_DIR
.'setup/license/license_'.self
::$selectedLanguageCode.'.txt');
347 $license = file_get_contents(TMP_DIR
.'setup/license/license_en.txt');
350 WCF
::getTPL()->assign([
351 'license' => $license,
352 'nextStep' => 'showLicense'
354 WCF
::getTPL()->display('stepShowLicense');
358 * Shows the system requirements.
360 protected function showSystemRequirements() {
364 $system['phpVersion']['value'] = phpversion();
365 $comparePhpVersion = preg_replace('/^(\d+\.\d+\.\d+).*$/', '\\1', $system['phpVersion']['value']);
366 $system['phpVersion']['result'] = (version_compare($comparePhpVersion, '5.5.4') >= 0);
369 $system['sql']['result'] = MySQLDatabase
::isSupported();
371 // upload_max_filesize
372 $system['uploadMaxFilesize']['value'] = ini_get('upload_max_filesize');
373 $system['uploadMaxFilesize']['result'] = (intval($system['uploadMaxFilesize']['value']) > 0);
376 $system['gdLib']['value'] = '0.0.0';
377 if (function_exists('gd_info')) {
380 if (preg_match('!([0-9]+\.[0-9]+(?:\.[0-9]+)?)!', $temp['GD Version'], $match)) {
381 if (preg_match('/^[0-9]+\.[0-9]+$/', $match[1])) $match[1] .= '.0';
382 $system['gdLib']['value'] = $match[1];
385 $system['gdLib']['result'] = (version_compare($system['gdLib']['value'], '2.0.0') >= 0);
388 $system['memoryLimit']['value'] = ini_get('memory_limit');
389 $system['memoryLimit']['result'] = $this->compareMemoryLimit();
391 WCF
::getTPL()->assign([
393 'nextStep' => 'configureDirectories'
395 WCF
::getTPL()->display('stepShowSystemRequirements');
399 * Returns true if memory_limit is set to at least 128 MB
403 protected function compareMemoryLimit() {
404 $memoryLimit = ini_get('memory_limit');
407 if ($memoryLimit == -1) {
411 // completely numeric, PHP assumes byte
412 if (is_numeric($memoryLimit)) {
413 $memoryLimit = $memoryLimit / 1024 / 1024;
414 return ($memoryLimit >= 128);
417 // PHP supports 'K', 'M' and 'G' shorthand notation
418 if (preg_match('~^(\d+)([KMG])$~', $memoryLimit, $matches)) {
419 switch ($matches[2]) {
421 $memoryLimit = $matches[1] * 1024;
422 return ($memoryLimit >= 128);
426 return ($matches[1] >= 128);
430 return ($matches[1] >= 1);
439 * Searches the wcf dir.
443 protected function configureDirectories() {
444 // get available packages
445 $applications = $packages = [];
446 foreach (glob(TMP_DIR
. 'install/packages/*') as $file) {
447 $filename = basename($file);
448 if (preg_match('~\.(?:tar|tar\.gz|tgz)$~', $filename)) {
449 $package = new PackageArchive($file);
450 $package->openArchive();
452 $application = Package
::getAbbreviation($package->getPackageInfo('name'));
454 $applications[] = $application;
455 $packages[$application] = [
456 'directory' => ($package->getPackageInfo('applicationDirectory') ?
: $application),
457 'packageDescription' => $package->getLocalizedPackageInfo('packageDescription'),
458 'packageName' => $package->getLocalizedPackageInfo('packageName')
464 uasort($packages, function($a, $b) {
465 return strcmp($a['packageName'], $b['packageName']);
468 // force cms being shown first
469 $showOrder = ['wcf'];
470 foreach (array_keys($packages) as $application) {
471 if ($application !== 'wcf') $showOrder[] = $application;
474 $documentRoot = FileUtil
::unifyDirSeparator($_SERVER['DOCUMENT_ROOT']);
476 if (!empty(self
::$directories)) {
477 $applicationPaths = $knownPaths = [];
479 // use $showOrder instead of $applications to ensure that the error message for
480 // duplicate directories will trigger in display order rather than the random
481 // sort order returned by glob() above
482 foreach ($showOrder as $application) {
483 $path = FileUtil
::getRealPath($documentRoot . '/' . FileUtil
::addTrailingSlash(FileUtil
::removeLeadingSlash(self
::$directories[$application])));
484 if (strpos($path, $documentRoot) !== 0) {
485 // verify that given path is still within the current document root
486 $errors[$application] = 'outsideDocumentRoot';
488 else if (in_array($path, $knownPaths)) {
489 // prevent the same path for two or more applications
490 $errors[$application] = 'duplicate';
492 else if (@is_file
($path . 'global.php')) {
493 // check if directory is empty (dotfiles are okay)
494 $errors[$application] = 'notEmpty';
497 // try to create directory if it does not exist
498 if (!is_dir($path) && !FileUtil
::makePath($path)) {
499 $errors[$application] = 'makePath';
503 FileUtil
::makeWritable($path);
505 catch (SystemException
$e) {
506 $errors[$application] = 'makeWritable';
510 $applicationPaths[$application] = $path;
511 $knownPaths[] = $path;
514 if (empty($errors)) {
515 // copy over the actual paths
516 self
::$directories = array_merge(self
::$directories, $applicationPaths);
517 WCF
::getTPL()->assign(['directories' => self
::$directories]);
524 // resolve path relative to document root
525 $relativePath = str_replace(FileUtil
::unifyDirSeparator($_SERVER['DOCUMENT_ROOT']), '', FileUtil
::unifyDirSeparator(INSTALL_SCRIPT_DIR
));
526 foreach ($packages as $application => $packageData) {
527 self
::$directories[$application] = $relativePath . ($application === 'wcf' ?
'' : $packageData['directory'] . '/');
531 WCF
::getTPL()->assign([
532 'directories' => self
::$directories,
533 'documentRoot' => $documentRoot,
535 'installScriptDir' => FileUtil
::unifyDirSeparator(INSTALL_SCRIPT_DIR
),
536 'nextStep' => 'configureDirectories', // call this step again to validate paths
537 'packages' => $packages,
538 'showOrder' => $showOrder
541 WCF
::getTPL()->display('stepConfigureDirectories');
545 * Unzips the files of the wcfsetup tar archive.
547 protected function unzipFiles() {
548 // WCF seems to be installed, abort
549 if (@is_file
(self
::$directories['wcf'].'lib/system/WCF.class.php')) {
550 throw new SystemException('Target directory seems to be an existing installation of WCF, unable to continue.');
552 // WCF not yet installed, install files first
554 $this->installFiles();
556 $this->gotoNextStep('selectLanguages');
561 * Shows the page for choosing the installed languages.
563 protected function selectLanguages() {
564 $errorField = $errorType = '';
566 // skip step in developer mode
567 // select all available languages automatically
568 if (self
::$developerMode) {
569 self
::$selectedLanguages = [];
570 foreach (self
::$availableLanguages as $languageCode => $language) {
571 self
::$selectedLanguages[] = $languageCode;
574 self
::getTPL()->assign(['selectedLanguages' => self
::$selectedLanguages]);
575 $this->gotoNextStep('configureDB');
579 // start error handling
580 if (isset($_POST['send'])) {
582 // no languages selected
583 if (empty(self
::$selectedLanguages)) {
584 throw new UserInputException('selectedLanguages');
588 foreach (self
::$selectedLanguages as $language) {
589 if (!isset(self
::$availableLanguages[$language])) {
590 throw new UserInputException('selectedLanguages');
596 $this->gotoNextStep('configureDB');
599 catch (UserInputException
$e) {
600 $errorField = $e->getField();
601 $errorType = $e->getType();
605 self
::$selectedLanguages[] = self
::$selectedLanguageCode;
606 WCF
::getTPL()->assign(['selectedLanguages' => self
::$selectedLanguages]);
609 WCF
::getTPL()->assign([
610 'errorField' => $errorField,
611 'errorType' => $errorType,
612 'availableLanguages' => self
::$availableLanguages,
613 'nextStep' => 'selectLanguages'
615 WCF
::getTPL()->display('stepSelectLanguages');
619 * Shows the page for configurating the database connection.
621 protected function configureDB() {
622 if (self
::$developerMode && isset($_ENV['WCFSETUP_DBHOST'])) {
623 $dbHost = $_ENV['WCFSETUP_DBHOST'];
624 $dbUser = $_ENV['WCFSETUP_DBUSER'];
625 $dbPassword = $_ENV['WCFSETUP_DBPASSWORD'];
626 $dbName = $_ENV['WCFSETUP_DBNAME'];
630 $dbHost = 'localhost';
637 if (isset($_POST['send']) ||
(self
::$developerMode && isset($_ENV['WCFSETUP_DBHOST']))) {
638 if (isset($_POST['dbHost'])) $dbHost = $_POST['dbHost'];
639 if (isset($_POST['dbUser'])) $dbUser = $_POST['dbUser'];
640 if (isset($_POST['dbPassword'])) $dbPassword = $_POST['dbPassword'];
641 if (isset($_POST['dbName'])) $dbName = $_POST['dbName'];
643 // ensure that $dbNumber is zero or a positive integer
644 if (isset($_POST['dbNumber'])) $dbNumber = max(0, intval($_POST['dbNumber']));
648 if (preg_match('/^(.+?):(\d+)$/', $dbHost, $match)) {
650 $dbPort = intval($match[2]);
655 // check connection data
656 /** @var \wcf\system\database\Database $db */
657 $db = new MySQLDatabase($dbHost, $dbUser, $dbPassword, $dbName, $dbPort, true);
661 $sqlVersion = $db->getVersion();
662 $compareSQLVersion = preg_replace('/^(\d+\.\d+\.\d+).*$/', '\\1', $sqlVersion);
663 if (stripos($sqlVersion, 'MariaDB')) {
665 if (!(version_compare($compareSQLVersion, '10.0.22') >= 0)) {
666 throw new SystemException("Insufficient MariaDB version '".$compareSQLVersion."'. Version '10.0.22' or greater is needed.");
671 if (!(version_compare($compareSQLVersion, '5.5.35') >= 0)) {
672 throw new SystemException("Insufficient MySQL version '".$compareSQLVersion."'. Version '5.5.35' or greater is needed.");
676 // check innodb support
677 $sql = "SHOW ENGINES";
678 $statement = $db->prepareStatement($sql);
679 $statement->execute();
681 while ($row = $statement->fetchArray()) {
682 if ($row['Engine'] == 'InnoDB' && in_array($row['Support'], ['DEFAULT', 'YES'])) {
689 throw new SystemException("Support for InnoDB is missing.");
692 // check for table conflicts
693 $conflictedTables = $this->getConflictedTables($db, $dbNumber);
696 if (empty($conflictedTables)) {
697 // connection successfully established
698 // write configuration to config.inc.php
699 $file = new File(WCF_DIR
.'config.inc.php');
700 $file->write("<?php\n");
701 $file->write("\$dbHost = '".str_replace("'", "\\'", $dbHost)."';\n");
702 $file->write("\$dbPort = ".$dbPort.";\n");
703 $file->write("\$dbUser = '".str_replace("'", "\\'", $dbUser)."';\n");
704 $file->write("\$dbPassword = '".str_replace("'", "\\'", $dbPassword)."';\n");
705 $file->write("\$dbName = '".str_replace("'", "\\'", $dbName)."';\n");
706 $file->write("if (!defined('WCF_N')) define('WCF_N', $dbNumber);\n");
710 $this->gotoNextStep('createDB');
713 // show configure template again
715 WCF
::getTPL()->assign(['conflictedTables' => $conflictedTables]);
718 catch (SystemException
$e) {
719 WCF
::getTPL()->assign(['exception' => $e]);
722 WCF
::getTPL()->assign([
725 'dbPassword' => $dbPassword,
727 'dbNumber' => $dbNumber,
728 'nextStep' => 'configureDB'
730 WCF
::getTPL()->display('stepConfigureDB');
734 * Checks if in the chosen database are tables in conflict with the wcf tables
735 * which will be created in the next step.
737 * @param \wcf\system\database\Database $db
738 * @param integer $dbNumber
739 * @return string[] list of already existing tables
741 protected function getConflictedTables($db, $dbNumber) {
742 // get content of the sql structure file
743 $sql = file_get_contents(TMP_DIR
.'setup/db/install.sql');
745 // installation number value 'n' (WCF_N) must be reflected in the executed sql queries
746 $sql = str_replace('wcf1_', 'wcf'.$dbNumber.'_', $sql);
748 // get all tablenames which should be created
749 preg_match_all("%CREATE\s+TABLE\s+(\w+)%", $sql, $matches);
751 // get all installed tables from chosen database
752 $existingTables = $db->getEditor()->getTableNames();
754 // check if existing tables are in conflict with wcf tables
755 $conflictedTables = [];
756 foreach ($existingTables as $existingTableName) {
757 foreach ($matches[1] as $wcfTableName) {
758 if ($existingTableName == $wcfTableName) {
759 $conflictedTables[] = $wcfTableName;
763 return $conflictedTables;
767 * Creates the database structure of the wcf.
769 protected function createDB() {
772 // get content of the sql structure file
773 $sql = file_get_contents(TMP_DIR
.'setup/db/install.sql');
776 $sqlData = explode('/* SQL_PARSER_OFFSET */', $sql);
777 $offset = (isset($_POST['offset'])) ?
intval($_POST['offset']) : 0;
778 if (!isset($sqlData[$offset])) {
779 throw new SystemException("Offset for SQL parser is out of bounds, ".$offset." was requested, but there are only ".count($sqlData)." sections");
781 $sql = $sqlData[$offset];
783 // installation number value 'n' (WCF_N) must be reflected in the executed sql queries
784 $sql = str_replace('wcf1_', 'wcf'.WCF_N
.'_', $sql);
786 // execute sql queries
787 $parser = new SQLParser($sql);
791 preg_match_all("~CREATE\s+TABLE\s+(\w+)~i", $sql, $matches);
793 if (!empty($matches[1])) {
794 $sql = "INSERT INTO wcf".WCF_N
."_package_installation_sql_log
797 $statement = self
::getDB()->prepareStatement($sql);
798 foreach ($matches[1] as $tableName) {
799 $statement->execute([$tableName]);
803 if ($offset < (count($sqlData) - 1)) {
804 WCF
::getTPL()->assign([
805 '__additionalParameters' => [
806 'offset' => $offset +
1
810 $this->gotoNextStep('createDB');
814 * Manually install PIPPackageInstallationPlugin since install.sql content is not escaped resulting
815 * in different behaviour in MySQL and MSSQL. You SHOULD NOT move this into install.sql!
817 $sql = "INSERT INTO wcf".WCF_N
."_package_installation_plugin
818 (pluginName, priority, className)
820 $statement = self
::getDB()->prepareStatement($sql);
821 $statement->execute([
822 'packageInstallationPlugin',
824 'wcf\system\package\plugin\PIPPackageInstallationPlugin'
827 $this->gotoNextStep('logFiles');
832 * Logs the unzipped files.
834 protected function logFiles() {
837 $this->getInstalledFiles(WCF_DIR
);
838 $acpTemplateInserts = $fileInserts = [];
839 foreach (self
::$installedFiles as $file) {
841 if (preg_match('!/acp/templates/([^/]+)\.tpl$!', $file, $match)) {
843 $acpTemplateInserts[] = $match[1];
847 $fileInserts[] = str_replace(WCF_DIR
, '', $file);
851 // save acp template log
852 if (!empty($acpTemplateInserts)) {
853 $sql = "INSERT INTO wcf".WCF_N
."_acp_template
854 (templateName, application)
856 $statement = self
::getDB()->prepareStatement($sql);
858 self
::getDB()->beginTransaction();
859 foreach ($acpTemplateInserts as $acpTemplate) {
860 $statement->execute([$acpTemplate, 'wcf']);
862 self
::getDB()->commitTransaction();
866 if (!empty($fileInserts)) {
867 $sql = "INSERT INTO wcf".WCF_N
."_package_installation_file_log
868 (filename, application)
870 $statement = self
::getDB()->prepareStatement($sql);
872 self
::getDB()->beginTransaction();
873 foreach ($fileInserts as $file) {
874 $statement->execute([$file, 'wcf']);
876 self
::getDB()->commitTransaction();
879 $this->gotoNextStep('installLanguage');
883 * Scans the given dir for installed files.
887 protected function getInstalledFiles($dir) {
888 if ($files = glob($dir.'*')) {
889 foreach ($files as $file) {
891 $this->getInstalledFiles(FileUtil
::addTrailingSlash($file));
894 self
::$installedFiles[] = FileUtil
::unifyDirSeparator($file);
901 * Installs the selected languages.
903 protected function installLanguage() {
906 foreach (self
::$selectedLanguages as $language) {
907 // get language.xml file name
908 $filename = TMP_DIR
.'install/lang/'.$language.'.xml';
911 if (!file_exists($filename)) {
912 throw new SystemException("unable to find language file '".$filename."'");
917 $xml->load($filename);
920 LanguageEditor
::importFromXML($xml, 0);
923 // set default language
924 $language = LanguageFactory
::getInstance()->getLanguageByCode(in_array(self
::$selectedLanguageCode, self
::$selectedLanguages) ? self
::$selectedLanguageCode : self
::$selectedLanguages[0]);
925 LanguageFactory
::getInstance()->makeDefault($language->languageID
);
927 // rebuild language cache
928 LanguageCacheBuilder
::getInstance()->reset();
931 $this->gotoNextStep('createUser');
935 * Shows the page for creating the admin account.
937 protected function createUser() {
938 $errorType = $errorField = $username = $email = $confirmEmail = $password = $confirmPassword = '';
941 $email = $confirmEmail = '';
942 $password = $confirmPassword = '';
944 if (isset($_POST['send']) || self
::$developerMode) {
945 if (isset($_POST['send'])) {
946 if (isset($_POST['username'])) $username = StringUtil
::trim($_POST['username']);
947 if (isset($_POST['email'])) $email = StringUtil
::trim($_POST['email']);
948 if (isset($_POST['confirmEmail'])) $confirmEmail = StringUtil
::trim($_POST['confirmEmail']);
949 if (isset($_POST['password'])) $password = $_POST['password'];
950 if (isset($_POST['confirmPassword'])) $confirmPassword = $_POST['confirmPassword'];
953 $username = $password = $confirmPassword = 'root';
954 $email = $confirmEmail = 'woltlab@woltlab.com';
960 if (empty($username)) {
961 throw new UserInputException('username');
963 if (!UserUtil
::isValidUsername($username)) {
964 throw new UserInputException('username', 'notValid');
969 throw new UserInputException('email');
971 if (!UserUtil
::isValidEmail($email)) {
972 throw new UserInputException('email', 'notValid');
975 // confirm e-mail address
976 if ($email != $confirmEmail) {
977 throw new UserInputException('confirmEmail', 'notEqual');
981 if (empty($password)) {
982 throw new UserInputException('password');
985 // confirm e-mail address
986 if ($password != $confirmPassword) {
987 throw new UserInputException('confirmPassword', 'notEqual');
991 // init database connection
996 $sql = "SELECT languageID
997 FROM wcf".WCF_N
."_language
998 WHERE languageCode = ?";
999 $statement = self
::getDB()->prepareStatement($sql);
1000 $statement->execute([self
::$selectedLanguageCode]);
1001 $row = $statement->fetchArray();
1002 if (isset($row['languageID'])) $languageID = $row['languageID'];
1005 $languageID = LanguageFactory
::getInstance()->getDefaultLanguageID();
1012 'languageID' => $languageID,
1013 'password' => $password,
1014 'username' => $username
1026 $userAction = new UserAction([], 'create', $data);
1027 $userAction->executeAction();
1030 $this->gotoNextStep('installPackages');
1033 catch (UserInputException
$e) {
1034 $errorField = $e->getField();
1035 $errorType = $e->getType();
1039 WCF
::getTPL()->assign([
1040 'errorField' => $errorField,
1041 'errorType' => $errorType,
1042 'username' => $username,
1044 'confirmEmail' => $confirmEmail,
1045 'password' => $password,
1046 'confirmPassword' => $confirmPassword,
1047 'nextStep' => 'createUser'
1049 WCF
::getTPL()->display('stepCreateUser');
1053 * Registers with wcf setup delivered packages in the package installation queue.
1055 protected function installPackages() {
1056 // init database connection
1059 // get admin account
1060 $admin = new User(1);
1062 // get delivered packages
1063 $wcfPackageFile = '';
1064 $otherPackages = [];
1065 $tar = new Tar(SETUP_FILE
);
1066 foreach ($tar->getContentList() as $file) {
1067 if ($file['type'] != 'folder' && mb_strpos($file['filename'], 'install/packages/') === 0) {
1068 $packageFile = basename($file['filename']);
1070 // ignore any files which aren't an archive
1071 if (preg_match('~\.(tar\.gz|tgz|tar)$~', $packageFile)) {
1072 $packageName = preg_replace('!\.(tar\.gz|tgz|tar)$!', '', $packageFile);
1074 if ($packageName == 'com.woltlab.wcf') {
1075 $wcfPackageFile = $packageFile;
1078 $isStrato = (!empty($_SERVER['DOCUMENT_ROOT']) && (strpos($_SERVER['DOCUMENT_ROOT'], 'strato') !== false));
1079 if (!$isStrato && preg_match('!\.(tar\.gz|tgz)$!', $packageFile)) {
1080 // try to unzip zipped package files
1081 if (FileUtil
::uncompressFile(TMP_DIR
.'install/packages/'.$packageFile, TMP_DIR
.'install/packages/'.$packageName.'.tar')) {
1082 @unlink
(TMP_DIR
.'install/packages/'.$packageFile);
1083 $packageFile = $packageName.'.tar';
1087 $otherPackages[$packageName] = $packageFile;
1094 // register packages in queue
1095 // get new process id
1096 $sql = "SELECT MAX(processNo) AS processNo
1097 FROM wcf".WCF_N
."_package_installation_queue";
1098 $statement = self
::getDB()->prepareStatement($sql);
1099 $statement->execute();
1100 $result = $statement->fetchArray();
1101 $processNo = intval($result['processNo']) +
1;
1103 // search existing wcf package
1104 $sql = "SELECT COUNT(*) AS count
1105 FROM wcf".WCF_N
."_package
1106 WHERE package = 'com.woltlab.wcf'";
1107 $statement = self
::getDB()->prepareStatement($sql);
1108 $statement->execute();
1109 if (!$statement->fetchSingleColumn()) {
1110 if (empty($wcfPackageFile)) {
1111 throw new SystemException('the essential package com.woltlab.wcf is missing.');
1114 // register essential wcf package
1115 $queue = PackageInstallationQueueEditor
::create([
1116 'processNo' => $processNo,
1117 'userID' => $admin->userID
,
1118 'package' => 'com.woltlab.wcf',
1119 'packageName' => 'WoltLab Community Framework',
1120 'archive' => TMP_DIR
.'install/packages/'.$wcfPackageFile,
1121 'isApplication' => 1
1125 // register all other delivered packages
1126 asort($otherPackages);
1127 foreach ($otherPackages as $packageName => $packageFile) {
1128 // extract packageName from archive's package.xml
1129 $archive = new PackageArchive(TMP_DIR
.'install/packages/'.$packageFile);
1131 $archive->openArchive();
1133 catch (\Exception
$e) {
1134 // we've encountered a broken archive, revert everything and then fail
1135 $sql = "SELECT queueID, parentQueueID
1136 FROM wcf".WCF_N
."_package_installation_queue";
1137 $statement = WCF
::getDB()->prepareStatement($sql);
1138 $statement->execute();
1140 while ($row = $statement->fetchArray()) {
1141 $queues[$row['queueID']] = $row['parentQueueID'];
1145 /** @noinspection PhpUndefinedVariableInspection */
1146 $queueID = $queue->queueID
;
1148 $queueIDs[] = $queueID;
1150 $queueID = (isset($queues[$queueID])) ?
$queues[$queueID] : 0;
1153 // remove previously created queues
1154 if (!empty($queueIDs)) {
1155 $sql = "DELETE FROM wcf".WCF_N
."_package_installation_queue
1157 $statement = WCF
::getDB()->prepareStatement($sql);
1158 WCF
::getDB()->beginTransaction();
1159 foreach ($queueIDs as $queueID) {
1160 $statement->execute([$queueID]);
1162 WCF
::getDB()->commitTransaction();
1165 // remove package files
1166 @unlink
(TMP_DIR
.'install/packages/'.$wcfPackageFile);
1167 foreach ($otherPackages as $otherPackageFile) {
1168 @unlink
(TMP_DIR
.'install/packages/'.$otherPackageFile);
1171 // throw exception again
1172 throw new SystemException('', 0, '', $e);
1175 /** @noinspection PhpUndefinedVariableInspection */
1176 $queue = PackageInstallationQueueEditor
::create([
1177 'parentQueueID' => $queue->queueID
,
1178 'processNo' => $processNo,
1179 'userID' => $admin->userID
,
1180 'package' => $packageName,
1181 'packageName' => $archive->getLocalizedPackageInfo('packageName'),
1182 'archive' => TMP_DIR
.'install/packages/'.$packageFile,
1183 'isApplication' => 1
1188 define('COOKIE_PREFIX', 'wcf22_');
1190 $factory = new ACPSessionFactory();
1193 SessionHandler
::getInstance()->changeUser($admin);
1194 SessionHandler
::getInstance()->register('masterPassword', 1);
1195 SessionHandler
::getInstance()->register('__wcfSetup_developerMode', self
::$developerMode);
1196 SessionHandler
::getInstance()->register('__wcfSetup_directories', self
::$directories);
1197 SessionHandler
::getInstance()->update();
1199 $installPhpDeleted = @unlink
('./install.php');
1200 @unlink
('./test.php');
1201 $wcfSetupTarDeleted = @unlink
('./WCFSetup.tar.gz');
1204 WCF
::getTPL()->assign([
1205 'installPhpDeleted' => $installPhpDeleted,
1206 'wcfSetupTarDeleted' => $wcfSetupTarDeleted
1208 WCF
::getTPL()->display('stepInstallPackages');
1211 $directory = TMP_DIR
.'/';
1212 DirectoryUtil
::getInstance($directory)->removePattern(new Regex('\.tar(\.gz)?$'), true);
1216 * Goes to the next step.
1218 * @param string $nextStep
1220 protected function gotoNextStep($nextStep) {
1221 WCF
::getTPL()->assign(['nextStep' => $nextStep]);
1222 WCF
::getTPL()->display('stepNext');
1226 * Installs the files of the tar archive.
1228 protected static function installFiles() {
1229 new Installer(self
::$directories['wcf'], SETUP_FILE
, null, 'install/files/');
1233 * Gets the package name of the first application in WCFSetup.tar.gz.
1235 protected static function getPackageName() {
1237 $tar = new Tar(SETUP_FILE
);
1238 foreach ($tar->getContentList() as $file) {
1239 if ($file['type'] != 'folder' && mb_strpos($file['filename'], 'install/packages/') === 0) {
1240 $packageFile = basename($file['filename']);
1241 $packageName = preg_replace('!\.(tar\.gz|tgz|tar)$!', '', $packageFile);
1243 if ($packageName != 'com.woltlab.wcf') {
1245 $archive = new PackageArchive(TMP_DIR
.'install/packages/'.$packageFile);
1246 $archive->openArchive();
1247 self
::$setupPackageName = $archive->getLocalizedPackageInfo('packageName');
1248 $archive->getTar()->close();
1251 catch (SystemException
$e) {}
1257 // assign package name
1258 WCF
::getTPL()->assign(['setupPackageName' => self
::$setupPackageName]);