3 * @copyright Copyright (C) 2010-2023, the Friendica project
5 * @license GNU AGPL version 3 or any later version
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 namespace Friendica\Module;
25 use Friendica\BaseModule;
27 use Friendica\Core\Config\ValueObject\Cache;
28 use Friendica\Core\L10n;
29 use Friendica\Core\Renderer;
30 use Friendica\Core\Theme;
32 use Friendica\Network\HTTPException;
33 use Friendica\Util\BasePath;
34 use Friendica\Util\Profiler;
35 use Friendica\Util\Temporal;
36 use Psr\Log\LoggerInterface;
37 use GuzzleHttp\Psr7\Uri;
39 class Install extends BaseModule
42 * Step one - System check
44 const SYSTEM_CHECK = 1;
46 * Step two - Base information
48 const BASE_CONFIG = 2;
50 * Step three - Database configuration
52 const DATABASE_CONFIG = 3;
54 * Step four - Adapt site settings
56 const SITE_SETTINGS = 4;
58 * Step five - All steps finished
63 * @var int The current step of the wizard
65 private $currentWizardStep;
68 * @var Core\Installer The installer
77 public function __construct(App $app, BasePath $basePath, App\Mode $mode, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, Core\Installer $installer, array $server, array $parameters = [])
79 parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
83 $this->installer = $installer;
85 if (!$this->mode->isInstall()) {
86 throw new HTTPException\ForbiddenException();
89 // route: install/testrwrite
90 // $baseurl/install/testrwrite to test if rewrite in .htaccess is working
91 if ($args->get(1, '') == 'testrewrite') {
92 // Status Code 204 means that it worked without content
93 throw new HTTPException\NoContentException();
96 // get basic installation information and save them to the config cache
97 $configCache = $this->app->getConfigCache();
98 $this->installer->setUpCache($configCache, $basePath->getPath());
100 // We overwrite current theme css, because during install we may not have a working mod_rewrite
101 // so we may not have a css at all. Here we set a static css file for the install procedure pages
102 Renderer::$theme['stylesheet'] = $this->baseUrl . '/view/install/style.css';
104 $this->currentWizardStep = ($_POST['pass'] ?? '') ?: self::SYSTEM_CHECK;
107 protected function post(array $request = [])
109 $configCache = $this->app->getConfigCache();
111 switch ($this->currentWizardStep) {
112 case self::SYSTEM_CHECK:
113 case self::BASE_CONFIG:
114 $this->checkSetting($configCache, $_POST, 'config', 'php_path');
117 case self::DATABASE_CONFIG:
118 $this->checkSetting($configCache, $_POST, 'config', 'php_path');
120 $this->checkSetting($configCache, $_POST, 'system', 'basepath');
121 $this->checkSetting($configCache, $_POST, 'system', 'url');
124 case self::SITE_SETTINGS:
125 $this->checkSetting($configCache, $_POST, 'config', 'php_path');
127 $this->checkSetting($configCache, $_POST, 'system', 'basepath');
128 $this->checkSetting($configCache, $_POST, 'system', 'url');
130 $this->checkSetting($configCache, $_POST, 'database', 'hostname', Core\Installer::DEFAULT_HOST);
131 $this->checkSetting($configCache, $_POST, 'database', 'username', '');
132 $this->checkSetting($configCache, $_POST, 'database', 'password', '');
133 $this->checkSetting($configCache, $_POST, 'database', 'database', '');
135 // If we cannot connect to the database, return to the previous step
136 if (!$this->installer->checkDB(DI::dba())) {
137 $this->currentWizardStep = self::DATABASE_CONFIG;
143 $this->checkSetting($configCache, $_POST, 'config', 'php_path');
145 $this->checkSetting($configCache, $_POST, 'system', 'basepath');
146 $this->checkSetting($configCache, $_POST, 'system', 'url');
148 $this->checkSetting($configCache, $_POST, 'database', 'hostname', Core\Installer::DEFAULT_HOST);
149 $this->checkSetting($configCache, $_POST, 'database', 'username', '');
150 $this->checkSetting($configCache, $_POST, 'database', 'password', '');
151 $this->checkSetting($configCache, $_POST, 'database', 'database', '');
153 $this->checkSetting($configCache, $_POST, 'system', 'default_timezone', Core\Installer::DEFAULT_TZ);
154 $this->checkSetting($configCache, $_POST, 'system', 'language', Core\Installer::DEFAULT_LANG);
155 $this->checkSetting($configCache, $_POST, 'config', 'admin_email', '');
157 // If we cannot connect to the database, return to the Database config wizard
158 if (!$this->installer->checkDB(DI::dba())) {
159 $this->currentWizardStep = self::DATABASE_CONFIG;
163 if (!$this->installer->createConfig($configCache)) {
167 $this->installer->installDatabase();
169 // install allowed themes to register theme hooks
170 // this is same as "Reload active theme" in /admin/themes
171 $allowed_themes = Theme::getAllowedList();
172 $allowed_themes = array_unique($allowed_themes);
173 foreach ($allowed_themes as $theme) {
174 Theme::uninstall($theme);
175 Theme::install($theme);
177 Theme::setAllowedList($allowed_themes);
183 protected function content(array $request = []): string
185 $configCache = $this->app->getConfigCache();
189 $install_title = $this->t('Friendica Communications Server - Setup');
191 switch ($this->currentWizardStep) {
192 case self::SYSTEM_CHECK:
193 $php_path = $configCache->get('config', 'php_path');
195 $status = $this->installer->checkEnvironment($this->baseUrl, $php_path);
197 $tpl = Renderer::getMarkupTemplate('install/01_checks.tpl');
198 $output .= Renderer::replaceMacros($tpl, [
199 '$title' => $install_title,
200 '$pass' => $this->t('System check'),
201 '$required' => $this->t('Required'),
202 '$requirement_not_satisfied' => $this->t('Requirement not satisfied'),
203 '$optional_requirement_not_satisfied' => $this->t('Optional requirement not satisfied'),
204 '$ok' => $this->t('OK'),
205 '$checks' => $this->installer->getChecks(),
206 '$passed' => $status,
207 '$see_install' => $this->t('Please see the file "doc/INSTALL.md".'),
208 '$next' => $this->t('Next'),
209 '$reload' => $this->t('Check again'),
210 '$php_path' => $php_path,
214 case self::BASE_CONFIG:
215 $baseUrl = $configCache->get('system', 'url') ?
216 new Uri($configCache->get('system', 'url')) :
219 $tpl = Renderer::getMarkupTemplate('install/02_base_config.tpl');
220 $output .= Renderer::replaceMacros($tpl, [
221 '$title' => $install_title,
222 '$pass' => $this->t('Base settings'),
223 '$basepath' => ['system-basepath',
224 $this->t("Base path to installation"),
225 $configCache->get('system', 'basepath'),
226 $this->t("If the system cannot detect the correct path to your installation, enter the correct path here. This setting should only be set if you are using a restricted system and symbolic links to your webroot."),
227 $this->t('Required')],
228 '$system_url' => ['system-url',
229 $this->t('The Friendica system URL'),
231 $this->t("Overwrite this field in case the system URL determination isn't right, otherwise leave it as is."),
232 $this->t('Required')],
233 '$php_path' => $configCache->get('config', 'php_path'),
234 '$submit' => $this->t('Submit'),
238 case self::DATABASE_CONFIG:
239 $tpl = Renderer::getMarkupTemplate('install/03_database_config.tpl');
240 $output .= Renderer::replaceMacros($tpl, [
241 '$title' => $install_title,
242 '$pass' => $this->t('Database connection'),
243 '$info_01' => $this->t('In order to install Friendica we need to know how to connect to your database.'),
244 '$info_02' => $this->t('Please contact your hosting provider or site administrator if you have questions about these settings.'),
245 '$info_03' => $this->t('The database you specify below should already exist. If it does not, please create it before continuing.'),
246 '$required' => $this->t('Required'),
247 '$requirement_not_satisfied' => $this->t('Requirement not satisfied'),
248 '$checks' => $this->installer->getChecks(),
249 '$basepath' => $configCache->get('system', 'basepath'),
250 '$system_url' => $configCache->get('system', 'url'),
251 '$dbhost' => ['database-hostname',
252 $this->t('Database Server Name'),
253 $configCache->get('database', 'hostname'),
255 $this->t('Required')],
256 '$dbuser' => ['database-username',
257 $this->t('Database Login Name'),
258 $configCache->get('database', 'username'),
260 $this->t('Required'),
262 '$dbpass' => ['database-password',
263 $this->t('Database Login Password'),
264 $configCache->get('database', 'password'),
265 $this->t("For security reasons the password must not be empty"),
266 $this->t('Required')],
267 '$dbdata' => ['database-database',
268 $this->t('Database Name'),
269 $configCache->get('database', 'database'),
271 $this->t('Required')],
272 '$lbl_10' => $this->t('Please select a default timezone for your website'),
273 '$php_path' => $configCache->get('config', 'php_path'),
274 '$submit' => $this->t('Submit')
278 case self::SITE_SETTINGS:
279 /* Installed langs */
280 $lang_choices = $this->l10n->getAvailableLanguages();
282 $tpl = Renderer::getMarkupTemplate('install/04_site_settings.tpl');
283 $output .= Renderer::replaceMacros($tpl, [
284 '$title' => $install_title,
285 '$required' => $this->t('Required'),
286 '$checks' => $this->installer->getChecks(),
287 '$pass' => $this->t('Site settings'),
288 '$basepath' => $configCache->get('system', 'basepath'),
289 '$system_url' => $configCache->get('system', 'url'),
290 '$dbhost' => $configCache->get('database', 'hostname'),
291 '$dbuser' => $configCache->get('database', 'username'),
292 '$dbpass' => $configCache->get('database', 'password'),
293 '$dbdata' => $configCache->get('database', 'database'),
294 '$adminmail' => ['config-admin_email',
295 $this->t('Site administrator email address'),
296 $configCache->get('config', 'admin_email'),
297 $this->t('Your account email address must match this in order to use the web admin panel.'),
298 $this->t('Required'), 'autofocus', 'email'],
299 '$timezone' => Temporal::getTimezoneField('system-default_timezone',
300 $this->t('Please select a default timezone for your website'),
301 $configCache->get('system', 'default_timezone'),
303 '$language' => ['system-language',
304 $this->t('System Language:'),
305 $configCache->get('system', 'language'),
306 $this->t('Set the default language for your Friendica installation interface and to send emails.'),
308 '$php_path' => $configCache->get('config', 'php_path'),
309 '$submit' => $this->t('Submit')
314 $db_return_text = "";
316 if (count($this->installer->getChecks()) == 0) {
317 $txt = '<p style="font-size: 130%;">';
318 $txt .= $this->t('Your Friendica site database has been installed.') . '<br />';
319 $db_return_text .= $txt;
322 $tpl = Renderer::getMarkupTemplate('install/05_finished.tpl');
323 $output .= Renderer::replaceMacros($tpl, [
324 '$title' => $install_title,
325 '$required' => $this->t('Required'),
326 '$requirement_not_satisfied' => $this->t('Requirement not satisfied'),
327 '$checks' => $this->installer->getChecks(),
328 '$pass' => $this->t('Installation finished'),
329 '$text' => $db_return_text . $this->whatNext(),
339 * Creates the text for the next steps
341 * @return string The text for the next steps
342 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
344 private function whatNext(): string
346 $baseurl = (string)$this->baseUrl;
348 $this->t('<h1>What next</h1>')
349 . "<p>" . $this->t('IMPORTANT: You will need to [manually] setup a scheduled task for the worker.')
350 . $this->t('Please see the file "doc/INSTALL.md".')
352 . $this->t('Go to your new Friendica node <a href="%s/register">registration page</a> and register as new user. Remember to use the same email you have entered as administrator email. This will allow you to enter the site admin panel.', $baseurl)
357 * Checks the $_POST settings and updates the config Cache for it
359 * @param \Friendica\Core\Config\ValueObject\Cache $configCache The current config cache
360 * @param array $post The $_POST data
361 * @param string $cat The category of the setting
362 * @param string $key The key of the setting
363 * @param null|string $default The default value
366 private function checkSetting(Cache $configCache, array $post, string $cat, string $key, ?string $default = null)
370 if (isset($post[sprintf('%s-%s', $cat, $key)])) {
371 $value = trim($post[sprintf('%s-%s', $cat, $key)]);
375 $configCache->set($cat, $key, $value, Cache::SOURCE_ENV);
379 if (isset($default)) {
380 $configCache->set($cat, $key, $default, Cache::SOURCE_ENV);