PageRenderTime 28ms CodeModel.GetById 31ms RepoModel.GetById 1ms app.codeStats 0ms

/install/lib/class.installer.php

http://github.com/symphonycms/symphony-2
PHP | 750 lines | 535 code | 90 blank | 125 comment | 87 complexity | eee2168374d4e07933026605182ac06e MD5 | raw file
  1. <?php
  2. /**
  3. * @package install
  4. */
  5. class Installer extends Administration
  6. {
  7. private static $POST = [];
  8. /**
  9. * Override the default Symphony constructor to initialise the Log, Config
  10. * and Database objects for installation/update. This allows us to use the
  11. * normal accessors.
  12. */
  13. protected function __construct()
  14. {
  15. self::$Profiler = Profiler::instance();
  16. self::$Profiler->sample('Engine Initialisation');
  17. if (get_magic_quotes_gpc()) {
  18. General::cleanArray($_SERVER);
  19. General::cleanArray($_COOKIE);
  20. General::cleanArray($_GET);
  21. General::cleanArray($_POST);
  22. General::cleanArray($_REQUEST);
  23. }
  24. // Include the default Config for installation.
  25. include(INSTALL . '/includes/config_default.php');
  26. static::initialiseConfiguration($settings);
  27. // Initialize date/time
  28. define_safe('__SYM_DATE_FORMAT__', self::Configuration()->get('date_format', 'region'));
  29. define_safe('__SYM_TIME_FORMAT__', self::Configuration()->get('time_format', 'region'));
  30. define_safe('__SYM_DATETIME_FORMAT__', __SYM_DATE_FORMAT__ . self::Configuration()->get('datetime_separator', 'region') . __SYM_TIME_FORMAT__);
  31. DateTimeObj::setSettings(self::Configuration()->get('region'));
  32. // Initialize Language, Logs and Database
  33. static::initialiseLang();
  34. static::initialiseLog(INSTALL_LOGS . '/install');
  35. // Initialize error handlers
  36. ExceptionHandler::initialise(Symphony::Log());
  37. ErrorHandler::initialise(Symphony::Log());
  38. // Copy POST
  39. self::$POST = $_POST;
  40. }
  41. /**
  42. * This function returns an instance of the Installer
  43. * class. It is the only way to create a new Installer, as
  44. * it implements the Singleton interface
  45. *
  46. * @return Installer
  47. */
  48. public static function instance()
  49. {
  50. if (!(self::$_instance instanceof Installer)) {
  51. self::$_instance = new Installer;
  52. }
  53. return self::$_instance;
  54. }
  55. /**
  56. * Initialises the language by looking at the `lang` key,
  57. * passed via GET or POST
  58. */
  59. public static function initialiseLang()
  60. {
  61. $lang = !empty($_REQUEST['lang']) ? preg_replace('/[^a-zA-Z\-]/', null, $_REQUEST['lang']) : 'en';
  62. Lang::initialize();
  63. Lang::set($lang, false);
  64. }
  65. /**
  66. * Overrides the default `initialiseLog()` method and writes
  67. * logs to manifest/logs/install
  68. */
  69. public static function initialiseLog($filename = null)
  70. {
  71. if (is_dir(INSTALL_LOGS) || General::realiseDirectory(INSTALL_LOGS, self::Configuration()->get('write_mode', 'directory'))) {
  72. parent::initialiseLog($filename);
  73. }
  74. }
  75. /**
  76. * Overrides the default `initialiseDatabase()` method to do nothing
  77. */
  78. public static function initialiseDatabase()
  79. {
  80. // nothing
  81. }
  82. public function run()
  83. {
  84. // Make sure a log file is available
  85. if (is_null(Symphony::Log()) || !file_exists(Symphony::Log()->getLogPath())) {
  86. $this->render(new InstallerPage('missing-log'));
  87. }
  88. // Check essential server requirements
  89. $errors = $this->checkRequirements();
  90. if (!empty($errors)) {
  91. Symphony::Log()->pushToLog(
  92. sprintf('Installer - Missing requirements.'),
  93. E_ERROR, true
  94. );
  95. foreach ($errors as $err) {
  96. Symphony::Log()->pushToLog(
  97. sprintf('Requirement - %s', $err['msg']),
  98. E_ERROR, true
  99. );
  100. }
  101. $this->render(new InstallerPage('requirements', [
  102. 'errors'=> $errors
  103. ]));
  104. }
  105. // Check for unattended installation
  106. $unattended = $this->checkUnattended();
  107. if (!empty($unattended)) {
  108. // Merge unattended information with the POST
  109. self::$POST = array_replace_recursive($unattended, self::$POST);
  110. }
  111. // If language is not set and there is language packs available, show language selection pages
  112. if (!isset(self::$POST['lang']) && count(Lang::getAvailableLanguages(false)) > 1) {
  113. $this->render(new InstallerPage('languages'));
  114. }
  115. // Check for configuration errors and, if there are no errors, install Symphony!
  116. if (isset(self::$POST['fields'])) {
  117. $errors = $this->checkConfiguration();
  118. if (!empty($errors)) {
  119. Symphony::Log()->pushToLog(
  120. sprintf('Installer - Wrong configuration.'),
  121. E_ERROR, true
  122. );
  123. foreach ($errors as $err) {
  124. Symphony::Log()->pushToLog(
  125. sprintf('Configuration - %s', $err['msg']),
  126. E_ERROR, true
  127. );
  128. }
  129. } elseif (isset(self::$POST['action']['install'])) {
  130. $disabled_extensions = $this->install();
  131. $this->render(new InstallerPage('success', [
  132. 'disabled-extensions' => $disabled_extensions
  133. ]));
  134. }
  135. }
  136. // Display the Installation page
  137. $this->render(new InstallerPage('configuration', [
  138. 'errors' => $errors,
  139. 'default-config' => !empty($unattended) ? $unattended['fields'] : Symphony::Configuration()->get()
  140. ]));
  141. }
  142. /**
  143. * Extends the given $fields array with the mandatory database values from
  144. * the default configuration.
  145. *
  146. * @param array $fields
  147. * The array to extend
  148. * @return array
  149. * The modified array
  150. */
  151. protected function extendDatabaseFields(array $fields)
  152. {
  153. $db = self::Configuration()->get('database');
  154. foreach (['engine', 'driver', 'charset', 'collate'] as $key) {
  155. $fields['database'][$key] = $db[$key];
  156. }
  157. return $fields;
  158. }
  159. /**
  160. * This function checks the server can support a Symphony installation.
  161. * It checks that PHP is 5.2+, MySQL, Zlib, LibXML, XSLT modules are enabled
  162. * and a `install.sql` file exists.
  163. * If any of these requirements fail the installation will not proceed.
  164. *
  165. * @return array
  166. * An associative array of errors, with `msg` and `details` keys
  167. */
  168. private function checkRequirements()
  169. {
  170. $errors = [];
  171. $phpVc = new VersionComparator(phpversion());
  172. // Check for PHP 5.6+
  173. if ($phpVc->lessThan('5.6')) {
  174. $errors[] = [
  175. 'msg' => __('PHP Version is not correct'),
  176. 'details' => __('Symphony requires %1$s or greater to work, however version %2$s was detected.', ['<code><abbr title="PHP: Hypertext Pre-processor">PHP</abbr> 5.6</code>', '<code>' . phpversion() . '</code>'])
  177. ];
  178. }
  179. // Make sure the install.sql file exists
  180. if (!file_exists(INSTALL . '/includes/install.sql') || !is_readable(INSTALL . '/includes/install.sql')) {
  181. $errors[] = [
  182. 'msg' => __('Missing install.sql file'),
  183. 'details' => __('It appears that %s is either missing or not readable. This is required to populate the database and must be uploaded before installation can commence. Ensure that PHP has read permissions.', ['<code>install.sql</code>'])
  184. ];
  185. }
  186. // Is PDO available?
  187. if (!class_exists('PDO') || !extension_loaded('pdo_mysql')) {
  188. $errors[] = [
  189. 'msg' => __('PDO extension not present'),
  190. 'details' => __('Symphony requires PHP to be configured with PDO for MySQL to work.')
  191. ];
  192. }
  193. // Is ZLib available?
  194. if (!extension_loaded('zlib')) {
  195. $errors[] = [
  196. 'msg' => __('ZLib extension not present'),
  197. 'details' => __('Symphony uses the ZLib compression library for log rotation.')
  198. ];
  199. }
  200. // Is libxml available?
  201. if (!extension_loaded('xml') && !extension_loaded('libxml')) {
  202. $errors[] = [
  203. 'msg' => __('XML extension not present'),
  204. 'details' => __('Symphony needs the XML extension to pass data to the site frontend.')
  205. ];
  206. }
  207. // Is libxslt available?
  208. if (!extension_loaded('xsl') && !extension_loaded('xslt') && !function_exists('domxml_xslt_stylesheet')) {
  209. $errors[] = [
  210. 'msg' => __('XSLT extension not present'),
  211. 'details' => __('Symphony needs an XSLT processor such as %s or Sablotron to build pages.', ['Lib<abbr title="eXtensible Stylesheet Language Transformation">XSLT</abbr>'])
  212. ];
  213. }
  214. // Is json_encode available?
  215. if (!function_exists('json_decode')) {
  216. $errors[] = [
  217. 'msg' => __('JSON functionality is not present'),
  218. 'details' => __('Symphony uses JSON functionality throughout the backend for translations and the interface.')
  219. ];
  220. }
  221. // Cannot write to root folder.
  222. if (!is_writable(DOCROOT)) {
  223. $errors['no-write-permission-root'] = [
  224. 'msg' => 'Root folder not writable: ' . DOCROOT,
  225. 'details' => __('Symphony does not have write permission to the root directory. Please modify permission settings on %s. This can be reverted once installation is complete.', ['<code>' . DOCROOT . '</code>'])
  226. ];
  227. }
  228. // Cannot write to workspace
  229. if (is_dir(DOCROOT . '/workspace') && !is_writable(DOCROOT . '/workspace')) {
  230. $errors['no-write-permission-workspace'] = [
  231. 'msg' => 'Workspace folder not writable: ' . DOCROOT . '/workspace',
  232. 'details' => __('Symphony does not have write permission to the existing %1$s directory. Please modify permission settings on this directory and its contents to allow this, such as with a recursive %2$s command.', ['<code>/workspace</code>', '<code>chmod -R</code>'])
  233. ];
  234. }
  235. return $errors;
  236. }
  237. /**
  238. * This function checks the current Configuration (which is the values entered
  239. * by the user on the installation form) to ensure that `/symphony` and `/workspace`
  240. * folders exist and are writable and that the Database credentials are correct.
  241. * Once those initial checks pass, the rest of the form values are validated.
  242. *
  243. * @return
  244. * An associative array of errors if something went wrong, otherwise an empty array.
  245. */
  246. private function checkConfiguration()
  247. {
  248. $db = null;
  249. $errors = [];
  250. $fields = $this->extendDatabaseFields(self::$POST['fields']);
  251. // Testing the database connection
  252. try {
  253. $db = new Database($fields['database']);
  254. $db->connect();
  255. } catch (DatabaseException $e) {
  256. // Invalid credentials
  257. // @link http://dev.mysql.com/doc/refman/5.5/en/error-messages-server.html
  258. if ($e->getDatabaseErrorCode() === 1044 || $e->getDatabaseErrorCode() === 1045) {
  259. $errors['database-invalid-credentials'] = [
  260. 'msg' => 'Database credentials were denied',
  261. 'details' => __('Symphony was unable to access the database with these credentials.'),
  262. ];
  263. }
  264. // Connection related
  265. else {
  266. $errors['no-database-connection'] = [
  267. 'msg' => 'Could not establish database connection.',
  268. 'details' => __(
  269. 'Symphony was unable to establish a valid database connection. You may need to modify host or port settings.'
  270. ) . ' ' . $e->getDatabaseErrorMessage(),
  271. ];
  272. }
  273. }
  274. try {
  275. // Check the database table prefix is legal. #1815
  276. if (!preg_match('/^[0-9a-zA-Z_]+$/', $fields['database']['tbl_prefix'])) {
  277. $errors['database-table-prefix'] = [
  278. 'msg' => 'Invalid database table prefix: ‘' . $fields['database']['tbl_prefix'] . '’',
  279. 'details' => __('The table prefix %s is invalid. The table prefix must only contain numbers, letters or underscore characters.', ['<code>' . $fields['database']['tbl_prefix'] . '</code>'])
  280. ];
  281. }
  282. // Check the database credentials
  283. elseif ($db && $db->isConnected()) {
  284. // Incorrect MySQL version
  285. $mysqlVc = new VersionComparator($db->getVersion());
  286. if ($mysqlVc->lessThan('5.6')) {
  287. $errors['database-incorrect-version'] = [
  288. 'msg' => 'MySQL Version is not correct. '. $db->getVersion() . ' detected.',
  289. 'details' => __('Symphony requires %1$s or greater to work, however version %2$s was detected. This requirement must be met before installation can proceed.', [
  290. '<code>MySQL 5.6</code>',
  291. '<code>' . $db->getVersion() . '</code>'
  292. ])
  293. ];
  294. } else {
  295. // Existing table prefix
  296. $tables = $db->show()
  297. ->from($fields['database']['db'])
  298. ->like($fields['database']['tbl_prefix'] . '%')
  299. ->execute()
  300. ->rows();
  301. if (!empty($tables)) {
  302. $errors['database-table-prefix'] = [
  303. 'msg' => 'Database table prefix clash with ‘' . $fields['database']['db'] . '’',
  304. 'details' => __('The table prefix %s is already in use. Please choose a different prefix to use with Symphony.', ['<code>' . $fields['database']['tbl_prefix'] . '</code>'])
  305. ];
  306. }
  307. }
  308. }
  309. } catch (DatabaseException $e) {
  310. $errors['unknown-database'] = [
  311. 'msg' => 'Database ‘' . $fields['database']['db'] . '’ not found.',
  312. 'details' => __('Symphony was unable to connect to the specified database.')
  313. ];
  314. }
  315. // Website name not entered
  316. if (trim($fields['general']['sitename']) == '') {
  317. $errors['general-no-sitename'] = [
  318. 'msg' => 'No sitename entered.',
  319. 'details' => __('You must enter a Site name. This will be shown at the top of your backend.')
  320. ];
  321. }
  322. // Username Not Entered
  323. if (trim($fields['user']['username']) == '') {
  324. $errors['user-no-username'] = [
  325. 'msg' => 'No username entered.',
  326. 'details' => __('You must enter a Username. This will be your Symphony login information.')
  327. ];
  328. }
  329. // Password Not Entered
  330. if (trim($fields['user']['password']) == '') {
  331. $errors['user-no-password'] = [
  332. 'msg' => 'No password entered.',
  333. 'details' => __('You must enter a Password. This will be your Symphony login information.')
  334. ];
  335. }
  336. // Password mismatch
  337. elseif ($fields['user']['password'] != $fields['user']['confirm-password']) {
  338. $errors['user-password-mismatch'] = [
  339. 'msg' => 'Passwords did not match.',
  340. 'details' => __('The password and confirmation did not match. Please retype your password.')
  341. ];
  342. }
  343. // No Name entered
  344. if (trim($fields['user']['firstname']) == '' || trim($fields['user']['lastname']) == '') {
  345. $errors['user-no-name'] = [
  346. 'msg' => 'Did not enter First and Last names.',
  347. 'details' => __('You must enter your name.')
  348. ];
  349. }
  350. // Invalid Email
  351. if (!preg_match('/^\w(?:\.?[\w%+-]+)*@\w(?:[\w-]*\.)+?[a-z]{2,}$/i', $fields['user']['email'])) {
  352. $errors['user-invalid-email'] = [
  353. 'msg' => 'Invalid email address supplied.',
  354. 'details' => __('This is not a valid email address. You must provide an email address since you will need it if you forget your password.')
  355. ];
  356. }
  357. // Admin path not entered
  358. if (trim($fields['symphony']['admin-path']) == '') {
  359. $errors['no-symphony-path'] = [
  360. 'msg' => 'No Symphony path entered.',
  361. 'details' => __('You must enter a path for accessing Symphony, or leave the default. This will be used to access Symphony\'s backend.')
  362. ];
  363. }
  364. return $errors;
  365. }
  366. /**
  367. * This function checks if there is a unattend.php file in the MANIFEST folder.
  368. * If it finds one, it will load it and check for the $settings variable.
  369. * It will also merge the default config values into the 'fields' array.
  370. *
  371. * You can find an empty version at install/include/unattend.php
  372. *
  373. * @return array
  374. * An associative array of values, as if it was submitted by a POST
  375. */
  376. private function checkUnattended()
  377. {
  378. $filepath = MANIFEST . '/unattend.php';
  379. if (!@file_exists($filepath) || !@is_readable($filepath)) {
  380. return false;
  381. }
  382. try {
  383. include $filepath;
  384. if (!isset($settings) || !is_array($settings) || !isset($settings['fields'])) {
  385. return false;
  386. }
  387. // Merge with default values
  388. $settings['fields'] = array_replace_recursive(Symphony::Configuration()->get(), $settings['fields']);
  389. // Special case for the password
  390. if (isset($settings['fields']['user']) && isset($settings['fields']['user']['password'])) {
  391. $settings['fields']['user']['confirm-password'] = $settings['fields']['user']['password'];
  392. }
  393. return $settings;
  394. } catch (Exception $ex) {
  395. Symphony::Log()->pushExceptionToLog($ex, true);
  396. }
  397. return false;
  398. }
  399. /**
  400. * If something went wrong, the `abort` function will write an entry to the Log
  401. * file and display the failure page to the user.
  402. * @todo: Resume installation after an error has been fixed.
  403. */
  404. protected function abort($message, $start)
  405. {
  406. $result = Symphony::Log()->pushToLog($message, E_ERROR, true);
  407. if ($result) {
  408. Symphony::Log()->writeToLog('============================================', true);
  409. Symphony::Log()->writeToLog(sprintf('INSTALLATION ABORTED: Execution Time - %d sec (%s)',
  410. max(1, time() - $start),
  411. date('d.m.y H:i:s')
  412. ), true);
  413. Symphony::Log()->writeToLog('============================================' . PHP_EOL . PHP_EOL . PHP_EOL, true);
  414. }
  415. $this->render(new InstallerPage('failure'));
  416. }
  417. private function install()
  418. {
  419. $db = null;
  420. $fields = $this->extendDatabaseFields(self::$POST['fields']);
  421. $start = time();
  422. Symphony::Log()->writeToLog(PHP_EOL . '============================================', true);
  423. Symphony::Log()->writeToLog('INSTALLATION PROCESS STARTED (' . DateTimeObj::get('c') . ')', true);
  424. Symphony::Log()->writeToLog('============================================', true);
  425. // MySQL: Establishing connection
  426. Symphony::Log()->pushToLog('MYSQL: Establishing Connection', E_NOTICE, true, true);
  427. try {
  428. $db = new Database($fields['database']);
  429. $db->connect();
  430. } catch (DatabaseException $e) {
  431. $this->abort(
  432. 'There was a problem while trying to establish a connection to the MySQL server. ' .
  433. 'Please check your settings.',
  434. $start
  435. );
  436. }
  437. // MySQL: Importing schema
  438. Symphony::Log()->pushToLog('MYSQL: Importing Table Schema', E_NOTICE, true, true);
  439. try {
  440. $db->import(file_get_contents(INSTALL . '/includes/install.sql'));
  441. } catch (DatabaseException $e) {
  442. $this->abort(
  443. __('There was an error while trying to import data to the database. MySQL returned: ') .
  444. $e->getDatabaseErrorCode() . ': ' . $e->getDatabaseErrorMessage() .
  445. __(' in query ') . PHP_EOL . $e->getQuery(),
  446. $start
  447. );
  448. }
  449. // MySQL: Creating default author
  450. Symphony::Log()->pushToLog('MYSQL: Creating Default Author', E_NOTICE, true, true);
  451. try {
  452. $db->insert('tbl_authors')->values([
  453. 'id' => 1,
  454. 'username' => $fields['user']['username'],
  455. 'password' => Cryptography::hash($fields['user']['password']),
  456. 'first_name' => $fields['user']['firstname'],
  457. 'last_name' => $fields['user']['lastname'],
  458. 'email' => $fields['user']['email'],
  459. 'last_seen' => null,
  460. 'user_type' => 'developer',
  461. 'primary' => 'yes',
  462. 'default_area' => '/blueprints/sections/',
  463. 'auth_token' => null,
  464. ])->execute();
  465. } catch (DatabaseException $e) {
  466. $this->abort(
  467. 'There was an error while trying create the default author. MySQL returned: ' .
  468. $e->getDatabaseErrorCode() . ': ' . $e->getDatabaseErrorMessage(),
  469. $start
  470. );
  471. }
  472. // Configuration: Populating array
  473. $conf = Symphony::Configuration()->get();
  474. if (!is_array($conf)) {
  475. $this->abort('The configuration is not an array, can not continue', $start);
  476. }
  477. foreach ($conf as $group => $settings) {
  478. if (!is_array($settings)) {
  479. continue;
  480. }
  481. foreach ($settings as $key => $value) {
  482. if (isset($fields[$group]) && isset($fields[$group][$key])) {
  483. $conf[$group][$key] = $fields[$group][$key];
  484. }
  485. }
  486. }
  487. // Create manifest folder structure
  488. Symphony::Log()->pushToLog('WRITING: Creating ‘manifest’ folder (/manifest)', E_NOTICE, true, true);
  489. if (!General::realiseDirectory(MANIFEST, $conf['directory']['write_mode'])) {
  490. $this->abort(
  491. 'Could not create ‘manifest’ directory. Check permission on the root folder.',
  492. $start
  493. );
  494. }
  495. Symphony::Log()->pushToLog('WRITING: Creating ‘logs’ folder (/manifest/logs)', E_NOTICE, true, true);
  496. if (!General::realiseDirectory(LOGS, $conf['directory']['write_mode'])) {
  497. $this->abort(
  498. 'Could not create ‘logs’ directory. Check permission on /manifest.',
  499. $start
  500. );
  501. }
  502. Symphony::Log()->pushToLog('WRITING: Creating ‘cache’ folder (/manifest/cache)', E_NOTICE, true, true);
  503. if (!General::realiseDirectory(CACHE, $conf['directory']['write_mode'])) {
  504. $this->abort(
  505. 'Could not create ‘cache’ directory. Check permission on /manifest.',
  506. $start
  507. );
  508. }
  509. Symphony::Log()->pushToLog('WRITING: Creating ‘tmp’ folder (/manifest/tmp)', E_NOTICE, true, true);
  510. if (!General::realiseDirectory(MANIFEST . '/tmp', $conf['directory']['write_mode'])) {
  511. $this->abort(
  512. 'Could not create ‘tmp’ directory. Check permission on /manifest.',
  513. $start
  514. );
  515. }
  516. // Writing configuration file
  517. Symphony::Log()->pushToLog('WRITING: Configuration File', E_NOTICE, true, true);
  518. Symphony::Configuration()->setArray($conf);
  519. if (!Symphony::Configuration()->write(CONFIG, $conf['file']['write_mode'])) {
  520. $this->abort(
  521. 'Could not create config file ‘' . CONFIG . '’. Check permission on /manifest.',
  522. $start
  523. );
  524. }
  525. // Writing .htaccess file
  526. Symphony::Log()->pushToLog('CONFIGURING: Frontend', E_NOTICE, true, true);
  527. $rewrite_base = ltrim(preg_replace('/\/install$/i', null, dirname($_SERVER['PHP_SELF'])), '/');
  528. $htaccess = str_replace(
  529. '<!-- REWRITE_BASE -->',
  530. $rewrite_base,
  531. file_get_contents(INSTALL . '/includes/htaccess.txt')
  532. );
  533. if (!General::writeFile(DOCROOT . "/.htaccess", $htaccess, $conf['file']['write_mode'], 'a')) {
  534. $this->abort(
  535. 'Could not write ‘.htaccess’ file. Check permission on ' . DOCROOT,
  536. $start
  537. );
  538. }
  539. // Writing /workspace folder
  540. if (!is_dir(DOCROOT . '/workspace')) {
  541. // Create workspace folder structure
  542. Symphony::Log()->pushToLog('WRITING: Creating ‘workspace’ folder (/workspace)', E_NOTICE, true, true);
  543. if (!General::realiseDirectory(WORKSPACE, $conf['directory']['write_mode'])) {
  544. $this->abort(
  545. 'Could not create ‘workspace’ directory. Check permission on the root folder.',
  546. $start
  547. );
  548. }
  549. Symphony::Log()->pushToLog('WRITING: Creating ‘data-sources’ folder (/workspace/data-sources)', E_NOTICE, true, true);
  550. if (!General::realiseDirectory(DATASOURCES, $conf['directory']['write_mode'])) {
  551. $this->abort(
  552. 'Could not create ‘workspace/data-sources’ directory. Check permission on the root folder.',
  553. $start
  554. );
  555. }
  556. Symphony::Log()->pushToLog('WRITING: Creating ‘events’ folder (/workspace/events)', E_NOTICE, true, true);
  557. if (!General::realiseDirectory(EVENTS, $conf['directory']['write_mode'])) {
  558. $this->abort(
  559. 'Could not create ‘workspace/events’ directory. Check permission on the root folder.',
  560. $start
  561. );
  562. }
  563. Symphony::Log()->pushToLog('WRITING: Creating ‘pages’ folder (/workspace/pages)', E_NOTICE, true, true);
  564. if (!General::realiseDirectory(PAGES, $conf['directory']['write_mode'])) {
  565. $this->abort(
  566. 'Could not create ‘workspace/pages’ directory. Check permission on the root folder.',
  567. $start
  568. );
  569. }
  570. Symphony::Log()->pushToLog('WRITING: Creating ‘utilities’ folder (/workspace/utilities)', E_NOTICE, true, true);
  571. if (!General::realiseDirectory(UTILITIES, $conf['directory']['write_mode'])) {
  572. $this->abort(
  573. 'Could not create ‘workspace/utilities’ directory. Check permission on the root folder.',
  574. $start
  575. );
  576. }
  577. } else {
  578. Symphony::Log()->pushToLog('An existing ‘workspace’ directory was found at this location. Symphony will use this workspace.', E_NOTICE, true, true);
  579. // MySQL: Importing workspace data
  580. Symphony::Log()->pushToLog('MYSQL: Importing Workspace Data...', E_NOTICE, true, true);
  581. if (General::checkFileReadable(WORKSPACE . '/install.sql')) {
  582. try {
  583. $db->import(
  584. file_get_contents(WORKSPACE . '/install.sql'),
  585. true
  586. );
  587. } catch (DatabaseException $e) {
  588. $this->abort(
  589. 'There was an error while trying to import data to the database. MySQL returned: ' . $e->getDatabaseErrorCode() . ': ' . $e->getDatabaseErrorMessage(),
  590. $start
  591. );
  592. }
  593. }
  594. }
  595. // Write extensions folder
  596. if (!is_dir(EXTENSIONS)) {
  597. // Create extensions folder
  598. Symphony::Log()->pushToLog('WRITING: Creating ‘extensions’ folder (/extensions)', E_NOTICE, true, true);
  599. if (!General::realiseDirectory(EXTENSIONS, $conf['directory']['write_mode'])) {
  600. $this->abort(
  601. 'Could not create ‘extension’ directory. Check permission on the root folder.',
  602. $start
  603. );
  604. }
  605. }
  606. // Configure Symphony Database object
  607. parent::initialiseDatabase();
  608. // Configure a fake Administration page
  609. Administration::instance()->Page = new AdministrationPage;
  610. // Install existing extensions
  611. Symphony::Log()->pushToLog('CONFIGURING: Installing existing extensions', E_NOTICE, true, true);
  612. $disabled_extensions = [];
  613. foreach (new DirectoryIterator(EXTENSIONS) as $e) {
  614. if ($e->isDot() || $e->isFile() || !is_file($e->getRealPath() . '/extension.driver.php')) {
  615. continue;
  616. }
  617. $handle = $e->getBasename();
  618. try {
  619. if (!ExtensionManager::enable($handle)) {
  620. $disabled_extensions[] = $handle;
  621. Symphony::Log()->pushToLog('Could not enable the extension ‘' . $handle . '’.', E_NOTICE, true, true);
  622. }
  623. } catch (Exception $ex) {
  624. $disabled_extensions[] = $handle;
  625. Symphony::Log()->pushToLog('Could not enable the extension ‘' . $handle . '’. '. $ex->getMessage(), E_NOTICE, true, true);
  626. }
  627. }
  628. // Loading default language
  629. if (isset($_REQUEST['lang']) && $_REQUEST['lang'] != 'en') {
  630. Symphony::Log()->pushToLog('CONFIGURING: Default language', E_NOTICE, true, true);
  631. $language = Lang::Languages();
  632. $language = $language[$_REQUEST['lang']];
  633. // Is the language extension enabled?
  634. if (in_array('lang_' . $language['handle'], ExtensionManager::listInstalledHandles())) {
  635. Symphony::Configuration()->set('lang', $_REQUEST['lang'], 'symphony');
  636. if (!Symphony::Configuration()->write(CONFIG, $conf['file']['write_mode'])) {
  637. Symphony::Log()->pushToLog('Could not write default language ‘' . $language['name'] . '’ to config file.', E_NOTICE, true, true);
  638. }
  639. } else {
  640. Symphony::Log()->pushToLog('Could not enable the desired language ‘' . $language['name'] . '’.', E_NOTICE, true, true);
  641. }
  642. }
  643. // Installation completed. Woo-hoo!
  644. Symphony::Log()->writeToLog('============================================', true);
  645. Symphony::Log()->writeToLog(sprintf(
  646. 'INSTALLATION COMPLETED: Execution Time - %d sec (%s)',
  647. max(1, time() - $start),
  648. date('d.m.y H:i:s')
  649. ), true);
  650. Symphony::Log()->writeToLog('============================================' . PHP_EOL . PHP_EOL . PHP_EOL, true);
  651. return $disabled_extensions;
  652. }
  653. protected function render(InstallerPage $page)
  654. {
  655. $output = $page->generate();
  656. header('Content-Type: text/html; charset=utf-8');
  657. echo $output;
  658. exit;
  659. }
  660. }