Update copyright
[friendica.git/.git] / src / Module / Install.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2010-2021, 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\Cache;
28 use Friendica\Core\Renderer;
29 use Friendica\Core\Theme;
30 use Friendica\DI;
31 use Friendica\Network\HTTPException;
32 use Friendica\Util\BasePath;
33 use Friendica\Util\Strings;
34 use Friendica\Util\Temporal;
35
36 class Install extends BaseModule
37 {
38         /**
39          * Step one - System check
40          */
41         const SYSTEM_CHECK = 1;
42         /**
43          * Step two - Base information
44          */
45         const BASE_CONFIG = 2;
46         /**
47          * Step three - Database configuration
48          */
49         const DATABASE_CONFIG = 3;
50         /**
51          * Step four - Adapt site settings
52          */
53         const SITE_SETTINGS = 4;
54         /**
55          * Step five - All steps finished
56          */
57         const FINISHED = 5;
58
59         /**
60          * @var int The current step of the wizard
61          */
62         private static $currentWizardStep;
63
64         /**
65          * @var Core\Installer The installer
66          */
67         private static $installer;
68
69         public static function init(array $parameters = [])
70         {
71                 $a = DI::app();
72
73                 if (!DI::mode()->isInstall()) {
74                         throw new HTTPException\ForbiddenException();
75                 }
76
77                 // route: install/testrwrite
78                 // $baseurl/install/testrwrite to test if rewrite in .htaccess is working
79                 if (DI::args()->get(1, '') == 'testrewrite') {
80                         // Status Code 204 means that it worked without content
81                         throw new HTTPException\NoContentException();
82                 }
83
84                 self::$installer = new Core\Installer();
85
86                 // get basic installation information and save them to the config cache
87                 $configCache = $a->getConfigCache();
88                 $basePath = new BasePath($a->getBasePath());
89                 self::$installer->setUpCache($configCache, $basePath->getPath());
90
91                 // We overwrite current theme css, because during install we may not have a working mod_rewrite
92                 // so we may not have a css at all. Here we set a static css file for the install procedure pages
93                 Renderer::$theme['stylesheet'] = DI::baseUrl()->get() . '/view/install/style.css';
94
95                 self::$currentWizardStep = ($_POST['pass'] ?? '') ?: self::SYSTEM_CHECK;
96         }
97
98         public static function post(array $parameters = [])
99         {
100                 $a           = DI::app();
101                 $configCache = $a->getConfigCache();
102
103                 switch (self::$currentWizardStep) {
104                         case self::SYSTEM_CHECK:
105                         case self::BASE_CONFIG:
106                                 self::checkSetting($configCache, $_POST, 'config', 'php_path');
107                                 break;
108
109                         case self::DATABASE_CONFIG:
110                                 self::checkSetting($configCache, $_POST, 'config', 'php_path');
111
112                                 self::checkSetting($configCache, $_POST, 'config', 'hostname');
113                                 self::checkSetting($configCache, $_POST, 'system', 'ssl_policy');
114                                 self::checkSetting($configCache, $_POST, 'system', 'basepath');
115                                 self::checkSetting($configCache, $_POST, 'system', 'urlpath');
116                                 break;
117
118                         case self::SITE_SETTINGS:
119                                 self::checkSetting($configCache, $_POST, 'config', 'php_path');
120
121                                 self::checkSetting($configCache, $_POST, 'config', 'hostname');
122                                 self::checkSetting($configCache, $_POST, 'system', 'ssl_policy');
123                                 self::checkSetting($configCache, $_POST, 'system', 'basepath');
124                                 self::checkSetting($configCache, $_POST, 'system', 'urlpath');
125
126                                 self::checkSetting($configCache, $_POST, 'database', 'hostname', Core\Installer::DEFAULT_HOST);
127                                 self::checkSetting($configCache, $_POST, 'database', 'username', '');
128                                 self::checkSetting($configCache, $_POST, 'database', 'password', '');
129                                 self::checkSetting($configCache, $_POST, 'database', 'database', '');
130
131                                 // If we cannot connect to the database, return to the previous step
132                                 if (!self::$installer->checkDB(DI::dba())) {
133                                         self::$currentWizardStep = self::DATABASE_CONFIG;
134                                 }
135
136                                 break;
137
138                         case self::FINISHED:
139                                 self::checkSetting($configCache, $_POST, 'config', 'php_path');
140
141                                 self::checkSetting($configCache, $_POST, 'config', 'hostname');
142                                 self::checkSetting($configCache, $_POST, 'system', 'ssl_policy');
143                                 self::checkSetting($configCache, $_POST, 'system', 'basepath');
144                                 self::checkSetting($configCache, $_POST, 'system', 'urlpath');
145
146                                 self::checkSetting($configCache, $_POST, 'database', 'hostname', Core\Installer::DEFAULT_HOST);
147                                 self::checkSetting($configCache, $_POST, 'database', 'username', '');
148                                 self::checkSetting($configCache, $_POST, 'database', 'password', '');
149                                 self::checkSetting($configCache, $_POST, 'database', 'database', '');
150
151                                 self::checkSetting($configCache, $_POST, 'system', 'default_timezone', Core\Installer::DEFAULT_TZ);
152                                 self::checkSetting($configCache, $_POST, 'system', 'language', Core\Installer::DEFAULT_LANG);
153                                 self::checkSetting($configCache, $_POST, 'config', 'admin_email', '');
154
155                                 // If we cannot connect to the database, return to the Database config wizard
156                                 if (!self::$installer->checkDB(DI::dba())) {
157                                         self::$currentWizardStep = self::DATABASE_CONFIG;
158                                         return;
159                                 }
160
161                                 if (!self::$installer->createConfig($configCache)) {
162                                         return;
163                                 }
164
165                                 self::$installer->installDatabase($configCache->get('system', 'basepath'));
166                         
167                                 // install allowed themes to register theme hooks
168                                 // this is same as "Reload active theme" in /admin/themes
169                                 $allowed_themes = Theme::getAllowedList();
170                                 $allowed_themes = array_unique($allowed_themes);
171                                 foreach ($allowed_themes as $theme) {
172                                         Theme::uninstall($theme);
173                                         Theme::install($theme);
174                                 }
175                                 Theme::setAllowedList($allowed_themes);
176
177                                 break;
178                 }
179         }
180
181         public static function content(array $parameters = [])
182         {
183                 $a           = DI::app();
184                 $configCache = $a->getConfigCache();
185
186                 $output = '';
187
188                 $install_title = DI::l10n()->t('Friendica Communications Server - Setup');
189
190                 switch (self::$currentWizardStep) {
191                         case self::SYSTEM_CHECK:
192                                 $php_path = $configCache->get('config', 'php_path');
193
194                                 $status = self::$installer->checkEnvironment(DI::baseUrl()->get(), $php_path);
195
196                                 $tpl    = Renderer::getMarkupTemplate('install_checks.tpl');
197                                 $output .= Renderer::replaceMacros($tpl, [
198                                         '$title'       => $install_title,
199                                         '$pass'        => DI::l10n()->t('System check'),
200                                         '$required'    => DI::l10n()->t('Required'),
201                                         '$requirement_not_satisfied' => DI::l10n()->t('Requirement not satisfied'),
202                                         '$optional_requirement_not_satisfied' => DI::l10n()->t('Optional requirement not satisfied'),
203                                         '$ok'          => DI::l10n()->t('OK'),
204                                         '$checks'      => self::$installer->getChecks(),
205                                         '$passed'      => $status,
206                                         '$see_install' => DI::l10n()->t('Please see the file "doc/INSTALL.md".'),
207                                         '$next'        => DI::l10n()->t('Next'),
208                                         '$reload'      => DI::l10n()->t('Check again'),
209                                         '$php_path'    => $php_path,
210                                 ]);
211                                 break;
212
213                         case self::BASE_CONFIG:
214                                 $ssl_choices = [
215                                         App\BaseURL::SSL_POLICY_NONE     => DI::l10n()->t("No SSL policy, links will track page SSL state"),
216                                         App\BaseURL::SSL_POLICY_FULL     => DI::l10n()->t("Force all links to use SSL"),
217                                         App\BaseURL::SSL_POLICY_SELFSIGN => DI::l10n()->t("Self-signed certificate, use SSL for local links only \x28discouraged\x29")
218                                 ];
219
220                                 $tpl    = Renderer::getMarkupTemplate('install_base.tpl');
221                                 $output .= Renderer::replaceMacros($tpl, [
222                                         '$title'      => $install_title,
223                                         '$pass'       => DI::l10n()->t('Base settings'),
224                                         '$ssl_policy' => ['system-ssl_policy',
225                                                 DI::l10n()->t("SSL link policy"),
226                                                 $configCache->get('system', 'ssl_policy'),
227                                                 DI::l10n()->t("Determines whether generated links should be forced to use SSL"),
228                                                 $ssl_choices],
229                                         '$hostname'   => ['config-hostname',
230                                                 DI::l10n()->t('Host name'),
231                                                 $configCache->get('config', 'hostname'),
232                                                 DI::l10n()->t('Overwrite this field in case the determinated hostname isn\'t right, otherweise leave it as is.'),
233                                                 DI::l10n()->t('Required')],
234                                         '$basepath'   => ['system-basepath',
235                                                 DI::l10n()->t("Base path to installation"),
236                                                 $configCache->get('system', 'basepath'),
237                                                 DI::l10n()->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."),
238                                                 DI::l10n()->t('Required')],
239                                         '$urlpath'    => ['system-urlpath',
240                                                 DI::l10n()->t('Sub path of the URL'),
241                                                 $configCache->get('system', 'urlpath'),
242                                                 DI::l10n()->t('Overwrite this field in case the sub path determination isn\'t right, otherwise leave it as is. Leaving this field blank means the installation is at the base URL without sub path.'),
243                                                 ''],
244                                         '$php_path'   => $configCache->get('config', 'php_path'),
245                                         '$submit'     => DI::l10n()->t('Submit'),
246                                 ]);
247                                 break;
248
249                         case self::DATABASE_CONFIG:
250                                 $tpl    = Renderer::getMarkupTemplate('install_db.tpl');
251                                 $output .= Renderer::replaceMacros($tpl, [
252                                         '$title'      => $install_title,
253                                         '$pass'       => DI::l10n()->t('Database connection'),
254                                         '$info_01'    => DI::l10n()->t('In order to install Friendica we need to know how to connect to your database.'),
255                                         '$info_02'    => DI::l10n()->t('Please contact your hosting provider or site administrator if you have questions about these settings.'),
256                                         '$info_03'    => DI::l10n()->t('The database you specify below should already exist. If it does not, please create it before continuing.'),
257                                         '$required'   => DI::l10n()->t('Required'),
258                                         '$requirement_not_satisfied' => DI::l10n()->t('Requirement not satisfied'),
259                                         '$checks'     => self::$installer->getChecks(),
260                                         '$hostname'   => $configCache->get('config', 'hostname'),
261                                         '$ssl_policy' => $configCache->get('system', 'ssl_policy'),
262                                         '$basepath'   => $configCache->get('system', 'basepath'),
263                                         '$urlpath'    => $configCache->get('system', 'urlpath'),
264                                         '$dbhost'     => ['database-hostname',
265                                                 DI::l10n()->t('Database Server Name'),
266                                                 $configCache->get('database', 'hostname'),
267                                                 '',
268                                                 DI::l10n()->t('Required')],
269                                         '$dbuser'     => ['database-username',
270                                                 DI::l10n()->t('Database Login Name'),
271                                                 $configCache->get('database', 'username'),
272                                                 '',
273                                                 DI::l10n()->t('Required'),
274                                                 'autofocus'],
275                                         '$dbpass'     => ['database-password',
276                                                 DI::l10n()->t('Database Login Password'),
277                                                 $configCache->get('database', 'password'),
278                                                 DI::l10n()->t("For security reasons the password must not be empty"),
279                                                 DI::l10n()->t('Required')],
280                                         '$dbdata'     => ['database-database',
281                                                 DI::l10n()->t('Database Name'),
282                                                 $configCache->get('database', 'database'),
283                                                 '',
284                                                 DI::l10n()->t('Required')],
285                                         '$lbl_10'     => DI::l10n()->t('Please select a default timezone for your website'),
286                                         '$php_path'   => $configCache->get('config', 'php_path'),
287                                         '$submit'     => DI::l10n()->t('Submit')
288                                 ]);
289                                 break;
290
291                         case self::SITE_SETTINGS:
292                                 /* Installed langs */
293                                 $lang_choices = DI::l10n()->getAvailableLanguages();
294
295                                 $tpl    = Renderer::getMarkupTemplate('install_settings.tpl');
296                                 $output .= Renderer::replaceMacros($tpl, [
297                                         '$title'      => $install_title,
298                                         '$required'   => DI::l10n()->t('Required'),
299                                         '$checks'     => self::$installer->getChecks(),
300                                         '$pass'       => DI::l10n()->t('Site settings'),
301                                         '$hostname'   => $configCache->get('config', 'hostname'),
302                                         '$ssl_policy' => $configCache->get('system', 'ssl_policy'),
303                                         '$basepath'   => $configCache->get('system', 'basepath'),
304                                         '$urlpath'    => $configCache->get('system', 'urlpath'),
305                                         '$dbhost'     => $configCache->get('database', 'hostname'),
306                                         '$dbuser'     => $configCache->get('database', 'username'),
307                                         '$dbpass'     => $configCache->get('database', 'password'),
308                                         '$dbdata'     => $configCache->get('database', 'database'),
309                                         '$adminmail'  => ['config-admin_email',
310                                                 DI::l10n()->t('Site administrator email address'),
311                                                 $configCache->get('config', 'admin_email'),
312                                                 DI::l10n()->t('Your account email address must match this in order to use the web admin panel.'),
313                                                 DI::l10n()->t('Required'), 'autofocus', 'email'],
314                                         '$timezone'   => Temporal::getTimezoneField('system-default_timezone',
315                                                 DI::l10n()->t('Please select a default timezone for your website'),
316                                                 $configCache->get('system', 'default_timezone'),
317                                                 ''),
318                                         '$language'   => ['system-language',
319                                                 DI::l10n()->t('System Language:'),
320                                                 $configCache->get('system', 'language'),
321                                                 DI::l10n()->t('Set the default language for your Friendica installation interface and to send emails.'),
322                                                 $lang_choices],
323                                         '$php_path'   => $configCache->get('config', 'php_path'),
324                                         '$submit'     => DI::l10n()->t('Submit')
325                                 ]);
326                                 break;
327
328                         case self::FINISHED:
329                                 $db_return_text = "";
330
331                                 if (count(self::$installer->getChecks()) == 0) {
332                                         $txt            = '<p style="font-size: 130%;">';
333                                         $txt            .= DI::l10n()->t('Your Friendica site database has been installed.') . EOL;
334                                         $db_return_text .= $txt;
335                                 }
336
337                                 $tpl    = Renderer::getMarkupTemplate('install_finished.tpl');
338                                 $output .= Renderer::replaceMacros($tpl, [
339                                         '$title'    => $install_title,
340                                         '$required' => DI::l10n()->t('Required'),
341                                         '$requirement_not_satisfied' => DI::l10n()->t('Requirement not satisfied'),
342                                         '$checks'   => self::$installer->getChecks(),
343                                         '$pass'     => DI::l10n()->t('Installation finished'),
344                                         '$text'     => $db_return_text . self::whatNext(),
345                                 ]);
346
347                                 break;
348                 }
349
350                 return $output;
351         }
352
353         /**
354          * Creates the text for the next steps
355          *
356          * @return string The text for the next steps
357          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
358          */
359         private static function whatNext()
360         {
361                 $baseurl = DI::baseUrl()->get();
362                 return
363                         DI::l10n()->t('<h1>What next</h1>')
364                         . "<p>" . DI::l10n()->t('IMPORTANT: You will need to [manually] setup a scheduled task for the worker.')
365                         . DI::l10n()->t('Please see the file "doc/INSTALL.md".')
366                         . "</p><p>"
367                         . DI::l10n()->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)
368                         . "</p>";
369         }
370
371         /**
372          * Checks the $_POST settings and updates the config Cache for it
373          *
374          * @param Cache       $configCache The current config cache
375          * @param array       $post        The $_POST data
376          * @param string      $cat         The category of the setting
377          * @param string      $key         The key of the setting
378          * @param null|string $default     The default value
379          */
380         private static function checkSetting(Cache $configCache, array $post, $cat, $key, $default = null)
381         {
382                 $configCache->set($cat, $key,
383                         Strings::escapeTags(
384                                 trim(($post[sprintf('%s-%s', $cat, $key)] ?? '') ?:
385                                                 ($default ?? $configCache->get($cat, $key))
386                                 )
387                         )
388                 );
389         }
390 }