]> git.immae.eu Git - github/shaarli/Shaarli.git/blob - application/front/controller/visitor/InstallController.php
Fix: soft fail if the mutex is not working
[github/shaarli/Shaarli.git] / application / front / controller / visitor / InstallController.php
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Shaarli\Front\Controller\Visitor;
6
7 use Shaarli\Container\ShaarliContainer;
8 use Shaarli\Front\Exception\AlreadyInstalledException;
9 use Shaarli\Front\Exception\ResourcePermissionException;
10 use Shaarli\Helper\ApplicationUtils;
11 use Shaarli\Languages;
12 use Shaarli\Security\SessionManager;
13 use Slim\Http\Request;
14 use Slim\Http\Response;
15
16 /**
17 * Slim controller used to render install page, and create initial configuration file.
18 */
19 class InstallController extends ShaarliVisitorController
20 {
21 public const SESSION_TEST_KEY = 'session_tested';
22 public const SESSION_TEST_VALUE = 'Working';
23
24 public function __construct(ShaarliContainer $container)
25 {
26 parent::__construct($container);
27
28 if (is_file($this->container->conf->getConfigFileExt())) {
29 throw new AlreadyInstalledException();
30 }
31 }
32
33 /**
34 * Display the install template page.
35 * Also test file permissions and sessions beforehand.
36 */
37 public function index(Request $request, Response $response): Response
38 {
39 // Before installation, we'll make sure that permissions are set properly, and sessions are working.
40 $this->checkPermissions();
41
42 if (
43 static::SESSION_TEST_VALUE
44 !== $this->container->sessionManager->getSessionParameter(static::SESSION_TEST_KEY)
45 ) {
46 $this->container->sessionManager->setSessionParameter(static::SESSION_TEST_KEY, static::SESSION_TEST_VALUE);
47
48 return $this->redirect($response, '/install/session-test');
49 }
50
51 [$continents, $cities] = generateTimeZoneData(timezone_identifiers_list(), date_default_timezone_get());
52
53 $this->assignView('continents', $continents);
54 $this->assignView('cities', $cities);
55 $this->assignView('languages', Languages::getAvailableLanguages());
56
57 $phpEol = new \DateTimeImmutable(ApplicationUtils::getPhpEol(PHP_VERSION));
58
59 $permissions = array_merge(
60 ApplicationUtils::checkResourcePermissions($this->container->conf),
61 ApplicationUtils::checkDatastoreMutex()
62 );
63
64 $this->assignView('php_version', PHP_VERSION);
65 $this->assignView('php_eol', format_date($phpEol, false));
66 $this->assignView('php_has_reached_eol', $phpEol < new \DateTimeImmutable());
67 $this->assignView('php_extensions', ApplicationUtils::getPhpExtensionsRequirement());
68 $this->assignView('permissions', $permissions);
69
70 $this->assignView('pagetitle', t('Install Shaarli'));
71
72 return $response->write($this->render('install'));
73 }
74
75 /**
76 * Route checking that the session parameter has been properly saved between two distinct requests.
77 * If the session parameter is preserved, redirect to install template page, otherwise displays error.
78 */
79 public function sessionTest(Request $request, Response $response): Response
80 {
81 // This part makes sure sessions works correctly.
82 // (Because on some hosts, session.save_path may not be set correctly,
83 // or we may not have write access to it.)
84 if (
85 static::SESSION_TEST_VALUE
86 !== $this->container->sessionManager->getSessionParameter(static::SESSION_TEST_KEY)
87 ) {
88 // Step 2: Check if data in session is correct.
89 $msg = t(
90 '<pre>Sessions do not seem to work correctly on your server.<br>' .
91 'Make sure the variable "session.save_path" is set correctly in your PHP config, ' .
92 'and that you have write access to it.<br>' .
93 'It currently points to %s.<br>' .
94 'On some browsers, accessing your server via a hostname like \'localhost\' ' .
95 'or any custom hostname without a dot causes cookie storage to fail. ' .
96 'We recommend accessing your server via it\'s IP address or Fully Qualified Domain Name.<br>'
97 );
98 $msg = sprintf($msg, $this->container->sessionManager->getSavePath());
99
100 $this->assignView('message', $msg);
101
102 return $response->write($this->render('error'));
103 }
104
105 return $this->redirect($response, '/install');
106 }
107
108 /**
109 * Save installation form and initialize config file and datastore if necessary.
110 */
111 public function save(Request $request, Response $response): Response
112 {
113 $timezone = 'UTC';
114 if (
115 !empty($request->getParam('continent'))
116 && !empty($request->getParam('city'))
117 && isTimeZoneValid($request->getParam('continent'), $request->getParam('city'))
118 ) {
119 $timezone = $request->getParam('continent') . '/' . $request->getParam('city');
120 }
121 $this->container->conf->set('general.timezone', $timezone);
122
123 $login = $request->getParam('setlogin');
124 $this->container->conf->set('credentials.login', $login);
125 $salt = sha1(uniqid('', true) . '_' . mt_rand());
126 $this->container->conf->set('credentials.salt', $salt);
127 $this->container->conf->set('credentials.hash', sha1($request->getParam('setpassword') . $login . $salt));
128
129 if (!empty($request->getParam('title'))) {
130 $this->container->conf->set('general.title', escape($request->getParam('title')));
131 } else {
132 $this->container->conf->set(
133 'general.title',
134 'Shared bookmarks on ' . escape(index_url($this->container->environment))
135 );
136 }
137
138 $this->container->conf->set('translation.language', escape($request->getParam('language')));
139 $this->container->conf->set('updates.check_updates', !empty($request->getParam('updateCheck')));
140 $this->container->conf->set('api.enabled', !empty($request->getParam('enableApi')));
141 $this->container->conf->set(
142 'api.secret',
143 generate_api_secret(
144 $this->container->conf->get('credentials.login'),
145 $this->container->conf->get('credentials.salt')
146 )
147 );
148 $this->container->conf->set('general.header_link', $this->container->basePath . '/');
149
150 try {
151 // Everything is ok, let's create config file.
152 $this->container->conf->write($this->container->loginManager->isLoggedIn());
153 } catch (\Exception $e) {
154 $this->assignView('message', t('Error while writing config file after configuration update.'));
155 $this->assignView('stacktrace', $e->getMessage() . PHP_EOL . $e->getTraceAsString());
156
157 return $response->write($this->render('error'));
158 }
159
160 $this->container->sessionManager->setSessionParameter(
161 SessionManager::KEY_SUCCESS_MESSAGES,
162 [t('Shaarli is now configured. Please login and start shaaring your bookmarks!')]
163 );
164
165 return $this->redirect($response, '/login');
166 }
167
168 protected function checkPermissions(): bool
169 {
170 // Ensure Shaarli has proper access to its resources
171 $errors = ApplicationUtils::checkResourcePermissions($this->container->conf, true);
172 if (empty($errors)) {
173 return true;
174 }
175
176 $message = t('Insufficient permissions:') . PHP_EOL;
177 foreach ($errors as $error) {
178 $message .= PHP_EOL . $error;
179 }
180
181 throw new ResourcePermissionException($message);
182 }
183 }