Major overhaul of caching system (work in progress)
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / WCFSetup.class.php
CommitLineData
158bd3ca
TD
1<?php
2namespace wcf\system;
3use wcf\data\language\LanguageEditor;
4use wcf\data\language\SetupLanguage;
89142da9 5use wcf\data\package\installation\queue\PackageInstallationQueueEditor;
158bd3ca
TD
6use wcf\data\user\User;
7use wcf\data\user\UserAction;
b401cd0d 8use wcf\system\cache\builder\LanguageCacheBuilder;
158bd3ca
TD
9use wcf\system\database\util\SQLParser;
10use wcf\system\exception\SystemException;
8a81a7bd 11use wcf\system\exception\UserInputException;
158bd3ca
TD
12use wcf\system\io\File;
13use wcf\system\io\Tar;
14use wcf\system\language\LanguageFactory;
15use wcf\system\package\PackageArchive;
16use wcf\system\session\ACPSessionFactory;
17use wcf\system\session\SessionHandler;
18use wcf\system\setup\Installer;
19use wcf\system\template\SetupTemplateEngine;
931f6597 20use wcf\system\Regex;
2bc9f31d 21use wcf\system\WCF;
364a2e49 22use wcf\util\DirectoryUtil;
158bd3ca
TD
23use wcf\util\FileUtil;
24use wcf\util\StringUtil;
25use wcf\util\UserUtil;
26use wcf\util\XML;
27
28// define
29define('PACKAGE_ID', '0');
30define('HTTP_ENABLE_NO_CACHE_HEADERS', 0);
31define('HTTP_ENABLE_GZIP', 0);
32define('HTTP_GZIP_LEVEL', 0);
33define('CACHE_SOURCE_TYPE', 'disk');
158bd3ca 34define('MODULE_MASTER_PASSWORD', 1);
0c07c50c 35define('ENABLE_DEBUG_MODE', 1);
8a12b687 36define('ENABLE_BENCHMARK', 0);
158bd3ca
TD
37
38/**
a17de04e
MS
39 * Executes the installation of the basic WCF systems.
40 *
9f959ced 41 * @author Marcel Werk
b4f1ea02 42 * @copyright 2001-2013 WoltLab GmbH
158bd3ca
TD
43 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
44 * @package com.woltlab.wcf
45 * @subpackage system
9f959ced 46 * @category Community Framework
158bd3ca
TD
47 */
48class WCFSetup extends WCF {
9f959ced
MS
49 /**
50 * list of available languages
51 * @var array
52 */
158bd3ca 53 protected static $availableLanguages = array();
9f959ced
MS
54
55 /**
56 * language code of selected installation language
57 * @var string
58 */
158bd3ca 59 protected static $selectedLanguageCode = 'en';
9f959ced
MS
60
61 /**
62 * selected languages to be installed
63 * @var array
64 */
158bd3ca 65 protected static $selectedLanguages = array();
9f959ced
MS
66
67 /**
68 * directory of the framework
69 * @var string
70 */
158bd3ca 71 protected static $wcfDir = '';
9f959ced
MS
72
73 /**
74 * list of installed files
75 * @var array
76 */
158bd3ca 77 protected static $installedFiles = array();
9f959ced
MS
78
79 /**
80 * name of installed primary application
81 * @var string
82 */
158bd3ca 83 protected static $setupPackageName = 'WoltLab Community Framework';
9f959ced
MS
84
85 /**
86 * indicates if developer mode is used to install
87 * @var boolean
88 */
158bd3ca 89 protected static $developerMode = 0;
9f959ced
MS
90
91 /**
92 * supported databases
93 * @var array<array>
94 */
158bd3ca 95 protected static $dbClasses = array(
c17ae73c
AE
96 'MySQLDatabase' => array('class' => 'wcf\system\database\MySQLDatabase', 'minversion' => '5.1.17')//, // MySQL 5.1.17+
97 //'PostgreSQLDatabase' => array('class' => 'wcf\system\database\PostgreSQLDatabase', 'minversion' => '8.2.0') // PostgreSQL 8.2.0+
158bd3ca
TD
98 );
99
100 /**
101 * Calls all init functions of the WCFSetup class and starts the setup process.
102 */
103 public function __construct() {
104 @set_time_limit(0);
105 $this->initMagicQuotes();
106 $this->getDeveloperMode();
107 $this->getLanguageSelection();
108 $this->getWCFDir();
109 $this->initLanguage();
110 $this->initTPL();
111 self::getLanguage()->loadLanguage();
112 $this->getPackageName();
113
114 // start setup
115 $this->setup();
116 }
117
118 /**
119 * Gets the status of the developer mode.
120 */
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']);
124 }
125
126 /**
127 * Gets the selected language.
128 */
129 protected static function getLanguageSelection() {
1157f8be 130 self::$availableLanguages = self::getAvailableLanguages();
158bd3ca 131
1157f8be 132 if (isset($_REQUEST['languageCode']) && isset(self::$availableLanguages[$_REQUEST['languageCode']])) {
158bd3ca
TD
133 self::$selectedLanguageCode = $_REQUEST['languageCode'];
134 }
135 else {
1157f8be 136 self::$selectedLanguageCode = LanguageFactory::getPreferredLanguage(array_keys(self::$availableLanguages), self::$selectedLanguageCode);
158bd3ca
TD
137 }
138
139 if (isset($_POST['selectedLanguages']) && is_array($_POST['selectedLanguages'])) {
140 self::$selectedLanguages = $_POST['selectedLanguages'];
141 }
142 }
143
144 /**
145 * Gets the available database classes.
9f959ced 146 *
158bd3ca
TD
147 * @return array
148 */
149 protected static function getAvailableDBClasses() {
150 $availableDBClasses = array();
151 foreach (self::$dbClasses as $class => $data) {
152 if (call_user_func(array($data['class'], 'isSupported'))) {
153 $availableDBClasses[$class] = $data;
154 }
155 }
156
157 return $availableDBClasses;
158 }
9f959ced 159
158bd3ca
TD
160 /**
161 * Gets the selected wcf dir from request.
162 */
163 protected static function getWCFDir() {
164 if (isset($_REQUEST['wcfDir']) && $_REQUEST['wcfDir'] != '') {
165 self::$wcfDir = FileUtil::addTrailingSlash(FileUtil::unifyDirSeperator($_REQUEST['wcfDir']));
166 if (@file_exists(self::$wcfDir)) {
167 define('RELATIVE_WCF_DIR', FileUtil::getRelativePath(INSTALL_SCRIPT_DIR, self::$wcfDir));
168 }
169 }
170
171 define('WCF_DIR', self::$wcfDir);
172 }
9f959ced 173
158bd3ca
TD
174 /**
175 * Initialises the language engine.
176 */
177 protected function initLanguage() {
178 // set mb settings
179 mb_internal_encoding('UTF-8');
180 if (function_exists('mb_regex_encoding')) mb_regex_encoding('UTF-8');
181 mb_language('uni');
182
183 // init setup language
184 self::$languageObj = new SetupLanguage(null, array(
185 'languageCode' => self::$selectedLanguageCode
186 ));
187 }
188
189 /**
190 * Initialises the template engine.
191 */
192 protected function initTPL() {
193 self::$tplObj = SetupTemplateEngine::getInstance();
1157f8be 194 self::getTPL()->setLanguageID((self::$selectedLanguageCode == 'en' ? 0 : 1));
158bd3ca 195 self::getTPL()->setCompileDir(TMP_DIR);
669e16fe 196 self::getTPL()->addApplication('wcf', PACKAGE_ID, TMP_DIR);
158bd3ca
TD
197 self::getTPL()->registerPrefilter(array('lang'));
198 self::getTPL()->assign(array(
199 '__wcf' => $this,
200 'tmpFilePrefix' => TMP_FILE_PREFIX,
201 'languageCode' => self::$selectedLanguageCode,
202 'selectedLanguages' => self::$selectedLanguages,
203 'wcfDir' => self::$wcfDir,
204 'developerMode' => self::$developerMode
205 ));
206 }
207
208 /**
209 * Returns all languages from WCFSetup.tar.gz.
9f959ced 210 *
158bd3ca
TD
211 * @return array
212 */
1157f8be 213 protected static function getAvailableLanguages() {
158bd3ca
TD
214 $languages = $match = array();
215 $tar = new Tar(SETUP_FILE);
216 foreach ($tar->getContentList() as $file) {
1157f8be
MW
217 if (strpos($file['filename'], 'setup/lang/') === 0 && substr($file['filename'], -4) == '.xml') {
218 $xml = new XML();
219 $xml->load(TMP_DIR.$file['filename']);
220 $languageCode = LanguageEditor::readLanguageCodeFromXML($xml);
221 $languageName = LanguageEditor::readLanguageNameFromXML($xml);
222
223 $languages[$languageCode] = $languageName;
158bd3ca
TD
224 }
225 }
226 $tar->close();
9f959ced 227
1157f8be
MW
228 // sort languages by language name
229 asort($languages);
9f959ced 230
158bd3ca
TD
231 return $languages;
232 }
233
234 /**
235 * Calculates the current state of the progress bar.
9f959ced 236 *
158bd3ca
TD
237 * @param integer $currentStep
238 */
239 protected function calcProgress($currentStep) {
158bd3ca 240 // calculate progress
f5ad2373 241 $progress = round((100 / 12) * ++$currentStep, 0);
158bd3ca
TD
242 self::getTPL()->assign(array('progress' => $progress));
243 }
244
245 /**
246 * Executes the setup steps.
247 */
248 protected function setup() {
249 // get current step
250 if (isset($_REQUEST['step'])) $step = $_REQUEST['step'];
251 else $step = 'selectSetupLanguage';
9f959ced 252
158bd3ca
TD
253 // execute current step
254 switch ($step) {
255 case 'selectSetupLanguage':
256 if (!self::$developerMode) {
257 $this->calcProgress(0);
258 $this->selectSetupLanguage();
259 break;
260 }
9f959ced 261
158bd3ca
TD
262 case 'showLicense':
263 if (!self::$developerMode) {
264 $this->calcProgress(1);
265 $this->showLicense();
266 break;
267 }
9f959ced 268
158bd3ca
TD
269 case 'showSystemRequirements':
270 if (!self::$developerMode) {
271 $this->calcProgress(2);
272 $this->showSystemRequirements();
273 break;
274 }
9f959ced 275
158bd3ca
TD
276 case 'searchWcfDir':
277 $this->calcProgress(3);
278 $this->searchWcfDir();
9f959ced
MS
279 break;
280
158bd3ca
TD
281 case 'unzipFiles':
282 $this->calcProgress(4);
283 $this->unzipFiles();
9f959ced
MS
284 break;
285
158bd3ca
TD
286 case 'selectLanguages':
287 $this->calcProgress(5);
288 $this->selectLanguages();
9f959ced
MS
289 break;
290
158bd3ca
TD
291 case 'configureDB':
292 $this->calcProgress(6);
293 $this->configureDB();
9f959ced
MS
294 break;
295
158bd3ca
TD
296 case 'createDB':
297 $this->calcProgress(7);
298 $this->createDB();
9f959ced
MS
299 break;
300
158bd3ca
TD
301 case 'logFiles':
302 $this->calcProgress(8);
303 $this->logFiles();
9f959ced
MS
304 break;
305
158bd3ca
TD
306 case 'installLanguage':
307 $this->calcProgress(9);
308 $this->installLanguage();
9f959ced
MS
309 break;
310
158bd3ca
TD
311 case 'createUser':
312 $this->calcProgress(10);
313 $this->createUser();
9f959ced
MS
314 break;
315
158bd3ca
TD
316 case 'installPackages':
317 $this->calcProgress(11);
318 $this->installPackages();
9f959ced 319 break;
158bd3ca
TD
320 }
321 }
322
323 /**
324 * Shows the first setup page.
325 */
326 protected function selectSetupLanguage() {
158bd3ca 327 WCF::getTPL()->assign(array(
1157f8be 328 'availableLanguages' => self::$availableLanguages,
158bd3ca
TD
329 'nextStep' => 'showLicense'
330 ));
331 WCF::getTPL()->display('stepSelectSetupLanguage');
332 }
333
334 /**
335 * Shows the license agreement.
336 */
337 protected function showLicense() {
338 if (isset($_POST['send'])) {
339 if (isset($_POST['accepted'])) {
340 $this->gotoNextStep('showSystemRequirements');
341 exit;
342 }
343 else {
344 WCF::getTPL()->assign(array('missingAcception' => true));
345 }
346
347 }
348
349 if (file_exists(TMP_DIR.'setup/license/license_'.self::$selectedLanguageCode.'.txt')) {
350 $license = file_get_contents(TMP_DIR.'setup/license/license_'.self::$selectedLanguageCode.'.txt');
351 }
352 else {
353 $license = file_get_contents(TMP_DIR.'setup/license/license_en.txt');
354 }
355
356 WCF::getTPL()->assign(array(
357 'license' => $license,
358 'nextStep' => 'showLicense'
359 ));
360 WCF::getTPL()->display('stepShowLicense');
361 }
362
363 /**
364 * Shows the system requirements.
365 */
366 protected function showSystemRequirements() {
367 $system = array();
368
369 // php version
370 $system['phpVersion']['value'] = phpversion();
371 $comparePhpVersion = preg_replace('/^(\d+\.\d+\.\d+).*$/', '\\1', $system['phpVersion']['value']);
cec6c73a 372 $system['phpVersion']['result'] = (version_compare($comparePhpVersion, '5.3.2') >= 0);
158bd3ca
TD
373
374 // sql
375 $system['sql']['value'] = array_keys(self::getAvailableDBClasses());
15fa2802 376 $system['sql']['result'] = !empty($system['sql']['value']);
158bd3ca
TD
377
378 // upload_max_filesize
379 $system['uploadMaxFilesize']['value'] = ini_get('upload_max_filesize');
380 $system['uploadMaxFilesize']['result'] = (intval($system['uploadMaxFilesize']['value']) > 0);
381
382 // gdlib version
383 $system['gdLib']['value'] = '0.0.0';
384 if (function_exists('gd_info')) {
385 $temp = gd_info();
386 $match = array();
387 if (preg_match('!([0-9]+\.[0-9]+(?:\.[0-9]+)?)!', $temp['GD Version'], $match)) {
388 if (preg_match('/^[0-9]+\.[0-9]+$/', $match[1])) $match[1] .= '.0';
389 $system['gdLib']['value'] = $match[1];
390 }
391 }
392 $system['gdLib']['result'] = (version_compare($system['gdLib']['value'], '2.0.0') >= 0);
393
394 // mb string
395 $system['mbString']['result'] = extension_loaded('mbstring');
396
b4f1ea02
AE
397 // memory limit
398 $system['memoryLimit']['value'] = ini_get('memory_limit');
399 $system['memoryLimit']['result'] = $this->compareMemoryLimit();
400
158bd3ca
TD
401 WCF::getTPL()->assign(array(
402 'system' => $system,
403 'nextStep' => 'searchWcfDir'
404 ));
405 WCF::getTPL()->display('stepShowSystemRequirements');
406 }
407
408 /**
b4f1ea02
AE
409 * Returns true, if memory_limit is set to at least 64 MB
410 *
411 * @return boolean
412 */
413 protected function compareMemoryLimit() {
414 $memoryLimit = ini_get('memory_limit');
415
416 // no limit
417 if ($memoryLimit == -1) {
418 return true;
419 }
420
421 // completely numeric, PHP assumes byte
422 if (is_numeric($memoryLimit)) {
423 $memoryLimit = $memoryLimit / 1024;
424 return ($memoryLimit >= 64);
425 }
426
427 // PHP supports 'K', 'M' and 'G' shorthand notation
53e6372f 428 if (preg_match('~^(\d+)([KMG])$~', $memoryLimit, $matches)) {
b4f1ea02
AE
429 switch ($matches[2]) {
430 case 'K':
431 $memoryLimit = $matches[1] * 1024;
432 return ($memoryLimit >= 64);
433 break;
434
435 case 'M':
436 return ($matches[1] >= 64);
437 break;
438
439 case 'G':
440 return ($matches[1] >= 1);
441 break;
442 }
443 }
444
445 return false;
446 }
447
448 /**
158bd3ca
TD
449 * Searches the wcf dir.
450 */
451 protected function searchWcfDir() {
452 $foundDirectory = '';
453 if (self::$wcfDir) {
454 $wcfDir = self::$wcfDir;
455 }
456 else {
457 if ($foundDirectory = FileUtil::scanFolder(INSTALL_SCRIPT_DIR, "WCF.class.php", true)) {
458 $foundDirectory = $wcfDir = FileUtil::unifyDirSeperator(dirname(dirname(dirname($foundDirectory))).'/');
459
460 if (dirname(dirname($wcfDir)).'/' == TMP_DIR) {
461 $foundDirectory = false;
462 $wcfDir = FileUtil::unifyDirSeperator(INSTALL_SCRIPT_DIR).'wcf/';
463 }
464 }
465 else {
466 $wcfDir = FileUtil::unifyDirSeperator(INSTALL_SCRIPT_DIR).'wcf/';
467 }
468 }
469
470 // domain
471 $domainName = '';
472 if (!empty($_SERVER['SERVER_NAME'])) $domainName = 'http://' . $_SERVER['SERVER_NAME'];
473 // port
474 if (!empty($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] != 80) $domainName .= ':' . $_SERVER['SERVER_PORT'];
475 // script url
476 $installScriptUrl = '';
477 if (!empty($_SERVER['REQUEST_URI'])) $installScriptUrl = FileUtil::removeLeadingSlash(FileUtil::removeTrailingSlash(FileUtil::unifyDirSeperator(dirname($_SERVER['REQUEST_URI']))));
478
479 WCF::getTPL()->assign(array(
480 'nextStep' => 'unzipFiles',
481 'foundDirectory' => $foundDirectory,
482 'wcfDir' => $wcfDir,
483 'domainName' => $domainName,
484 'installScriptUrl' => $installScriptUrl,
485 'installScriptDir' => FileUtil::unifyDirSeperator(INSTALL_SCRIPT_DIR)
486 ));
487
488 WCF::getTPL()->display('stepSearchWcfDir');
489 }
490
491 /**
492 * Unzips the files of the wcfsetup tar archive.
493 */
494 protected function unzipFiles() {
a17de04e
MS
495 // WCF seems to be installed, skip installation of files, database
496 // and admin account and go directly to the installation of packages
158bd3ca
TD
497 if (@is_file(self::$wcfDir.'lib/system/WCF.class.php')) {
498 $this->gotoNextStep('installPackages');
499 exit;
500 }
501 // WCF not yet installed, install files first
502 else {
503 try {
504 $this->installFiles();
505 }
6286572b 506 catch (\Exception $e) {
158bd3ca
TD
507 WCF::getTPL()->assign(array('exception' => $e));
508 $this->searchWcfDir();
509 return;
510 }
511
512 $this->gotoNextStep('selectLanguages');
513 }
514 }
515
516 /**
517 * Shows the page for choosing the installed languages.
518 */
519 protected function selectLanguages() {
520 $errorField = $errorType = '';
158bd3ca 521
158bd3ca
TD
522 // skip step in developer mode
523 // select all available languages automatically
524 if (self::$developerMode) {
525 self::$selectedLanguages = array();
f2f5a09c 526 foreach (self::$availableLanguages as $languageCode => $language) {
158bd3ca
TD
527 self::$selectedLanguages[] = $languageCode;
528 }
529
530 self::getTPL()->assign(array('selectedLanguages' => self::$selectedLanguages));
531 $this->gotoNextStep('configureDB');
532 exit;
533 }
534
158bd3ca
TD
535 // start error handling
536 if (isset($_POST['send'])) {
537 try {
538 // no languages selected
15fa2802 539 if (empty(self::$selectedLanguages)) {
158bd3ca
TD
540 throw new UserInputException('selectedLanguages');
541 }
542
543 // illegal selection
544 foreach (self::$selectedLanguages as $language) {
1157f8be 545 if (!isset(self::$availableLanguages[$language])) {
158bd3ca
TD
546 throw new UserInputException('selectedLanguages');
547 }
548 }
549
550 // no errors
551 // go to next step
552 $this->gotoNextStep('configureDB');
553 exit;
554 }
555 catch (UserInputException $e) {
556 $errorField = $e->getField();
557 $errorType = $e->getType();
558 }
559 }
560 else {
561 self::$selectedLanguages[] = self::$selectedLanguageCode;
562 WCF::getTPL()->assign(array('selectedLanguages' => self::$selectedLanguages));
563 }
564
565 WCF::getTPL()->assign(array(
566 'errorField' => $errorField,
567 'errorType' => $errorType,
1157f8be 568 'availableLanguages' => self::$availableLanguages,
158bd3ca
TD
569 'nextStep' => 'selectLanguages'
570 ));
571 WCF::getTPL()->display('stepSelectLanguages');
572 }
573
574 /**
575 * Shows the page for configurating the database connection.
576 */
577 protected function configureDB() {
578 $availableDBClasses = self::getAvailableDBClasses();
579 $dbHost = 'localhost';
580 $dbUser = 'root';
581 $dbPassword = '';
582 $dbName = 'wcf';
f9600494 583 $dbNumber = 1;
cdf15dc3 584 $dbClass = '';
f9600494
TD
585 // set $dbClass to first item in $availableDBClasses
586 foreach ($availableDBClasses as $dbClass) {
587 $dbClass = $dbClass['class'];
588 break;
589 }
158bd3ca
TD
590 $overwriteTables = false;
591
592 if (isset($_POST['send'])) {
593 if (isset($_POST['dbHost'])) $dbHost = $_POST['dbHost'];
594 if (isset($_POST['dbUser'])) $dbUser = $_POST['dbUser'];
595 if (isset($_POST['dbPassword'])) $dbPassword = $_POST['dbPassword'];
596 if (isset($_POST['dbName'])) $dbName = $_POST['dbName'];
597 if (isset($_POST['overwriteTables'])) $overwriteTables = intval($_POST['overwriteTables']);
598 // Should the user not be prompted if converted or default n match an
599 // existing installation number? By now the existing installation
600 // will be overwritten just so!
f9600494
TD
601
602 // ensure that $dbNumber is zero or a positive integer
603 if (isset($_POST['dbNumber'])) $dbNumber = max(0, intval($_POST['dbNumber']));
158bd3ca
TD
604 if (isset($_POST['dbClass'])) $dbClass = $_POST['dbClass'];
605
606 // get port
607 $dbPort = 0;
608 if (preg_match('/^(.+?):(\d+)$/', $dbHost, $match)) {
609 $dbHost = $match[1];
610 $dbPort = intval($match[2]);
611 }
612
613 // test connection
614 try {
615 // check db class
616 $validDB = false;
617 foreach ($availableDBClasses as $dbData) {
618 if ($dbData['class'] == $dbClass) {
619 $validDB = true;
620 break;
621 }
622 }
623
624 if (!$validDB) {
625 throw new SystemException("Database type '".$dbClass."'. is not available on this system.");
626 }
627
628 // check connection data
629 $db = new $dbClass($dbHost, $dbUser, $dbPassword, $dbName, $dbPort);
630 $db->connect();
631
632 // check sql version
633 if (!empty($availableDBClasses[$dbClass]['minversion'])) {
634 $sqlVersion = $db->getVersion();
635 if ($sqlVersion != 'unknown') {
636 $compareSQLVersion = preg_replace('/^(\d+\.\d+\.\d+).*$/', '\\1', $sqlVersion);
637 if (!(version_compare($compareSQLVersion, $availableDBClasses[$dbClass]['minversion']) >= 0)) {
638 throw new SystemException("Insufficient SQL version '".$compareSQLVersion."'. Version '".$availableDBClasses[$dbClass]['minversion']."' or greater is needed.");
639 }
640 }
641 }
642
643 // check for table conflicts
644 $conflictedTables = $this->getConflictedTables($db, $dbNumber);
76d74665
AE
645 if (!empty($conflictedTables) && ($overwriteTables || self::$developerMode)) {
646 // remove tables
647 $db->getEditor()->dropConflictedTables($conflictedTables);
648 }
158bd3ca
TD
649
650 // write config.inc
651 if (empty($conflictedTables) || $overwriteTables || self::$developerMode) {
652 // connection successfully established
653 // write configuration to config.inc.php
654 $file = new File(WCF_DIR.'config.inc.php');
655 $file->write("<?php\n");
656 $file->write("\$dbHost = '".StringUtil::replace("'", "\\'", $dbHost)."';\n");
657 $file->write("\$dbPort = ".$dbPort.";\n");
658 $file->write("\$dbUser = '".StringUtil::replace("'", "\\'", $dbUser)."';\n");
659 $file->write("\$dbPassword = '".StringUtil::replace("'", "\\'", $dbPassword)."';\n");
660 $file->write("\$dbName = '".StringUtil::replace("'", "\\'", $dbName)."';\n");
661 $file->write("\$dbClass = '".StringUtil::replace("'", "\\'", $dbClass)."';\n");
662 $file->write("if (!defined('WCF_N')) define('WCF_N', $dbNumber);\n?>");
663 $file->close();
a17de04e 664
158bd3ca
TD
665 // go to next step
666 $this->gotoNextStep('createDB');
667 exit;
668 }
669 // show configure temnplate again
670 else {
671 WCF::getTPL()->assign(array('conflictedTables' => $conflictedTables));
672 }
673 }
674 catch (SystemException $e) {
675 WCF::getTPL()->assign(array('exception' => $e));
676 }
677 }
678 WCF::getTPL()->assign(array(
679 'dbHost' => $dbHost,
680 'dbUser' => $dbUser,
681 'dbPassword' => $dbPassword,
682 'dbName' => $dbName,
683 'dbNumber' => $dbNumber,
684 'dbClass' => $dbClass,
685 'availableDBClasses' => $availableDBClasses,
686 'nextStep' => 'configureDB'
687 ));
688 WCF::getTPL()->display('stepConfigureDB');
689 }
690
691
692 /**
693 * Checks if in the chosen database are tables in conflict with the wcf tables
694 * which will be created in the next step.
9f959ced
MS
695 *
696 * @param wcf\system\database\Database $db
697 * @param integer $dbNumber
158bd3ca
TD
698 */
699 protected function getConflictedTables($db, $dbNumber) {
700 // get content of the sql structure file
701 $sql = file_get_contents(TMP_DIR.'setup/db/install.sql');
702
703 // installation number value 'n' (WCF_N) must be reflected in the executed sql queries
704 $sql = StringUtil::replace('wcf1_', 'wcf'.$dbNumber.'_', $sql);
705
706 // get all tablenames which should be created
707 preg_match_all("%CREATE\s+TABLE\s+(\w+)%", $sql, $matches);
708
709 // get all installed tables from chosen database
710 $existingTables = $db->getEditor()->getTableNames();
711
712 // check if existing tables are in conflict with wcf tables
713 $conflictedTables = array();
714 foreach ($existingTables as $existingTableName) {
715 foreach ($matches[1] as $wcfTableName) {
716 if ($existingTableName == $wcfTableName) {
717 $conflictedTables[] = $wcfTableName;
718 }
719 }
720 }
721 return $conflictedTables;
722 }
723
724 /**
725 * Creates the database structure of the wcf.
726 */
727 protected function createDB() {
728 $this->initDB();
729
730 // get content of the sql structure file
731 $sql = file_get_contents(TMP_DIR.'setup/db/install.sql');
732
733 // installation number value 'n' (WCF_N) must be reflected in the executed sql queries
734 $sql = StringUtil::replace('wcf1_', 'wcf'.WCF_N.'_', $sql);
735
736 // execute sql queries
737 $parser = new SQLParser($sql);
738 $parser->execute();
739
740 // log sql queries
741 preg_match_all("~CREATE\s+TABLE\s+(\w+)~i", $sql, $matches);
742
15fa2802 743 if (!empty($matches[1])) {
158bd3ca
TD
744 $sql = "INSERT INTO wcf".WCF_N."_package_installation_sql_log
745 (sqlTable)
746 VALUES (?)";
747 $statement = self::getDB()->prepareStatement($sql);
748 foreach ($matches[1] as $tableName) {
749 $statement->execute(array($tableName));
750 }
751 }
752
753 /*
754 * Manually install PIPPackageInstallationPlugin since install.sql content is not escaped resulting
755 * in different behaviour in MySQL and MSSQL. You SHOULD NOT move this into install.sql!
756 */
757 $sql = "INSERT INTO wcf".WCF_N."_package_installation_plugin
758 (pluginName, priority, className)
759 VALUES (?, ?, ?)";
760 $statement = self::getDB()->prepareStatement($sql);
761 $statement->execute(array(
db92cb8f 762 'packageInstallationPlugin',
158bd3ca
TD
763 1,
764 'wcf\system\package\plugin\PIPPackageInstallationPlugin'
765 ));
766
767 $this->gotoNextStep('logFiles');
768 }
769
770 /**
771 * Logs the unzipped files.
772 */
773 protected function logFiles() {
774 $this->initDB();
775
776 $this->getInstalledFiles(WCF_DIR);
777 $acpTemplateInserts = $fileInserts = array();
778 foreach (self::$installedFiles as $file) {
779 $match = array();
780 if (preg_match('!/acp/templates/([^/]+)\.tpl$!', $file, $match)) {
781 // acp template
782 $acpTemplateInserts[] = $match[1];
783 }
784 else {
785 // regular file
786 $fileInserts[] = StringUtil::replace(WCF_DIR, '', $file);
787 }
788 }
789
790 // save acp template log
791 if (!empty($acpTemplateInserts)) {
792 $sql = "INSERT INTO wcf".WCF_N."_acp_template
793 (templateName)
794 VALUES (?)";
795 $statement = self::getDB()->prepareStatement($sql);
796
dcd38d37 797 self::getDB()->beginTransaction();
158bd3ca 798 foreach ($acpTemplateInserts as $acpTemplate) {
dcd38d37 799 $statement->executeUnbuffered(array($acpTemplate));
158bd3ca 800 }
dcd38d37 801 self::getDB()->commitTransaction();
158bd3ca
TD
802 }
803
804 // save file log
805 if (!empty($fileInserts)) {
806 $sql = "INSERT INTO wcf".WCF_N."_package_installation_file_log
807 (filename)
808 VALUES (?)";
809 $statement = self::getDB()->prepareStatement($sql);
810
dcd38d37 811 self::getDB()->beginTransaction();
158bd3ca 812 foreach ($fileInserts as $file) {
dcd38d37 813 $statement->executeUnbuffered(array($file));
158bd3ca 814 }
dcd38d37 815 self::getDB()->commitTransaction();
158bd3ca
TD
816 }
817
818 $this->gotoNextStep('installLanguage');
819 }
820
821 /**
822 * Scans the given dir for installed files.
9f959ced
MS
823 *
824 * @param string $dir
158bd3ca
TD
825 */
826 protected function getInstalledFiles($dir) {
827 if ($files = glob($dir.'*')) {
828 foreach ($files as $file) {
829 if (is_dir($file)) {
830 $this->getInstalledFiles(FileUtil::addTrailingSlash($file));
831 }
832 else {
833 self::$installedFiles[] = FileUtil::unifyDirSeperator($file);
834 }
835 }
836 }
837 }
838
839 /**
840 * Installs the selected languages.
841 */
842 protected function installLanguage() {
843 $this->initDB();
844
845 foreach (self::$selectedLanguages as $language) {
846 // get language.xml file name
847 $filename = TMP_DIR.'install/lang/'.$language.'.xml';
848
849 // check the file
850 if (!file_exists($filename)) {
4fe0b42b 851 throw new SystemException("unable to find language file '".$filename."'");
158bd3ca
TD
852 }
853
854 // open the file
855 $xml = new XML();
856 $xml->load($filename);
857
858 // import xml
859 LanguageEditor::importFromXML($xml, 0);
860 }
861
862 // set default language
61022658
AE
863 $language = LanguageFactory::getInstance()->getLanguageByCode(in_array(self::$selectedLanguageCode, self::$selectedLanguages) ? self::$selectedLanguageCode : self::$selectedLanguages[0]);
864 LanguageFactory::getInstance()->makeDefault($language->languageID);
158bd3ca 865
158bd3ca 866 // rebuild language cache
b401cd0d 867 LanguageCacheBuilder::getInstance()->reset();
158bd3ca
TD
868
869 // go to next step
870 $this->gotoNextStep('createUser');
871 }
872
873 /**
874 * Shows the page for creating the admin account.
875 */
876 protected function createUser() {
877 $errorType = $errorField = $username = $email = $confirmEmail = $password = $confirmPassword = '';
878
879 $username = '';
880 $email = $confirmEmail = '';
881 $password = $confirmPassword = '';
882
883 if (isset($_POST['send']) || self::$developerMode) {
884 if (isset($_POST['send'])) {
39bea7dd
MS
885 if (isset($_POST['username'])) $username = StringUtil::trim($_POST['username']);
886 if (isset($_POST['email'])) $email = StringUtil::trim($_POST['email']);
887 if (isset($_POST['confirmEmail'])) $confirmEmail = StringUtil::trim($_POST['confirmEmail']);
888 if (isset($_POST['password'])) $password = $_POST['password'];
889 if (isset($_POST['confirmPassword'])) $confirmPassword = $_POST['confirmPassword'];
158bd3ca
TD
890 }
891 else {
892 $username = $password = $confirmPassword = 'root';
893 $email = $confirmEmail = 'woltlab@woltlab.com';
894 }
895
896 // error handling
897 try {
898 // username
899 if (empty($username)) {
900 throw new UserInputException('username');
901 }
902 if (!UserUtil::isValidUsername($username)) {
903 throw new UserInputException('username', 'notValid');
904 }
905
906 // e-mail address
907 if (empty($email)) {
908 throw new UserInputException('email');
909 }
910 if (!UserUtil::isValidEmail($email)) {
911 throw new UserInputException('email', 'notValid');
912 }
913
914 // confirm e-mail address
915 if ($email != $confirmEmail) {
916 throw new UserInputException('confirmEmail', 'notEqual');
917 }
918
919 // password
920 if (empty($password)) {
921 throw new UserInputException('password');
922 }
923
924 // confirm e-mail address
925 if ($password != $confirmPassword) {
926 throw new UserInputException('confirmPassword', 'notEqual');
927 }
928
929 // no errors
930 // init database connection
931 $this->initDB();
932
933 // get language id
934 $languageID = 0;
935 $sql = "SELECT languageID
936 FROM wcf".WCF_N."_language
937 WHERE languageCode = ?";
938 $statement = self::getDB()->prepareStatement($sql);
939 $statement->execute(array(self::$selectedLanguageCode));
940 $row = $statement->fetchArray();
941 if (isset($row['languageID'])) $languageID = $row['languageID'];
942
943 // create user
944 $data = array(
945 'data' => array(
946 'email' => $email,
947 'languageID' => $languageID,
948 'password' => $password,
949 'username' => $username
950 ),
951 'groups' => array(
952 1,
953 3,
954 4
955 ),
956 'languages' => array(
957 $languageID
958 )
959 );
960
961 $userAction = new UserAction(array(), 'create', $data);
962 $userAction->executeAction();
963
964 // go to next step
965 $this->gotoNextStep('installPackages');
966 exit;
967 }
968 catch (UserInputException $e) {
969 $errorField = $e->getField();
970 $errorType = $e->getType();
971 }
972 }
973
974 WCF::getTPL()->assign(array(
975 'errorField' => $errorField,
976 'errorType' => $errorType,
977 'username' => $username,
978 'email' => $email,
979 'confirmEmail' => $confirmEmail,
980 'password' => $password,
981 'confirmPassword' => $confirmPassword,
982 'nextStep' => 'createUser'
983 ));
984 WCF::getTPL()->display('stepCreateUser');
985 }
986
987 /**
988 * Registers with wcf setup delivered packages in the package installation queue.
989 */
990 protected function installPackages() {
991 // init database connection
992 $this->initDB();
993
994 // get admin account
995 $admin = new User(1);
996
997 // get delivered packages
998 $wcfPackageFile = '';
999 $otherPackages = array();
1000 $tar = new Tar(SETUP_FILE);
1001 foreach ($tar->getContentList() as $file) {
1002 if ($file['type'] != 'folder' && StringUtil::indexOf($file['filename'], 'install/packages/') === 0) {
1003 $packageFile = basename($file['filename']);
158bd3ca 1004
1d9fd26a
AE
1005 // ignore any files which aren't an archive
1006 if (preg_match('~\.(tar\.gz|tgz|tar)$~', $packageFile)) {
1007 $packageName = preg_replace('!\.(tar\.gz|tgz|tar)$!', '', $packageFile);
1008
1009 if ($packageName == 'com.woltlab.wcf') {
1010 $wcfPackageFile = $packageFile;
1011 }
1012 else {
1013 $isStrato = (!empty($_SERVER['DOCUMENT_ROOT']) && (strpos($_SERVER['DOCUMENT_ROOT'], 'strato') !== false));
1014 if (!$isStrato && preg_match('!\.(tar\.gz|tgz)$!', $packageFile)) {
1015 // try to unzip zipped package files
1016 if (FileUtil::uncompressFile(TMP_DIR.'install/packages/'.$packageFile, TMP_DIR.'install/packages/'.$packageName.'.tar')) {
1017 @unlink(TMP_DIR.'install/packages/'.$packageFile);
1018 $packageFile = $packageName.'.tar';
1019 }
158bd3ca 1020 }
1d9fd26a
AE
1021
1022 $otherPackages[$packageName] = $packageFile;
158bd3ca 1023 }
158bd3ca
TD
1024 }
1025 }
1026 }
1027 $tar->close();
1028
1029 // register packages in queue
1030 // get new process id
1031 $sql = "SELECT MAX(processNo) AS processNo
1032 FROM wcf".WCF_N."_package_installation_queue";
1033 $statement = self::getDB()->prepareStatement($sql);
1034 $statement->execute();
1035 $result = $statement->fetchArray();
1036 $processNo = intval($result['processNo']) + 1;
158bd3ca
TD
1037
1038 // search existing wcf package
1039 $sql = "SELECT COUNT(*) AS count
1040 FROM wcf".WCF_N."_package
1041 WHERE package = 'com.woltlab.wcf'";
1042 $statement = self::getDB()->prepareStatement($sql);
1043 $statement->execute();
1044 $row = $statement->fetchArray();
1045 if (!$row['count']) {
1046 if (empty($wcfPackageFile)) {
4fe0b42b 1047 throw new SystemException('the essential package com.woltlab.wcf is missing.');
158bd3ca
TD
1048 }
1049
1050 // register essential wcf package
89142da9 1051 $queue = PackageInstallationQueueEditor::create(array(
f287c18d
AE
1052 'processNo' => $processNo,
1053 'userID' => $admin->userID,
d7281d46
AE
1054 'package' => 'com.woltlab.wcf',
1055 'packageName' => 'WoltLab Community Framework',
158bd3ca 1056 'archive' => TMP_DIR.'install/packages/'.$wcfPackageFile
89142da9 1057 ));
158bd3ca
TD
1058 }
1059
1060 // register all other delivered packages
1061 asort($otherPackages);
1062 foreach ($otherPackages as $packageName => $packageFile) {
d7281d46
AE
1063 // extract packageName from archive's package.xml
1064 $archive = new PackageArchive(TMP_DIR.'install/packages/'.$packageFile);
1065 try {
1066 $archive->openArchive();
1067 }
1068 catch (\Exception $e) {
8e2c12f5
AE
1069 // we've encountered a broken archive, revert everything and then fail
1070 $sql = "SELECT queueID, parentQueueID
1071 FROM wcf".WCF_N."_package_installation_queue";
1072 $statement = WCF::getDB()->prepareStatement($sql);
1073 $statement->execute();
1074 $queues = array();
1075 while ($row = $statement->fetchArray()) {
1076 $queues[$row['queueID']] = $row['parentQueueID'];
1077 }
1078
1079 $queueIDs = array();
1080 $queueID = $queue->queueID;
1081 while ($queueID) {
1082 $queueIDs[] = $queueID;
1083
1084 $queueID = (isset($queues[$queueID])) ?: 0;
1085 }
1086
1087 // remove previously created queues
1088 if (!empty($queueIDs)) {
1089 $sql = "DELETE FROM wcf".WCF_N."_package_installation_queue
1090 WHERE queueID = ?";
1091 $statement = WCF::getDB()->prepareStatement($sql);
1092 WCF::getDB()->beginTransaction();
1093 foreach ($queueIDs as $queueID) {
1094 $statement->execute(array($queueID));
1095 }
1096 WCF::getDB()->commitTransaction();
1097 }
1098
1099 // remove package files
1100 @unlink(TMP_DIR.'install/packages/'.$wcfPackageFile);
1101 foreach ($otherPackages as $packageFile) {
1102 @unlink(TMP_DIR.'install/packages/'.$packageFile);
1103 }
1104
1105 // throw exception again
1106 throw new SystemException('', 0, '', $e);
d7281d46
AE
1107 }
1108
89142da9
AE
1109 $queue = PackageInstallationQueueEditor::create(array(
1110 'parentQueueID' => $queue->queueID,
f287c18d
AE
1111 'processNo' => $processNo,
1112 'userID' => $admin->userID,
d7281d46 1113 'package' => $packageName,
a2ad7897 1114 'packageName' => $archive->getLocalizedPackageInfo('packageName'),
158bd3ca 1115 'archive' => TMP_DIR.'install/packages/'.$packageFile
89142da9 1116 ));
158bd3ca
TD
1117 }
1118
1119 // login as admin
1120 $factory = new ACPSessionFactory();
1121 $factory->load();
1122
1123 SessionHandler::getInstance()->changeUser($admin);
1124 SessionHandler::getInstance()->register('masterPassword', 1);
1125 SessionHandler::getInstance()->update();
1126
158bd3ca 1127 $installPhpDeleted = @unlink('./install.php');
a17de04e 1128 @unlink('./test.php');
158bd3ca
TD
1129 $wcfSetupTarDeleted = @unlink('./WCFSetup.tar.gz');
1130
1131 // print page
1132 WCF::getTPL()->assign(array(
1133 'installPhpDeleted' => $installPhpDeleted,
1134 'wcfSetupTarDeleted' => $wcfSetupTarDeleted
1135 ));
1136 WCF::getTPL()->display('stepInstallPackages');
1137
1138 // delete tmp files
364a2e49 1139 $directory = TMP_DIR.'/';
ac9b0f6e 1140 DirectoryUtil::getInstance($directory)->removePattern(new Regex('\.tar(\.gz)?$'), true);
158bd3ca
TD
1141 }
1142
1143 /**
1144 * Goes to the next step.
9f959ced 1145 *
158bd3ca
TD
1146 * @param string $nextStep
1147 */
1148 protected function gotoNextStep($nextStep) {
1149 WCF::getTPL()->assign(array('nextStep' => $nextStep));
1150 WCF::getTPL()->display('stepNext');
1151 }
1152
1153 /**
1154 * Installs the files of the tar archive.
1155 */
1156 protected static function installFiles() {
1157 new Installer(self::$wcfDir, SETUP_FILE, null, 'install/files/');
1158 }
1159
1160 /**
aac1247e 1161 * Gets the package name of the first application in WCFSetup.tar.gz.
158bd3ca
TD
1162 */
1163 protected static function getPackageName() {
1164 // get package name
1165 $tar = new Tar(SETUP_FILE);
1166 foreach ($tar->getContentList() as $file) {
1167 if ($file['type'] != 'folder' && StringUtil::indexOf($file['filename'], 'install/packages/') === 0) {
1168 $packageFile = basename($file['filename']);
1169 $packageName = preg_replace('!\.(tar\.gz|tgz|tar)$!', '', $packageFile);
1170
1171 if ($packageName != 'com.woltlab.wcf') {
1172 try {
1173 $archive = new PackageArchive(TMP_DIR.'install/packages/'.$packageFile);
1174 $archive->openArchive();
a2ad7897 1175 self::$setupPackageName = $archive->getLocalizedPackageInfo('packageName');
158bd3ca
TD
1176 $archive->getTar()->close();
1177 break;
1178 }
1179 catch (SystemException $e) {}
1180 }
1181 }
1182 }
1183 $tar->close();
1184
1185 // assign package name
1186 WCF::getTPL()->assign(array('setupPackageName' => self::$setupPackageName));
1187 }
1188}