*/ class Base extends Controller { public Layout $layoutParams; public ?Consultation $consultation = null; public ?Motion $motion = null; public ?Amendment $amendment = null; public ?Site $site = null; /** @var string */ public $layout = '@app/views/layouts/column1'; /** @var null|bool - currently only null (default) and true (allow not-logged in, e.g. by plugins) are supported. false to come. */ public ?bool $allowNotLoggedIn = null; public bool $limitedAccessBecauseOfOverride = false; /** * @param string $cid the ID of this controller. * @param Application $module the module that this controller belongs to. * @param array $config name-value pairs that will be used to initialize the object properties. */ public function __construct($cid, $module, $config = []) { // Hint: can be overwritten in loadConsultation $this->layoutParams = new Layout(); parent::__construct($cid, $module, $config); } protected function beforeActionAuthorizationHandling(\yii\base\Action $action): void { $usernamePasswordForm = new LoginUsernamePasswordForm(RequestContext::getSession(), User::getExternalAuthenticator()); $usernamePasswordForm->onPageView(get_class($this), $action->id); $tfa = new SecondFactorAuthentication(RequestContext::getSession()); $tfa->onPageView(get_class($this), $action->id); } /** * @throws Internal * @throws \Exception * @throws \yii\base\ExitException * @throws \yii\web\BadRequestHttpException */ public function beforeAction($action): bool { /** @var Response $response */ $response = Yii::$app->response; $response->headers->add('X-Xss-Protection', '1'); $response->headers->add('X-Content-Type-Options', 'nosniff'); $response->headers->add('X-Frame-Options', 'sameorigin'); $this->beforeActionAuthorizationHandling($action); if (!parent::beforeAction($action)) { return false; } $params = $this->getHttpRequest()->resolve(); $appParams = AntragsgruenApp::getInstance(); if ($appParams->updateKey) { $this->showErrorpage(503, Yii::t('base', 'err_update_mode')); } $inManager = (get_class($this) === ManagerController::class); $inInstaller = (get_class($this) === InstallationController::class); if ($inInstaller) { $this->layoutParams->setLayout(Layout::getDefaultLayout()); } elseif ($appParams->siteSubdomain || isset($params[1]['subdomain'])) { $subdomain = $appParams->siteSubdomain ?? $params[1]['subdomain']; if (str_starts_with($subdomain, 'xn--')) { $convertedSubdomain = idn_to_utf8($subdomain); if ($convertedSubdomain === false) { $convertedSubdomain = ''; } $subdomain = $convertedSubdomain; } $consultation = $params[1]['consultationPath'] ?? ''; if ($consultation === '' && $this->isGetSet('passConId')) { $consultation = $this->getHttpRequest()->get('passConId'); } $this->loadConsultation($subdomain, $consultation); if ($this->site) { $this->layoutParams->setLayout($this->site->getSettings()->siteLayout); } else { $this->layoutParams->setLayout(Layout::getDefaultLayout()); } } elseif (!$inManager && !$appParams->multisiteMode) { $this->layoutParams->setLayout(Layout::getDefaultLayout()); $this->showErrorpage(500, Yii::t('base', 'err_no_site_internal')); } else { $this->layoutParams->setLayout(Layout::getDefaultLayout()); } if ($this->allowNotLoggedIn === true) { return true; } // If re-installing while being logged in in the old installation, the testConsultationPwd below would break the site if ($inInstaller) { return true; } if (get_class($this) === PagesController::class && in_array($action->id, [PagesController::VIEW_ID_SHOW_PAGE, PagesController::VIEW_ID_CSS])) { return true; } if (get_class($this) === PagesController::class && $action->id === PagesController::VIEW_ID_FILES && $this->consultation) { if ($this->consultation->getSettings()->logoUrl) { $logo = urldecode(basename($this->consultation->getSettings()->logoUrl)); if (isset($params[1]['filename']) && $logo && $params[1]['filename'] === $logo) { return true; } } if (isset($this->site->getSettings()->getStylesheet()->backgroundImage)) { $bg = urldecode(basename($this->site->getSettings()->getStylesheet()->backgroundImage)); if (isset($params[1]['filename']) && $params[1]['filename'] === $bg) { return true; } } } $accessCheck = (new ConsultationAccess($this->consultation))->testForDenyReason(get_class($this), $action->id); if (isset($accessCheck['limitedAccessBecauseOfOverride'])) { $this->limitedAccessBecauseOfOverride = $accessCheck['limitedAccessBecauseOfOverride']; } if ($accessCheck['denied']) { if (isset($accessCheck['deniedRedirect'])) { $this->redirect($accessCheck['deniedRedirect']); } return false; } return true; } public function runAction($id, $params = []): ?string { try { $response = parent::runAction($id, $params); /** @phpstan-ignore-next-line */ } /** @noinspection PhpRedundantCatchClauseInspection */ catch (ApiResponseException $e) { $response = new RestApiExceptionResponse($e->getCode(), $e->getMessage()); return $response->renderYii($this->layoutParams, $this->getHttpResponse()); /** @phpstan-ignore-next-line */ } /** @noinspection PhpRedundantCatchClauseInspection */ catch (ResponseException $e) { return $e->response->renderYii($this->layoutParams, $this->getHttpResponse()); } if (is_string($response)) { return $response; } if (is_object($response) && is_a($response, ResponseInterface::class)) { /** @var ResponseInterface $response */ return $response->renderYii($this->layoutParams, $this->getHttpResponse()); } return $response; } /** * @param array|string $url * @param int $statusCode * @throws \yii\base\ExitException */ public function redirect($url, $statusCode = 302): never { parent::redirect($url, $statusCode); Yii::$app->end(); } protected function getHttpRequest(): Request { /** @var Request $request */ $request = Yii::$app->request; return $request; } protected function getHttpResponse(): Response { /** @var Response $response */ $response = Yii::$app->response; return $response; } protected function getHttpSession(): Session { return RequestContext::getSession(); } protected function getHttpMethod(): string { return $this->getHttpRequest()->method; } protected function getHttpHeader(string $headerName): ?string { return $this->getHttpRequest()->headers->get($headerName); } protected function getPostBody(): string { return $this->getHttpRequest()->getRawBody(); } protected function isPostSet(string $name): bool { $post = $this->getPostValues(); return isset($post[$name]); } protected function isGetSet(string $name): bool { $get = $this->getHttpRequest()->get(); return isset($get[$name]); } public function isRequestSet(string $name): bool { return $this->isPostSet($name) || $this->isGetSet($name); } public function getRequestValue(string $name, mixed $default = null): mixed { $post = $this->getPostValues(); if (isset($post[$name])) { return $post[$name]; } $get = $this->getHttpRequest()->get(); if (isset($get[$name])) { return $get[$name]; } return $default; } public function getPostValue(string $name, mixed $default = null): mixed { return $this->getHttpRequest()->post($name, $default); } public function getPostValues(): array { /** @var array $post */ $post = $this->getHttpRequest()->post(); return $post; } public function renderContentPage(string $pageKey): HtmlResponse { if ($this->consultation) { $admin = User::havePrivilege($this->consultation, Privileges::PRIVILEGE_CONTENT_EDIT, null); } else { $user = User::getCurrentUser(); $admin = ($user && in_array($user->id, $this->getParams()->adminUserIds)); } return new HtmlResponse($this->render( '@app/views/pages/contentpage', [ 'pageKey' => $pageKey, 'admin' => $admin, ] )); } /** * @param string $view * @param array $params * @return string */ public function render($view, $params = array()) { $params = array_merge( [ 'consultation' => $this->consultation, 'reducedNavigation' => $this->limitedAccessBecauseOfOverride, ], $params ); return parent::render($view, $params); } /** * @throws ApiResponseException */ public function handleRestHeaders(array $allowedMethods, bool $alwaysEnabled = false): void { $this->layoutParams->setFallbackLayoutIfNotInitializedYet(); $this->layoutParams->robotsNoindex = true; if (JwtCreator::isCurrentTokenRestrictedToOtherSite($this->site)) { throw new ApiResponseException('Token not valid for this site', 401); } if (!$this->site->getSettings()->apiEnabled && !$alwaysEnabled && !User::getCurrentUser()) { throw new ApiResponseException('Public API disabled', 403); } if ($this->consultation && ($this->consultation->urlPath === null || $this->consultation->dateDeletion)) { throw new ApiResponseException('Consultation not found', 404); } if ($this->consultation && $this->consultation->getSettings()->maintenanceMode && !$alwaysEnabled) { if (!User::havePrivilege($this->consultation, Privileges::PRIVILEGE_CONSULTATION_SETTINGS, null)) { throw new ApiResponseException('Consultation in maintenance mode', 404); } } /** @var Response $response */ $response = Yii::$app->response; if ($this->site->getSettings()->apiCorsOrigins) { if (in_array('*', $this->site->getSettings()->apiCorsOrigins)) { $response->headers->add('Access-Control-Allow-Origin', '*'); } elseif ($this->getHttpRequest()->origin && in_array($this->getHttpRequest()->origin, $this->site->getSettings()->apiCorsOrigins)) { $response->headers->add('Access-Control-Allow-Origin', $this->getHttpRequest()->origin); } } $response->headers->add('Access-Control-Allow-Methods', implode(', ', $allowedMethods)); if ($this->getHttpMethod() === 'OPTIONS') { Yii::$app->end(); } if (!in_array($this->getHttpMethod(), $allowedMethods)) { throw new ApiResponseException('Method not allowed', 405); } } public function returnRestResponseFromException(\Exception $exception): RestApiResponse { return new RestApiExceptionResponse($exception->getCode() > 0 ? $exception->getCode() : 500, $exception->getMessage()); } public function getParams(): AntragsgruenApp { /** @var AntragsgruenApp $app */ $app = Yii::$app->params; return $app; } public function forceLogin(): void { if (RequestContext::getYiiUser()->getIsGuest()) { $loginUrl = UrlHelper::createUrl(['/user/login', 'backUrl' => $this->getHttpRequest()->url]); $this->redirect($loginUrl); } } public function showErrors(bool $addBorder = false): string { $session = $this->getHttpSession(); if (!$session->isActive) { return ''; } $str = ''; $error = $session->getFlash('error', null, true); if ($error) { $str .= '