Merge pull request #14039 from annando/widget-features
[friendica.git/.git] / src / Module / Install.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2010-2024, the Friendica project
4  *
5  * @license GNU AGPL version 3 or any later version
6  *
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.
11  *
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.
16  *
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/>.
19  *
20  */
21
22 namespace Friendica\Module;
23
24 use Friendica\App;
25 use Friendica\BaseModule;
26 use Friendica\Core;
27 use Friendica\Core\Config\ValueObject\Cache;
28 use Friendica\Core\L10n;
29 use Friendica\Core\Renderer;
30 use Friendica\Core\Theme;
31 use Friendica\DI;
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;
38
39 class Install extends BaseModule
40 {
41         /**
42          * Step one - System check
43          */
44         const SYSTEM_CHECK = 1;
45         /**
46          * Step two - Base information
47          */
48         const BASE_CONFIG = 2;
49         /**
50          * Step three - Database configuration
51          */
52         const DATABASE_CONFIG = 3;
53         /**
54          * Step four - Adapt site settings
55          */
56         const SITE_SETTINGS = 4;
57         /**
58          * Step five - All steps finished
59          */
60         const FINISHED = 5;
61
62         /**
63          * @var int The current step of the wizard
64          */
65         private $currentWizardStep;
66
67         /**
68          * @var Core\Installer The installer
69          */
70         private $installer;
71
72         /** @var App */
73         protected $app;
74         /** @var App\Mode */
75         protected $mode;
76
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 = [])
78         {
79                 parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
80
81                 $this->app       = $app;
82                 $this->mode      = $mode;
83                 $this->installer = $installer;
84
85                 if (!$this->mode->isInstall()) {
86                         throw new HTTPException\ForbiddenException();
87                 }
88
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();
94                 }
95
96                 // get basic installation information and save them to the config cache
97                 $configCache = $this->app->getConfigCache();
98                 $this->installer->setUpCache($configCache, $basePath->getPath());
99
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';
103
104                 $this->currentWizardStep = ($_POST['pass'] ?? '') ?: self::SYSTEM_CHECK;
105         }
106
107         protected function post(array $request = [])
108         {
109                 $configCache = $this->app->getConfigCache();
110
111                 switch ($this->currentWizardStep) {
112                         case self::SYSTEM_CHECK:
113                         case self::BASE_CONFIG:
114                                 $this->checkSetting($configCache, $_POST, 'config', 'php_path');
115                                 break;
116
117                         case self::DATABASE_CONFIG:
118                                 $this->checkSetting($configCache, $_POST, 'config', 'php_path');
119
120                                 $this->checkSetting($configCache, $_POST, 'system', 'basepath');
121                                 $this->checkSetting($configCache, $_POST, 'system', 'url');
122                                 break;
123
124                         case self::SITE_SETTINGS:
125                                 $this->checkSetting($configCache, $_POST, 'config', 'php_path');
126
127                                 $this->checkSetting($configCache, $_POST, 'system', 'basepath');
128                                 $this->checkSetting($configCache, $_POST, 'system', 'url');
129
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', '');
134
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;
138                                 }
139
140                                 break;
141
142                         case self::FINISHED:
143                                 $this->checkSetting($configCache, $_POST, 'config', 'php_path');
144
145                                 $this->checkSetting($configCache, $_POST, 'system', 'basepath');
146                                 $this->checkSetting($configCache, $_POST, 'system', 'url');
147
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', '');
152
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', '');
156
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;
160                                         return;
161                                 }
162
163                                 if (!$this->installer->createConfig($configCache)) {
164                                         return;
165                                 }
166
167                                 $this->installer->installDatabase();
168
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);
176                                 }
177                                 Theme::setAllowedList($allowed_themes);
178
179                                 break;
180                 }
181         }
182
183         protected function content(array $request = []): string
184         {
185                 $configCache = $this->app->getConfigCache();
186
187                 $output = '';
188
189                 $install_title = $this->t('Friendica Communications Server - Setup');
190
191                 switch ($this->currentWizardStep) {
192                         case self::SYSTEM_CHECK:
193                                 $php_path = $configCache->get('config', 'php_path');
194
195                                 $status = $this->installer->checkEnvironment($this->baseUrl, $php_path);
196
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,
211                                 ]);
212                                 break;
213
214                         case self::BASE_CONFIG:
215                                 $baseUrl = $configCache->get('system', 'url') ?
216                                         new Uri($configCache->get('system', 'url')) :
217                                         $this->baseUrl;
218
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'),
230                                                 (string)$baseUrl,
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'),
235                                 ]);
236                                 break;
237
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'),
254                                                 '',
255                                                 $this->t('Required')],
256                                         '$dbuser'     => ['database-username',
257                                                 $this->t('Database Login Name'),
258                                                 $configCache->get('database', 'username'),
259                                                 '',
260                                                 $this->t('Required'),
261                                                 'autofocus'],
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'),
270                                                 '',
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')
275                                 ]);
276                                 break;
277
278                         case self::SITE_SETTINGS:
279                                 /* Installed langs */
280                                 $lang_choices = $this->l10n->getAvailableLanguages();
281
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'),
302                                                 ''),
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.'),
307                                                 $lang_choices],
308                                         '$php_path'   => $configCache->get('config', 'php_path'),
309                                         '$submit'     => $this->t('Submit')
310                                 ]);
311                                 break;
312
313                         case self::FINISHED:
314                                 $db_return_text = "";
315
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;
320                                 }
321
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(),
330                                 ]);
331
332                                 break;
333                 }
334
335                 return $output;
336         }
337
338         /**
339          * Creates the text for the next steps
340          *
341          * @return string The text for the next steps
342          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
343          */
344         private function whatNext(): string
345         {
346                 $baseurl = (string)$this->baseUrl;
347                 return
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".')
351                         . "</p><p>"
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)
353                         . "</p>";
354         }
355
356         /**
357          * Checks the $_POST settings and updates the config Cache for it
358          *
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
364          * @return void
365          */
366         private function checkSetting(Cache $configCache, array $post, string $cat, string $key, ?string $default = null)
367         {
368                 $value = null;
369
370                 if (isset($post[sprintf('%s-%s', $cat, $key)])) {
371                         $value = trim($post[sprintf('%s-%s', $cat, $key)]);
372                 }
373
374                 if (isset($value)) {
375                         $configCache->set($cat, $key, $value, Cache::SOURCE_ENV);
376                         return;
377                 }
378
379                 if (isset($default)) {
380                         $configCache->set($cat, $key, $default, Cache::SOURCE_ENV);
381                 }
382         }
383 }