Added work-around for detection of older MySQL versions during setup
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / WCFSetup.class.php
index c2c8786b86ad7dfff9b0b0fcf4dafcbbd35efb3d..f78181c059b4c94622393abccc7c6c0161a76332 100644 (file)
@@ -7,7 +7,9 @@ use wcf\data\package\Package;
 use wcf\data\user\User;
 use wcf\data\user\UserAction;
 use wcf\system\cache\builder\LanguageCacheBuilder;
+use wcf\system\database\exception\DatabaseException;
 use wcf\system\database\util\SQLParser;
+use wcf\system\database\MySQLDatabase;
 use wcf\system\exception\SystemException;
 use wcf\system\exception\UserInputException;
 use wcf\system\io\File;
@@ -20,13 +22,13 @@ use wcf\system\setup\Installer;
 use wcf\system\template\SetupTemplateEngine;
 use wcf\util\DirectoryUtil;
 use wcf\util\FileUtil;
+use wcf\util\HeaderUtil;
 use wcf\util\StringUtil;
 use wcf\util\UserUtil;
 use wcf\util\XML;
 
 // define
-define('PACKAGE_ID', '0');
-define('HTTP_ENABLE_NO_CACHE_HEADERS', 0);
+define('PACKAGE_ID', 0);
 define('HTTP_ENABLE_GZIP', 0);
 define('HTTP_GZIP_LEVEL', 0);
 define('HTTP_SEND_X_FRAME_OPTIONS', 0);
@@ -39,11 +41,9 @@ define('ENABLE_BENCHMARK', 0);
  * Executes the installation of the basic WCF systems.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2015 WoltLab GmbH
