Elgg  Version 4.3
ElggInstaller.php
Go to the documentation of this file.
1 <?php
2 
4 use Elgg\Config;
5 use Elgg\Database;
15 
41 
42  public const MARIADB_MINIMAL_VERSION = '10.3';
43  public const MYSQL_MINIMAL_VERSION = '5.7';
44  public const PHP_MINIMAL_VERSION = '7.4.0';
45 
46  private $steps = [
47  'welcome',
48  'requirements',
49  'database',
50  'settings',
51  'admin',
52  'complete',
53  ];
54 
55  private $has_completed = [
56  'config' => false,
57  'database' => false,
58  'settings' => false,
59  'admin' => false,
60  ];
61 
62  private $is_action = false;
63 
64  private $autoLogin = true;
65 
69  private $app;
70 
76  public function run() {
77  $app = $this->getApp();
78 
79  $this->is_action = $app->internal_services->request->getMethod() === 'POST';
80 
81  $step = $this->getCurrentStep();
82 
83  $this->determineInstallStatus();
84 
85  $response = $this->checkInstallCompletion($step);
86  if ($response) {
87  return $response;
88  }
89 
90  // check if this is an install being resumed
91  $response = $this->resumeInstall($step);
92  if ($response) {
93  return $response;
94  }
95 
96  $this->finishBootstrapping($step);
97 
98  $params = $app->internal_services->request->request->all();
99 
100  $method = "run" . ucwords($step);
101 
102  return $this->$method($params);
103  }
104 
111  protected function getApp() {
112  if ($this->app) {
113  return $this->app;
114  }
115 
116  try {
117  $config = new Config();
118  $config->installer_running = true;
119  $config->dbencoding = 'utf8mb4';
120  $config->boot_cache_ttl = 0;
121  $config->system_cache_enabled = false;
122  $config->simplecache_enabled = false;
123  $config->debug = \Psr\Log\LogLevel::WARNING;
124  $config->cacheroot = Paths::sanitize(sys_get_temp_dir()) . 'elgginstaller/caches/';
125  $config->assetroot = Paths::sanitize(sys_get_temp_dir()) . 'elgginstaller/assets/';
126 
127  $app = Application::factory([
128  'config' => $config,
129  'handle_exceptions' => false,
130  'handle_shutdown' => false,
131  ]);
132 
133  // Don't set global $CONFIG, because loading the settings file may require it to write to
134  // it, and it can have array sets (e.g. cookie config) that fail when using a proxy for
135  // the config service.
136  //$app->setGlobalConfig();
137 
138  Application::setInstance($app);
139  $app->loadCore();
140  $this->app = $app;
141 
142  $app->internal_services->boot->getCache()->disable();
143  $app->internal_services->plugins->getCache()->disable();
144  $app->internal_services->sessionCache->disable();
145  $app->internal_services->dataCache->disable();
146  $app->internal_services->autoloadManager->getCache()->disable();
147 
148  $current_step = $this->getCurrentStep();
149  $index_admin = array_search('admin', $this->getSteps());
150  $index_complete = array_search('complete', $this->getSteps());
151  $index_step = array_search($current_step, $this->getSteps());
152 
153  // For the admin creation action and the complete step we use the Elgg core session handling.
154  // Otherwise, use default php session handling
155  $use_elgg_session = ($index_step == $index_admin) || ($index_step == $index_complete);
156  if (!$use_elgg_session) {
157  $session = \ElggSession::fromFiles($app->internal_services->config);
158  $session->setName('Elgg_install');
159  $app->internal_services->set('session', $session);
160  }
161 
162  $app->internal_services->views->setViewtype('installation');
163  $app->internal_services->views->registerViewtypeFallback('installation');
164  $app->internal_services->views->registerPluginViews(Paths::elgg());
165  $app->internal_services->translator->registerTranslations(Paths::elgg() . 'install/languages/', true);
166 
167  return $this->app;
168  } catch (ConfigurationException $ex) {
169  throw new InstallationException($ex->getMessage());
170  }
171  }
172 
180  public function setAutoLogin($flag) {
181  $this->autoLogin = (bool) $flag;
182  }
183 
199  public function batchInstall(array $params, $create_htaccess = false) {
200  $app = $this->getApp();
201 
202  $defaults = [
203  'dbhost' => 'localhost',
204  'dbport' => '3306',
205  'dbprefix' => 'elgg_',
206  'language' => 'en',
207  'siteaccess' => ACCESS_PUBLIC,
208  ];
209  $params = array_merge($defaults, $params);
210 
211  $required_params = [
212  'dbuser',
213  'dbpassword',
214  'dbname',
215  'sitename',
216  'wwwroot',
217  'dataroot',
218  'displayname',
219  'email',
220  'username',
221  'password',
222  ];
223  foreach ($required_params as $key) {
224  if (empty($params[$key])) {
225  $msg = elgg_echo('install:error:requiredfield', [$key]);
226  throw new InstallationException($msg);
227  }
228  }
229 
230  // password is passed in once
231  $params['password1'] = $params['password2'] = $params['password'];
232 
233  if ($create_htaccess) {
234  $rewrite_tester = new ElggRewriteTester();
235  if (!$rewrite_tester->createHtaccess($params['wwwroot'])) {
236  throw new InstallationException(elgg_echo('install:error:htaccess'));
237  }
238  }
239 
240  if (!\Elgg\Http\Urls::isValidMultiByteUrl($params['wwwroot'])) {
241  throw new InstallationException(elgg_echo('install:error:wwwroot', [$params['wwwroot']]));
242  }
243 
244  // sanitize dataroot path
245  $params['dataroot'] = Paths::sanitize($params['dataroot']);
246 
247  $this->determineInstallStatus();
248 
249  if (!$this->has_completed['config']) {
250  if (!$this->createSettingsFile($params)) {
251  throw new InstallationException(elgg_echo('install:error:settings'));
252  }
253  }
254 
255  $this->loadSettingsFile();
256 
257  // Make sure settings file matches parameters
258  $config = $app->internal_services->config;
259  $config_keys = [
260  // param key => config key
261  'dbhost' => 'dbhost',
262  'dbport' => 'dbport',
263  'dbuser' => 'dbuser',
264  'dbpassword' => 'dbpass',
265  'dbname' => 'dbname',
266  'dataroot' => 'dataroot',
267  'dbprefix' => 'dbprefix',
268  ];
269  foreach ($config_keys as $params_key => $config_key) {
270  if ($params[$params_key] !== $config->$config_key) {
271  throw new InstallationException(elgg_echo('install:error:settings_mismatch', [$config_key]) . $params[$params_key] . ' ' . $config->$config_key);
272  }
273  }
274 
275  if (!$this->connectToDatabase()) {
276  throw new InstallationException(elgg_echo('install:error:databasesettings'));
277  }
278 
279  if (!$this->has_completed['database']) {
280  if (!$this->installDatabase()) {
281  throw new InstallationException(elgg_echo('install:error:cannotloadtables'));
282  }
283  }
284 
285  // load remaining core libraries
286  $this->finishBootstrapping('settings');
287 
288  if (!$this->saveSiteSettings($params)) {
289  throw new InstallationException(elgg_echo('install:error:savesitesettings'));
290  }
291 
292  if (!$this->createAdminAccount($params)) {
293  throw new InstallationException(elgg_echo('install:admin:cannot_create'));
294  }
295  }
296 
305  protected function render($step, $vars = []) {
306  $vars['next_step'] = $this->getNextStep($step);
307 
308  $title = elgg_echo("install:{$step}");
309  $body = elgg_view("install/pages/{$step}", $vars);
310 
312  $title,
313  $body,
314  'default',
315  [
316  'step' => $step,
317  'steps' => $this->getSteps(),
318  ]
319  );
320 
321  return new \Elgg\Http\OkResponse($output);
322  }
323 
335  protected function runWelcome($vars) {
336  return $this->render('welcome');
337  }
338 
348  protected function runRequirements($vars) {
349 
350  $report = [];
351 
352  // check PHP parameters and libraries
353  $this->checkPHP($report);
354 
355  // check URL rewriting
356  $this->checkRewriteRules($report);
357 
358  // check for existence of settings file
359  if ($this->checkSettingsFile($report) !== true) {
360  // no file, so check permissions on engine directory
362  }
363 
364  // check the database later
365  $report['database'] = [
366  [
367  'severity' => 'notice',
368  'message' => elgg_echo('install:check:database'),
369  ]
370  ];
371 
372  // any failures?
373  $numFailures = $this->countNumConditions($report, 'error');
374 
375  // any warnings
376  $numWarnings = $this->countNumConditions($report, 'warning');
377 
378 
379  $params = [
380  'report' => $report,
381  'num_failures' => $numFailures,
382  'num_warnings' => $numWarnings,
383  ];
384 
385  return $this->render('requirements', $params);
386  }
387 
397  protected function runDatabase($submissionVars) {
398 
399  $app = $this->getApp();
400 
401  $formVars = [
402  'dbuser' => [
403  'type' => 'text',
404  'value' => '',
405  'required' => true,
406  ],
407  'dbpassword' => [
408  'type' => 'password',
409  'value' => '',
410  'required' => false,
411  ],
412  'dbname' => [
413  'type' => 'text',
414  'value' => '',
415  'required' => true,
416  ],
417  'dbhost' => [
418  'type' => 'text',
419  'value' => 'localhost',
420  'required' => true,
421  ],
422  'dbport' => [
423  'type' => 'number',
424  'value' => 3306,
425  'required' => true,
426  'min' => 0,
427  'max' => 65535,
428  ],
429  'dbprefix' => [
430  'type' => 'text',
431  'value' => 'elgg_',
432  'required' => false,
433  ],
434  'dataroot' => [
435  'type' => 'text',
436  'value' => '',
437  'required' => true,
438  ],
439  'wwwroot' => [
440  'type' => 'url',
441  'value' => $app->internal_services->config->wwwroot,
442  'required' => true,
443  ],
444  'timezone' => [
445  'type' => 'dropdown',
446  'value' => 'UTC',
447  'options' => \DateTimeZone::listIdentifiers(),
448  'required' => true
449  ]
450  ];
451 
452  if ($this->checkSettingsFile()) {
453  // user manually created settings file so we fake out action test
454  $this->is_action = true;
455  }
456 
457  if ($this->is_action) {
458  $getResponse = function () use ($app, $submissionVars, $formVars) {
459  // only create settings file if it doesn't exist
460  if (!$this->checkSettingsFile()) {
461  if (!$this->validateDatabaseVars($submissionVars, $formVars)) {
462  // error so we break out of action and serve same page
463  return;
464  }
465 
466  if (!$this->createSettingsFile($submissionVars)) {
467  return;
468  }
469  }
470 
471  // check db version and connect
472  if (!$this->connectToDatabase()) {
473  return;
474  }
475 
476  if (!$this->installDatabase()) {
477  return;
478  }
479 
480  $app->internal_services->system_messages->addSuccessMessage(elgg_echo('install:success:database'));
481 
482  return $this->continueToNextStep('database');
483  };
484 
485  $response = $getResponse();
486  if ($response) {
487  return $response;
488  }
489  }
490 
491  $formVars = $this->makeFormSticky($formVars, $submissionVars);
492 
493  $params = ['variables' => $formVars,];
494 
495  if ($this->checkSettingsFile()) {
496  // settings file exists and we're here so failed to create database
497  $params['failure'] = true;
498  }
499 
500  return $this->render('database', $params);
501  }
502 
512  protected function runSettings($submissionVars) {
513 
514  $app = $this->getApp();
515 
516  $formVars = [
517  'sitename' => [
518  'type' => 'text',
519  'value' => 'My New Community',
520  'required' => true,
521  ],
522  'siteemail' => [
523  'type' => 'email',
524  'value' => '',
525  'required' => false,
526  ],
527  'siteaccess' => [
528  'type' => 'access',
529  'value' => ACCESS_PUBLIC,
530  'required' => true,
531  ],
532  ];
533 
534  if ($this->is_action) {
535  $getResponse = function () use ($app, $submissionVars, $formVars) {
536 
537  if (!$this->validateSettingsVars($submissionVars, $formVars)) {
538  return;
539  }
540 
541  if (!$this->saveSiteSettings($submissionVars)) {
542  return;
543  }
544 
545  $app->internal_services->system_messages->addSuccessMessage(elgg_echo('install:success:settings'));
546 
547  return $this->continueToNextStep('settings');
548  };
549 
550  $response = $getResponse();
551  if ($response) {
552  return $response;
553  }
554  }
555 
556  $formVars = $this->makeFormSticky($formVars, $submissionVars);
557 
558  return $this->render('settings', ['variables' => $formVars]);
559  }
560 
570  protected function runAdmin($submissionVars) {
571  $app = $this->getApp();
572 
573  $formVars = [
574  'displayname' => [
575  'type' => 'text',
576  'value' => '',
577  'required' => true,
578  ],
579  'email' => [
580  'type' => 'email',
581  'value' => '',
582  'required' => true,
583  ],
584  'username' => [
585  'type' => 'text',
586  'value' => '',
587  'required' => true,
588  ],
589  'password1' => [
590  'type' => 'password',
591  'value' => '',
592  'required' => true,
593  'pattern' => '.{6,}',
594  ],
595  'password2' => [
596  'type' => 'password',
597  'value' => '',
598  'required' => true,
599  ],
600  ];
601 
602  if ($this->is_action) {
603  $getResponse = function () use ($app, $submissionVars, $formVars) {
604  if (!$this->validateAdminVars($submissionVars, $formVars)) {
605  return;
606  }
607 
608  if (!$this->createAdminAccount($submissionVars, $this->autoLogin)) {
609  return;
610  }
611 
612  $app->internal_services->system_messages->addSuccessMessage(elgg_echo('install:success:admin'));
613 
614  return $this->continueToNextStep('admin');
615  };
616 
617  $response = $getResponse();
618  if ($response) {
619  return $response;
620  }
621  }
622 
623  // Bit of a hack to get the password help to show right number of characters
624  // We burn the value into the stored translation.
625  $app = $this->getApp();
626  $lang = $app->internal_services->translator->getCurrentLanguage();
627  $translations = $app->internal_services->translator->getLoadedTranslations();
628  $app->internal_services->translator->addTranslation($lang, [
629  'install:admin:help:password1' => sprintf(
630  $translations[$lang]['install:admin:help:password1'],
631  $app->internal_services->config->min_password_length
632  ),
633  ]);
634 
635  $formVars = $this->makeFormSticky($formVars, $submissionVars);
636 
637  return $this->render('admin', ['variables' => $formVars]);
638  }
639 
645  protected function runComplete() {
646 
647  // nudge to check out settings
648  $link = elgg_view_url(elgg_normalize_url('admin/site_settings'), elgg_echo('install:complete:admin_notice:link_text'));
649  $notice = elgg_format_element('p', [], elgg_echo('install:complete:admin_notice', [$link]));
650 
651  $custom_index_link = elgg_view_url(elgg_normalize_url('admin/plugin_settings/custom_index'), elgg_echo('admin:plugin_settings'));
652  $notice .= elgg_format_element('p', [], elgg_echo('install:complete:admin_notice:custom_index', [$custom_index_link]));
653 
654  elgg_add_admin_notice('fresh_install', $notice);
655 
656  $result = $this->render('complete');
657 
658  elgg_delete_directory(Paths::sanitize(sys_get_temp_dir()) . 'elgginstaller/');
659 
660  return $result;
661  }
662 
672  protected function getSteps() {
673  return $this->steps;
674  }
675 
681  protected function getCurrentStep() {
682  $step = get_input('step', 'welcome');
683 
684  if (!in_array($step, $this->getSteps())) {
685  $step = 'welcome';
686  }
687 
688  return $step;
689  }
690 
698  protected function continueToNextStep($currentStep) {
699  $this->is_action = false;
700 
701  return new \Elgg\Http\RedirectResponse($this->getNextStepUrl($currentStep));
702  }
703 
711  protected function getNextStep($currentStep) {
712  $index = 1 + array_search($currentStep, $this->steps);
713 
714  return $this->steps[$index] ?? null;
715  }
716 
724  protected function getNextStepUrl($currentStep) {
725  $app = $this->getApp();
726  $nextStep = $this->getNextStep($currentStep);
727 
728  return $app->internal_services->config->wwwroot . "install.php?step={$nextStep}";
729  }
730 
737  protected function determineInstallStatus() {
738  $app = $this->getApp();
739 
740  $path = Config::resolvePath();
741  if (!is_file($path) || !is_readable($path)) {
742  return;
743  }
744 
745  $this->loadSettingsFile();
746 
747  $this->has_completed['config'] = true;
748 
749  // must be able to connect to database to jump install steps
750  $dbSettingsPass = $this->checkDatabaseSettings(
751  $app->internal_services->config->dbuser,
752  $app->internal_services->config->dbpass,
753  $app->internal_services->config->dbname,
754  $app->internal_services->config->dbhost,
755  $app->internal_services->config->dbport
756  );
757 
758  if (!$dbSettingsPass) {
759  return;
760  }
761 
762  $db = $app->internal_services->db;
763 
764  try {
765  // check that the config table has been created
766  $result = $db->getConnection('read')->executeQuery('SHOW TABLES');
767  if (empty($result)) {
768  return;
769  }
770  foreach ($result->fetchAllAssociative() as $table) {
771  if (in_array("{$db->prefix}config", $table)) {
772  $this->has_completed['database'] = true;
773  }
774  }
775  if ($this->has_completed['database'] == false) {
776  return;
777  }
778 
779  // check that the config table has entries
780  $qb = \Elgg\Database\Select::fromTable('config');
781  $qb->select('COUNT(*) AS total');
782 
783  $result = $db->getDataRow($qb);
784  if (!empty($result) && $result->total > 0) {
785  $this->has_completed['settings'] = true;
786  } else {
787  return;
788  }
789 
790  // check that the users entity table has an entry
791  $qb = \Elgg\Database\Select::fromTable('entities', 'e');
792  $qb->select('COUNT(*) AS total')
793  ->where($qb->compare('type', '=', 'user', ELGG_VALUE_STRING));
794 
795  $result = $db->getDataRow($qb);
796  if (!empty($result) && $result->total > 0) {
797  $this->has_completed['admin'] = true;
798  } else {
799  return;
800  }
801  } catch (DatabaseException $ex) {
802  throw new InstallationException('Elgg can not connect to the database: ' . $ex->getMessage(), $ex->getCode(), $ex);
803  }
804 
805  return;
806  }
807 
816  protected function checkInstallCompletion($step) {
817  if ($step === 'complete') {
818  return;
819  }
820 
821  if (!in_array(false, $this->has_completed)) {
822  // install complete but someone is trying to view an install page
823  return new \Elgg\Http\RedirectResponse('/');
824  }
825  }
826 
835  protected function resumeInstall($step) {
836  // only do a resume from the first step
837  if ($step !== 'welcome') {
838  return null;
839  }
840 
841  if ($this->has_completed['database'] == false) {
842  return null;
843  }
844 
845  if ($this->has_completed['settings'] == false) {
846  return new \Elgg\Http\RedirectResponse('install.php?step=settings');
847  }
848 
849  if ($this->has_completed['admin'] == false) {
850  return new \Elgg\Http\RedirectResponse('install.php?step=admin');
851  }
852 
853  // everything appears to be set up
854  return new \Elgg\Http\RedirectResponse('install.php?step=complete');
855  }
856 
869  protected function finishBootstrapping($step) {
870 
871  $app = $this->getApp();
872 
873  $index_db = array_search('database', $this->getSteps());
874  $index_step = array_search($step, $this->getSteps());
875 
876  if ($index_step > $index_db) {
877  // once the database has been created, load rest of engine
878 
879  // dummy site needed to boot
880  $app->internal_services->config->site = new \ElggSite();
881 
882  $app->bootCore();
883  }
884  }
885 
892  protected function loadSettingsFile() {
893  try {
894  $app = $this->getApp();
895 
896  $config = Config::fromFile(Config::resolvePath());
897  $app->internal_services->set('config', $config);
898 
899  // in case the DB instance is already captured in services, we re-inject its settings.
900  $app->internal_services->db->resetConnections(DbConfig::fromElggConfig($config));
901  } catch (\Exception $e) {
902  throw new InstallationException(elgg_echo('InstallationException:CannotLoadSettings'), 0, $e);
903  }
904  }
905 
918  protected function makeFormSticky($formVars, $submissionVars) {
919  foreach ($submissionVars as $field => $value) {
920  $formVars[$field]['value'] = $value;
921  }
922 
923  return $formVars;
924  }
925 
926  /* Requirement checks support methods */
927 
935  protected function isInstallDirWritable(&$report) {
936  if (!is_writable(Paths::projectConfig())) {
937  $report['settings'] = [
938  [
939  'severity' => 'error',
940  'message' => elgg_echo('install:check:installdir', [Paths::PATH_TO_CONFIG]),
941  ]
942  ];
943 
944  return false;
945  }
946 
947  return true;
948  }
949 
957  protected function checkSettingsFile(&$report = []) {
958  if (!is_file(Config::resolvePath())) {
959  return false;
960  }
961 
962  if (!is_readable(Config::resolvePath())) {
963  $report['settings'] = [
964  [
965  'severity' => 'error',
966  'message' => elgg_echo('install:check:readsettings'),
967  ]
968  ];
969  }
970 
971  return true;
972  }
973 
981  protected function checkPHP(&$report) {
982  $phpReport = [];
983 
984  if (version_compare(PHP_VERSION, self::PHP_MINIMAL_VERSION, '<')) {
985  $phpReport[] = [
986  'severity' => 'error',
987  'message' => elgg_echo('install:check:php:version', [self::PHP_MINIMAL_VERSION, PHP_VERSION]),
988  ];
989  }
990 
991  $this->checkPhpExtensions($phpReport);
992 
993  $this->checkPhpDirectives($phpReport);
994 
995  if (count($phpReport) == 0) {
996  $phpReport[] = [
997  'severity' => 'success',
998  'message' => elgg_echo('install:check:php:success'),
999  ];
1000  }
1001 
1002  $report['php'] = $phpReport;
1003  }
1004 
1012  protected function checkPhpExtensions(&$phpReport) {
1013  $extensions = get_loaded_extensions();
1015  'pdo_mysql',
1016  'json',
1017  'xml',
1018  'gd',
1019  ];
1020  foreach ($requiredExtensions as $extension) {
1021  if (!in_array($extension, $extensions)) {
1022  $phpReport[] = [
1023  'severity' => 'error',
1024  'message' => elgg_echo('install:check:php:extension', [$extension]),
1025  ];
1026  }
1027  }
1028 
1030  'mbstring',
1031  ];
1032  foreach ($recommendedExtensions as $extension) {
1033  if (!in_array($extension, $extensions)) {
1034  $phpReport[] = [
1035  'severity' => 'warning',
1036  'message' => elgg_echo('install:check:php:extension:recommend', [$extension]),
1037  ];
1038  }
1039  }
1040  }
1041 
1049  protected function checkPhpDirectives(&$phpReport) {
1050  if (ini_get('open_basedir')) {
1051  $phpReport[] = [
1052  'severity' => 'warning',
1053  'message' => elgg_echo('install:check:php:open_basedir'),
1054  ];
1055  }
1056 
1057  if (ini_get('safe_mode')) {
1058  $phpReport[] = [
1059  'severity' => 'warning',
1060  'message' => elgg_echo('install:check:php:safe_mode'),
1061  ];
1062  }
1063 
1064  if (ini_get('arg_separator.output') !== '&') {
1065  $separator = htmlspecialchars(ini_get('arg_separator.output'));
1066  $phpReport[] = [
1067  'severity' => 'error',
1068  'message' => elgg_echo('install:check:php:arg_separator', [$separator]),
1069  ];
1070  }
1071 
1072  if (ini_get('register_globals')) {
1073  $phpReport[] = [
1074  'severity' => 'error',
1075  'message' => elgg_echo('install:check:php:register_globals'),
1076  ];
1077  }
1078 
1079  if (ini_get('session.auto_start')) {
1080  $phpReport[] = [
1081  'severity' => 'error',
1082  'message' => elgg_echo('install:check:php:session.auto_start'),
1083  ];
1084  }
1085  }
1086 
1094  protected function checkRewriteRules(&$report) {
1095  $app = $this->getApp();
1096 
1097  $tester = new ElggRewriteTester();
1098  $url = $app->internal_services->config->wwwroot;
1099  $url .= Request::REWRITE_TEST_TOKEN . '?' . http_build_query([
1100  Request::REWRITE_TEST_TOKEN => '1',
1101  ]);
1102  $report['rewrite'] = [$tester->run($url)];
1103  }
1104 
1113  protected function countNumConditions($report, $condition) {
1114  $count = 0;
1115  foreach ($report as $checks) {
1116  foreach ($checks as $check) {
1117  if ($check['severity'] === $condition) {
1118  $count++;
1119  }
1120  }
1121  }
1122 
1123  return $count;
1124  }
1125 
1126 
1139  protected function validateDatabaseVars($submissionVars, $formVars) {
1140 
1141  $app = $this->getApp();
1142 
1143  foreach ($formVars as $field => $info) {
1144  if ($info['required'] == true && !$submissionVars[$field]) {
1145  $name = elgg_echo("install:database:label:{$field}");
1146  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:requiredfield', [$name]));
1147 
1148  return false;
1149  }
1150  }
1151 
1152  if (!empty($submissionVars['wwwroot']) && !\Elgg\Http\Urls::isValidMultiByteUrl($submissionVars['wwwroot'])) {
1153  $save_value = $this->sanitizeInputValue($submissionVars['wwwroot']);
1154  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:wwwroot', [$save_value]));
1155 
1156  return false;
1157  }
1158 
1159  // check that data root is absolute path
1160  if (stripos(PHP_OS, 'win') === 0) {
1161  if (strpos($submissionVars['dataroot'], ':') !== 1) {
1162  $save_value = $this->sanitizeInputValue($submissionVars['dataroot']);
1163  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:relative_path', [$save_value]));
1164 
1165  return false;
1166  }
1167  } else {
1168  if (strpos($submissionVars['dataroot'], '/') !== 0) {
1169  $save_value = $this->sanitizeInputValue($submissionVars['dataroot']);
1170  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:relative_path', [$save_value]));
1171 
1172  return false;
1173  }
1174  }
1175 
1176  // check that data root exists
1177  if (!is_dir($submissionVars['dataroot'])) {
1178  $save_value = $this->sanitizeInputValue($submissionVars['dataroot']);
1179  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:datadirectoryexists', [$save_value]));
1180 
1181  return false;
1182  }
1183 
1184  // check that data root is writable
1185  if (!is_writable($submissionVars['dataroot'])) {
1186  $save_value = $this->sanitizeInputValue($submissionVars['dataroot']);
1187  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:writedatadirectory', [$save_value]));
1188 
1189  return false;
1190  }
1191 
1192  // check that data root is not subdirectory of Elgg root
1193  if (stripos($submissionVars['dataroot'], Paths::project()) === 0) {
1194  $save_value = $this->sanitizeInputValue($submissionVars['dataroot']);
1195  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:locationdatadirectory', [$save_value]));
1196 
1197  return false;
1198  }
1199 
1200  // according to postgres documentation: SQL identifiers and key words must
1201  // begin with a letter (a-z, but also letters with diacritical marks and
1202  // non-Latin letters) or an underscore (_). Subsequent characters in an
1203  // identifier or key word can be letters, underscores, digits (0-9), or dollar signs ($).
1204  // Refs #4994
1205  if (!empty($submissionVars['dbprefix']) && !preg_match("/^[a-zA-Z_][\w]*$/", $submissionVars['dbprefix'])) {
1206  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:database_prefix'));
1207 
1208  return false;
1209  }
1210 
1211  return $this->checkDatabaseSettings(
1212  $submissionVars['dbuser'],
1213  $submissionVars['dbpassword'],
1214  $submissionVars['dbname'],
1215  $submissionVars['dbhost'],
1216  $submissionVars['dbport']
1217  );
1218  }
1219 
1231  protected function checkDatabaseSettings($user, $password, $dbname, $host, $port) {
1232  $app = $this->getApp();
1233 
1234  $config = new DbConfig((object) [
1235  'dbhost' => $host,
1236  'dbport' => $port,
1237  'dbuser' => $user,
1238  'dbpass' => $password,
1239  'dbname' => $dbname,
1240  'dbencoding' => 'utf8mb4',
1241  ]);
1242  $db = new Database($config, $app->internal_services->queryCache);
1243 
1244  try {
1245  $db->getConnection('read')->executeQuery('SELECT 1');
1246  } catch (DatabaseException $e) {
1247  if (0 === strpos($e->getMessage(), "Elgg couldn't connect")) {
1248  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:databasesettings'));
1249  } else {
1250  $save_value = $this->sanitizeInputValue($dbname);
1251  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:nodatabase', [$save_value]));
1252  }
1253 
1254  return false;
1255  }
1256 
1257  // check MySQL version
1258  $version = $db->getServerVersion();
1259  $min_version = $db->isMariaDB() ? self::MARIADB_MINIMAL_VERSION : self::MYSQL_MINIMAL_VERSION;
1260 
1261  if (version_compare($version, $min_version, '<')) {
1262  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:database_version', [$min_version, $version]));
1263 
1264  return false;
1265  }
1266 
1267  return true;
1268  }
1269 
1277  protected function createSettingsFile($params) {
1278  $app = $this->getApp();
1279 
1280  $template = Application::elggDir()->getContents('elgg-config/settings.example.php');
1281  if (!$template) {
1282  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:readsettingsphp'));
1283 
1284  return false;
1285  }
1286 
1287  foreach ($params as $k => $v) {
1288  // do some sanitization
1289  switch ($k) {
1290  case 'dataroot':
1291  $v = Paths::sanitize($v);
1292  break;
1293  case 'dbpassword':
1294  $v = addslashes($v);
1295  break;
1296  }
1297 
1298  $template = str_replace("{{" . $k . "}}", $v, $template);
1299  }
1300 
1301  $result = file_put_contents(Config::resolvePath(), $template);
1302  if ($result === false) {
1303  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:writesettingphp'));
1304 
1305  return false;
1306  }
1307 
1308  $config = (object) [
1309  'dbhost' => elgg_extract('dbhost', $params, 'localhost'),
1310  'dbport' => elgg_extract('dbport', $params, 3306),
1311  'dbuser' => elgg_extract('dbuser', $params),
1312  'dbpass' => elgg_extract('dbpassword', $params),
1313  'dbname' => elgg_extract('dbname', $params),
1314  'dbencoding' => elgg_extract('dbencoding', $params, 'utf8mb4'),
1315  'dbprefix' => elgg_extract('dbprefix', $params, 'elgg_'),
1316  ];
1317 
1318  $dbConfig = new DbConfig($config);
1319  $this->getApp()->internal_services->set('dbConfig', $dbConfig);
1320  $this->getApp()->internal_services->db->resetConnections($dbConfig);
1321 
1322  return true;
1323  }
1324 
1330  protected function connectToDatabase() {
1331  try {
1332  $app = $this->getApp();
1333  $app->internal_services->db->setupConnections();
1334  } catch (DatabaseException $e) {
1335  $app->internal_services->system_messages->addErrorMessage($e->getMessage());
1336 
1337  return false;
1338  }
1339 
1340  return true;
1341  }
1342 
1348  protected function installDatabase() {
1349  try {
1350  return $this->getApp()->migrate();
1351  } catch (\Exception $e) {
1352  return false;
1353  }
1354  }
1355 
1368  protected function createDataDirectory(&$submissionVars, $formVars) {
1369  // did the user have option of Elgg creating the data directory
1370  if ($formVars['dataroot']['type'] != 'combo') {
1371  return true;
1372  }
1373 
1374  // did the user select the option
1375  if ($submissionVars['dataroot'] != 'dataroot-checkbox') {
1376  return true;
1377  }
1378 
1379  $dir = \Elgg\Project\Paths::sanitize($submissionVars['path']) . 'data';
1380  if (file_exists($dir) || mkdir($dir, 0755)) {
1381  $submissionVars['dataroot'] = $dir;
1382  if (!file_exists("{$dir}/.htaccess")) {
1383  $htaccess = "Order Deny,Allow\nDeny from All\n";
1384  if (!file_put_contents("$dir/.htaccess", $htaccess)) {
1385  return false;
1386  }
1387  }
1388 
1389  return true;
1390  }
1391 
1392  return false;
1393  }
1394 
1403  protected function validateSettingsVars($submissionVars, $formVars) {
1404  $app = $this->getApp();
1405 
1406  foreach ($formVars as $field => $info) {
1407  $submissionVars[$field] = trim($submissionVars[$field]);
1408  if ($info['required'] == true && $submissionVars[$field] === '') {
1409  $name = elgg_echo("install:settings:label:{$field}");
1410  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:requiredfield', [$name]));
1411 
1412  return false;
1413  }
1414  }
1415 
1416  // check that email address is email address
1417  if ($submissionVars['siteemail'] && !elgg_is_valid_email((string) $submissionVars['siteemail'])) {
1418  $save_value = $this->sanitizeInputValue($submissionVars['siteemail']);
1419  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:emailaddress', [$save_value]));
1420 
1421  return false;
1422  }
1423 
1424  return true;
1425  }
1426 
1434  protected function saveSiteSettings($submissionVars) {
1435  $app = $this->getApp();
1436 
1438 
1439  if (!$site->guid) {
1440  $site = new \ElggSite();
1441  $site->name = strip_tags($submissionVars['sitename']);
1442  $site->access_id = ACCESS_PUBLIC;
1443  $site->email = $submissionVars['siteemail'];
1444  $site->save();
1445  }
1446 
1447  if ($site->guid !== 1) {
1448  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:createsite'));
1449 
1450  return false;
1451  }
1452 
1453  $app->internal_services->config->site = $site;
1454 
1455  $sets = [
1456  'installed' => time(),
1457  'simplecache_enabled' => 1,
1458  'system_cache_enabled' => 1,
1459  'simplecache_minify_js' => true,
1460  'simplecache_minify_css' => true,
1461  'lastcache' => time(),
1462  'language' => 'en',
1463  'default_access' => $submissionVars['siteaccess'],
1464  'allow_registration' => false,
1465  'require_admin_validation' => false,
1466  'walled_garden' => false,
1467  'allow_user_default_access' => '',
1468  'default_limit' => 10,
1469  ];
1470 
1471  foreach ($sets as $key => $value) {
1473  }
1474 
1475  try {
1476  _elgg_services()->plugins->generateEntities();
1477 
1478  $app->internal_services->reset('plugins');
1479 
1480  $plugins = $app->internal_services->plugins->find('any');
1481 
1482  foreach ($plugins as $plugin) {
1483  $plugin_config = $plugin->getStaticConfig('plugin', []);
1484  if (!elgg_extract('activate_on_install', $plugin_config, false)) {
1485  continue;
1486  }
1487 
1488  try {
1489  $plugin->activate();
1490  } catch (PluginException $e) {
1491  // do nothing
1492  }
1493  }
1494 
1495  // Wo don't need to run upgrades on new installations
1496  $app->internal_services->events->unregisterHandler('create', 'object', \Elgg\Upgrade\CreateAdminNoticeHandler::class);
1497  $upgrades = $app->internal_services->upgradeLocator->locate();
1498  foreach ($upgrades as $upgrade) {
1499  $upgrade->setCompleted();
1500  }
1501  } catch (\Exception $e) {
1502  $app->internal_services->logger->log(\Psr\Log\LogLevel::ERROR, $e);
1503  }
1504 
1505  return true;
1506  }
1507 
1516  protected function validateAdminVars($submissionVars, $formVars) {
1517 
1518  $app = $this->getApp();
1519 
1520  foreach ($formVars as $field => $info) {
1521  if ($info['required'] == true && !$submissionVars[$field]) {
1522  $name = elgg_echo("install:admin:label:{$field}");
1523  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:requiredfield', [$name]));
1524 
1525  return false;
1526  }
1527  }
1528 
1529  if ($submissionVars['password1'] !== $submissionVars['password2']) {
1530  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:admin:password:mismatch'));
1531 
1532  return false;
1533  }
1534 
1535  if (trim($submissionVars['password1']) == "") {
1536  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:admin:password:empty'));
1537 
1538  return false;
1539  }
1540 
1541  $minLength = $app->internal_services->configTable->get('min_password_length');
1542  if (strlen($submissionVars['password1']) < $minLength) {
1543  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:admin:password:tooshort'));
1544 
1545  return false;
1546  }
1547 
1548  // check that email address is email address
1549  if ($submissionVars['email'] && !elgg_is_valid_email((string) $submissionVars['email'])) {
1550  $save_value = $this->sanitizeInputValue($submissionVars['email']);
1551  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:emailaddress', [$save_value]));
1552 
1553  return false;
1554  }
1555 
1556  return true;
1557  }
1558 
1567  protected function createAdminAccount($submissionVars, $login = false) {
1568  $app = $this->getApp();
1569 
1570  try {
1572  'username' => $submissionVars['username'],
1573  'password' => $submissionVars['password1'],
1574  'name' => $submissionVars['displayname'],
1575  'email' => $submissionVars['email'],
1576  ]);
1577  } catch (RegistrationException $e) {
1578  $app->internal_services->system_messages->addErrorMessage($e->getMessage());
1579 
1580  return false;
1581  }
1582 
1583  $ia = $app->internal_services->session->setIgnoreAccess(true);
1584  if (!$user->makeAdmin()) {
1585  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:adminaccess'));
1586  }
1587  $app->internal_services->session->setIgnoreAccess($ia);
1588 
1589  // add validation data to satisfy user validation plugins
1590  $user->validated = true;
1591  $user->validated_method = 'admin_user';
1592 
1593  if (!$login) {
1594  return true;
1595  }
1596 
1597  try {
1598  elgg_login($user);
1599  } catch (LoginException $ex) {
1600  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:adminlogin'));
1601 
1602  return false;
1603  }
1604 
1605  return true;
1606  }
1607 
1615  protected function sanitizeInputValue($input_value) {
1616  if (is_array($input_value)) {
1617  return array_map([$this, __FUNCTION__], $input_value);
1618  }
1619 
1620  if (!is_string($input_value)) {
1621  return $input_value;
1622  }
1623 
1624  return htmlspecialchars($input_value);
1625  }
1626 }
$current_step
Install sidebar.
Definition: sidebar.php:9
$plugin
foreach(array_keys($combine_languages) as $language) $translations
createDataDirectory(&$submissionVars, $formVars)
Site settings support methods.
A generic parent class for Configuration exceptions.
Bundled plugins(the contents of the"/mod"directory) are available only under the GPLv2 license.The remainder of the project is available under either MIT or GPLv2.Both licenses can be found below.More info and that you know you can do these things To protect your we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights These restrictions translate to certain responsibilities for you if you distribute copies of the or if you modify it For if you distribute copies of such a whether gratis or for a you must give the recipients all the rights that you have You must make sure that receive or can get the source code And you must show them these terms so they know their rights We protect your rights with two steps
Definition: LICENSE.txt:1
$requiredExtensions
$params
Saves global plugin settings.
Definition: save.php:13
Database configuration service.
Definition: DbConfig.php:13
countNumConditions($report, $condition)
Count the number of failures in the requirements report.
Elgg registration action.
elgg_normalize_url($url)
Definition: output.php:153
Elgg RewriteTester.
runAdmin($submissionVars)
Admin account controller.
checkDatabaseSettings($user, $password, $dbname, $host, $port)
Confirm the settings for the database.
Generic parent class for login exceptions.
if(!$user||!$user->canDelete()) $name
Definition: delete.php:22
checkPhpDirectives(&$phpReport)
Check PHP parameters.
$defaults
runWelcome($vars)
Step controllers.
$title
Definition: generic.php:50
$version
elgg_register_user(array $params=[])
Registers a user.
Definition: users.php:111
installDatabase()
Create the database tables.
run()
Dispatches a request to one of the step controllers.
checkInstallCompletion($step)
Security check to ensure the installer cannot be run after installation has finished.
getNextStep($currentStep)
Get the next step as a string.
static fromFiles(Config $config)
Create a session stored in files.
$plugins
Definition: categories.php:3
Could not register a new user for whatever reason.
getSteps()
Step management.
getApp()
Build the application needed by the installer.
$site
Definition: icons.php:5
elgg_echo($message_key, array $args=[], $language="")
Elgg language module Functions to manage language and translations.
Definition: languages.php:18
sanitizeInputValue($input_value)
Sanitize input to help prevent XSS.
checkPHP(&$report)
Check version of PHP, extensions, and variables.
if(!$item instanceof ElggEntity) $link
Definition: container.php:16
const MARIADB_MINIMAL_VERSION
runDatabase($submissionVars)
Database set up controller.
elgg_view_page($title, $body, $page_shell= 'default', $vars=[])
Assembles and outputs a full page.
Definition: views.php:262
$value
Definition: generic.php:51
validateSettingsVars($submissionVars, $formVars)
Validate the site settings form variables.
elgg_save_config($name, $value)
Save a configuration setting.
resumeInstall($step)
Check if this is a case of a install being resumed and figure out where to continue from...
$config
Advanced site settings, debugging section.
Definition: debugging.php:6
string project
Definition: conf.py:48
Updates the basic settings for the primary site object.
$upgrades
Lists pending upgrades.
Definition: upgrades.php:11
$path
Definition: details.php:68
$tester
getCurrentStep()
Returns current step.
continueToNextStep($currentStep)
Forwards the browser to the next step.
$separator
Definition: tags.php:56
getNextStepUrl($currentStep)
Get the URL of the next step.
if(empty($guid)) $upgrade
Definition: upgrade.php:11
if(!$user||!$user->canEdit()) $password
const MYSQL_MINIMAL_VERSION
finishBootstrapping($step)
Bootstrapping.
get_input($variable, $default=null, $filter_result=true)
Parameter input functions.
Definition: input.php:20
$lang
Definition: html.php:13
render($step, $vars=[])
Renders the data passed by a controller.
foreach($recommendedExtensions as $extension) if(empty(ini_get('session.gc_probability'))||empty(ini_get('session.gc_divisor'))) $db
runComplete()
Controller for last step.
elgg_format_element($tag_name, array $attributes=[], $text= '', array $options=[])
Format an HTML element.
Definition: output.php:135
Thrown when there is a major problem with the installation.
makeFormSticky($formVars, $submissionVars)
Action handling methods.
A generic parent class for database exceptions.
$user
Definition: ban.php:7
checkPhpExtensions(&$phpReport)
Check the server&#39;s PHP extensions.
$count
Definition: ban.php:24
$body
Definition: useradd.php:59
runSettings($submissionVars)
Site settings controller.
elgg_get_site_entity()
Get the current site entity.
Definition: entities.php:99
checkRewriteRules(&$report)
Confirm that the rewrite rules are firing.
elgg_extract($key, $array, $default=null, $strict=true)
Checks for $array[$key] and returns its value if it exists, else returns $default.
Definition: elgglib.php:547
runRequirements($vars)
Requirements controller.
validateDatabaseVars($submissionVars, $formVars)
Database support methods.
$extensions
if(elgg_extract('required', $vars)) if(elgg_extract('disabled', $vars)) $field
Definition: field.php:37
determineInstallStatus()
Updates $this->has_completed according to the current installation.
if($container instanceof ElggGroup &&$container->guid!=elgg_get_page_owner_guid()) $key
Definition: summary.php:44
createSettingsFile($params)
Writes the settings file to the engine directory.
elgg_delete_directory(string $directory, bool $leave_base_directory=false)
Delete a directory and all its contents.
Definition: filestore.php:51
$step
Definition: time.php:41
$report
if(isset($_COOKIE['elggperm'])) $session
Definition: login_as.php:28
checkSettingsFile(&$report=[])
Check that the settings file exists.
elgg_is_valid_email(string $address)
Validates an email address.
Definition: input.php:91
elgg_view_url(string $href, string $text=null, array $options=[])
Helper function for outputting urls.
Definition: views.php:1519
isInstallDirWritable(&$report)
Indicates whether the webserver can add settings.php on its own or not.
const ELGG_VALUE_STRING
Definition: constants.php:127
loadSettingsFile()
Load settings.
foreach($requiredExtensions as $extension) $recommendedExtensions
elgg_login(\ElggUser $user, bool $persistent=false)
Log in a user.
Definition: sessions.php:81
$min_version
foreach($plugin_guids as $guid) if(empty($deactivated_plugins)) $url
Definition: deactivate.php:39
static sanitize($path, $append_slash=true)
Sanitize file paths ensuring that they begin and end with slashes etc.
Definition: Paths.php:76
elgg_add_admin_notice(string $id, string $message)
Write a persistent message to the admin view.
Definition: admin.php:51
_elgg_services()
Get the global service provider.
Definition: elgglib.php:638
const ACCESS_PUBLIC
Definition: constants.php:14
$vars['head']
Definition: html.php:24
batchInstall(array $params, $create_htaccess=false)
A batch install of Elgg.
connectToDatabase()
Bootstrap database connection before entire engine is available.
setAutoLogin($flag)
Set the auto login flag.
$index
Definition: gallery.php:40
createAdminAccount($submissionVars, $login=false)
Create a user account for the admin.
saveSiteSettings($submissionVars)
Initialize the site including site entity, plugins, and configuration.
Login as the specified user.
$output
Definition: download.php:9
elgg_view($view, $vars=[], $viewtype= '')
Return a parsed view.
Definition: views.php:179
$qb
Definition: queue.php:11
var elgg
Definition: elgglib.js:4
$table
Definition: cron.php:56
const PHP_MINIMAL_VERSION
$extension
Definition: default.php:25
validateAdminVars($submissionVars, $formVars)
Validate account form variables.