Elgg  Version 4.x
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->elgg_config_locks = false;
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  $msg = elgg_echo('install:error:requiredfield', [$key]);
227  throw new InstallationException($msg);
228  }
229  }
230 
231  // password is passed in once
232  $params['password1'] = $params['password2'] = $params['password'];
233 
234  if ($create_htaccess) {
235  $rewrite_tester = new ElggRewriteTester();
236  if (!$rewrite_tester->createHtaccess($params['wwwroot'])) {
237  throw new InstallationException(elgg_echo('install:error:htaccess'));
238  }
239  }
240 
241  if (!_elgg_sane_validate_url($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  if (isset($this->steps[$index])) {
715  return $this->steps[$index];
716  } else {
717  return null;
718  }
719  }
720 
728  protected function getNextStepUrl($currentStep) {
729  $app = $this->getApp();
730  $nextStep = $this->getNextStep($currentStep);
731 
732  return $app->internal_services->config->wwwroot . "install.php?step=$nextStep";
733  }
734 
741  protected function determineInstallStatus() {
742  $app = $this->getApp();
743 
744  $path = Config::resolvePath();
745  if (!is_file($path) || !is_readable($path)) {
746  return;
747  }
748 
749  $this->loadSettingsFile();
750 
751  $this->has_completed['config'] = true;
752 
753  // must be able to connect to database to jump install steps
754  $dbSettingsPass = $this->checkDatabaseSettings(
755  $app->internal_services->config->dbuser,
756  $app->internal_services->config->dbpass,
757  $app->internal_services->config->dbname,
758  $app->internal_services->config->dbhost,
759  $app->internal_services->config->dbport
760  );
761 
762  if (!$dbSettingsPass) {
763  return;
764  }
765 
766  $db = $app->internal_services->db;
767 
768  try {
769  // check that the config table has been created
770  $result = $db->getConnection('read')->executeQuery('SHOW TABLES');
771  if (empty($result)) {
772  return;
773  }
774  foreach ($result->fetchAllAssociative() as $table) {
775  if (in_array("{$db->prefix}config", $table)) {
776  $this->has_completed['database'] = true;
777  }
778  }
779  if ($this->has_completed['database'] == false) {
780  return;
781  }
782 
783  // check that the config table has entries
784  $qb = \Elgg\Database\Select::fromTable('config');
785  $qb->select('COUNT(*) AS total');
786 
787  $result = $db->getDataRow($qb);
788  if (!empty($result) && $result->total > 0) {
789  $this->has_completed['settings'] = true;
790  } else {
791  return;
792  }
793 
794  // check that the users entity table has an entry
795  $qb = \Elgg\Database\Select::fromTable('entities', 'e');
796  $qb->select('COUNT(*) AS total')
797  ->where($qb->compare('type', '=', 'user', ELGG_VALUE_STRING));
798 
799  $result = $db->getDataRow($qb);
800  if (!empty($result) && $result->total > 0) {
801  $this->has_completed['admin'] = true;
802  } else {
803  return;
804  }
805  } catch (DatabaseException $ex) {
806  throw new InstallationException('Elgg can not connect to the database: ' . $ex->getMessage());
807  }
808 
809  return;
810  }
811 
820  protected function checkInstallCompletion($step) {
821  if ($step != 'complete') {
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  }
828 
837  protected function resumeInstall($step) {
838  // only do a resume from the first step
839  if ($step !== 'welcome') {
840  return null;
841  }
842 
843  if ($this->has_completed['database'] == false) {
844  return null;
845  }
846 
847  if ($this->has_completed['settings'] == false) {
848  return new \Elgg\Http\RedirectResponse("install.php?step=settings");
849  }
850 
851  if ($this->has_completed['admin'] == false) {
852  return new \Elgg\Http\RedirectResponse("install.php?step=admin");
853  }
854 
855  // everything appears to be set up
856  return new \Elgg\Http\RedirectResponse("install.php?step=complete");
857  }
858 
871  protected function finishBootstrapping($step) {
872 
873  $app = $this->getApp();
874 
875  $index_db = array_search('database', $this->getSteps());
876  $index_step = array_search($step, $this->getSteps());
877 
878  if ($index_step > $index_db) {
879  // once the database has been created, load rest of engine
880 
881  // dummy site needed to boot
882  $app->internal_services->config->site = new ElggSite();
883 
884  $app->bootCore();
885  }
886  }
887 
894  protected function loadSettingsFile() {
895  try {
896  $app = $this->getApp();
897 
898  $config = Config::fromFile(Config::resolvePath());
899  $app->internal_services->set('config', $config);
900 
901  // in case the DB instance is already captured in services, we re-inject its settings.
902  $app->internal_services->db->resetConnections(DbConfig::fromElggConfig($config));
903  } catch (\Exception $e) {
904  $msg = elgg_echo('InstallationException:CannotLoadSettings');
905  throw new InstallationException($msg, 0, $e);
906  }
907  }
908 
921  protected function makeFormSticky($formVars, $submissionVars) {
922  foreach ($submissionVars as $field => $value) {
923  $formVars[$field]['value'] = $value;
924  }
925 
926  return $formVars;
927  }
928 
929  /* Requirement checks support methods */
930 
938  protected function isInstallDirWritable(&$report) {
939  if (!is_writable(Paths::projectConfig())) {
940  $msg = elgg_echo('install:check:installdir', [Paths::PATH_TO_CONFIG]);
941  $report['settings'] = [
942  [
943  'severity' => 'error',
944  'message' => $msg,
945  ]
946  ];
947 
948  return false;
949  }
950 
951  return true;
952  }
953 
961  protected function checkSettingsFile(&$report = []) {
962  if (!is_file(Config::resolvePath())) {
963  return false;
964  }
965 
966  if (!is_readable(Config::resolvePath())) {
967  $report['settings'] = [
968  [
969  'severity' => 'error',
970  'message' => elgg_echo('install:check:readsettings'),
971  ]
972  ];
973  }
974 
975  return true;
976  }
977 
985  protected function checkPHP(&$report) {
986  $phpReport = [];
987 
988  if (version_compare(PHP_VERSION, self::PHP_MINIMAL_VERSION, '<')) {
989  $phpReport[] = [
990  'severity' => 'error',
991  'message' => elgg_echo('install:check:php:version', [self::PHP_MINIMAL_VERSION, PHP_VERSION])
992  ];
993  }
994 
995  $this->checkPhpExtensions($phpReport);
996 
997  $this->checkPhpDirectives($phpReport);
998 
999  if (count($phpReport) == 0) {
1000  $phpReport[] = [
1001  'severity' => 'success',
1002  'message' => elgg_echo('install:check:php:success')
1003  ];
1004  }
1005 
1006  $report['php'] = $phpReport;
1007  }
1008 
1016  protected function checkPhpExtensions(&$phpReport) {
1017  $extensions = get_loaded_extensions();
1019  'pdo_mysql',
1020  'json',
1021  'xml',
1022  'gd',
1023  ];
1024  foreach ($requiredExtensions as $extension) {
1025  if (!in_array($extension, $extensions)) {
1026  $phpReport[] = [
1027  'severity' => 'error',
1028  'message' => elgg_echo('install:check:php:extension', [$extension])
1029  ];
1030  }
1031  }
1032 
1034  'mbstring',
1035  ];
1036  foreach ($recommendedExtensions as $extension) {
1037  if (!in_array($extension, $extensions)) {
1038  $phpReport[] = [
1039  'severity' => 'warning',
1040  'message' => elgg_echo('install:check:php:extension:recommend', [$extension])
1041  ];
1042  }
1043  }
1044  }
1045 
1053  protected function checkPhpDirectives(&$phpReport) {
1054  if (ini_get('open_basedir')) {
1055  $phpReport[] = [
1056  'severity' => 'warning',
1057  'message' => elgg_echo("install:check:php:open_basedir")
1058  ];
1059  }
1060 
1061  if (ini_get('safe_mode')) {
1062  $phpReport[] = [
1063  'severity' => 'warning',
1064  'message' => elgg_echo("install:check:php:safe_mode")
1065  ];
1066  }
1067 
1068  if (ini_get('arg_separator.output') !== '&') {
1069  $separator = htmlspecialchars(ini_get('arg_separator.output'));
1070  $msg = elgg_echo("install:check:php:arg_separator", [$separator]);
1071  $phpReport[] = [
1072  'severity' => 'error',
1073  'message' => $msg,
1074  ];
1075  }
1076 
1077  if (ini_get('register_globals')) {
1078  $phpReport[] = [
1079  'severity' => 'error',
1080  'message' => elgg_echo("install:check:php:register_globals")
1081  ];
1082  }
1083 
1084  if (ini_get('session.auto_start')) {
1085  $phpReport[] = [
1086  'severity' => 'error',
1087  'message' => elgg_echo("install:check:php:session.auto_start")
1088  ];
1089  }
1090  }
1091 
1099  protected function checkRewriteRules(&$report) {
1100  $app = $this->getApp();
1101 
1102  $tester = new ElggRewriteTester();
1103  $url = $app->internal_services->config->wwwroot;
1104  $url .= Request::REWRITE_TEST_TOKEN . '?' . http_build_query([
1105  Request::REWRITE_TEST_TOKEN => '1',
1106  ]);
1107  $report['rewrite'] = [$tester->run($url)];
1108  }
1109 
1118  protected function countNumConditions($report, $condition) {
1119  $count = 0;
1120  foreach ($report as $checks) {
1121  foreach ($checks as $check) {
1122  if ($check['severity'] === $condition) {
1123  $count++;
1124  }
1125  }
1126  }
1127 
1128  return $count;
1129  }
1130 
1131 
1144  protected function validateDatabaseVars($submissionVars, $formVars) {
1145 
1146  $app = $this->getApp();
1147 
1148  foreach ($formVars as $field => $info) {
1149  if ($info['required'] == true && !$submissionVars[$field]) {
1150  $name = elgg_echo("install:database:label:$field");
1151  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:requiredfield', [$name]));
1152 
1153  return false;
1154  }
1155  }
1156 
1157  if (!empty($submissionVars['wwwroot']) && !_elgg_sane_validate_url($submissionVars['wwwroot'])) {
1158  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:wwwroot', [$submissionVars['wwwroot']]));
1159 
1160  return false;
1161  }
1162 
1163  // check that data root is absolute path
1164  if (stripos(PHP_OS, 'win') === 0) {
1165  if (strpos($submissionVars['dataroot'], ':') !== 1) {
1166  $msg = elgg_echo('install:error:relative_path', [$submissionVars['dataroot']]);
1167  $app->internal_services->system_messages->addErrorMessage($msg);
1168 
1169  return false;
1170  }
1171  } else {
1172  if (strpos($submissionVars['dataroot'], '/') !== 0) {
1173  $msg = elgg_echo('install:error:relative_path', [$submissionVars['dataroot']]);
1174  $app->internal_services->system_messages->addErrorMessage($msg);
1175 
1176  return false;
1177  }
1178  }
1179 
1180  // check that data root exists
1181  if (!is_dir($submissionVars['dataroot'])) {
1182  $msg = elgg_echo('install:error:datadirectoryexists', [$submissionVars['dataroot']]);
1183  $app->internal_services->system_messages->addErrorMessage($msg);
1184 
1185  return false;
1186  }
1187 
1188  // check that data root is writable
1189  if (!is_writable($submissionVars['dataroot'])) {
1190  $msg = elgg_echo('install:error:writedatadirectory', [$submissionVars['dataroot']]);
1191  $app->internal_services->system_messages->addErrorMessage($msg);
1192 
1193  return false;
1194  }
1195 
1196  if (!$app->internal_services->config->data_dir_override) {
1197  // check that data root is not subdirectory of Elgg root
1198  if (stripos($submissionVars['dataroot'], $app->internal_services->config->path) === 0) {
1199  $msg = elgg_echo('install:error:locationdatadirectory', [$submissionVars['dataroot']]);
1200  $app->internal_services->system_messages->addErrorMessage($msg);
1201 
1202  return false;
1203  }
1204  }
1205 
1206  // according to postgres documentation: SQL identifiers and key words must
1207  // begin with a letter (a-z, but also letters with diacritical marks and
1208  // non-Latin letters) or an underscore (_). Subsequent characters in an
1209  // identifier or key word can be letters, underscores, digits (0-9), or dollar signs ($).
1210  // Refs #4994
1211  if (!empty($submissionVars['dbprefix']) && !preg_match("/^[a-zA-Z_][\w]*$/", $submissionVars['dbprefix'])) {
1212  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:database_prefix'));
1213 
1214  return false;
1215  }
1216 
1217  return $this->checkDatabaseSettings(
1218  $submissionVars['dbuser'],
1219  $submissionVars['dbpassword'],
1220  $submissionVars['dbname'],
1221  $submissionVars['dbhost'],
1222  $submissionVars['dbport']
1223  );
1224  }
1225 
1237  protected function checkDatabaseSettings($user, $password, $dbname, $host, $port) {
1238  $app = $this->getApp();
1239 
1240  $config = new DbConfig((object) [
1241  'dbhost' => $host,
1242  'dbport' => $port,
1243  'dbuser' => $user,
1244  'dbpass' => $password,
1245  'dbname' => $dbname,
1246  'dbencoding' => 'utf8mb4',
1247  ]);
1248  $db = new Database($config, $app->internal_services->queryCache);
1249 
1250  try {
1251  $db->getConnection('read')->executeQuery('SELECT 1');
1252  } catch (DatabaseException $e) {
1253  if (0 === strpos($e->getMessage(), "Elgg couldn't connect")) {
1254  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:databasesettings'));
1255  } else {
1256  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:nodatabase', [$dbname]));
1257  }
1258 
1259  return false;
1260  }
1261 
1262  // check MySQL version
1263  $version = $db->getServerVersion();
1264  $min_version = $db->isMariaDB() ? self::MARIADB_MINIMAL_VERSION : self::MYSQL_MINIMAL_VERSION;
1265 
1266  if (version_compare($version, $min_version, '<')) {
1267  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:database_version', [$min_version, $version]));
1268 
1269  return false;
1270  }
1271 
1272  return true;
1273  }
1274 
1282  protected function createSettingsFile($params) {
1283  $app = $this->getApp();
1284 
1285  $template = Application::elggDir()->getContents("elgg-config/settings.example.php");
1286  if (!$template) {
1287  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:readsettingsphp'));
1288 
1289  return false;
1290  }
1291 
1292  foreach ($params as $k => $v) {
1293  // do some sanitization
1294  switch ($k) {
1295  case 'dataroot':
1296  $v = Paths::sanitize($v);
1297  break;
1298  case 'dbpassword':
1299  $v = addslashes($v);
1300  break;
1301  }
1302 
1303  $template = str_replace("{{" . $k . "}}", $v, $template);
1304  }
1305 
1306  $result = file_put_contents(Config::resolvePath(), $template);
1307  if ($result === false) {
1308  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:writesettingphp'));
1309 
1310  return false;
1311  }
1312 
1313  $config = (object) [
1314  'dbhost' => elgg_extract('dbhost', $params, 'localhost'),
1315  'dbport' => elgg_extract('dbport', $params, 3306),
1316  'dbuser' => elgg_extract('dbuser', $params),
1317  'dbpass' => elgg_extract('dbpassword', $params),
1318  'dbname' => elgg_extract('dbname', $params),
1319  'dbencoding' => elgg_extract('dbencoding', $params, 'utf8mb4'),
1320  'dbprefix' => elgg_extract('dbprefix', $params, 'elgg_'),
1321  ];
1322 
1323  $dbConfig = new DbConfig($config);
1324  $this->getApp()->internal_services->set('dbConfig', $dbConfig);
1325  $this->getApp()->internal_services->db->resetConnections($dbConfig);
1326 
1327  return true;
1328  }
1329 
1335  protected function connectToDatabase() {
1336  try {
1337  $app = $this->getApp();
1338  $app->internal_services->db->setupConnections();
1339  } catch (DatabaseException $e) {
1340  $app->internal_services->system_messages->addErrorMessage($e->getMessage());
1341 
1342  return false;
1343  }
1344 
1345  return true;
1346  }
1347 
1353  protected function installDatabase() {
1354  try {
1355  return $this->getApp()->migrate();
1356  } catch (\Exception $e) {
1357  return false;
1358  }
1359  }
1360 
1373  protected function createDataDirectory(&$submissionVars, $formVars) {
1374  // did the user have option of Elgg creating the data directory
1375  if ($formVars['dataroot']['type'] != 'combo') {
1376  return true;
1377  }
1378 
1379  // did the user select the option
1380  if ($submissionVars['dataroot'] != 'dataroot-checkbox') {
1381  return true;
1382  }
1383 
1384  $dir = \Elgg\Project\Paths::sanitize($submissionVars['path']) . 'data';
1385  if (file_exists($dir) || mkdir($dir, 0755)) {
1386  $submissionVars['dataroot'] = $dir;
1387  if (!file_exists("$dir/.htaccess")) {
1388  $htaccess = "Order Deny,Allow\nDeny from All\n";
1389  if (!file_put_contents("$dir/.htaccess", $htaccess)) {
1390  return false;
1391  }
1392  }
1393 
1394  return true;
1395  }
1396 
1397  return false;
1398  }
1399 
1408  protected function validateSettingsVars($submissionVars, $formVars) {
1409  $app = $this->getApp();
1410 
1411  foreach ($formVars as $field => $info) {
1412  $submissionVars[$field] = trim($submissionVars[$field]);
1413  if ($info['required'] == true && $submissionVars[$field] === '') {
1414  $name = elgg_echo("install:settings:label:$field");
1415  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:requiredfield', [$name]));
1416 
1417  return false;
1418  }
1419  }
1420 
1421  // check that email address is email address
1422  if ($submissionVars['siteemail'] && !is_email_address($submissionVars['siteemail'])) {
1423  $msg = elgg_echo('install:error:emailaddress', [$submissionVars['siteemail']]);
1424  $app->internal_services->system_messages->addErrorMessage($msg);
1425 
1426  return false;
1427  }
1428 
1429  return true;
1430  }
1431 
1439  protected function saveSiteSettings($submissionVars) {
1440  $app = $this->getApp();
1441 
1443 
1444  if (!$site->guid) {
1445  $site = new ElggSite();
1446  $site->name = strip_tags($submissionVars['sitename']);
1447  $site->access_id = ACCESS_PUBLIC;
1448  $site->email = $submissionVars['siteemail'];
1449  $site->save();
1450  }
1451 
1452  if ($site->guid !== 1) {
1453  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:createsite'));
1454 
1455  return false;
1456  }
1457 
1458  $app->internal_services->config->site = $site;
1459 
1460  $sets = [
1461  'installed' => time(),
1462  'simplecache_enabled' => 1,
1463  'system_cache_enabled' => 1,
1464  'simplecache_minify_js' => true,
1465  'simplecache_minify_css' => true,
1466  'lastcache' => time(),
1467  'language' => 'en',
1468  'default_access' => $submissionVars['siteaccess'],
1469  'allow_registration' => false,
1470  'require_admin_validation' => false,
1471  'walled_garden' => false,
1472  'allow_user_default_access' => '',
1473  'default_limit' => 10,
1474  ];
1475 
1476  foreach ($sets as $key => $value) {
1478  }
1479 
1480  try {
1481  _elgg_services()->plugins->generateEntities();
1482 
1483  $app->internal_services->reset('plugins');
1484 
1485  $plugins = $app->internal_services->plugins->find('any');
1486 
1487  foreach ($plugins as $plugin) {
1488  $plugin_config = $plugin->getStaticConfig('plugin', []);
1489  if (!elgg_extract('activate_on_install', $plugin_config, false)) {
1490  continue;
1491  }
1492 
1493  try {
1494  $plugin->activate();
1495  } catch (PluginException $e) {
1496  // do nothing
1497  }
1498  }
1499 
1500  // Wo don't need to run upgrades on new installations
1501  $app->internal_services->events->unregisterHandler('create', 'object', \Elgg\Upgrade\CreateAdminNoticeHandler::class);
1502  $upgrades = $app->internal_services->upgradeLocator->locate();
1503  foreach ($upgrades as $upgrade) {
1504  $upgrade->setCompleted();
1505  }
1506  } catch (Exception $e) {
1507  $app->internal_services->logger->log(\Psr\Log\LogLevel::ERROR, $e);
1508  }
1509 
1510  return true;
1511  }
1512 
1521  protected function validateAdminVars($submissionVars, $formVars) {
1522 
1523  $app = $this->getApp();
1524 
1525  foreach ($formVars as $field => $info) {
1526  if ($info['required'] == true && !$submissionVars[$field]) {
1527  $name = elgg_echo("install:admin:label:$field");
1528  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:requiredfield', [$name]));
1529 
1530  return false;
1531  }
1532  }
1533 
1534  if ($submissionVars['password1'] !== $submissionVars['password2']) {
1535  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:admin:password:mismatch'));
1536 
1537  return false;
1538  }
1539 
1540  if (trim($submissionVars['password1']) == "") {
1541  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:admin:password:empty'));
1542 
1543  return false;
1544  }
1545 
1546  $minLength = $app->internal_services->configTable->get('min_password_length');
1547  if (strlen($submissionVars['password1']) < $minLength) {
1548  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:admin:password:tooshort'));
1549 
1550  return false;
1551  }
1552 
1553  // check that email address is email address
1554  if ($submissionVars['email'] && !is_email_address($submissionVars['email'])) {
1555  $msg = elgg_echo('install:error:emailaddress', [$submissionVars['email']]);
1556  $app->internal_services->system_messages->addErrorMessage($msg);
1557 
1558  return false;
1559  }
1560 
1561  return true;
1562  }
1563 
1572  protected function createAdminAccount($submissionVars, $login = false) {
1573  $app = $this->getApp();
1574 
1575  try {
1576  $guid = register_user(
1577  $submissionVars['username'],
1578  $submissionVars['password1'],
1579  $submissionVars['displayname'],
1580  $submissionVars['email']
1581  );
1582  } catch (RegistrationException $e) {
1583  $app->internal_services->system_messages->addErrorMessage($e->getMessage());
1584 
1585  return false;
1586  }
1587 
1588  if ($guid === false) {
1589  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:admin:cannot_create'));
1590 
1591  return false;
1592  }
1593 
1594  $user = get_entity($guid);
1595 
1596  if (!$user instanceof ElggUser) {
1597  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:loadadmin'));
1598 
1599  return false;
1600  }
1601 
1602  $ia = $app->internal_services->session->setIgnoreAccess(true);
1603  if (!$user->makeAdmin()) {
1604  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:adminaccess'));
1605  }
1606  $app->internal_services->session->setIgnoreAccess($ia);
1607 
1608  // add validation data to satisfy user validation plugins
1609  $user->validated = true;
1610  $user->validated_method = 'admin_user';
1611 
1612  if (!$login) {
1613  return true;
1614  }
1615 
1616  try {
1617  login($user);
1618  } catch (LoginException $ex) {
1619  $app->internal_services->system_messages->addErrorMessage(elgg_echo('install:error:adminlogin'));
1620 
1621  return false;
1622  }
1623 
1624  return true;
1625  }
1626 }
$current_step
Install sidebar.
Definition: sidebar.php:9
$plugin
foreach(array_keys($combine_languages) as $language) $translations
if(!$user||!$user->canDelete()) $name
Definition: delete.php:22
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:152
Elgg RewriteTester.
runAdmin($submissionVars)
Admin account controller.
checkDatabaseSettings($user, $password, $dbname, $host, $port)
Confirm the settings for the database.
_elgg_sane_validate_url($url)
Use a "fixed" filter_var() with FILTER_VALIDATE_URL that handles multi-byte chars.
Definition: output.php:426
Generic parent class for login exceptions.
checkPhpDirectives(&$phpReport)
Check PHP parameters.
$defaults
runWelcome($vars)
Step controllers.
$title
Definition: generic.php:50
$version
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
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:288
$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
Updates the basic settings for the primary site object.
$upgrades
Lists pending upgrades.
Definition: upgrades.php:11
is_email_address($address)
Validates an email address.
Definition: input.php:100
$path
Definition: details.php:68
$tester
getCurrentStep()
Returns current step.
continueToNextStep($currentStep)
Forwards the browser to the next step.
$separator
Definition: tags.php:56
register_user($username, $password, $name, $email, $allow_multiple_emails=false, $subtype=null, array $params=[])
Registers a user, returning false if the username already exists.
Definition: users.php:151
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:134
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:61
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:686
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:80
$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_view_url(string $href, string $text=null, array $options=[])
Helper function for outputting urls.
Definition: views.php:2039
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
login(\ElggUser $user, $persistent=false)
Logs in a specified .
Definition: sessions.php:180
$min_version
foreach($plugin_guids as $guid) if(empty($deactivated_plugins)) $url
Definition: deactivate.php:39
static sanitize($path, $append_slash=true)
Sanitise 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:777
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:205
$qb
Definition: queue.php:11
var elgg
Definition: elgglib.js:4
$table
Definition: cron.php:56
$guid
Reset an ElggUpgrade.
Definition: reset.php:6
const PHP_MINIMAL_VERSION
$extension
Definition: default.php:25
get_entity($guid)
Loads and returns an entity object from a guid.
Definition: entities.php:69
validateAdminVars($submissionVars, $formVars)
Validate account form variables.