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\exception\SystemException
;
12 use wcf\system\exception\UserInputException
;
13 use wcf\system\io\File
;
14 use wcf\system\io\Tar
;
15 use wcf\system\language\LanguageFactory
;
16 use wcf\system\package\PackageArchive
;
17 use wcf\system\session\ACPSessionFactory
;
18 use wcf\system\session\SessionHandler
;
19 use wcf\system\setup\Installer
;
20 use wcf\system\template\SetupTemplateEngine
;
21 use wcf\util\DirectoryUtil
;
22 use wcf\util\FileUtil
;
23 use wcf\util\StringUtil
;
24 use wcf\util\UserUtil
;
28 define('PACKAGE_ID', 0);
29 define('HTTP_ENABLE_NO_CACHE_HEADERS', 0);
30 define('HTTP_ENABLE_GZIP', 0);
31 define('HTTP_GZIP_LEVEL', 0);
32 define('HTTP_SEND_X_FRAME_OPTIONS', 0);
33 define('CACHE_SOURCE_TYPE', 'disk');
34 define('MODULE_MASTER_PASSWORD', 1);
35 define('ENABLE_DEBUG_MODE', 1);
36 define('ENABLE_BENCHMARK', 0);
39 * Executes the installation of the basic WCF systems.
42 * @copyright 2001-2015 WoltLab GmbH
43 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
44 * @package com.woltlab.wcf
46 * @category Community Framework
48 class WCFSetup
extends WCF
{
50 * list of available languages
53 protected static $availableLanguages = [];
56 * installation directories
59 protected static $directories = [];
62 * language code of selected installation language
65 protected static $selectedLanguageCode = 'en';
68 * selected languages to be installed
71 protected static $selectedLanguages = [];
74 * list of installed files
77 protected static $installedFiles = [];
80 * name of installed primary application
83 protected static $setupPackageName = 'WoltLab Community Framework';
86 * indicates if developer mode is used to install
89 protected static $developerMode = 0;
95 protected static $dbClasses = [
96 'MySQLDatabase' => ['class' => 'wcf\system\database\MySQLDatabase', 'minversion' => '5.1.17']//, // MySQL 5.1.17+
97 //'PostgreSQLDatabase' => ['class' => 'wcf\system\database\PostgreSQLDatabase', 'minversion' => '8.2.0'] // PostgreSQL 8.2.0+
101 * Calls all init functions of the WCFSetup class and starts the setup process.
103 public function __construct() {
106 $this->getDeveloperMode();
107 $this->getLanguageSelection();
108 $this->getInstallationDirectories();
109 $this->initLanguage();
111 self
::getLanguage()->loadLanguage();
112 $this->getPackageName();
119 * Gets the status of the developer mode.
121 protected static function getDeveloperMode() {
122 if (isset($_GET['dev'])) self
::$developerMode = intval($_GET['dev']);
123 else if (isset($_POST['dev'])) self
::$developerMode = intval($_POST['dev']);
127 * Gets the selected language.
129 protected static function getLanguageSelection() {
130 self
::$availableLanguages = self
::getAvailableLanguages();
132 if (isset($_REQUEST['languageCode']) && isset(self
::$availableLanguages[$_REQUEST['languageCode']])) {
133 self
::$selectedLanguageCode = $_REQUEST['languageCode'];
136 self
::$selectedLanguageCode = LanguageFactory
::getPreferredLanguage(array_keys(self
::$availableLanguages), self
::$selectedLanguageCode);
139 if (isset($_POST['selectedLanguages']) && is_array($_POST['selectedLanguages'])) {
140 self
::$selectedLanguages = $_POST['selectedLanguages'];
145 * Gets the available database classes.
149 protected static function getAvailableDBClasses() {
150 $availableDBClasses = [];
151 foreach (self
::$dbClasses as $class => $data) {
152 if (call_user_func([$data['class'], 'isSupported'])) {
153 $availableDBClasses[$class] = $data;
157 return $availableDBClasses;
161 * Gets the selected wcf dir from request.
165 protected static function getInstallationDirectories() {
166 if (self
::$developerMode && isset($_ENV['WCFSETUP_USEDEFAULTWCFDIR'])) {
167 if (!isset($_REQUEST['directories']) ||
!is_array($_REQUEST['directories'])) $_REQUEST['directories'] = [];
168 $_REQUEST['directories']['wcf'] = FileUtil
::unifyDirSeparator(INSTALL_SCRIPT_DIR
).'wcf/';
171 if (!empty($_REQUEST['directories']) && is_array($_REQUEST['directories'])) {
172 foreach ($_REQUEST['directories'] as $application => $directory) {
173 self
::$directories[$application] = $directory;
175 if ($application === 'wcf' && @file_exists
(self
::$directories['wcf'])) {
176 define('RELATIVE_WCF_DIR', FileUtil
::getRelativePath(INSTALL_SCRIPT_DIR
, self
::$directories['wcf']));
181 define('WCF_DIR', (isset(self
::$directories['wcf']) ? self
::$directories['wcf'] : ''));
185 * Initialises the language engine.
187 protected function initLanguage() {
189 mb_internal_encoding('UTF-8');
190 if (function_exists('mb_regex_encoding')) mb_regex_encoding('UTF-8');
193 // init setup language
194 self
::$languageObj = new SetupLanguage(null, ['languageCode' => self
::$selectedLanguageCode]);
198 * Initialises the template engine.
200 protected function initTPL() {
201 self
::$tplObj = SetupTemplateEngine
::getInstance();
202 self
::getTPL()->setLanguageID((self
::$selectedLanguageCode == 'en' ?
0 : 1));
203 self
::getTPL()->setCompileDir(TMP_DIR
);
204 self
::getTPL()->addApplication('wcf', TMP_DIR
);
205 self
::getTPL()->registerPrefilter(['lang']);
206 self
::getTPL()->assign([
208 'tmpFilePrefix' => TMP_FILE_PREFIX
,
209 'languageCode' => self
::$selectedLanguageCode,
210 'selectedLanguages' => self
::$selectedLanguages,
211 'directories' => self
::$directories,
212 'developerMode' => self
::$developerMode
217 * Returns all languages from WCFSetup.tar.gz.
221 protected static function getAvailableLanguages() {
222 $languages = $match = [];
223 foreach (glob(TMP_DIR
.'setup/lang/*.xml') as $file) {
226 $languageCode = LanguageEditor
::readLanguageCodeFromXML($xml);
227 $languageName = LanguageEditor
::readLanguageNameFromXML($xml);
229 $languages[$languageCode] = $languageName;
232 // sort languages by language name
239 * Calculates the current state of the progress bar.
241 * @param integer $currentStep
243 protected function calcProgress($currentStep) {
244 // calculate progress
245 $progress = round((100 / 18) * ++
$currentStep, 0);
246 self
::getTPL()->assign(['progress' => $progress]);
250 * Executes the setup steps.
252 protected function setup() {
254 if (isset($_REQUEST['step'])) $step = $_REQUEST['step'];
255 else $step = 'selectSetupLanguage';
257 // execute current step
259 /** @noinspection PhpMissingBreakStatementInspection */
260 case 'selectSetupLanguage':
261 if (!self
::$developerMode) {
262 $this->calcProgress(0);
263 $this->selectSetupLanguage();
267 /** @noinspection PhpMissingBreakStatementInspection */
269 if (!self
::$developerMode) {
270 $this->calcProgress(1);
271 $this->showLicense();
275 /** @noinspection PhpMissingBreakStatementInspection */
276 case 'showSystemRequirements':
277 if (!self
::$developerMode) {
278 $this->calcProgress(2);
279 $this->showSystemRequirements();
283 /** @noinspection PhpMissingBreakStatementInspection */
284 case 'configureDirectories':
285 if (!self
::$developerMode ||
!isset($_ENV['WCFSETUP_USEDEFAULTWCFDIR'])) {
286 $this->calcProgress(3);
287 $this->configureDirectories();
292 $this->calcProgress(4);
296 case 'selectLanguages':
297 $this->calcProgress(5);
298 $this->selectLanguages();
302 $this->calcProgress(6);
303 $this->configureDB();
308 if (isset($_POST['offset'])) {
309 $currentStep +
= intval($_POST['offset']);
312 $this->calcProgress($currentStep);
317 $this->calcProgress(14);
321 case 'installLanguage':
322 $this->calcProgress(15);
323 $this->installLanguage();
327 $this->calcProgress(16);
331 case 'installPackages':
332 $this->calcProgress(17);
333 $this->installPackages();
339 * Shows the first setup page.
341 protected function selectSetupLanguage() {
342 WCF
::getTPL()->assign([
343 'availableLanguages' => self
::$availableLanguages,
344 'nextStep' => 'showLicense'
346 WCF
::getTPL()->display('stepSelectSetupLanguage');
350 * Shows the license agreement.
352 protected function showLicense() {
353 if (isset($_POST['send'])) {
354 if (isset($_POST['accepted'])) {
355 $this->gotoNextStep('showSystemRequirements');
359 WCF
::getTPL()->assign(['missingAcception' => true]);
364 if (file_exists(TMP_DIR
.'setup/license/license_'.self
::$selectedLanguageCode.'.txt')) {
365 $license = file_get_contents(TMP_DIR
.'setup/license/license_'.self
::$selectedLanguageCode.'.txt');
368 $license = file_get_contents(TMP_DIR
.'setup/license/license_en.txt');
371 WCF
::getTPL()->assign([
372 'license' => $license,
373 'nextStep' => 'showLicense'
375 WCF
::getTPL()->display('stepShowLicense');
379 * Shows the system requirements.
381 protected function showSystemRequirements() {
385 $system['phpVersion']['value'] = phpversion();
386 $comparePhpVersion = preg_replace('/^(\d+\.\d+\.\d+).*$/', '\\1', $system['phpVersion']['value']);
387 $system['phpVersion']['result'] = (version_compare($comparePhpVersion, '5.5.4') >= 0);
390 $system['sql']['value'] = array_keys(self
::getAvailableDBClasses());
391 $system['sql']['result'] = !empty($system['sql']['value']);
393 // upload_max_filesize
394 $system['uploadMaxFilesize']['value'] = ini_get('upload_max_filesize');
395 $system['uploadMaxFilesize']['result'] = (intval($system['uploadMaxFilesize']['value']) > 0);
398 $system['gdLib']['value'] = '0.0.0';
399 if (function_exists('gd_info')) {
402 if (preg_match('!([0-9]+\.[0-9]+(?:\.[0-9]+)?)!', $temp['GD Version'], $match)) {
403 if (preg_match('/^[0-9]+\.[0-9]+$/', $match[1])) $match[1] .= '.0';
404 $system['gdLib']['value'] = $match[1];
407 $system['gdLib']['result'] = (version_compare($system['gdLib']['value'], '2.0.0') >= 0);
410 $system['memoryLimit']['value'] = ini_get('memory_limit');
411 $system['memoryLimit']['result'] = $this->compareMemoryLimit();
413 WCF
::getTPL()->assign([
415 'nextStep' => 'configureDirectories'
417 WCF
::getTPL()->display('stepShowSystemRequirements');
421 * Returns true if memory_limit is set to at least 128 MB
425 protected function compareMemoryLimit() {
426 $memoryLimit = ini_get('memory_limit');
429 if ($memoryLimit == -1) {
433 // completely numeric, PHP assumes byte
434 if (is_numeric($memoryLimit)) {
435 $memoryLimit = $memoryLimit / 1024 / 1024;
436 return ($memoryLimit >= 128);
439 // PHP supports 'K', 'M' and 'G' shorthand notation
440 if (preg_match('~^(\d+)([KMG])$~', $memoryLimit, $matches)) {
441 switch ($matches[2]) {
443 $memoryLimit = $matches[1] * 1024;
444 return ($memoryLimit >= 128);
448 return ($matches[1] >= 128);
452 return ($matches[1] >= 1);
461 * Searches the wcf dir.
465 protected function configureDirectories() {
466 // get available packages
467 $applications = $packages = [];
468 foreach (glob(TMP_DIR
. 'install/packages/*') as $file) {
469 $filename = basename($file);
470 if (preg_match('~\.(?:tar|tar\.gz|tgz)$~', $filename)) {
471 $package = new PackageArchive($file);
472 $package->openArchive();
474 $application = Package
::getAbbreviation($package->getPackageInfo('name'));
476 $applications[] = $application;
477 $packages[$application] = [
478 'directory' => ($package->getPackageInfo('applicationDirectory') ?
: $application),
479 'packageDescription' => $package->getLocalizedPackageInfo('packageDescription'),
480 'packageName' => $package->getLocalizedPackageInfo('packageName')
486 uasort($packages, function($a, $b) {
487 return strcmp($a['packageName'], $b['packageName']);
490 // force cms being shown first
491 $showOrder = ['wcf'];
492 foreach (array_keys($packages) as $application) {
493 if ($application !== 'wcf') $showOrder[] = $application;
496 $documentRoot = FileUtil
::unifyDirSeparator($_SERVER['DOCUMENT_ROOT']);
498 if (!empty(self
::$directories)) {
499 $applicationPaths = $knownPaths = [];
501 // use $showOrder instead of $applications to ensure that the error message for
502 // duplicate directories will trigger in display order rather than the random
503 // sort order returned by glob() above
504 foreach ($showOrder as $application) {
505 $path = FileUtil
::getRealPath($documentRoot . '/' . FileUtil
::addTrailingSlash(FileUtil
::removeLeadingSlash(self
::$directories[$application])));
506 if (strpos($path, $documentRoot) !== 0) {
507 // verify that given path is still within the current document root
508 $errors[$application] = 'outsideDocumentRoot';
510 else if (in_array($path, $knownPaths)) {
511 // prevent the same path for two or more applications
512 $errors[$application] = 'duplicate';
514 else if (@is_file
($path . 'global.php')) {
515 // check if directory is empty (dotfiles are okay)
516 $errors[$application] = 'notEmpty';
519 // try to create directory if it does not exist
520 if (!is_dir($path) && !FileUtil
::makePath($path)) {
521 $errors[$application] = 'makePath';
525 FileUtil
::makeWritable($path);
527 catch (SystemException
$e) {
528 $errors[$application] = 'makeWritable';
532 $applicationPaths[$application] = $path;
533 $knownPaths[] = $path;
536 if (empty($errors)) {
537 // copy over the actual paths
538 self
::$directories = array_merge(self
::$directories, $applicationPaths);
539 WCF
::getTPL()->assign(['directories' => self
::$directories]);
546 // resolve path relative to document root
547 $relativePath = str_replace(FileUtil
::unifyDirSeparator($_SERVER['DOCUMENT_ROOT']), '', FileUtil
::unifyDirSeparator(INSTALL_SCRIPT_DIR
));
548 foreach ($packages as $application => $packageData) {
549 self
::$directories[$application] = $relativePath . ($application === 'wcf' ?
'' : $packageData['directory'] . '/');
553 WCF
::getTPL()->assign([
554 'directories' => self
::$directories,
555 'documentRoot' => $documentRoot,
557 'installScriptDir' => FileUtil
::unifyDirSeparator(INSTALL_SCRIPT_DIR
),
558 'nextStep' => 'configureDirectories', // call this step again to validate paths
559 'packages' => $packages,
560 'showOrder' => $showOrder
563 WCF
::getTPL()->display('stepConfigureDirectories');
567 * Unzips the files of the wcfsetup tar archive.
569 protected function unzipFiles() {
570 // WCF seems to be installed, abort
571 if (@is_file
(self
::$directories['wcf'].'lib/system/WCF.class.php')) {
572 throw new SystemException('Target directory seems to be an existing installation of WCF, unable to continue.');
574 // WCF not yet installed, install files first
576 $this->installFiles();
578 $this->gotoNextStep('selectLanguages');
583 * Shows the page for choosing the installed languages.
585 protected function selectLanguages() {
586 $errorField = $errorType = '';
588 // skip step in developer mode
589 // select all available languages automatically
590 if (self
::$developerMode) {
591 self
::$selectedLanguages = [];
592 foreach (self
::$availableLanguages as $languageCode => $language) {
593 self
::$selectedLanguages[] = $languageCode;
596 self
::getTPL()->assign(['selectedLanguages' => self
::$selectedLanguages]);
597 $this->gotoNextStep('configureDB');
601 // start error handling
602 if (isset($_POST['send'])) {
604 // no languages selected
605 if (empty(self
::$selectedLanguages)) {
606 throw new UserInputException('selectedLanguages');
610 foreach (self
::$selectedLanguages as $language) {
611 if (!isset(self
::$availableLanguages[$language])) {
612 throw new UserInputException('selectedLanguages');
618 $this->gotoNextStep('configureDB');
621 catch (UserInputException
$e) {
622 $errorField = $e->getField();
623 $errorType = $e->getType();
627 self
::$selectedLanguages[] = self
::$selectedLanguageCode;
628 WCF
::getTPL()->assign(['selectedLanguages' => self
::$selectedLanguages]);
631 WCF
::getTPL()->assign([
632 'errorField' => $errorField,
633 'errorType' => $errorType,
634 'availableLanguages' => self
::$availableLanguages,
635 'nextStep' => 'selectLanguages'
637 WCF
::getTPL()->display('stepSelectLanguages');
641 * Shows the page for configurating the database connection.
643 protected function configureDB() {
644 $availableDBClasses = self
::getAvailableDBClasses();
646 if (self
::$developerMode && isset($_ENV['WCFSETUP_DBHOST'])) {
647 $dbHost = $_ENV['WCFSETUP_DBHOST'];
648 $dbUser = $_ENV['WCFSETUP_DBUSER'];
649 $dbPassword = $_ENV['WCFSETUP_DBPASSWORD'];
650 $dbName = $_ENV['WCFSETUP_DBNAME'];
654 $dbHost = 'localhost';
661 // set $dbClass to first item in $availableDBClasses
662 foreach ($availableDBClasses as $dbClass) {
663 $dbClass = $dbClass['class'];
667 if (isset($_POST['send']) ||
(self
::$developerMode && isset($_ENV['WCFSETUP_DBHOST']))) {
668 if (isset($_POST['dbHost'])) $dbHost = $_POST['dbHost'];
669 if (isset($_POST['dbUser'])) $dbUser = $_POST['dbUser'];
670 if (isset($_POST['dbPassword'])) $dbPassword = $_POST['dbPassword'];
671 if (isset($_POST['dbName'])) $dbName = $_POST['dbName'];
673 // ensure that $dbNumber is zero or a positive integer
674 if (isset($_POST['dbNumber'])) $dbNumber = max(0, intval($_POST['dbNumber']));
675 if (isset($_POST['dbClass'])) $dbClass = $_POST['dbClass'];
679 if (preg_match('/^(.+?):(\d+)$/', $dbHost, $match)) {
681 $dbPort = intval($match[2]);
688 foreach ($availableDBClasses as $dbData) {
689 if ($dbData['class'] == $dbClass) {
696 throw new SystemException("Database type '".$dbClass."'. is not available on this system.");
699 // check connection data
700 /** @var \wcf\system\database\Database $db */
701 $db = new $dbClass($dbHost, $dbUser, $dbPassword, $dbName, $dbPort, true);
705 if (!empty($availableDBClasses[$dbClass]['minversion'])) {
706 $compareSQLVersion = preg_replace('/^(\d+\.\d+\.\d+).*$/', '\\1', $db->getVersion());
707 if (!(version_compare($compareSQLVersion, $availableDBClasses[$dbClass]['minversion']) >= 0)) {
708 throw new SystemException("Insufficient SQL version '".$compareSQLVersion."'. Version '".$availableDBClasses[$dbClass]['minversion']."' or greater is needed.");
711 // check innodb support
712 if ($dbClass == 'wcf\system\database\MySQLDatabase') {
713 $sql = "SHOW ENGINES";
714 $statement = $db->prepareStatement($sql);
715 $statement->execute();
717 while ($row = $statement->fetchArray()) {
718 if ($row['Engine'] == 'InnoDB' && in_array($row['Support'], ['DEFAULT', 'YES'])) {
725 throw new SystemException("Support for InnoDB is missing.");
729 // check for table conflicts
730 $conflictedTables = $this->getConflictedTables($db, $dbNumber);
733 if (empty($conflictedTables)) {
734 // connection successfully established
735 // write configuration to config.inc.php
736 $file = new File(WCF_DIR
.'config.inc.php');
737 $file->write("<?php\n");
738 $file->write("\$dbHost = '".str_replace("'", "\\'", $dbHost)."';\n");
739 $file->write("\$dbPort = ".$dbPort.";\n");
740 $file->write("\$dbUser = '".str_replace("'", "\\'", $dbUser)."';\n");
741 $file->write("\$dbPassword = '".str_replace("'", "\\'", $dbPassword)."';\n");
742 $file->write("\$dbName = '".str_replace("'", "\\'", $dbName)."';\n");
743 $file->write("\$dbClass = '".str_replace("'", "\\'", $dbClass)."';\n");
744 $file->write("if (!defined('WCF_N')) define('WCF_N', $dbNumber);\n");
748 $this->gotoNextStep('createDB');
751 // show configure template again
753 WCF
::getTPL()->assign(['conflictedTables' => $conflictedTables]);
756 catch (SystemException
$e) {
757 WCF
::getTPL()->assign(['exception' => $e]);
760 WCF
::getTPL()->assign([
763 'dbPassword' => $dbPassword,
765 'dbNumber' => $dbNumber,
766 'dbClass' => $dbClass,
767 'availableDBClasses' => $availableDBClasses,
768 'nextStep' => 'configureDB'
770 WCF
::getTPL()->display('stepConfigureDB');
774 * Checks if in the chosen database are tables in conflict with the wcf tables
775 * which will be created in the next step.
777 * @param \wcf\system\database\Database $db
778 * @param integer $dbNumber
779 * @return string[] list of already existing tables
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');
785 // installation number value 'n' (WCF_N) must be reflected in the executed sql queries
786 $sql = str_replace('wcf1_', 'wcf'.$dbNumber.'_', $sql);
788 // get all tablenames which should be created
789 preg_match_all("%CREATE\s+TABLE\s+(\w+)%", $sql, $matches);
791 // get all installed tables from chosen database
792 $existingTables = $db->getEditor()->getTableNames();
794 // check if existing tables are in conflict with wcf tables
795 $conflictedTables = [];
796 foreach ($existingTables as $existingTableName) {
797 foreach ($matches[1] as $wcfTableName) {
798 if ($existingTableName == $wcfTableName) {
799 $conflictedTables[] = $wcfTableName;
803 return $conflictedTables;
807 * Creates the database structure of the wcf.
809 protected function createDB() {
812 // get content of the sql structure file
813 $sql = file_get_contents(TMP_DIR
.'setup/db/install.sql');
816 $sqlData = explode('/* SQL_PARSER_OFFSET */', $sql);
817 $offset = (isset($_POST['offset'])) ?
intval($_POST['offset']) : 0;
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");
821 $sql = $sqlData[$offset];
823 // installation number value 'n' (WCF_N) must be reflected in the executed sql queries
824 $sql = str_replace('wcf1_', 'wcf'.WCF_N
.'_', $sql);
826 // execute sql queries
827 $parser = new SQLParser($sql);
831 preg_match_all("~CREATE\s+TABLE\s+(\w+)~i", $sql, $matches);
833 if (!empty($matches[1])) {
834 $sql = "INSERT INTO wcf".WCF_N
."_package_installation_sql_log
837 $statement = self
::getDB()->prepareStatement($sql);
838 foreach ($matches[1] as $tableName) {
839 $statement->execute([$tableName]);
843 if ($offset < (count($sqlData) - 1)) {
844 WCF
::getTPL()->assign([
845 '__additionalParameters' => [
846 'offset' => $offset +
1
850 $this->gotoNextStep('createDB');
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!
857 $sql = "INSERT INTO wcf".WCF_N
."_package_installation_plugin
858 (pluginName, priority, className)
860 $statement = self
::getDB()->prepareStatement($sql);
861 $statement->execute([
862 'packageInstallationPlugin',
864 'wcf\system\package\plugin\PIPPackageInstallationPlugin'
867 $this->gotoNextStep('logFiles');
872 * Logs the unzipped files.
874 protected function logFiles() {
877 $this->getInstalledFiles(WCF_DIR
);
878 $acpTemplateInserts = $fileInserts = [];
879 foreach (self
::$installedFiles as $file) {
881 if (preg_match('!/acp/templates/([^/]+)\.tpl$!', $file, $match)) {
883 $acpTemplateInserts[] = $match[1];
887 $fileInserts[] = str_replace(WCF_DIR
, '', $file);
891 // save acp template log
892 if (!empty($acpTemplateInserts)) {
893 $sql = "INSERT INTO wcf".WCF_N
."_acp_template
894 (templateName, application)
896 $statement = self
::getDB()->prepareStatement($sql);
898 self
::getDB()->beginTransaction();
899 foreach ($acpTemplateInserts as $acpTemplate) {
900 $statement->execute([$acpTemplate, 'wcf']);
902 self
::getDB()->commitTransaction();
906 if (!empty($fileInserts)) {
907 $sql = "INSERT INTO wcf".WCF_N
."_package_installation_file_log
908 (filename, application)
910 $statement = self
::getDB()->prepareStatement($sql);
912 self
::getDB()->beginTransaction();
913 foreach ($fileInserts as $file) {
914 $statement->execute([$file, 'wcf']);
916 self
::getDB()->commitTransaction();
919 $this->gotoNextStep('installLanguage');
923 * Scans the given dir for installed files.
927 protected function getInstalledFiles($dir) {
928 if ($files = glob($dir.'*')) {
929 foreach ($files as $file) {
931 $this->getInstalledFiles(FileUtil
::addTrailingSlash($file));
934 self
::$installedFiles[] = FileUtil
::unifyDirSeparator($file);
941 * Installs the selected languages.
943 protected function installLanguage() {
946 foreach (self
::$selectedLanguages as $language) {
947 // get language.xml file name
948 $filename = TMP_DIR
.'install/lang/'.$language.'.xml';
951 if (!file_exists($filename)) {
952 throw new SystemException("unable to find language file '".$filename."'");
957 $xml->load($filename);
960 LanguageEditor
::importFromXML($xml, 0);
963 // set default language
964 $language = LanguageFactory
::getInstance()->getLanguageByCode(in_array(self
::$selectedLanguageCode, self
::$selectedLanguages) ? self
::$selectedLanguageCode : self
::$selectedLanguages[0]);
965 LanguageFactory
::getInstance()->makeDefault($language->languageID
);
967 // rebuild language cache
968 LanguageCacheBuilder
::getInstance()->reset();
971 $this->gotoNextStep('createUser');
975 * Shows the page for creating the admin account.
977 protected function createUser() {
978 $errorType = $errorField = $username = $email = $confirmEmail = $password = $confirmPassword = '';
981 $email = $confirmEmail = '';
982 $password = $confirmPassword = '';
984 if (isset($_POST['send']) || self
::$developerMode) {
985 if (isset($_POST['send'])) {
986 if (isset($_POST['username'])) $username = StringUtil
::trim($_POST['username']);
987 if (isset($_POST['email'])) $email = StringUtil
::trim($_POST['email']);
988 if (isset($_POST['confirmEmail'])) $confirmEmail = StringUtil
::trim($_POST['confirmEmail']);
989 if (isset($_POST['password'])) $password = $_POST['password'];
990 if (isset($_POST['confirmPassword'])) $confirmPassword = $_POST['confirmPassword'];
993 $username = $password = $confirmPassword = 'root';
994 $email = $confirmEmail = 'woltlab@woltlab.com';
1000 if (empty($username)) {
1001 throw new UserInputException('username');
1003 if (!UserUtil
::isValidUsername($username)) {
1004 throw new UserInputException('username', 'notValid');
1008 if (empty($email)) {
1009 throw new UserInputException('email');
1011 if (!UserUtil
::isValidEmail($email)) {
1012 throw new UserInputException('email', 'notValid');
1015 // confirm e-mail address
1016 if ($email != $confirmEmail) {
1017 throw new UserInputException('confirmEmail', 'notEqual');
1021 if (empty($password)) {
1022 throw new UserInputException('password');
1025 // confirm e-mail address
1026 if ($password != $confirmPassword) {
1027 throw new UserInputException('confirmPassword', 'notEqual');
1031 // init database connection
1036 $sql = "SELECT languageID
1037 FROM wcf".WCF_N
."_language
1038 WHERE languageCode = ?";
1039 $statement = self
::getDB()->prepareStatement($sql);
1040 $statement->execute([self
::$selectedLanguageCode]);
1041 $row = $statement->fetchArray();
1042 if (isset($row['languageID'])) $languageID = $row['languageID'];
1045 $languageID = LanguageFactory
::getInstance()->getDefaultLanguageID();
1052 'languageID' => $languageID,
1053 'password' => $password,
1054 'username' => $username
1066 $userAction = new UserAction([], 'create', $data);
1067 $userAction->executeAction();
1070 $this->gotoNextStep('installPackages');
1073 catch (UserInputException
$e) {
1074 $errorField = $e->getField();
1075 $errorType = $e->getType();
1079 WCF
::getTPL()->assign([
1080 'errorField' => $errorField,
1081 'errorType' => $errorType,
1082 'username' => $username,
1084 'confirmEmail' => $confirmEmail,
1085 'password' => $password,
1086 'confirmPassword' => $confirmPassword,
1087 'nextStep' => 'createUser'
1089 WCF
::getTPL()->display('stepCreateUser');
1093 * Registers with wcf setup delivered packages in the package installation queue.
1095 protected function installPackages() {
1096 // init database connection
1099 // get admin account
1100 $admin = new User(1);
1102 // get delivered packages
1103 $wcfPackageFile = '';
1104 $otherPackages = [];
1105 $tar = new Tar(SETUP_FILE
);
1106 foreach ($tar->getContentList() as $file) {
1107 if ($file['type'] != 'folder' && mb_strpos($file['filename'], 'install/packages/') === 0) {
1108 $packageFile = basename($file['filename']);
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);
1114 if ($packageName == 'com.woltlab.wcf') {
1115 $wcfPackageFile = $packageFile;
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';
1127 $otherPackages[$packageName] = $packageFile;
1134 // register packages in queue
1135 // get new process id
1136 $sql = "SELECT MAX(processNo) AS processNo
1137 FROM wcf".WCF_N
."_package_installation_queue";
1138 $statement = self
::getDB()->prepareStatement($sql);
1139 $statement->execute();
1140 $result = $statement->fetchArray();
1141 $processNo = intval($result['processNo']) +
1;
1143 // search existing wcf package
1144 $sql = "SELECT COUNT(*) AS count
1145 FROM wcf".WCF_N
."_package
1146 WHERE package = 'com.woltlab.wcf'";
1147 $statement = self
::getDB()->prepareStatement($sql);
1148 $statement->execute();
1149 if (!$statement->fetchSingleColumn()) {
1150 if (empty($wcfPackageFile)) {
1151 throw new SystemException('the essential package com.woltlab.wcf is missing.');
1154 // register essential wcf package
1155 $queue = PackageInstallationQueueEditor
::create([
1156 'processNo' => $processNo,
1157 'userID' => $admin->userID
,
1158 'package' => 'com.woltlab.wcf',
1159 'packageName' => 'WoltLab Community Framework',
1160 'archive' => TMP_DIR
.'install/packages/'.$wcfPackageFile,
1161 'isApplication' => 1
1165 // register all other delivered packages
1166 asort($otherPackages);
1167 foreach ($otherPackages as $packageName => $packageFile) {
1168 // extract packageName from archive's package.xml
1169 $archive = new PackageArchive(TMP_DIR
.'install/packages/'.$packageFile);
1171 $archive->openArchive();
1173 catch (\Exception
$e) {
1174 // we've encountered a broken archive, revert everything and then fail
1175 $sql = "SELECT queueID, parentQueueID
1176 FROM wcf".WCF_N
."_package_installation_queue";
1177 $statement = WCF
::getDB()->prepareStatement($sql);
1178 $statement->execute();
1180 while ($row = $statement->fetchArray()) {
1181 $queues[$row['queueID']] = $row['parentQueueID'];
1185 $queueID = $queue->queueID
;
1187 $queueIDs[] = $queueID;
1189 $queueID = (isset($queues[$queueID])) ?
$queues[$queueID] : 0;
1192 // remove previously created queues
1193 if (!empty($queueIDs)) {
1194 $sql = "DELETE FROM wcf".WCF_N
."_package_installation_queue
1196 $statement = WCF
::getDB()->prepareStatement($sql);
1197 WCF
::getDB()->beginTransaction();
1198 foreach ($queueIDs as $queueID) {
1199 $statement->execute([$queueID]);
1201 WCF
::getDB()->commitTransaction();
1204 // remove package files
1205 @unlink
(TMP_DIR
.'install/packages/'.$wcfPackageFile);
1206 foreach ($otherPackages as $otherPackageFile) {
1207 @unlink
(TMP_DIR
.'install/packages/'.$otherPackageFile);
1210 // throw exception again
1211 throw new SystemException('', 0, '', $e);
1214 $queue = PackageInstallationQueueEditor
::create([
1215 'parentQueueID' => $queue->queueID
,
1216 'processNo' => $processNo,
1217 'userID' => $admin->userID
,
1218 'package' => $packageName,
1219 'packageName' => $archive->getLocalizedPackageInfo('packageName'),
1220 'archive' => TMP_DIR
.'install/packages/'.$packageFile,
1221 'isApplication' => 1
1226 define('COOKIE_PREFIX', 'wcf22_');
1228 $factory = new ACPSessionFactory();
1231 SessionHandler
::getInstance()->changeUser($admin);
1232 SessionHandler
::getInstance()->register('masterPassword', 1);
1233 SessionHandler
::getInstance()->register('__wcfSetup_developerMode', self
::$developerMode);
1234 SessionHandler
::getInstance()->register('__wcfSetup_directories', self
::$directories);
1235 SessionHandler
::getInstance()->update();
1237 $installPhpDeleted = @unlink
('./install.php');
1238 @unlink
('./test.php');
1239 $wcfSetupTarDeleted = @unlink
('./WCFSetup.tar.gz');
1242 WCF
::getTPL()->assign([
1243 'installPhpDeleted' => $installPhpDeleted,
1244 'wcfSetupTarDeleted' => $wcfSetupTarDeleted
1246 WCF
::getTPL()->display('stepInstallPackages');
1249 $directory = TMP_DIR
.'/';
1250 DirectoryUtil
::getInstance($directory)->removePattern(new Regex('\.tar(\.gz)?$'), true);
1254 * Goes to the next step.
1256 * @param string $nextStep
1258 protected function gotoNextStep($nextStep) {
1259 WCF
::getTPL()->assign(['nextStep' => $nextStep]);
1260 WCF
::getTPL()->display('stepNext');
1264 * Installs the files of the tar archive.
1266 protected static function installFiles() {
1267 new Installer(self
::$directories['wcf'], SETUP_FILE
, null, 'install/files/');
1271 * Gets the package name of the first application in WCFSetup.tar.gz.
1273 protected static function getPackageName() {
1275 $tar = new Tar(SETUP_FILE
);
1276 foreach ($tar->getContentList() as $file) {
1277 if ($file['type'] != 'folder' && mb_strpos($file['filename'], 'install/packages/') === 0) {
1278 $packageFile = basename($file['filename']);
1279 $packageName = preg_replace('!\.(tar\.gz|tgz|tar)$!', '', $packageFile);
1281 if ($packageName != 'com.woltlab.wcf') {
1283 $archive = new PackageArchive(TMP_DIR
.'install/packages/'.$packageFile);
1284 $archive->openArchive();
1285 self
::$setupPackageName = $archive->getLocalizedPackageInfo('packageName');
1286 $archive->getTar()->close();
1289 catch (SystemException
$e) {}
1295 // assign package name
1296 WCF
::getTPL()->assign(['setupPackageName' => self
::$setupPackageName]);