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\session\ACPSessionFactory
;
19 use wcf\system\session\SessionHandler
;
20 use wcf\system\setup\Installer
;
21 use wcf\system\template\SetupTemplateEngine
;
22 use wcf\util\DirectoryUtil
;
23 use wcf\util\FileUtil
;
24 use wcf\util\StringUtil
;
25 use wcf\util\UserUtil
;
29 define('PACKAGE_ID', 0);
30 define('HTTP_ENABLE_NO_CACHE_HEADERS', 0);
31 define('HTTP_ENABLE_GZIP', 0);
32 define('HTTP_GZIP_LEVEL', 0);
33 define('HTTP_SEND_X_FRAME_OPTIONS', 0);
34 define('CACHE_SOURCE_TYPE', 'disk');
35 define('MODULE_MASTER_PASSWORD', 1);
36 define('ENABLE_DEBUG_MODE', 1);
37 define('ENABLE_BENCHMARK', 0);
40 * Executes the installation of the basic WCF systems.
43 * @copyright 2001-2016 WoltLab GmbH
44 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
45 * @package com.woltlab.wcf
47 * @category Community Framework
49 class WCFSetup
extends WCF
{
51 * list of available languages
54 protected static $availableLanguages = [];
57 * installation directories
60 protected static $directories = [];
63 * language code of selected installation language
66 protected static $selectedLanguageCode = 'en';
69 * selected languages to be installed
72 protected static $selectedLanguages = [];
75 * list of installed files
78 protected static $installedFiles = [];
81 * name of installed primary application
84 protected static $setupPackageName = 'WoltLab Community Framework';
87 * indicates if developer mode is used to install
90 protected static $developerMode = 0;
96 protected static $dbClasses = [
97 'MySQLDatabase' => ['class' => MySQLDatabase
::class, 'minversion' => '5.1.17']//, // MySQL 5.1.17+
98 //'PostgreSQLDatabase' => ['class' => 'wcf\system\database\PostgreSQLDatabase', 'minversion' => '8.2.0'] // PostgreSQL 8.2.0+
101 /** @noinspection PhpMissingParentConstructorInspection */
103 * Calls all init functions of the WCFSetup class and starts the setup process.
105 public function __construct() {
108 $this->getDeveloperMode();
109 $this->getLanguageSelection();
110 $this->getInstallationDirectories();
111 $this->initLanguage();
113 self
::getLanguage()->loadLanguage();
114 $this->getPackageName();
121 * Gets the status of the developer mode.
123 protected static function getDeveloperMode() {
124 if (isset($_GET['dev'])) self
::$developerMode = intval($_GET['dev']);
125 else if (isset($_POST['dev'])) self
::$developerMode = intval($_POST['dev']);
129 * Gets the selected language.
131 protected static function getLanguageSelection() {
132 self
::$availableLanguages = self
::getAvailableLanguages();
134 if (isset($_REQUEST['languageCode']) && isset(self
::$availableLanguages[$_REQUEST['languageCode']])) {
135 self
::$selectedLanguageCode = $_REQUEST['languageCode'];
138 self
::$selectedLanguageCode = LanguageFactory
::getPreferredLanguage(array_keys(self
::$availableLanguages), self
::$selectedLanguageCode);
141 if (isset($_POST['selectedLanguages']) && is_array($_POST['selectedLanguages'])) {
142 self
::$selectedLanguages = $_POST['selectedLanguages'];
147 * Gets the available database classes.
151 protected static function getAvailableDBClasses() {
152 $availableDBClasses = [];
153 foreach (self
::$dbClasses as $class => $data) {
154 if (call_user_func([$data['class'], 'isSupported'])) {
155 $availableDBClasses[$class] = $data;
159 return $availableDBClasses;
163 * Gets the selected wcf dir from request.
167 protected static function getInstallationDirectories() {
168 if (self
::$developerMode && isset($_ENV['WCFSETUP_USEDEFAULTWCFDIR'])) {
169 if (!isset($_REQUEST['directories']) ||
!is_array($_REQUEST['directories'])) $_REQUEST['directories'] = [];
170 $_REQUEST['directories']['wcf'] = FileUtil
::unifyDirSeparator(INSTALL_SCRIPT_DIR
).'wcf/';
173 if (!empty($_REQUEST['directories']) && is_array($_REQUEST['directories'])) {
174 foreach ($_REQUEST['directories'] as $application => $directory) {
175 self
::$directories[$application] = $directory;
177 if ($application === 'wcf' && @file_exists
(self
::$directories['wcf'])) {
178 define('RELATIVE_WCF_DIR', FileUtil
::getRelativePath(INSTALL_SCRIPT_DIR
, self
::$directories['wcf']));
183 define('WCF_DIR', (isset(self
::$directories['wcf']) ? self
::$directories['wcf'] : ''));
187 * Initialises the language engine.
189 protected function initLanguage() {
191 mb_internal_encoding('UTF-8');
192 if (function_exists('mb_regex_encoding')) mb_regex_encoding('UTF-8');
195 // init setup language
196 self
::$languageObj = new SetupLanguage(null, ['languageCode' => self
::$selectedLanguageCode]);
200 * Initialises the template engine.
202 protected function initTPL() {
203 self
::$tplObj = SetupTemplateEngine
::getInstance();
204 self
::getTPL()->setLanguageID((self
::$selectedLanguageCode == 'en' ?
0 : 1));
205 self
::getTPL()->setCompileDir(TMP_DIR
);
206 self
::getTPL()->addApplication('wcf', TMP_DIR
);
207 self
::getTPL()->registerPrefilter(['lang']);
208 self
::getTPL()->assign([
210 'tmpFilePrefix' => TMP_FILE_PREFIX
,
211 'languageCode' => self
::$selectedLanguageCode,
212 'selectedLanguages' => self
::$selectedLanguages,
213 'directories' => self
::$directories,
214 'developerMode' => self
::$developerMode
219 * Returns all languages from WCFSetup.tar.gz.
223 protected static function getAvailableLanguages() {
224 $languages = $match = [];
225 foreach (glob(TMP_DIR
.'setup/lang/*.xml') as $file) {
228 $languageCode = LanguageEditor
::readLanguageCodeFromXML($xml);
229 $languageName = LanguageEditor
::readLanguageNameFromXML($xml);
231 $languages[$languageCode] = $languageName;
234 // sort languages by language name
241 * Calculates the current state of the progress bar.
243 * @param integer $currentStep
245 protected function calcProgress($currentStep) {
246 // calculate progress
247 $progress = round((100 / 18) * ++
$currentStep, 0);
248 self
::getTPL()->assign(['progress' => $progress]);
252 * Executes the setup steps.
254 protected function setup() {
256 if (isset($_REQUEST['step'])) $step = $_REQUEST['step'];
257 else $step = 'selectSetupLanguage';
259 // execute current step
261 /** @noinspection PhpMissingBreakStatementInspection */
262 case 'selectSetupLanguage':
263 if (!self
::$developerMode) {
264 $this->calcProgress(0);
265 $this->selectSetupLanguage();
269 /** @noinspection PhpMissingBreakStatementInspection */
271 if (!self
::$developerMode) {
272 $this->calcProgress(1);
273 $this->showLicense();
277 /** @noinspection PhpMissingBreakStatementInspection */
278 case 'showSystemRequirements':
279 if (!self
::$developerMode) {
280 $this->calcProgress(2);
281 $this->showSystemRequirements();
285 /** @noinspection PhpMissingBreakStatementInspection */
286 case 'configureDirectories':
287 if (!self
::$developerMode ||
!isset($_ENV['WCFSETUP_USEDEFAULTWCFDIR'])) {
288 $this->calcProgress(3);
289 $this->configureDirectories();
294 $this->calcProgress(4);
298 case 'selectLanguages':
299 $this->calcProgress(5);
300 $this->selectLanguages();
304 $this->calcProgress(6);
305 $this->configureDB();
310 if (isset($_POST['offset'])) {
311 $currentStep +
= intval($_POST['offset']);
314 $this->calcProgress($currentStep);
319 $this->calcProgress(14);
323 case 'installLanguage':
324 $this->calcProgress(15);
325 $this->installLanguage();
329 $this->calcProgress(16);
333 case 'installPackages':
334 $this->calcProgress(17);
335 $this->installPackages();
341 * Shows the first setup page.
343 protected function selectSetupLanguage() {
344 WCF
::getTPL()->assign([
345 'availableLanguages' => self
::$availableLanguages,
346 'nextStep' => 'showLicense'
348 WCF
::getTPL()->display('stepSelectSetupLanguage');
352 * Shows the license agreement.
354 protected function showLicense() {
355 if (isset($_POST['send'])) {
356 if (isset($_POST['accepted'])) {
357 $this->gotoNextStep('showSystemRequirements');
361 WCF
::getTPL()->assign(['missingAcception' => true]);
366 if (file_exists(TMP_DIR
.'setup/license/license_'.self
::$selectedLanguageCode.'.txt')) {
367 $license = file_get_contents(TMP_DIR
.'setup/license/license_'.self
::$selectedLanguageCode.'.txt');
370 $license = file_get_contents(TMP_DIR
.'setup/license/license_en.txt');
373 WCF
::getTPL()->assign([
374 'license' => $license,
375 'nextStep' => 'showLicense'
377 WCF
::getTPL()->display('stepShowLicense');
381 * Shows the system requirements.
383 protected function showSystemRequirements() {
387 $system['phpVersion']['value'] = phpversion();
388 $comparePhpVersion = preg_replace('/^(\d+\.\d+\.\d+).*$/', '\\1', $system['phpVersion']['value']);
389 $system['phpVersion']['result'] = (version_compare($comparePhpVersion, '5.5.4') >= 0);
392 $system['sql']['value'] = array_keys(self
::getAvailableDBClasses());
393 $system['sql']['result'] = !empty($system['sql']['value']);
395 // upload_max_filesize
396 $system['uploadMaxFilesize']['value'] = ini_get('upload_max_filesize');
397 $system['uploadMaxFilesize']['result'] = (intval($system['uploadMaxFilesize']['value']) > 0);
400 $system['gdLib']['value'] = '0.0.0';
401 if (function_exists('gd_info')) {
404 if (preg_match('!([0-9]+\.[0-9]+(?:\.[0-9]+)?)!', $temp['GD Version'], $match)) {
405 if (preg_match('/^[0-9]+\.[0-9]+$/', $match[1])) $match[1] .= '.0';
406 $system['gdLib']['value'] = $match[1];
409 $system['gdLib']['result'] = (version_compare($system['gdLib']['value'], '2.0.0') >= 0);
412 $system['memoryLimit']['value'] = ini_get('memory_limit');
413 $system['memoryLimit']['result'] = $this->compareMemoryLimit();
415 WCF
::getTPL()->assign([
417 'nextStep' => 'configureDirectories'
419 WCF
::getTPL()->display('stepShowSystemRequirements');
423 * Returns true if memory_limit is set to at least 128 MB
427 protected function compareMemoryLimit() {
428 $memoryLimit = ini_get('memory_limit');
431 if ($memoryLimit == -1) {
435 // completely numeric, PHP assumes byte
436 if (is_numeric($memoryLimit)) {
437 $memoryLimit = $memoryLimit / 1024 / 1024;
438 return ($memoryLimit >= 128);
441 // PHP supports 'K', 'M' and 'G' shorthand notation
442 if (preg_match('~^(\d+)([KMG])$~', $memoryLimit, $matches)) {
443 switch ($matches[2]) {
445 $memoryLimit = $matches[1] * 1024;
446 return ($memoryLimit >= 128);
450 return ($matches[1] >= 128);
454 return ($matches[1] >= 1);
463 * Searches the wcf dir.
467 protected function configureDirectories() {
468 // get available packages
469 $applications = $packages = [];
470 foreach (glob(TMP_DIR
. 'install/packages/*') as $file) {
471 $filename = basename($file);
472 if (preg_match('~\.(?:tar|tar\.gz|tgz)$~', $filename)) {
473 $package = new PackageArchive($file);
474 $package->openArchive();
476 $application = Package
::getAbbreviation($package->getPackageInfo('name'));
478 $applications[] = $application;
479 $packages[$application] = [
480 'directory' => ($package->getPackageInfo('applicationDirectory') ?
: $application),
481 'packageDescription' => $package->getLocalizedPackageInfo('packageDescription'),
482 'packageName' => $package->getLocalizedPackageInfo('packageName')
488 uasort($packages, function($a, $b) {
489 return strcmp($a['packageName'], $b['packageName']);
492 // force cms being shown first
493 $showOrder = ['wcf'];
494 foreach (array_keys($packages) as $application) {
495 if ($application !== 'wcf') $showOrder[] = $application;
498 $documentRoot = FileUtil
::unifyDirSeparator($_SERVER['DOCUMENT_ROOT']);
500 if (!empty(self
::$directories)) {
501 $applicationPaths = $knownPaths = [];
503 // use $showOrder instead of $applications to ensure that the error message for
504 // duplicate directories will trigger in display order rather than the random
505 // sort order returned by glob() above
506 foreach ($showOrder as $application) {
507 $path = FileUtil
::getRealPath($documentRoot . '/' . FileUtil
::addTrailingSlash(FileUtil
::removeLeadingSlash(self
::$directories[$application])));
508 if (strpos($path, $documentRoot) !== 0) {
509 // verify that given path is still within the current document root
510 $errors[$application] = 'outsideDocumentRoot';
512 else if (in_array($path, $knownPaths)) {
513 // prevent the same path for two or more applications
514 $errors[$application] = 'duplicate';
516 else if (@is_file
($path . 'global.php')) {
517 // check if directory is empty (dotfiles are okay)
518 $errors[$application] = 'notEmpty';
521 // try to create directory if it does not exist
522 if (!is_dir($path) && !FileUtil
::makePath($path)) {
523 $errors[$application] = 'makePath';
527 FileUtil
::makeWritable($path);
529 catch (SystemException
$e) {
530 $errors[$application] = 'makeWritable';
534 $applicationPaths[$application] = $path;
535 $knownPaths[] = $path;
538 if (empty($errors)) {
539 // copy over the actual paths
540 self
::$directories = array_merge(self
::$directories, $applicationPaths);
541 WCF
::getTPL()->assign(['directories' => self
::$directories]);
548 // resolve path relative to document root
549 $relativePath = str_replace(FileUtil
::unifyDirSeparator($_SERVER['DOCUMENT_ROOT']), '', FileUtil
::unifyDirSeparator(INSTALL_SCRIPT_DIR
));
550 foreach ($packages as $application => $packageData) {
551 self
::$directories[$application] = $relativePath . ($application === 'wcf' ?
'' : $packageData['directory'] . '/');
555 WCF
::getTPL()->assign([
556 'directories' => self
::$directories,
557 'documentRoot' => $documentRoot,
559 'installScriptDir' => FileUtil
::unifyDirSeparator(INSTALL_SCRIPT_DIR
),
560 'nextStep' => 'configureDirectories', // call this step again to validate paths
561 'packages' => $packages,
562 'showOrder' => $showOrder
565 WCF
::getTPL()->display('stepConfigureDirectories');
569 * Unzips the files of the wcfsetup tar archive.
571 protected function unzipFiles() {
572 // WCF seems to be installed, abort
573 if (@is_file
(self
::$directories['wcf'].'lib/system/WCF.class.php')) {
574 throw new SystemException('Target directory seems to be an existing installation of WCF, unable to continue.');
576 // WCF not yet installed, install files first
578 $this->installFiles();
580 $this->gotoNextStep('selectLanguages');
585 * Shows the page for choosing the installed languages.
587 protected function selectLanguages() {
588 $errorField = $errorType = '';
590 // skip step in developer mode
591 // select all available languages automatically
592 if (self
::$developerMode) {
593 self
::$selectedLanguages = [];
594 foreach (self
::$availableLanguages as $languageCode => $language) {
595 self
::$selectedLanguages[] = $languageCode;
598 self
::getTPL()->assign(['selectedLanguages' => self
::$selectedLanguages]);
599 $this->gotoNextStep('configureDB');
603 // start error handling
604 if (isset($_POST['send'])) {
606 // no languages selected
607 if (empty(self
::$selectedLanguages)) {
608 throw new UserInputException('selectedLanguages');
612 foreach (self
::$selectedLanguages as $language) {
613 if (!isset(self
::$availableLanguages[$language])) {
614 throw new UserInputException('selectedLanguages');
620 $this->gotoNextStep('configureDB');
623 catch (UserInputException
$e) {
624 $errorField = $e->getField();
625 $errorType = $e->getType();
629 self
::$selectedLanguages[] = self
::$selectedLanguageCode;
630 WCF
::getTPL()->assign(['selectedLanguages' => self
::$selectedLanguages]);
633 WCF
::getTPL()->assign([
634 'errorField' => $errorField,
635 'errorType' => $errorType,
636 'availableLanguages' => self
::$availableLanguages,
637 'nextStep' => 'selectLanguages'
639 WCF
::getTPL()->display('stepSelectLanguages');
643 * Shows the page for configurating the database connection.
645 protected function configureDB() {
646 $availableDBClasses = self
::getAvailableDBClasses();
648 if (self
::$developerMode && isset($_ENV['WCFSETUP_DBHOST'])) {
649 $dbHost = $_ENV['WCFSETUP_DBHOST'];
650 $dbUser = $_ENV['WCFSETUP_DBUSER'];
651 $dbPassword = $_ENV['WCFSETUP_DBPASSWORD'];
652 $dbName = $_ENV['WCFSETUP_DBNAME'];
656 $dbHost = 'localhost';
663 // set $dbClass to first item in $availableDBClasses
664 foreach ($availableDBClasses as $dbClass) {
665 $dbClass = $dbClass['class'];
669 if (isset($_POST['send']) ||
(self
::$developerMode && isset($_ENV['WCFSETUP_DBHOST']))) {
670 if (isset($_POST['dbHost'])) $dbHost = $_POST['dbHost'];
671 if (isset($_POST['dbUser'])) $dbUser = $_POST['dbUser'];
672 if (isset($_POST['dbPassword'])) $dbPassword = $_POST['dbPassword'];
673 if (isset($_POST['dbName'])) $dbName = $_POST['dbName'];
675 // ensure that $dbNumber is zero or a positive integer
676 if (isset($_POST['dbNumber'])) $dbNumber = max(0, intval($_POST['dbNumber']));
677 if (isset($_POST['dbClass'])) $dbClass = $_POST['dbClass'];
681 if (preg_match('/^(.+?):(\d+)$/', $dbHost, $match)) {
683 $dbPort = intval($match[2]);
690 foreach ($availableDBClasses as $dbData) {
691 if ($dbData['class'] == $dbClass) {
698 throw new SystemException("Database type '".$dbClass."'. is not available on this system.");
701 // check connection data
702 /** @var \wcf\system\database\Database $db */
703 $db = new $dbClass($dbHost, $dbUser, $dbPassword, $dbName, $dbPort, true);
707 if (!empty($availableDBClasses[$dbClass]['minversion'])) {
708 $compareSQLVersion = preg_replace('/^(\d+\.\d+\.\d+).*$/', '\\1', $db->getVersion());
709 if (!(version_compare($compareSQLVersion, $availableDBClasses[$dbClass]['minversion']) >= 0)) {
710 throw new SystemException("Insufficient SQL version '".$compareSQLVersion."'. Version '".$availableDBClasses[$dbClass]['minversion']."' or greater is needed.");
713 // check innodb support
714 if ($dbClass == MySQLDatabase
::class) {
715 $sql = "SHOW ENGINES";
716 $statement = $db->prepareStatement($sql);
717 $statement->execute();
719 while ($row = $statement->fetchArray()) {
720 if ($row['Engine'] == 'InnoDB' && in_array($row['Support'], ['DEFAULT', 'YES'])) {
727 throw new SystemException("Support for InnoDB is missing.");
731 // check for table conflicts
732 $conflictedTables = $this->getConflictedTables($db, $dbNumber);
735 if (empty($conflictedTables)) {
736 // connection successfully established
737 // write configuration to config.inc.php
738 $file = new File(WCF_DIR
.'config.inc.php');
739 $file->write("<?php\n");
740 $file->write("\$dbHost = '".str_replace("'", "\\'", $dbHost)."';\n");
741 $file->write("\$dbPort = ".$dbPort.";\n");
742 $file->write("\$dbUser = '".str_replace("'", "\\'", $dbUser)."';\n");
743 $file->write("\$dbPassword = '".str_replace("'", "\\'", $dbPassword)."';\n");
744 $file->write("\$dbName = '".str_replace("'", "\\'", $dbName)."';\n");
745 $file->write("\$dbClass = '".str_replace("'", "\\'", $dbClass)."';\n");
746 $file->write("if (!defined('WCF_N')) define('WCF_N', $dbNumber);\n");
750 $this->gotoNextStep('createDB');
753 // show configure template again
755 WCF
::getTPL()->assign(['conflictedTables' => $conflictedTables]);
758 catch (SystemException
$e) {
759 WCF
::getTPL()->assign(['exception' => $e]);
762 WCF
::getTPL()->assign([
765 'dbPassword' => $dbPassword,
767 'dbNumber' => $dbNumber,
768 'dbClass' => $dbClass,
769 'availableDBClasses' => $availableDBClasses,
770 'nextStep' => 'configureDB'
772 WCF
::getTPL()->display('stepConfigureDB');
776 * Checks if in the chosen database are tables in conflict with the wcf tables
777 * which will be created in the next step.
779 * @param \wcf\system\database\Database $db
780 * @param integer $dbNumber
781 * @return string[] list of already existing tables
783 protected function getConflictedTables($db, $dbNumber) {
784 // get content of the sql structure file
785 $sql = file_get_contents(TMP_DIR
.'setup/db/install.sql');
787 // installation number value 'n' (WCF_N) must be reflected in the executed sql queries
788 $sql = str_replace('wcf1_', 'wcf'.$dbNumber.'_', $sql);
790 // get all tablenames which should be created
791 preg_match_all("%CREATE\s+TABLE\s+(\w+)%", $sql, $matches);
793 // get all installed tables from chosen database
794 $existingTables = $db->getEditor()->getTableNames();
796 // check if existing tables are in conflict with wcf tables
797 $conflictedTables = [];
798 foreach ($existingTables as $existingTableName) {
799 foreach ($matches[1] as $wcfTableName) {
800 if ($existingTableName == $wcfTableName) {
801 $conflictedTables[] = $wcfTableName;
805 return $conflictedTables;
809 * Creates the database structure of the wcf.
811 protected function createDB() {
814 // get content of the sql structure file
815 $sql = file_get_contents(TMP_DIR
.'setup/db/install.sql');
818 $sqlData = explode('/* SQL_PARSER_OFFSET */', $sql);
819 $offset = (isset($_POST['offset'])) ?
intval($_POST['offset']) : 0;
820 if (!isset($sqlData[$offset])) {
821 throw new SystemException("Offset for SQL parser is out of bounds, ".$offset." was requested, but there are only ".count($sqlData)." sections");
823 $sql = $sqlData[$offset];
825 // installation number value 'n' (WCF_N) must be reflected in the executed sql queries
826 $sql = str_replace('wcf1_', 'wcf'.WCF_N
.'_', $sql);
828 // execute sql queries
829 $parser = new SQLParser($sql);
833 preg_match_all("~CREATE\s+TABLE\s+(\w+)~i", $sql, $matches);
835 if (!empty($matches[1])) {
836 $sql = "INSERT INTO wcf".WCF_N
."_package_installation_sql_log
839 $statement = self
::getDB()->prepareStatement($sql);
840 foreach ($matches[1] as $tableName) {
841 $statement->execute([$tableName]);
845 if ($offset < (count($sqlData) - 1)) {
846 WCF
::getTPL()->assign([
847 '__additionalParameters' => [
848 'offset' => $offset +
1
852 $this->gotoNextStep('createDB');
856 * Manually install PIPPackageInstallationPlugin since install.sql content is not escaped resulting
857 * in different behaviour in MySQL and MSSQL. You SHOULD NOT move this into install.sql!
859 $sql = "INSERT INTO wcf".WCF_N
."_package_installation_plugin
860 (pluginName, priority, className)
862 $statement = self
::getDB()->prepareStatement($sql);
863 $statement->execute([
864 'packageInstallationPlugin',
866 'wcf\system\package\plugin\PIPPackageInstallationPlugin'
869 $this->gotoNextStep('logFiles');
874 * Logs the unzipped files.
876 protected function logFiles() {
879 $this->getInstalledFiles(WCF_DIR
);
880 $acpTemplateInserts = $fileInserts = [];
881 foreach (self
::$installedFiles as $file) {
883 if (preg_match('!/acp/templates/([^/]+)\.tpl$!', $file, $match)) {
885 $acpTemplateInserts[] = $match[1];
889 $fileInserts[] = str_replace(WCF_DIR
, '', $file);
893 // save acp template log
894 if (!empty($acpTemplateInserts)) {
895 $sql = "INSERT INTO wcf".WCF_N
."_acp_template
896 (templateName, application)
898 $statement = self
::getDB()->prepareStatement($sql);
900 self
::getDB()->beginTransaction();
901 foreach ($acpTemplateInserts as $acpTemplate) {
902 $statement->execute([$acpTemplate, 'wcf']);
904 self
::getDB()->commitTransaction();
908 if (!empty($fileInserts)) {
909 $sql = "INSERT INTO wcf".WCF_N
."_package_installation_file_log
910 (filename, application)
912 $statement = self
::getDB()->prepareStatement($sql);
914 self
::getDB()->beginTransaction();
915 foreach ($fileInserts as $file) {
916 $statement->execute([$file, 'wcf']);
918 self
::getDB()->commitTransaction();
921 $this->gotoNextStep('installLanguage');
925 * Scans the given dir for installed files.
929 protected function getInstalledFiles($dir) {
930 if ($files = glob($dir.'*')) {
931 foreach ($files as $file) {
933 $this->getInstalledFiles(FileUtil
::addTrailingSlash($file));
936 self
::$installedFiles[] = FileUtil
::unifyDirSeparator($file);
943 * Installs the selected languages.
945 protected function installLanguage() {
948 foreach (self
::$selectedLanguages as $language) {
949 // get language.xml file name
950 $filename = TMP_DIR
.'install/lang/'.$language.'.xml';
953 if (!file_exists($filename)) {
954 throw new SystemException("unable to find language file '".$filename."'");
959 $xml->load($filename);
962 LanguageEditor
::importFromXML($xml, 0);
965 // set default language
966 $language = LanguageFactory
::getInstance()->getLanguageByCode(in_array(self
::$selectedLanguageCode, self
::$selectedLanguages) ? self
::$selectedLanguageCode : self
::$selectedLanguages[0]);
967 LanguageFactory
::getInstance()->makeDefault($language->languageID
);
969 // rebuild language cache
970 LanguageCacheBuilder
::getInstance()->reset();
973 $this->gotoNextStep('createUser');
977 * Shows the page for creating the admin account.
979 protected function createUser() {
980 $errorType = $errorField = $username = $email = $confirmEmail = $password = $confirmPassword = '';
983 $email = $confirmEmail = '';
984 $password = $confirmPassword = '';
986 if (isset($_POST['send']) || self
::$developerMode) {
987 if (isset($_POST['send'])) {
988 if (isset($_POST['username'])) $username = StringUtil
::trim($_POST['username']);
989 if (isset($_POST['email'])) $email = StringUtil
::trim($_POST['email']);
990 if (isset($_POST['confirmEmail'])) $confirmEmail = StringUtil
::trim($_POST['confirmEmail']);
991 if (isset($_POST['password'])) $password = $_POST['password'];
992 if (isset($_POST['confirmPassword'])) $confirmPassword = $_POST['confirmPassword'];
995 $username = $password = $confirmPassword = 'root';
996 $email = $confirmEmail = 'woltlab@woltlab.com';
1002 if (empty($username)) {
1003 throw new UserInputException('username');
1005 if (!UserUtil
::isValidUsername($username)) {
1006 throw new UserInputException('username', 'notValid');
1010 if (empty($email)) {
1011 throw new UserInputException('email');
1013 if (!UserUtil
::isValidEmail($email)) {
1014 throw new UserInputException('email', 'notValid');
1017 // confirm e-mail address
1018 if ($email != $confirmEmail) {
1019 throw new UserInputException('confirmEmail', 'notEqual');
1023 if (empty($password)) {
1024 throw new UserInputException('password');
1027 // confirm e-mail address
1028 if ($password != $confirmPassword) {
1029 throw new UserInputException('confirmPassword', 'notEqual');
1033 // init database connection
1038 $sql = "SELECT languageID
1039 FROM wcf".WCF_N
."_language
1040 WHERE languageCode = ?";
1041 $statement = self
::getDB()->prepareStatement($sql);
1042 $statement->execute([self
::$selectedLanguageCode]);
1043 $row = $statement->fetchArray();
1044 if (isset($row['languageID'])) $languageID = $row['languageID'];
1047 $languageID = LanguageFactory
::getInstance()->getDefaultLanguageID();
1054 'languageID' => $languageID,
1055 'password' => $password,
1056 'username' => $username
1068 $userAction = new UserAction([], 'create', $data);
1069 $userAction->executeAction();
1072 $this->gotoNextStep('installPackages');
1075 catch (UserInputException
$e) {
1076 $errorField = $e->getField();
1077 $errorType = $e->getType();
1081 WCF
::getTPL()->assign([
1082 'errorField' => $errorField,
1083 'errorType' => $errorType,
1084 'username' => $username,
1086 'confirmEmail' => $confirmEmail,
1087 'password' => $password,
1088 'confirmPassword' => $confirmPassword,
1089 'nextStep' => 'createUser'
1091 WCF
::getTPL()->display('stepCreateUser');
1095 * Registers with wcf setup delivered packages in the package installation queue.
1097 protected function installPackages() {
1098 // init database connection
1101 // get admin account
1102 $admin = new User(1);
1104 // get delivered packages
1105 $wcfPackageFile = '';
1106 $otherPackages = [];
1107 $tar = new Tar(SETUP_FILE
);
1108 foreach ($tar->getContentList() as $file) {
1109 if ($file['type'] != 'folder' && mb_strpos($file['filename'], 'install/packages/') === 0) {
1110 $packageFile = basename($file['filename']);
1112 // ignore any files which aren't an archive
1113 if (preg_match('~\.(tar\.gz|tgz|tar)$~', $packageFile)) {
1114 $packageName = preg_replace('!\.(tar\.gz|tgz|tar)$!', '', $packageFile);
1116 if ($packageName == 'com.woltlab.wcf') {
1117 $wcfPackageFile = $packageFile;
1120 $isStrato = (!empty($_SERVER['DOCUMENT_ROOT']) && (strpos($_SERVER['DOCUMENT_ROOT'], 'strato') !== false));
1121 if (!$isStrato && preg_match('!\.(tar\.gz|tgz)$!', $packageFile)) {
1122 // try to unzip zipped package files
1123 if (FileUtil
::uncompressFile(TMP_DIR
.'install/packages/'.$packageFile, TMP_DIR
.'install/packages/'.$packageName.'.tar')) {
1124 @unlink
(TMP_DIR
.'install/packages/'.$packageFile);
1125 $packageFile = $packageName.'.tar';
1129 $otherPackages[$packageName] = $packageFile;
1136 // register packages in queue
1137 // get new process id
1138 $sql = "SELECT MAX(processNo) AS processNo
1139 FROM wcf".WCF_N
."_package_installation_queue";
1140 $statement = self
::getDB()->prepareStatement($sql);
1141 $statement->execute();
1142 $result = $statement->fetchArray();
1143 $processNo = intval($result['processNo']) +
1;
1145 // search existing wcf package
1146 $sql = "SELECT COUNT(*) AS count
1147 FROM wcf".WCF_N
."_package
1148 WHERE package = 'com.woltlab.wcf'";
1149 $statement = self
::getDB()->prepareStatement($sql);
1150 $statement->execute();
1151 if (!$statement->fetchSingleColumn()) {
1152 if (empty($wcfPackageFile)) {
1153 throw new SystemException('the essential package com.woltlab.wcf is missing.');
1156 // register essential wcf package
1157 $queue = PackageInstallationQueueEditor
::create([
1158 'processNo' => $processNo,
1159 'userID' => $admin->userID
,
1160 'package' => 'com.woltlab.wcf',
1161 'packageName' => 'WoltLab Community Framework',
1162 'archive' => TMP_DIR
.'install/packages/'.$wcfPackageFile,
1163 'isApplication' => 1
1167 // register all other delivered packages
1168 asort($otherPackages);
1169 foreach ($otherPackages as $packageName => $packageFile) {
1170 // extract packageName from archive's package.xml
1171 $archive = new PackageArchive(TMP_DIR
.'install/packages/'.$packageFile);
1173 $archive->openArchive();
1175 catch (\Exception
$e) {
1176 // we've encountered a broken archive, revert everything and then fail
1177 $sql = "SELECT queueID, parentQueueID
1178 FROM wcf".WCF_N
."_package_installation_queue";
1179 $statement = WCF
::getDB()->prepareStatement($sql);
1180 $statement->execute();
1182 while ($row = $statement->fetchArray()) {
1183 $queues[$row['queueID']] = $row['parentQueueID'];
1187 /** @noinspection PhpUndefinedVariableInspection */
1188 $queueID = $queue->queueID
;
1190 $queueIDs[] = $queueID;
1192 $queueID = (isset($queues[$queueID])) ?
$queues[$queueID] : 0;
1195 // remove previously created queues
1196 if (!empty($queueIDs)) {
1197 $sql = "DELETE FROM wcf".WCF_N
."_package_installation_queue
1199 $statement = WCF
::getDB()->prepareStatement($sql);
1200 WCF
::getDB()->beginTransaction();
1201 foreach ($queueIDs as $queueID) {
1202 $statement->execute([$queueID]);
1204 WCF
::getDB()->commitTransaction();
1207 // remove package files
1208 @unlink
(TMP_DIR
.'install/packages/'.$wcfPackageFile);
1209 foreach ($otherPackages as $otherPackageFile) {
1210 @unlink
(TMP_DIR
.'install/packages/'.$otherPackageFile);
1213 // throw exception again
1214 throw new SystemException('', 0, '', $e);
1217 /** @noinspection PhpUndefinedVariableInspection */
1218 $queue = PackageInstallationQueueEditor
::create([
1219 'parentQueueID' => $queue->queueID
,
1220 'processNo' => $processNo,
1221 'userID' => $admin->userID
,
1222 'package' => $packageName,
1223 'packageName' => $archive->getLocalizedPackageInfo('packageName'),
1224 'archive' => TMP_DIR
.'install/packages/'.$packageFile,
1225 'isApplication' => 1
1230 define('COOKIE_PREFIX', 'wcf22_');
1232 $factory = new ACPSessionFactory();
1235 SessionHandler
::getInstance()->changeUser($admin);
1236 SessionHandler
::getInstance()->register('masterPassword', 1);
1237 SessionHandler
::getInstance()->register('__wcfSetup_developerMode', self
::$developerMode);
1238 SessionHandler
::getInstance()->register('__wcfSetup_directories', self
::$directories);
1239 SessionHandler
::getInstance()->update();
1241 $installPhpDeleted = @unlink
('./install.php');
1242 @unlink
('./test.php');
1243 $wcfSetupTarDeleted = @unlink
('./WCFSetup.tar.gz');
1246 WCF
::getTPL()->assign([
1247 'installPhpDeleted' => $installPhpDeleted,
1248 'wcfSetupTarDeleted' => $wcfSetupTarDeleted
1250 WCF
::getTPL()->display('stepInstallPackages');
1253 $directory = TMP_DIR
.'/';
1254 DirectoryUtil
::getInstance($directory)->removePattern(new Regex('\.tar(\.gz)?$'), true);
1258 * Goes to the next step.
1260 * @param string $nextStep
1262 protected function gotoNextStep($nextStep) {
1263 WCF
::getTPL()->assign(['nextStep' => $nextStep]);
1264 WCF
::getTPL()->display('stepNext');
1268 * Installs the files of the tar archive.
1270 protected static function installFiles() {
1271 new Installer(self
::$directories['wcf'], SETUP_FILE
, null, 'install/files/');
1275 * Gets the package name of the first application in WCFSetup.tar.gz.
1277 protected static function getPackageName() {
1279 $tar = new Tar(SETUP_FILE
);
1280 foreach ($tar->getContentList() as $file) {
1281 if ($file['type'] != 'folder' && mb_strpos($file['filename'], 'install/packages/') === 0) {
1282 $packageFile = basename($file['filename']);
1283 $packageName = preg_replace('!\.(tar\.gz|tgz|tar)$!', '', $packageFile);
1285 if ($packageName != 'com.woltlab.wcf') {
1287 $archive = new PackageArchive(TMP_DIR
.'install/packages/'.$packageFile);
1288 $archive->openArchive();
1289 self
::$setupPackageName = $archive->getLocalizedPackageInfo('packageName');
1290 $archive->getTar()->close();
1293 catch (SystemException
$e) {}
1299 // assign package name
1300 WCF
::getTPL()->assign(['setupPackageName' => self
::$setupPackageName]);