Elgg  Version master
ElggInstaller.php
Go to the documentation of this file.
1 <?php
2 
4 use Elgg\Config;
5 use Elgg\Database;
16 
42 
43  public const MARIADB_MINIMAL_VERSION = '10.3';
44  public const MYSQL_MINIMAL_VERSION = '5.7';
45  public const PHP_MINIMAL_VERSION = '8.0.0';
46 
47  private $steps = [
48  'welcome',
49  'requirements',
50  'database',
51  'settings',
52  'admin',
53  'complete',
54  ];
55 
56  private $has_completed = [
57  'config' => false,
58  'database' => false,
59  'settings' => false,
60  'admin' => false,
61  ];
62 
63  private $is_action = false;
64 
65  private $autoLogin = true;
66 
70  private $app;
71 
77  public function run() {
78  $app = $this->getApp();
79 
80  $this->is_action = $app->internal_services->request->getMethod() === 'POST';
81 
82  $step = $this->getCurrentStep();
83 
84  $this->determineInstallStatus();
85 
87  if ($response) {
88  return $response;
89  }
90 
91  // check if this is an install being resumed
92  $response = $this->resumeInstall($step);
93  if ($response) {
94  return $response;
95  }
96 
97  $this->finishBootstrapping($step);
98 
99  $params = $app->internal_services->request->request->all();
100 
101  $method = 'run' . ucwords($step);
102 
103  return $this->$method($params);
104  }
105 
112  protected function getApp() {
113  if ($this->app) {
114  return $this->app;
115  }
116 
117  try {
118  $config = new Config();
119  $config->installer_running = true;
120  $config->dbencoding = 'utf8mb4';
121  $config->boot_cache_ttl = 0;
122  $config->system_cache_enabled = false;
123  $config->simplecache_enabled = false;
124  $config->debug = \Psr\Log\LogLevel::WARNING;
125  $config->cacheroot = Paths::sanitize(sys_get_temp_dir()) . 'elgginstaller/caches/';
126  $config->assetroot = Paths::sanitize(sys_get_temp_dir()) . 'elgginstaller/assets/';
127 
128  $app = Application::factory([
129  'config' => $config,
130  'handle_exceptions' => false,
131  'handle_shutdown' => false,
132  ]);
133 
134  // Don't set global $CONFIG, because loading the settings file may require it to write to
135  // it, and it can have array sets (e.g. cookie config) that fail when using a proxy for
136  // the config service.
137  //$app->setGlobalConfig();
138 
139  Application::setInstance($app);
140  $app->loadCore();
141  $this->app = $app;
142 
143  $app->internal_services->boot->getCache()->disable();
144  $app->internal_services->plugins->getCache()->disable();
145  $app->internal_services->sessionCache->disable();
146  $app->internal_services->dataCache->disable();
147  $app->internal_services->autoloadManager->getCache()->disable();
148 
149  $current_step = $this->getCurrentStep();
150  $index_admin = array_search('admin', $this->getSteps());
151  $index_complete = array_search('complete', $this->getSteps());
152  $index_step = array_search($current_step, $this->getSteps());
153 
154  // For the admin creation action and the complete step we use the Elgg core session handling.
155  // Otherwise, use default php session handling
156  $use_elgg_session = ($index_step == $index_admin) || ($index_step == $index_complete);
157  if (!$use_elgg_session) {
158  $session = \ElggSession::fromFiles($app->internal_services->config);
159  $session->setName('Elgg_install');
160  $app->internal_services->set('session', $session);
161  }
162 
163  $app->internal_services->views->setViewtype('installation');
164  $app->internal_services->views->registerViewtypeFallback('installation');
165  $app->internal_services->views->registerPluginViews(Paths::elgg());
166  $app->internal_services->translator->registerTranslations(Paths::elgg() . 'install/languages/', true);
167 
168  return $this->app;
169  } catch (ConfigurationException $ex) {
170  throw new InstallationException($ex->getMessage());
171  }
172  }
173 
181  public function setAutoLogin($flag) {
182  $this->autoLogin = (bool) $flag;
183  }
184 
200  public function batchInstall(array $params, $create_htaccess = false) {
201  $app = $this->getApp();
202 
203  $defaults = [
204  'dbhost' => 'localhost',
205  'dbport' => '3306',
206  'dbprefix' => 'elgg_',
207  'language' => 'en',
208  'siteaccess' => ACCESS_PUBLIC,
209  ];
210  $params = array_merge($defaults, $params);
211 
212  $required_params = [
213  'dbuser',
214  'dbpassword',
215  'dbname',
216  'sitename',
217  'wwwroot',
218  'dataroot',
219  'displayname',
220  'email',
221  'username',
222  'password',
223  ];
224  foreach ($required_params as $key) {
225  if (empty($params[$key])) {
226  throw new InstallationException(elgg_echo('install:error:requiredfield', [$key]));
227  }
228  }
229 
230  // password is passed in once
231  $params['password1'] = $params['password'];
232  $params['password2'] = $params['password'];
233 
234  if ($create_htaccess) {
235  $rewrite_tester = new RewriteTester();
236  if (!$rewrite_tester->createHtaccess($params['wwwroot'])) {
237  throw new InstallationException(elgg_echo('install:error:htaccess'));
238  }
239  }
240 
241  if (!\Elgg\Http\Urls::isValidMultiByteUrl($params['wwwroot'])) {
242  throw new InstallationException(elgg_echo('install:error:wwwroot', [$params['wwwroot']]));
243  }
244 
245  // sanitize dataroot path
246  $params['dataroot'] = Paths::sanitize($params['dataroot']);
247 
248  $this->determineInstallStatus();
249 
250  if (!$this->has_completed['config']) {
251  if (!$this->createSettingsFile($params)) {
252  throw new InstallationException(elgg_echo('install:error:settings'));
253  }
254  }
255 
256  $this->loadSettingsFile();
257 
258  // Make sure settings file matches parameters
259  $config = $app->internal_services->config;
260  $config_keys = [
261  // param key => config key
262  'dbhost' => 'dbhost',
263  'dbport' => 'dbport',
264  'dbuser' => 'dbuser',
265  'dbpassword' => 'dbpass',
266  'dbname' => 'dbname',
267  'dataroot' => 'dataroot',
268  'dbprefix' => 'dbprefix',
269  ];
270  foreach ($config_keys as $params_key => $config_key) {
271  if ($params[$params_key] !== $config->$config_key) {
272  throw new InstallationException(elgg_echo('install:error:settings_mismatch', [$config_key, $params[$params_key], $config->$config_key]));
273  }
274  }
275 
276  if (!$this->connectToDatabase()) {
277  throw new InstallationException(elgg_echo('install:error:databasesettings'));
278  }
279 
280  if (!$this->has_completed['database']) {
281  if (!$this->installDatabase()) {
282  throw new InstallationException(elgg_echo('install:error:cannotloadtables'));
283  }
284  }
285 
286  // load remaining core libraries
287  $this->finishBootstrapping('settings');
288 
289  if (!$this->saveSiteSettings($params)) {
290  throw new InstallationException(elgg_echo('install:error:savesitesettings'));
291  }
292 
293  if (!$this->createAdminAccount($params)) {
294  throw new InstallationException(elgg_echo('install:admin:cannot_create'));
295  }
296  }
297 
306  protected function render($step, $vars = []) {
307  $vars['next_step'] = $this->getNextStep($step);
308 
309  $title = elgg_echo("install:{$step}");
310  $body = elgg_view("install/pages/{$step}", $vars);
311 
313  $title,
314  $body,
315  'default',
316  [
317  'step' => $step,
318  'steps' => $this->getSteps(),
319  ]
320  );
321 
322  return new \Elgg\Http\OkResponse($output);
323  }
324 
336  protected function runWelcome($vars) {
337  return $this->render('welcome');
338  }
339 
349  protected function runRequirements($vars) {
350 
351  $report = [];
352 
353  // check PHP parameters and libraries
354  $this->checkPHP($report);
355 
356  // check URL rewriting
357  $this->checkRewriteRules($report);
358 
359  // check for existence of settings file
360  if ($this->checkSettingsFile($report) !== true) {
361  // no file, so check permissions on engine directory
363  }
364 
365  // check the database later
366  $report['database'] = [
367  [
368  'severity' => 'notice',
369  'message' => elgg_echo('install:check:database'),
370  ]
371  ];
372 
373  // any failures?
374  $numFailures = $this->countNumConditions($report, 'error');
375 
376  // any warnings
377  $numWarnings = $this->countNumConditions($report, 'warning');
378 
379 
380  $params = [
381  'report' => $report,
382  'num_failures' => $numFailures,
383  'num_warnings' => $numWarnings,
384  ];
385 
386  return $this->render('requirements', $params);
387  }
388 
398  protected function runDatabase($submissionVars) {
399 
400  $app = $this->getApp();
401 
402  $formVars = [
403  'dbuser' => [
404  'type' => 'text',
405  'value' => '',
406  'required' => true,
407  ],
408  'dbpassword' => [
409  'type' => 'password',
410  'value' => '',
411  'required' => false,
412  ],
413  'dbname' => [
414  'type' => 'text',
415  'value' => '',
416  'required' => true,
417  ],
418  'dbhost' => [
419  'type' => 'text',
420  'value' => 'localhost',
421  'required' => true,
422  ],
423  'dbport' => [
424  'type' => 'number',
425  'value' => 3306,
426  'required' => true,
427  'min' => 0,
428  'max' => 65535,
429  ],
430  'dbprefix' => [
431  'type' => 'text',
432  'value' => 'elgg_',
433  'required' => false,
434  ],
435  'dataroot' => [
436  'type' => 'text',
437  'value' => '',
438  'required' => true,
439  ],
440  'wwwroot' => [
441  'type' => 'url',
442  'value' => $app->internal_services->config->wwwroot,
443  'required' => true,
444  ],
445  'timezone' => [
446  'type' => 'dropdown',
447  'value' => 'UTC',
448  'options' => \DateTimeZone::listIdentifiers(),
449  'required' => true
450  ]
451  ];
452 
453  if ($this->checkSettingsFile()) {
454  // user manually created settings file so we fake out action test
455  $this->is_action = true;
456  }
457 
458  if ($this->is_action) {
459  $getResponse = function () use ($app, $submissionVars, $formVars) {
460  // only create settings file if it doesn't exist
461  if (!$this->checkSettingsFile()) {
462  if (!$this->validateDatabaseVars($submissionVars, $formVars)) {
463  // error so we break out of action and serve same page
464  return;
465  }
466 
467  if (!$this->createSettingsFile($submissionVars)) {
468  return;
469  }
470  }
471 
472  // check db version and connect
473  if (!$this->connectToDatabase()) {
474  return;
475  }
476 
477  if (!$this->installDatabase()) {
478  return;
479  }
480 
481  $app->internal_services->system_messages->addSuccessMessage(elgg_echo('install:success:database'));
482 
483  return $this->continueToNextStep('database');
484  };
485 
486  $response = $getResponse();
487  if ($response) {
488  return $response;
489  }
490  }
491 
492  $formVars = $this->makeFormSticky($formVars, $submissionVars);
493 
494  $params = ['variables' => $formVars,];
495 
496  if ($this->checkSettingsFile()) {
497  // settings file exists and we're here so failed to create database
498  $params['failure'] = true;
499  }
500 
501  return $this->render('database', $params);
502  }
503 
513  protected function runSettings($submissionVars) {
514 
515  $app = $this->getApp();
516 
517  $formVars = [
518  'sitename' => [
519  'type' => 'text',
520  'value' => 'My New Community',
521  'required' => true,
522  ],
523  'siteemail' => [
524  'type' => 'email',
525  'value' => '',
526  'required' => false,
527  ],
528  'siteaccess' => [
529  'type' => 'access',
530  'value' => ACCESS_PUBLIC,
531  'required' => true,
532  ],
533  ];
534 
535  if ($this->is_action) {
536  $getResponse = function () use ($app, $submissionVars, $formVars) {
537 
538  if (!$this->validateSettingsVars($submissionVars, $formVars)) {
539  return;
540  }
541 
542  if (!$this->saveSiteSettings($submissionVars)) {
543  return;
544  }
545 
546  $app->internal_services->system_messages->addSuccessMessage(elgg_echo('install:success:settings'));
547 
548  return $this->continueToNextStep('settings');
549  };
550 
551  $response = $getResponse();
552  if ($response) {
553  return $response;
554  }
555  }
556 
557  $formVars = $this->makeFormSticky($formVars, $submissionVars);
558 
559  return $this->render('settings', ['variables' => $formVars]);
560  }
561 
571  protected function runAdmin($submissionVars) {
572  $app = $this->getApp();
573 
574  $formVars = [
575  'displayname' => [
576  'type' => 'text',
577  'value' => '',
578  'required' => true,
579  ],
580  'email' => [
581  'type' => 'email',
582  'value' => '',
583  'required' => true,
584  ],
585  'username' => [
586  'type' => 'text',
587  'value' => '',
588  'required' => true,
589  ],
590  'password1' => [
591  'type' => 'password',
592  'value' => '',
593  'required' => true,
594  'pattern' => '.{6,}',
595  ],
596  'password2' => [
597  'type' => 'password',
598  'value' => '',
599  'required' => true,
600  ],
601  ];
602 
603  if ($this->is_action) {
604  $getResponse = function () use ($app, $submissionVars, $formVars) {
605  if (!$this->validateAdminVars($submissionVars, $formVars)) {
606  return;
607  }
608 
609  if (!$this->createAdminAccount($submissionVars, $this->autoLogin)) {
610  return;
611  }
612 
613  $app->internal_services->system_messages->addSuccessMessage(elgg_echo('install:success:admin'));
614 
615  return $this->continueToNextStep('admin');
616  };
617 
618  $response = $getResponse();
619  if ($response) {
620  return $response;
621  }
622  }
623 
624  // Bit of a hack to get the password help to show right number of characters
625  // We burn the value into the stored translation.
626  $app = $this->getApp();
627  $lang = $app->internal_services->translator->getCurrentLanguage();
628  $translations = $app->internal_services->translator->getLoadedTranslations();
629  $app->internal_services->translator->addTranslation($lang, [
630  'install:admin:help:password1' => sprintf(
631  $translations[$lang]['install:admin:help:password1'],
632  $app->internal_services->config->min_password_length
633  ),
634  ]);
635 
636  $formVars = $this->makeFormSticky($formVars, $submissionVars);
637 
638  return $this->render('admin', ['variables' => $formVars]);
639  }
640 
646  protected function runComplete() {
647 
648  // nudge to check out settings
649  $link = elgg_view_url(elgg_normalize_url('admin/site_settings'), elgg_echo('install:complete:admin_notice:link_text'));
650  $notice = elgg_format_element('p', [], elgg_echo('install:complete:admin_notice', [$link]));
651 
652  $custom_index_link = elgg_view_url(elgg_normalize_url('admin/plugin_settings/custom_index'), elgg_echo('admin:plugin_settings'));
653  $notice .= elgg_format_element('p', [], elgg_echo('install:complete:admin_notice:custom_index', [$custom_index_link]));
654 
655  elgg_add_admin_notice('fresh_install', $notice);
656 
657  $result = $this->render('complete');
658 
659  elgg_delete_directory(Paths::sanitize(sys_get_temp_dir()) . 'elgginstaller/');
660 
661  return $result;
662  }
663 
673  protected function getSteps() {
674  return $this->steps;
675  }
676 
682  protected function getCurrentStep() {
683  $step = get_input('step', 'welcome');
684 
685  if (!in_array($step, $this->getSteps())) {
686  $step = 'welcome';
687  }
688 
689  return $step;
690  }
691 
699  protected function continueToNextStep($currentStep) {
700  $this->is_action = false;
701 
702  return new \Elgg\Http\RedirectResponse($this->getNextStepUrl($currentStep));
703  }
704 
712  protected function getNextStep($currentStep) {
713  $index = 1 + array_search($currentStep, $this->steps);
714 
715  return $this->steps[$index] ?? null;
716  }
717 
725  protected function getNextStepUrl($currentStep) {
726  $app = $this->getApp();
727  $nextStep = $this->getNextStep($currentStep);
728 
729  return $app->internal_services->config->wwwroot . "install.php?step={$nextStep}";
730  }
731 
738  protected function determineInstallStatus() {
739  $app = $this->getApp();
740 
741  $path = Config::resolvePath();
742  if (!is_file($path) || !is_readable($path)) {
743  return;
744  }
745 
746  $this->loadSettingsFile();
747 
748  $this->has_completed['config'] = true;
749 
750  // must be able to connect to database to jump install steps
751  $dbSettingsPass = $this->checkDatabaseSettings(
752  $app->internal_services->config->dbuser,
753  $app->internal_services->config->dbpass,
754  $app->internal_services->config->dbname,
755  $app->internal_services->config->dbhost,
756  $app->internal_services->config->dbport
757  );
758 
759  if (!$dbSettingsPass) {
760  return;
761  }
762 
763  $db = $app->internal_services->db;
764 
765  try {
766  // check that the config table has been created
767  $result = $db->getConnection('read')->executeQuery('SHOW TABLES');
768  if (empty($result)) {
769  return;
770  }
771 
772  foreach ($result->fetchAllAssociative() as $table) {
773  if (in_array("{$db->prefix}config", $table)) {
774  $this->has_completed['database'] = true;
775  }
776  }
777 
778  if ($this->has_completed['database'] === false) {
779  return;
780  }
781 
782  // check that the config table has entries
783  $qb = \Elgg\Database\Select::fromTable('config');
784  $qb->select('COUNT(*) AS total');
785 
786  $result = $db->getDataRow($qb);
787  if (!empty($result) && $result->total > 0) {
788  $this->has_completed['settings'] = true;
789  } else {
790  return;
791  }
792 
793  // check that the users entity table has an entry
794  $qb = \Elgg\Database\Select::fromTable('entities', 'e');
795  $qb->select('COUNT(*) AS total')
796  ->where($qb->compare('type', '=', 'user', ELGG_VALUE_STRING));
797 
798  $result = $db->getDataRow($qb);
799  if (!empty($result) && $result->total > 0) {
800  $this->has_completed['admin'] = true;
801  } else {
802  return;
803  }
804  } catch (DatabaseException $ex) {
805  throw new InstallationException('Elgg can not connect to the database: ' . $ex->getMessage(), $ex->getCode(), $ex);
806  }
807  }
808 
817  protected function checkInstallCompletion($step) {
818  if ($step === 'complete') {
819  return;
820  }
821 
822  if (!in_array(false, $this->has_completed)) {
823  // install complete but someone is trying to view an install page
824  return new \Elgg\Http\RedirectResponse('/');
825  }
826  }
827 
836  protected function resumeInstall($step) {
837  // only do a resume from the first step
838  if ($step !== 'welcome') {
839  return null;
840  }
841 
842  if ($this->has_completed['database'] === false) {
843  return null;
844  }
845 
846  if ($this->has_completed['settings'] === false) {
847  return new \Elgg\Http\RedirectResponse('install.php?step=settings');
848  }
849 
850  if ($this->has_completed['admin'] === false) {
851  return new \Elgg\Http\RedirectResponse('install.php?step=admin');
852  }
853 
854  // everything appears to be set up
855  return new \Elgg\Http\RedirectResponse('install.php?step=complete');
856  }
857 
870  protected function finishBootstrapping($step) {
871 
872  $app = $this->getApp();
873 
874  $index_db = array_search('database', $this->getSteps());
875  $index_step = array_search($step, $this->getSteps());
876 
877  if ($index_step > $index_db) {
878  // once the database has been created, load rest of engine
879 
880  // dummy site needed to boot
881  $app->internal_services->config->site = new \ElggSite();
882 
883  $app->bootCore();
884  }
885  }
886 
893  protected function loadSettingsFile() {
894  try {
895  $app = $this->getApp();
896 
897  $config = Config::factory();
898  $app->internal_services->set('config', $config);
899 
900  // in case the DB instance is already captured in services, we re-inject its settings.
901  $app->internal_services->db->resetConnections(DbConfig::fromElggConfig($config));
902  } catch (\Exception $e) {
903  throw new InstallationException(elgg_echo('InstallationException:CannotLoadSettings'), 0, $e);
904  }
905  }
906 
919  protected function makeFormSticky($formVars, $submissionVars) {
920  foreach ($submissionVars as $field => $value) {
921  $formVars[$field]['value'] = $value;
922  }
923 
924  return $formVars;
925  }
926 
927  /* Requirement checks support methods */
928 
936  protected function isInstallDirWritable(&$report) {
937  if (!is_writable(Paths::projectConfig())) {
938  $report['settings'] = [
939  [
940  'severity' => 'error',
941  'message' => elgg_echo('install:check:installdir', [Paths::PATH_TO_CONFIG]),
942  ]
943  ];
944 
945  return false;
946  }
947 
948  return true;
949  }
950 
958  protected function checkSettingsFile(&$report = []) {
959  if (!is_file(Config::resolvePath())) {
960  return false;
961  }
962 
963  if (!is_readable(Config::resolvePath())) {
964  $report['settings'] = [
965  [
966  'severity' => 'error',
967  'message' => elgg_echo('install:check:readsettings'),
968  ]
969  ];
970  }
971 
972  return true;
973  }
974 
982  protected function checkPHP(&$report) {
983  $phpReport = [];
984 
985  if (version_compare(PHP_VERSION, self::PHP_MINIMAL_VERSION, '<')) {
986  $phpReport[] = [
987  'severity' => 'error',
988  'message' => elgg_echo('install:check:php:version', [self::PHP_MINIMAL_VERSION, PHP_VERSION]),
989  ];
990  }
991 
992  $this->checkPhpExtensions($phpReport);
993 
994  $this->checkPhpDirectives($phpReport);
995 
996  if (count($phpReport) == 0) {
997  $phpReport[] = [
998  'severity' => 'success',
999  'message' => elgg_echo('install:check:php:success'),
1000  ];
1001  }
1002 
1003  $report['php'] = $phpReport;
1004  }
1005 
1013  protected function checkPhpExtensions(&$phpReport) {
1014  $extensions = get_loaded_extensions();
1016  'pdo_mysql',
1017  'json',
1018  'xml',
1019  'gd',
1020  ];
1021  foreach ($requiredExtensions as $extension) {
1022  if (!in_array($extension, $extensions)) {
1023  $phpReport[] = [
1024  'severity' => 'error',
1025  'message' => elgg_echo('install:check:php:extension', [$extension]),
1026  ];
1027  }
1028  }
1029 
1031  'mbstring',
1032  ];
1033  foreach ($recommendedExtensions as $extension) {
1034  if (!in_array($extension, $extensions)) {
1035  $phpReport[] = [
1036  'severity' => 'warning',
1037  'message' => elgg_echo('install:check:php:extension:recommend', [$extension]),
1038  ];
1039  }
1040  }
1041  }
1042 
1050  protected function checkPhpDirectives(&$phpReport) {
1051  if (ini_get('open_basedir')) {
1052  $phpReport[] = [
1053  'severity' => 'warning',
1054  'message' => elgg_echo('install:check:php:open_basedir'),
1055  ];
1056  }
1057 
1058  if (ini_get('safe_mode')) {
1059  $phpReport[] = [
1060  'severity' => 'warning',
1061  'message' => elgg_echo('install:check:php:safe_mode'),
1062  ];
1063  }
1064 
1065  if (ini_get('arg_separator.output') !== '&') {
1066  $separator = htmlspecialchars(ini_get('arg_separator.output'));
1067  $phpReport[] = [
1068  'severity' => 'error',
1069  'message' => elgg_echo('install:check:php:arg_separator', [$separator]),
1070  ];
1071  }
1072 
1073  if (ini_get('register_globals')) {
1074  $phpReport[] = [
1075  'severity' => 'error',
1076  'message' => elgg_echo('install:check:php:register_globals'),
1077  ];
1078  }
1079 
1080  if (ini_get('session.auto_start')) {
1081  $phpReport[] = [
1082  'severity' => 'error',
1083  'message' => elgg_echo('install:check:php:session.auto_start'),
1084  ];
1085  }
1086  }
1087 
1095  protected function checkRewriteRules(&$report) {
1096  $app = $this->getApp();
1097 
1098  $tester = new RewriteTester();
1099  $url = $app->internal_services->config->wwwroot;
1100  $url .= Request::REWRITE_TEST_TOKEN . '?' . http_build_query([
1101  Request::REWRITE_TEST_TOKEN => '1',
1102  ]);
1103  $report['rewrite'] = [$tester->run($url)];
1104  }
1105 
1114  protected function countNumConditions($report, $condition) {
1115  $count = 0;
1116  foreach ($report as $checks) {
1117  foreach ($checks as $check) {
1118  if ($check['severity'] === $condition) {
1119  $count++;
1120  }
1121  }
1122  }
1123 
1124  return $count;
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 (!str_starts_with($submissionVars['dataroot'], '/')) {
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 (str_starts_with($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:after', '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_manager->setIgnoreAccess(true);
1584  if (!$user->makeAdmin()) {
1585  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:adminaccess'));
1586  }
1587 
1588  $app->internal_services->session_manager->setIgnoreAccess($ia);
1589 
1590  // add validation data to satisfy user validation plugins
1591  $user->validated = true;
1592  $user->validated_method = 'admin_user';
1593 
1594  if (!$login) {
1595  return true;
1596  }
1597 
1598  try {
1599  elgg_login($user);
1600  } catch (LoginException $ex) {
1601  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:adminlogin'));
1602 
1603  return false;
1604  }
1605 
1606  return true;
1607  }
1608 
1616  protected function sanitizeInputValue($input_value) {
1617  if (is_array($input_value)) {
1618  return array_map([$this, __FUNCTION__], $input_value);
1619  }
1620 
1621  if (!is_string($input_value)) {
1622  return $input_value;
1623  }
1624 
1625  return htmlspecialchars($input_value);
1626  }
1627 }
$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.
runAdmin($submissionVars)
Admin account controller.
checkDatabaseSettings($user, $password, $dbname, $host, $port)
Confirm the settings for the database.
$defaults
Generic entity header upload helper.
Definition: header.php:6
Generic parent class for login exceptions.
if(!$user||!$user->canDelete()) $name
Definition: delete.php:22
checkPhpDirectives(&$phpReport)
Check PHP parameters.
$response
Definition: content.php:10
runWelcome($vars)
Step controllers.
$title
Definition: generic.php:50
$version
elgg_register_user(array $params=[])
Registers a user.
Definition: users.php:162
elgg_echo(string $message_key, array $args=[], string $language= '')
Elgg language module Functions to manage language and translations.
Definition: languages.php:17
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
Test if URL rewriting is working.
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.
$value
Definition: generic.php:51
get_input(string $variable, $default=null, bool $filter_result=true)
Parameter input functions.
Definition: input.php:20
validateSettingsVars($submissionVars, $formVars)
Validate the site settings form variables.
resumeInstall($step)
Check if this is a case of a install being resumed and figure out where to continue from...
elgg_extract($key, $array, $default=null, bool $strict=true)
Checks for $array[$key] and returns its value if it exists, else returns $default.
Definition: elgglib.php:254
$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:70
$tester
getCurrentStep()
Returns current step.
continueToNextStep($currentStep)
Forwards the browser to the next step.
$separator
Definition: tags.php:56
elgg_view(string $view, array $vars=[], string $viewtype= '')
Return a parsed view.
Definition: views.php:177
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.
elgg_view_page(string $title, string|array $body, string $page_shell= 'default', array $vars=[])
Assembles and outputs a full page.
Definition: views.php:256
$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.
Thrown when there is a major problem with the installation.
makeFormSticky($formVars, $submissionVars)
Action handling methods.
elgg_save_config(string $name, $value)
Save a configuration setting.
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:55
runSettings($submissionVars)
Site settings controller.
elgg_get_site_entity()
Get the current site entity.
Definition: entities.php:98
checkRewriteRules(&$report)
Confirm that the rewrite rules are firing.
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:38
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:29
checkSettingsFile(&$report=[])
Check that the settings file exists.
elgg_is_valid_email(string $address)
Validates an email address.
Definition: input.php:89
elgg_view_url(string $href, string $text=null, array $options=[])
Helper function for outputting urls.
Definition: views.php:1471
isInstallDirWritable(&$report)
Indicates whether the webserver can add settings.php on its own or not.
const ELGG_VALUE_STRING
Definition: constants.php:112
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:346
const ACCESS_PUBLIC
Definition: constants.php:12
$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.
elgg_format_element(string $tag_name, array $attributes=[], string $text= '', array $options=[])
Format an HTML element.
Definition: output.php:142
setAutoLogin($flag)
Set the auto login flag.
$index
Definition: gallery.php:40
createAdminAccount($submissionVars, $login=false)
Create a user account for the admin.
elgg_normalize_url(string $url)
Definition: output.php:160
saveSiteSettings($submissionVars)
Initialize the site including site entity, plugins, and configuration.
Login as the specified user.
$output
Definition: download.php:9
$qb
Definition: queue.php:11
var elgg
Definition: elgglib.js:4
$table
Definition: cron.php:60
const PHP_MINIMAL_VERSION
$extension
Definition: default.php:25
validateAdminVars($submissionVars, $formVars)
Validate account form variables.