+ * @copyright  2001-2016 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    com.woltlab.wcf
- * @subpackage system
- * @category   Community Framework
+ * @package    WoltLabSuite\Core\System
  */
 class WCFSetup extends WCF {
        /**
@@ -76,47 +76,34 @@ class WCFSetup extends WCF {
         */
        protected static $installedFiles = [];
        
-       /**
-        * name of installed primary application
-        * @var string
-        */
-       protected static $setupPackageName = 'WoltLab Community Framework';
-       
        /**
         * indicates if developer mode is used to install
         * @var boolean
         */
        protected static $developerMode = 0;
        
-       /**
-        * supported databases
-        * @var string[][]
-        */
-       protected static $dbClasses = [
-               'MySQLDatabase' => ['class' => 'wcf\system\database\MySQLDatabase', 'minversion' => '5.1.17']//,                // MySQL 5.1.17+
-               //'PostgreSQLDatabase' => ['class' => 'wcf\system\database\PostgreSQLDatabase', 'minversion' => '8.2.0']        // PostgreSQL 8.2.0+
-       ];
-       
+       /** @noinspection PhpMissingParentConstructorInspection */
        /**
         * Calls all init functions of the WCFSetup class and starts the setup process.
         */
        public function __construct() {
                @set_time_limit(0);
                
-               $this->getDeveloperMode();
-               $this->getLanguageSelection();
-               $this->getInstallationDirectories();
+               static::getDeveloperMode();
+               static::getLanguageSelection();
+               static::getInstallationDirectories();
                $this->initLanguage();
                $this->initTPL();
+               /** @noinspection PhpUndefinedMethodInspection */
                self::getLanguage()->loadLanguage();
-               $this->getPackageName();
+               static::getPackageNames();
                
                // start setup
                $this->setup();
        }
        
        /**
-        * Gets the status of the developer mode.
+        * Sets the status of the developer mode.
         */
        protected static function getDeveloperMode() {
                if (isset($_GET['dev'])) self::$developerMode = intval($_GET['dev']);
@@ -124,7 +111,7 @@ class WCFSetup extends WCF {
        }
        
        /**
-        * Gets the selected language.
+        * Sets the selected language.
         */
        protected static function getLanguageSelection() {
                self::$availableLanguages = self::getAvailableLanguages();
@@ -142,32 +129,11 @@ class WCFSetup extends WCF {
        }
        
        /**
-        * Gets the available database classes.
+        * Sets the selected wcf dir from request.
         * 
-        * @return      string[]
-        */
-       protected static function getAvailableDBClasses() {
-               $availableDBClasses = [];
-               foreach (self::$dbClasses as $class => $data) {
-                       if (call_user_func([$data['class'], 'isSupported'])) {
-                               $availableDBClasses[$class] = $data;
-                       }
-               }
-               
-               return $availableDBClasses;
-       }
-       
-       /**
-        * Gets the selected wcf dir from request.
-        * 
-        * @since       2.2
+        * @since       3.0
         */
        protected static function getInstallationDirectories() {
-               if (self::$developerMode && isset($_ENV['WCFSETUP_USEDEFAULTWCFDIR'])) {
-                       if (!isset($_REQUEST['directories']) || !is_array($_REQUEST['directories'])) $_REQUEST['directories'] = [];
-                       $_REQUEST['directories']['wcf'] = FileUtil::unifyDirSeparator(INSTALL_SCRIPT_DIR).'wcf/';
-               }
-               
                if (!empty($_REQUEST['directories']) && is_array($_REQUEST['directories'])) {
                        foreach ($_REQUEST['directories'] as $application => $directory) {
                                self::$directories[$application] = $directory;
@@ -280,13 +246,10 @@ class WCFSetup extends WCF {
                                        break;
                                }
                        
-                       /** @noinspection PhpMissingBreakStatementInspection */
                        case 'configureDirectories':
-                               if (!self::$developerMode || !isset($_ENV['WCFSETUP_USEDEFAULTWCFDIR'])) {
-                                       $this->calcProgress(3);
-                                       $this->configureDirectories();
-                                       break;
-                               }
+                               $this->calcProgress(3);
+                               $this->configureDirectories();
+                       break;
                        
                        case 'unzipFiles':
                                $this->calcProgress(4);
@@ -387,11 +350,10 @@ class WCFSetup extends WCF {
                $system['phpVersion']['result'] = (version_compare($comparePhpVersion, '5.5.4') >= 0);
                
                // sql
-               $system['sql']['value'] = array_keys(self::getAvailableDBClasses());
-               $system['sql']['result'] = !empty($system['sql']['value']);
+               $system['sql']['result'] = MySQLDatabase::isSupported();
                
                // upload_max_filesize
-               $system['uploadMaxFilesize']['value'] = ini_get('upload_max_filesize');
+               $system['uploadMaxFilesize']['value'] = min(ini_get('upload_max_filesize'), ini_get('post_max_size'));
                $system['uploadMaxFilesize']['result'] = (intval($system['uploadMaxFilesize']['value']) > 0);
                
                // gdlib version
@@ -410,6 +372,9 @@ class WCFSetup extends WCF {
                $system['memoryLimit']['value'] = ini_get('memory_limit');
                $system['memoryLimit']['result'] = $this->compareMemoryLimit();
                
+               // openssl extension
+               $system['openssl']['result'] = @extension_loaded('openssl');
+               
                WCF::getTPL()->assign([
                        'system' => $system,
                        'nextStep' => 'configureDirectories'
@@ -460,11 +425,11 @@ class WCFSetup extends WCF {
        /**
         * Searches the wcf dir.
         * 
-        * @since       2.2
+        * @since       3.0
         */
        protected function configureDirectories() {
                // get available packages
-               $applications = $packages = [];
+               $packages = [];
                foreach (glob(TMP_DIR . 'install/packages/*') as $file) {
                        $filename = basename($file);
                        if (preg_match('~\.(?:tar|tar\.gz|tgz)$~', $filename)) {
@@ -473,13 +438,11 @@ class WCFSetup extends WCF {
                                
                                $application = Package::getAbbreviation($package->getPackageInfo('name'));
                                
-                               $applications[] = $application;
                                $packages[$application] = [
-                                       'directory' => ($package->getPackageInfo('applicationDirectory') ?: $application),
+                                       'directory' => $package->getPackageInfo('applicationDirectory') ?: $application,
                                        'packageDescription' => $package->getLocalizedPackageInfo('packageDescription'),
                                        'packageName' => $package->getLocalizedPackageInfo('packageName')
                                ];
-                               
                        }
                }
                
@@ -493,14 +456,22 @@ class WCFSetup extends WCF {
                        if ($application !== 'wcf') $showOrder[] = $application;
                }
                
-               $documentRoot = FileUtil::unifyDirSeparator($_SERVER['DOCUMENT_ROOT']);
+               $documentRoot = FileUtil::unifyDirSeparator(realpath($_SERVER['DOCUMENT_ROOT']));
+               if (self::$developerMode && isset($_ENV['WCFSETUP_USEDEFAULTWCFDIR'])) {
+                       // resolve path relative to document root
+                       $relativePath = FileUtil::getRelativePath($documentRoot, INSTALL_SCRIPT_DIR);
+                       foreach ($packages as $application => $packageData) {
+                               self::$directories[$application] = $relativePath . ($application === 'wcf' ? '' : $packageData['directory'] . '/');
+                       }
+               }
+               
                $errors = [];
                if (!empty(self::$directories)) {
                        $applicationPaths = $knownPaths = [];
                        
-                       // use $showOrder instead of $applications to ensure that the error message for
-                       // duplicate directories will trigger in display order rather than the random
-                       // sort order returned by glob() above
+                       // use $showOrder to ensure that the error message for duplicate directories
+                       // will trigger in display order rather than the random sort order returned
+                       // by glob() above
                        foreach ($showOrder as $application) {
                                $path = FileUtil::getRealPath($documentRoot . '/' . FileUtil::addTrailingSlash(FileUtil::removeLeadingSlash(self::$directories[$application])));
                                if (strpos($path, $documentRoot) !== 0) {
@@ -544,9 +515,12 @@ class WCFSetup extends WCF {
                }
                else {
                        // resolve path relative to document root
-                       $relativePath = str_replace(FileUtil::unifyDirSeparator($_SERVER['DOCUMENT_ROOT']), '', FileUtil::unifyDirSeparator(INSTALL_SCRIPT_DIR));
+                       $relativePath = FileUtil::getRelativePath($documentRoot, INSTALL_SCRIPT_DIR);
                        foreach ($packages as $application => $packageData) {
-                               self::$directories[$application] = $relativePath . ($application === 'wcf' ? '' : $packageData['directory'] . '/');
+                               $dir = $relativePath . ($application === 'wcf' ? '' : $packageData['directory'] . '/');
+                               if (mb_strpos($dir, './') === 0) $dir = mb_substr($dir, 1);
+                               
+                               self::$directories[$application] = $dir;
                        }
                }
                
@@ -573,7 +547,7 @@ class WCFSetup extends WCF {
                }
                // WCF not yet installed, install files first
                else {
-                       $this->installFiles();
+                       static::installFiles();
                        
                        $this->gotoNextStep('selectLanguages');
                }
@@ -638,11 +612,9 @@ class WCFSetup extends WCF {
        }
        
        /**
-        * Shows the page for configurating the database connection.
+        * Shows the page for configuring the database connection.
         */
        protected function configureDB() {
-               $availableDBClasses = self::getAvailableDBClasses();
-               $dbClass = '';
                if (self::$developerMode && isset($_ENV['WCFSETUP_DBHOST'])) {
                        $dbHost = $_ENV['WCFSETUP_DBHOST'];
                        $dbUser = $_ENV['WCFSETUP_DBUSER'];
@@ -658,12 +630,6 @@ class WCFSetup extends WCF {
                        $dbNumber = 1;
                }
                
-               // set $dbClass to first item in $availableDBClasses
-               foreach ($availableDBClasses as $dbClass) {
-                       $dbClass = $dbClass['class'];
-                       break;
-               }
-               
                if (isset($_POST['send']) || (self::$developerMode && isset($_ENV['WCFSETUP_DBHOST']))) {
                        if (isset($_POST['dbHost'])) $dbHost = $_POST['dbHost'];
                        if (isset($_POST['dbUser'])) $dbUser = $_POST['dbUser'];
@@ -672,7 +638,6 @@ class WCFSetup extends WCF {
                        
                        // ensure that $dbNumber is zero or a positive integer
                        if (isset($_POST['dbNumber'])) $dbNumber = max(0, intval($_POST['dbNumber']));
-                       if (isset($_POST['dbClass'])) $dbClass = $_POST['dbClass'];
                        
                        // get port
                        $dbPort = 0;
@@ -683,49 +648,51 @@ class WCFSetup extends WCF {
                        
                        // test connection
                        try {
-                               // check db class
-                               $validDB = false;
-                               foreach ($availableDBClasses as $dbData) {
-                                       if ($dbData['class'] == $dbClass) {
-                                               $validDB = true;
-                                               break;
-                                       }
-                               }
-                               
-                               if (!$validDB) {
-                                       throw new SystemException("Database type '".$dbClass."'. is not available on this system.");
-                               }
-                               
                                // check connection data
                                /** @var \wcf\system\database\Database $db */
-                               $db = new $dbClass($dbHost, $dbUser, $dbPassword, $dbName, $dbPort, true);
-                               $db->connect();
+                               try {
+                                       $db = new MySQLDatabase($dbHost, $dbUser, $dbPassword, $dbName, $dbPort, true);
+                               }
+                               catch (DatabaseException $e) {
+                                       if ($e->getPrevious()->getCode() == 1115) { // work-around for older MySQL versions that don't know utf8mb4
+                                               throw new SystemException("Insufficient MySQL version. Version '5.5.35' or greater is needed.");
+                                       }
+                                       
+                                       throw $e;
+                               }
                                
                                // check sql version
-                               if (!empty($availableDBClasses[$dbClass]['minversion'])) {
-                                       $compareSQLVersion = preg_replace('/^(\d+\.\d+\.\d+).*$/', '\\1', $db->getVersion());
-                                       if (!(version_compare($compareSQLVersion, $availableDBClasses[$dbClass]['minversion']) >= 0)) {
-                                               throw new SystemException("Insufficient SQL version '".$compareSQLVersion."'. Version '".$availableDBClasses[$dbClass]['minversion']."' or greater is needed.");
+                               $sqlVersion = $db->getVersion();
+                               $compareSQLVersion = preg_replace('/^(\d+\.\d+\.\d+).*$/', '\\1', $sqlVersion);
+                               if (stripos($sqlVersion, 'MariaDB')) {
+                                       // MariaDB 10.0.22+
+                                       if (!(version_compare($compareSQLVersion, '10.0.22') >= 0)) {
+                                               throw new SystemException("Insufficient MariaDB version '".$compareSQLVersion."'. Version '10.0.22' or greater is needed.");
                                        }
                                }
-                               // check innodb support
-                               if ($dbClass == 'wcf\system\database\MySQLDatabase') {
-                                       $sql = "SHOW ENGINES";
-                                       $statement = $db->prepareStatement($sql);
-                                       $statement->execute();
-                                       $hasInnoDB = false;
-                                       while ($row = $statement->fetchArray()) {
-                                               if ($row['Engine'] == 'InnoDB' && in_array($row['Support'], ['DEFAULT', 'YES'])) {
-                                                       $hasInnoDB = true;
-                                                       break;
-                                               }
+                               else {
+                                       // MySQL 5.5.35+
+                                       if (!(version_compare($compareSQLVersion, '5.5.35') >= 0)) {
+                                               throw new SystemException("Insufficient MySQL version '".$compareSQLVersion."'. Version '5.5.35' or greater is needed.");
                                        }
-                                       
-                                       if (!$hasInnoDB) {
-                                               throw new SystemException("Support for InnoDB is missing.");
+                               }
+                               
+                               // check innodb support
+                               $sql = "SHOW ENGINES";
+                               $statement = $db->prepareStatement($sql);
+                               $statement->execute();
+                               $hasInnoDB = false;
+                               while ($row = $statement->fetchArray()) {
+                                       if ($row['Engine'] == 'InnoDB' && in_array($row['Support'], ['DEFAULT', 'YES'])) {
+                                               $hasInnoDB = true;
+                                               break;
                                        }
                                }
                                
+                               if (!$hasInnoDB) {
+                                       throw new SystemException("Support for InnoDB is missing.");
+                               }
+                               
                                // check for table conflicts
                                $conflictedTables = $this->getConflictedTables($db, $dbNumber);
                                
@@ -740,7 +707,6 @@ class WCFSetup extends WCF {
                                        $file->write("\$dbUser = '".str_replace("'", "\\'", $dbUser)."';\n");
                                        $file->write("\$dbPassword = '".str_replace("'", "\\'", $dbPassword)."';\n");
                                        $file->write("\$dbName = '".str_replace("'", "\\'", $dbName)."';\n");
-                                       $file->write("\$dbClass = '".str_replace("'", "\\'", $dbClass)."';\n");
                                        $file->write("if (!defined('WCF_N')) define('WCF_N', $dbNumber);\n");
                                        $file->close();
                                        
@@ -763,8 +729,6 @@ class WCFSetup extends WCF {
                        'dbPassword' => $dbPassword,
                        'dbName' => $dbName,
                        'dbNumber' => $dbNumber,
-                       'dbClass' => $dbClass,
-                       'availableDBClasses' => $availableDBClasses,
                        'nextStep' => 'configureDB'
                ]);
                WCF::getTPL()->display('stepConfigureDB');
@@ -814,7 +778,7 @@ class WCFSetup extends WCF {
                
                // split by offsets
                $sqlData = explode('/* SQL_PARSER_OFFSET */', $sql);
-               $offset = (isset($_POST['offset'])) ? intval($_POST['offset']) : 0;
+               $offset = isset($_POST['offset']) ? intval($_POST['offset']) : 0;
                if (!isset($sqlData[$offset])) {
                        throw new SystemException("Offset for SQL parser is out of bounds, ".$offset." was requested, but there are only ".count($sqlData)." sections");
                }
@@ -884,7 +848,7 @@ class WCFSetup extends WCF {
                        }
                        else {
                                // regular file
-                               $fileInserts[] = str_replace(WCF_DIR, '', $file);
+                               $fileInserts[] = preg_replace('/^'.preg_quote(WCF_DIR, '/').'/', '', $file);
                        }
                }
                
@@ -991,7 +955,7 @@ class WCFSetup extends WCF {
                        }
                        else {
                                $username = $password = $confirmPassword = 'root';
-                               $email = $confirmEmail = 'woltlab@woltlab.com';
+                               $email = $confirmEmail = 'wsc-developer-mode@example.com';
                        }
                        
                        // error handling
@@ -1001,7 +965,7 @@ class WCFSetup extends WCF {
                                        throw new UserInputException('username');
                                }
                                if (!UserUtil::isValidUsername($username)) {
-                                       throw new UserInputException('username', 'notValid');
+                                       throw new UserInputException('username', 'invalid');
                                }
                                
                                // e-mail address
@@ -1009,7 +973,7 @@ class WCFSetup extends WCF {
                                        throw new UserInputException('email');
                                }
                                if (!UserUtil::isValidEmail($email)) {
-                                       throw new UserInputException('email', 'notValid');
+                                       throw new UserInputException('email', 'invalid');
                                }
                                
                                // confirm e-mail address
@@ -1131,6 +1095,18 @@ class WCFSetup extends WCF {
                }
                $tar->close();
                
+               // delete install files
+               $installPhpDeleted = @unlink('./install.php');
+               @unlink('./test.php');
+               $wcfSetupTarDeleted = @unlink('./WCFSetup.tar.gz');
+               
+               // render page
+               WCF::getTPL()->assign([
+                       'installPhpDeleted' => $installPhpDeleted,
+                       'wcfSetupTarDeleted' => $wcfSetupTarDeleted
+               ]);
+               $output = WCF::getTPL()->fetch('stepInstallPackages');
+               
                // register packages in queue
                // get new process id
                $sql = "SELECT  MAX(processNo) AS processNo
@@ -1156,7 +1132,7 @@ class WCFSetup extends WCF {
                                'processNo' => $processNo,
                                'userID' => $admin->userID,
                                'package' => 'com.woltlab.wcf',
-                               'packageName' => 'WoltLab Community Framework',
+                               'packageName' => 'WoltLab Suite Core',
                                'archive' => TMP_DIR.'install/packages/'.$wcfPackageFile,
                                'isApplication' => 1
                        ]);
@@ -1176,17 +1152,15 @@ class WCFSetup extends WCF {
                                        FROM    wcf".WCF_N."_package_installation_queue";
                                $statement = WCF::getDB()->prepareStatement($sql);
                                $statement->execute();
-                               $queues = [];
-                               while ($row = $statement->fetchArray()) {
-                                       $queues[$row['queueID']] = $row['parentQueueID'];
-                               }
+                               $queues = $statement->fetchMap('queueID', 'parentQueueID');
                                
                                $queueIDs = [];
+                               /** @noinspection PhpUndefinedVariableInspection */
                                $queueID = $queue->queueID;
                                while ($queueID) {
                                        $queueIDs[] = $queueID;
                                        
-                                       $queueID = (isset($queues[$queueID])) ? $queues[$queueID] : 0;
+                                       $queueID = isset($queues[$queueID]) ? $queues[$queueID] : 0;
                                }
                                
                                // remove previously created queues
@@ -1211,6 +1185,7 @@ class WCFSetup extends WCF {
                                throw new SystemException('', 0, '', $e);
                        }
                        
+                       /** @noinspection PhpUndefinedVariableInspection */
                        $queue = PackageInstallationQueueEditor::create([
                                'parentQueueID' => $queue->queueID,
                                'processNo' => $processNo,
@@ -1223,7 +1198,7 @@ class WCFSetup extends WCF {
                }
                
                // login as admin
-               define('COOKIE_PREFIX', 'wcf22_');
+               define('COOKIE_PREFIX', 'wsc30_');
                
                $factory = new ACPSessionFactory();
                $factory->load();
@@ -1232,18 +1207,12 @@ class WCFSetup extends WCF {
                SessionHandler::getInstance()->register('masterPassword', 1);
                SessionHandler::getInstance()->register('__wcfSetup_developerMode', self::$developerMode);
                SessionHandler::getInstance()->register('__wcfSetup_directories', self::$directories);
+               SessionHandler::getInstance()->unregister('__changeSessionID');
                SessionHandler::getInstance()->update();
                
-               $installPhpDeleted = @unlink('./install.php');
-               @unlink('./test.php');
-               $wcfSetupTarDeleted = @unlink('./WCFSetup.tar.gz');
-               
                // print page
-               WCF::getTPL()->assign([
-                       'installPhpDeleted' => $installPhpDeleted,
-                       'wcfSetupTarDeleted' => $wcfSetupTarDeleted
-               ]);
-               WCF::getTPL()->display('stepInstallPackages');
+               HeaderUtil::sendHeaders();
+               echo $output;
                
                // delete tmp files
                $directory = TMP_DIR.'/';
@@ -1268,31 +1237,30 @@ class WCFSetup extends WCF {
        }
        
        /**
-        * Gets the package name of the first application in WCFSetup.tar.gz.
+        * Reads the package names of the bundled applications in WCFSetup.tar.gz.
         */
-       protected static function getPackageName() {
+       protected static function getPackageNames() {
                // get package name
+               $packageNames = [];
                $tar = new Tar(SETUP_FILE);
                foreach ($tar->getContentList() as $file) {
                        if ($file['type'] != 'folder' && mb_strpos($file['filename'], 'install/packages/') === 0) {
                                $packageFile = basename($file['filename']);
-                               $packageName = preg_replace('!\.(tar\.gz|tgz|tar)$!', '', $packageFile);
                                
-                               if ($packageName != 'com.woltlab.wcf') {
-                                       try {
-                                               $archive = new PackageArchive(TMP_DIR.'install/packages/'.$packageFile);
-                                               $archive->openArchive();
-                                               self::$setupPackageName = $archive->getLocalizedPackageInfo('packageName');
-                                               $archive->getTar()->close();
-                                               break;
-                                       }
-                                       catch (SystemException $e) {}
+                               try {
+                                       $archive = new PackageArchive(TMP_DIR.'install/packages/'.$packageFile);
+                                       $archive->openArchive();
+                                       $packageNames[] = $archive->getLocalizedPackageInfo('packageName');
+                                       $archive->getTar()->close();
                                }
+                               catch (SystemException $e) {}
                        }
                }
                $tar->close();
                
+               sort($packageNames);
+               
                // assign package name
-               WCF::getTPL()->assign(['setupPackageName' => self::$setupPackageName]);
+               WCF::getTPL()->assign(['setupPackageNames' => $packageNames]);
        }
 }