Lines
84.88%
73 / 86
Methods
33.33%
2 / 6
Classes
0.00%
0 / 1
| Name | Lines | Methods | CRAP | ||||
|---|---|---|---|---|---|---|---|
| index | 84.61% | 22 / 26 | 0.00% | 0 / 1 | 4.06 | ||
| install | 100.00% | 15 / 15 | 100.00% | 1 / 1 | 3 | ||
| update | 85.71% | 24 / 28 | 0.00% | 0 / 1 | 7.14 | ||
| hasAdministratorSession | 42.85% | 3 / 7 | 0.00% | 0 / 1 | 6.99 | ||
| getRelativeTokenFilePath | 80.00% | 4 / 5 | 0.00% | 0 / 1 | 2.03 | ||
| render | 100.00% | 5 / 5 | 100.00% | 1 / 1 | 1 | ||
| 40 | final class SetupController | |
| 41 | { | |
| 42 | /** | |
| 43 | * @throws TemplateException | |
| 44 | * @throws \Exception | |
| 45 | */ | |
| 46 | #[Route(path: '/setup', name: 'public.setup.update', methods: ['GET'])] | |
| 47 | public function index(Request $request): Response | |
| 48 | { | |
| 49 | $system = new System(); | |
| 50 | ||
| 51 | if (!$system->checkInstallation()) { | |
| 52 | return new Response('phpMyFAQ is already installed.', Response::HTTP_FORBIDDEN); | |
| 53 | } | |
| 54 | ||
| 55 | $installer = new Installer($system); | |
| 56 | ||
| 57 | $checkBasicError = ''; | |
| 58 | try { | |
| 59 | $installer->checkBasicStuff(); | |
| 60 | } catch (Exception $exception) { | |
| 61 | $checkBasicError = $exception->getMessage(); | |
| 62 | } | |
| 63 | ||
| 64 | try { | |
| 65 | $installer->checkInitialRewriteBasePath($request); | |
| 66 | } catch (Exception $exception) { | |
| 67 | $checkBasicError = $exception->getMessage(); | |
| 68 | } | |
| 69 | ||
| 70 | return $this->render('@setup/index.twig', [ | |
| 71 | 'newVersion' => System::getVersion(), | |
| 72 | 'setupType' => 'Setup', | |
| 73 | 'currentYear' => date(format: 'Y'), | |
| 74 | 'currentLanguage' => 'en', | |
| 75 | 'documentationUrl' => System::getDocumentationUrl(), | |
| 76 | 'checkBasicError' => $checkBasicError, | |
| 77 | 'nonCriticalSettings' => $installer->checkNoncriticalSettings(), | |
| 78 | 'filePermissions' => $installer->checkFilesystemPermissions(), | |
| 79 | 'supportedDatabases' => $system->getSupportedSafeDatabases(), | |
| 80 | 'currentPath' => dirname(path: __DIR__, levels: 4), | |
| 81 | 'isLdapEnabled' => $installer->hasLdapSupport(), | |
| 82 | 'isElasticsearchEnabled' => $installer->hasElasticsearchSupport(), | |
| 83 | 'supportedTranslations' => LanguageCodes::getAllSupported(), | |
| 84 | ]); | |
| 85 | } | |
| 86 | ||
| 87 | /** | |
| 88 | * @throws TemplateException | |
| 89 | * @throws \Exception | |
| 90 | */ | |
| 91 | #[Route(path: '/setup/install', name: 'public.setup.install', methods: ['GET'])] | |
| 92 | public function install(): Response | |
| 93 | { | |
| 94 | $system = new System(); | |
| 95 | ||
| 96 | if (!$system->checkInstallation()) { | |
| 97 | return new Response('phpMyFAQ is already installed.', Response::HTTP_FORBIDDEN); | |
| 98 | } | |
| 99 | ||
| 100 | $installer = new Installer($system); | |
| 101 | ||
| 102 | $installationError = ''; | |
| 103 | ||
| 104 | try { | |
| 105 | $installer->startInstall(); | |
| 106 | } catch (Exception|AuthenticationException $exception) { | |
| 107 | $installationError = $exception->getMessage(); | |
| 108 | } | |
| 109 | ||
| 110 | return $this->render('@setup/install.twig', [ | |
| 111 | 'newVersion' => System::getVersion(), | |
| 112 | 'setupType' => 'Setup', | |
| 113 | 'currentYear' => date(format: 'Y'), | |
| 114 | 'documentationUrl' => System::getDocumentationUrl(), | |
| 115 | 'installationError' => $installationError, | |
| 116 | ]); | |
| 117 | } | |
| 118 | ||
| 119 | /** | |
| 120 | * @throws TemplateException | |
| 121 | * @throws Exception | |
| 122 | * @throws \Exception | |
| 123 | */ | |
| 124 | #[Route(path: '/update', name: 'public.update.index', methods: ['GET'])] | |
| 125 | #[Route(path: '/update/', name: 'public.update.index.slash', methods: ['GET'])] | |
| 126 | public function update(Request $request): Response | |
| 127 | { | |
| 128 | $currentStep = Filter::filterVar($request->query->get('step') ?? 1, FILTER_VALIDATE_INT); | |
| 129 | if ($currentStep === null || $currentStep < 1 || $currentStep > 3) { | |
| 130 | $currentStep = 1; | |
| 131 | } | |
| 132 | ||
| 133 | $configuration = Configuration::getConfigurationInstance(); | |
| 134 | ||
| 135 | $update = new Update(new System(), $configuration); | |
| 136 | ||
| 137 | $checkBasicError = ''; | |
| 138 | try { | |
| 139 | $update->checkInitialRewriteBasePath($request); | |
| 140 | } catch (Exception $exception) { | |
| 141 | $checkBasicError = $exception->getMessage(); | |
| 142 | } | |
| 143 | ||
| 144 | $updateTokenError = ''; | |
| 145 | $updateTokenRequired = !$this->hasAdministratorSession($configuration); | |
| 146 | if ($updateTokenRequired) { | |
| 147 | try { | |
| 148 | // The token itself is never rendered, it has to be read from the file system | |
| 149 | new UpdateToken((string) PMF_CONFIG_DIR)->getOrCreate(); | |
| 150 | } catch (Exception $exception) { | |
| 151 | $updateTokenError = $exception->getMessage(); | |
| 152 | } | |
| 153 | } | |
| 154 | ||
| 155 | return $this->render('@setup/update.twig', [ | |
| 156 | 'currentStep' => $currentStep, | |
| 157 | 'installedVersion' => $configuration->getVersion(), | |
| 158 | 'newVersion' => System::getVersion(), | |
| 159 | 'checkBasicError' => $checkBasicError, | |
| 160 | 'currentYear' => date(format: 'Y'), | |
| 161 | 'documentationUrl' => System::getDocumentationUrl(), | |
| 162 | 'configTableNotAvailable' => $update->isConfigTableNotAvailable($configuration->getDb()), | |
| 163 | 'updateTokenRequired' => $updateTokenRequired, | |
| 164 | 'updateTokenError' => $updateTokenError, | |
| 165 | 'updateTokenFile' => $this->getRelativeTokenFilePath(), | |
| 166 | 'updateTokenLifetime' => (int) (UpdateToken::TOKEN_LIFETIME / 60), | |
| 167 | ]); | |
| 168 | } | |
| 169 | ||
| 170 | /** | |
| 171 | * Returns true if the update is started by a logged-in administrator. This is only | |
| 172 | * possible as long as the database still matches what the new code expects, so the | |
| 173 | * update wizard falls back to the update token whenever this returns false. | |
| 174 | */ | |
| 175 | private function hasAdministratorSession(Configuration $configuration): bool | |
| 176 | { | |
| 177 | try { | |
| 178 | $currentUser = CurrentUser::getCurrentUser($configuration); | |
| 179 | ||
| 180 | if (!$currentUser->isLoggedIn()) { | |
| 181 | return false; | |
| 182 | } | |
| 183 | ||
| 184 | return $currentUser->isSuperAdmin() | |
| 185 | || $currentUser->perm->hasPermission($currentUser->getUserId(), PermissionType::CONFIGURATION_EDIT->value); | |
| 186 | } catch (\Throwable) { | |
| 187 | return false; | |
| 188 | } | |
| 189 | } | |
| 190 | ||
| 191 | /** | |
| 192 | * Returns the path of the token file relative to the phpMyFAQ root directory, | |
| 193 | * so that we can tell the administrator where to find it. | |
| 194 | */ | |
| 195 | private function getRelativeTokenFilePath(): string | |
| 196 | { | |
| 197 | $tokenFilePath = new UpdateToken((string) PMF_CONFIG_DIR)->getTokenFilePath(); | |
| 198 | $rootDir = (string) PMF_ROOT_DIR; | |
| 199 | ||
| 200 | if (str_starts_with($tokenFilePath, $rootDir . DIRECTORY_SEPARATOR)) { | |
| 201 | return substr($tokenFilePath, strlen($rootDir) + 1); | |
| 202 | } | |
| 203 | ||
| 204 | return $tokenFilePath; | |
| 205 | } | |
| 206 | ||
| 207 | /** | |
| 208 | * Returns a Twig-rendered template as a response. | |
| 209 | * | |
| 210 | * @param array<string, mixed> $templateVars | |
| 211 | * @throws Exception|LoaderError | |
| 212 | */ | |
| 213 | public function render(string $pathToTwigFile, array $templateVars = [], ?Response $response = null): Response | |
| 214 | { | |
| 215 | $response ??= new Response(); | |
| 216 | $twigWrapper = new TwigWrapper((string) PMF_ROOT_DIR . '/assets/templates', true); | |
| 217 | $templateWrapper = $twigWrapper->loadTemplate($pathToTwigFile); | |
| 218 | ||
| 219 | $response->setContent($templateWrapper->render($templateVars)); | |
| 220 | ||
| 221 | return $response; | |
| 222 | } | |
| 223 | } |