controllers/PagesController.php
Rama main · réplica de lectura del repositorio original.
<?php
namespace app\controllers;
use app\models\policies\{IPolicy, UserGroups};
use app\models\settings\{Layout, Privileges, AntragsgruenApp};
use app\models\http\{BinaryFileResponse, HtmlErrorResponse, HtmlResponse, JsonResponse, RedirectResponse, ResponseInterface, RestApiResponse};
use app\components\{ConsultationAccess, HTMLTools, Tools, UrlHelper, ZipWriter};
use app\models\db\{ConsultationFile, ConsultationFileGroup, ConsultationText, ConsultationUserGroup, User};
use app\models\exceptions\{Access, FormError, ResponseException};
use yii\web\Response;
class PagesController extends Base
{
public const VIEW_ID_FILES = 'file';
public const VIEW_ID_CSS = 'css';
public const VIEW_ID_SHOW_PAGE = 'show-page';
public function actionListPages(): ResponseInterface
{
if (!User::havePrivilege($this->consultation, Privileges::PRIVILEGE_CONTENT_EDIT, null)) {
return new HtmlErrorResponse(403, 'No permissions to edit this page');
}
if ($this->isPostSet('create')) {
try {
$url = $this->getHttpRequest()->post('url');
if (trim($url) === '' || preg_match('/[^\w_\-,.äöüß]/siu', $url)) {
throw new FormError('Invalid character in the URL');
}
$alreadyCreatedPage = ConsultationText::findOne([
'category' => 'pagedata',
'consultationId' => $this->consultation->id,
'textId' => $url,
]);
if ($alreadyCreatedPage) {
throw new FormError(\Yii::t('pages', 'err_exists_con'));
}
$page = new ConsultationText();
$page->category = ConsultationText::DEFAULT_CATEGORY;
$page->textId = $url;
$page->title = $this->getHttpRequest()->post('title');
$page->breadcrumb = $this->getHttpRequest()->post('title');
$page->consultationId = $this->consultation->id;
$page->siteId = $this->site->id;
$page->menuPosition = ($this->isPostSet('inMenu') ? 1 : null);
$page->text = '';
$page->editDate = date('Y-m-d H:i:s');
$page->save();
return new RedirectResponse($page->getUrl());
} catch (FormError $e) {
$this->getHttpSession()->setFlash('error', $e->getMessage());
}
}
return new HtmlResponse($this->render('list'));
}
protected function getPageForView(string $pageSlug): ConsultationText
{
$pageData = ConsultationText::getPageData($this->site, $this->consultation, $pageSlug);
// Site-wide pages are always visible. Also, maintenance and legal/privacy pages are always visible.
// For everything else, check for maintenance mode and login.
$allowedPages = [ConsultationText::DEFAULT_PAGE_MAINTENANCE, ConsultationText::DEFAULT_PAGE_LEGAL, ConsultationText::DEFAULT_PAGE_PRIVACY];
if ($pageData->consultation && !in_array($pageSlug, $allowedPages)) {
$accessTest = (new ConsultationAccess($this->consultation))->testForDenyReason(get_class($this), null);
if ($accessTest['denied']) {
if (isset($accessTest['deniedRedirect'])) {
$this->redirect($accessTest['deniedRedirect']);
}
throw new ResponseException(new HtmlErrorResponse(404, 'Page not found'));
}
}
return $pageData;
}
public function actionShowPage(string $pageSlug): ResponseInterface
{
$pageData = $this->getPageForView($pageSlug);
if ($pageData->consultationId !== null && !$pageData->getReadPolicy()->checkCurrUser()) {
return new HtmlErrorResponse(403, \Yii::t('admin', 'no_access'));
}
return $this->renderContentPage($pageSlug);
}
public function actionGetRest(string $pageSlug): RestApiResponse
{
$pageData = $this->getPageForView($pageSlug);
if (!$pageData->getReadPolicy()->checkCurrUser()) {
return new RestApiResponse(403, ['error' => 'No access']);
}
$data = [
'id' => $pageData->id,
'in_menu' => $pageData->menuPosition !== null,
'text_id' => $pageData->textId,
'title' => $pageData->title,
'url_json' => $pageData->getJsonUrl(),
'url_html' => $pageData->getUrl(),
'html' => $pageData->text,
];
return new RestApiResponse(200, $data);
}
/**
* @throws Access
*/
protected function getPageForEdit(string $pageSlug): ConsultationText
{
if ($this->getHttpRequest()->get('pageId')) {
$page = ConsultationText::findOne($this->getHttpRequest()->get('pageId'));
if (!$page) {
throw new Access('Page not found');
}
} else {
$page = ConsultationText::getPageData($this->site, $this->consultation, $pageSlug);
}
if ($page->id) {
if ($page->siteId && $page->siteId !== $this->site->id) {
throw new Access('Some inconsistency occurred (site): ' . $page->siteId . " / " . $this->site->id);
}
if ($page->consultationId && $page->consultationId !== $this->consultation->id) {
throw new Access('Some inconsistency occurred (consultation)');
}
}
if ($page->siteId) {
if (!User::havePrivilege($this->consultation, Privileges::PRIVILEGE_CONTENT_EDIT, null)) {
throw new Access(\Yii::t('pages', 'err_permission'));
}
} else {
if (!User::currentUserIsSuperuser()) {
throw new Access(\Yii::t('pages', 'err_permission'));
}
}
return $page;
}
/**
* @throws Access
*/
public function actionSavePage(string $pageSlug): JsonResponse
{
$page = $this->getPageForEdit($pageSlug);
$page = $page->getVariantForLanguage(ConsultationText::getContentLanguage($this->site));
$otherVariants = $page->getOtherLanguageVariants();
$needsReload = false;
$message = '';
$page->text = HTMLTools::correctHtmlErrors($this->getHttpRequest()->post('data'));
$page->editDate = date('Y-m-d H:i:s');
if (!in_array($page->textId, array_keys(ConsultationText::getDefaultPages()))) {
if ($this->getHttpRequest()->post('title')) {
if ($page->breadcrumb === $page->title) {
$page->breadcrumb = $this->getHttpRequest()->post('title');
}
$page->title = $this->getHttpRequest()->post('title');
}
if ($this->getHttpRequest()->post('inMenu') !== null) {
$menuPos = ($this->getHttpRequest()->post('inMenu') ? 1 : null);
if ($menuPos !== $page->menuPosition) {
$needsReload = true;
}
$page->menuPosition = $menuPos;
}
if ($this->getHttpRequest()->post('allConsultations') === '1' && $page->consultationId) {
$alreadyCreatedPage = ConsultationText::findOne([
'category' => 'pagedata',
'siteId' => $page->siteId,
'consultationId' => null,
'textId' => $page->textId,
]);
if ($alreadyCreatedPage) {
$message = \Yii::t('pages', 'err_exists_site');
} else {
$page->consultationId = null;
}
}
if ($this->getHttpRequest()->post('allConsultations') === '0' && $page->consultationId === null) {
$alreadyCreatedPage = ConsultationText::findOne([
'category' => 'pagedata',
'consultationId' => $this->consultation->id,
'textId' => $page->textId,
]);
if ($alreadyCreatedPage) {
$message = \Yii::t('pages', 'err_exists_con');
} else {
$page->consultationId = $this->consultation->id;
}
}
if ($this->getHttpRequest()->post('policyReadPage')) {
$data = $this->getHttpRequest()->post('policyReadPage');
$policy = IPolicy::getInstanceFromDb($data['id'], $this->consultation, $page);
if (is_a($policy, UserGroups::class)) {
$groups = ConsultationUserGroup::loadGroupsByIdForConsultation($this->consultation, $data['groups'] ?? []);
$policy->setAllowedUserGroups($groups);
}
$page->setReadPolicy($policy);
}
$newTextId = $this->getHttpRequest()->post('url');
if ($newTextId && !preg_match('/[^\w_\-,.äöüß]/siu', $newTextId) && $page->textId !== $newTextId) {
$page->textId = $newTextId;
$needsReload = true;
}
}
if ($page->textId === ConsultationText::DEFAULT_PAGE_HELP) {
$page->menuPosition = 1;
}
$result = [
'success' => true,
'message' => $message,
'id' => $page->id,
'title' => $page->title,
'url' => $page->textId,
'allConsultations' => ($page->consultationId === null),
'redirectTo' => ($needsReload ? $page->getUrl() : null),
];
$page->save();
$page->syncSharedSettingsToVariants($otherVariants);
$downloadableResult = $this->handleDownloadableFiles($page, $this->getPostValues());
$result = array_merge($result, $downloadableResult);
$page->refresh();
return new JsonResponse($result);
}
private function handleDownloadableFiles(ConsultationText $page, array $post): array
{
$result = [];
if (isset($post['uploadDownloadableFile']) && strlen($post['uploadDownloadableFile']) > 0) {
$group = $page->getMyFileGroup();
if (!$group) {
$group = new ConsultationFileGroup();
$group->consultationId = $this->consultation->id;
$group->consultationTextId = $page->id;
$group->title = $page->title ?? '-';
$group->parentGroupId = null;
$group->position = 0;
$group->save();
}
$file = ConsultationFile::createDownloadableFile(
$this->consultation,
User::getCurrentUser(),
base64_decode($post['uploadDownloadableFile']),
$post['uploadDownloadableFilename'],
$post['uploadDownloadableTitle'],
$group
);
$result['uploadedFile'] = [
'title' => $file->title,
'url' => $file->getUrl(),
'id' => $file->id,
];
}
return $result;
}
public function actionDeleteFile(): ResponseInterface
{
if (!User::havePrivilege($this->consultation, Privileges::PRIVILEGE_CONTENT_EDIT, null)) {
return new HtmlErrorResponse(403, 'No permissions to delete files');
}
$fileId = intval($this->getHttpRequest()->post('id'));
foreach ($this->consultation->files as $file) {
if ($file->id === $fileId) {
$file->delete();
}
}
return new JsonResponse([
'success' => true,
]);
}
public function actionDeletePage(string $pageSlug): RedirectResponse
{
$page = $this->getPageForEdit($pageSlug);
if ($this->getHttpRequest()->post('delete')) {
foreach ($page->getOtherLanguageVariants() as $variant) {
$variant->delete();
}
$page->delete();
}
$textUrl = UrlHelper::createUrl('pages/list-pages');
return new RedirectResponse($textUrl);
}
public function actionMaintenance(): HtmlResponse
{
return $this->renderContentPage('maintenance');
}
public function actionLegal(): HtmlResponse
{
if (AntragsgruenApp::getInstance()->multisiteMode) {
$admin = User::havePrivilege($this->consultation, Privileges::PRIVILEGE_CONTENT_EDIT, null);
$viewParams = ['pageKey' => 'legal', 'admin' => $admin];
return new HtmlResponse($this->render('imprint_multisite', $viewParams));
} else {
return $this->renderContentPage('legal');
}
}
/**
* @throws Access
*/
public function actionUpload(): JsonResponse
{
if (!User::havePrivilege($this->consultation, Privileges::PRIVILEGE_CONTENT_EDIT, null)) {
throw new Access('No permissions to upload files');
}
try {
$user = User::getCurrentUser();
$file = ConsultationFile::uploadImage($this->consultation, 'upload', $user);
return new JsonResponse([
'uploaded' => 1,
'fileName' => $file->filename,
'url' => $file->getUrl(),
]);
} catch (FormError $e) {
return new JsonResponse([
'uploaded' => 0,
'error' => [
'message' => $e->getMessage()
],
]);
}
}
/**
* @throws Access
*/
public function actionBrowseImages(): ResponseInterface
{
if (!User::havePrivilege($this->consultation, Privileges::PRIVILEGE_CONTENT_EDIT, null)) {
throw new Access('No permissions to upload files');
}
$msgSuccess = '';
$msgError = '';
if ($this->getHttpRequest()->post('delete') !== null) {
try {
$file = ConsultationFile::findOne([
'consultationId' => $this->consultation->id,
'id' => $this->getHttpRequest()->post('id'),
]);
if ($file) {
$file->delete();
}
$this->consultation->refresh();
$msgSuccess = \Yii::t('pages', 'images_deleted');
} catch (\Exception $e) {
$msgError = $e->getMessage();
}
}
$files = $this->site->files;
$files = array_values(array_filter($files, function (ConsultationFile $file) {
return $file->filename !== 'styles.css';
}));
usort($files, function (ConsultationFile $file1, ConsultationFile $file2) {
$currentCon = $this->consultation->id;
if ($file1->consultationId === $currentCon && $file2->consultationId !== $currentCon) {
return -1;
}
if ($file1->consultationId !== $currentCon && $file2->consultationId === $currentCon) {
return 1;
}
return Tools::compareSqlTimes($file1->dateCreation, $file2->dateCreation);
});
return new HtmlResponse($this->renderPartial('browse-images', [
'files' => $files,
'msgSuccess' => $msgSuccess,
'msgError' => $msgError,
]));
}
public function actionFile(string $filename): ResponseInterface
{
$file = ConsultationFile::findOne([
'consultationId' => $this->consultation->id,
'filename' => $filename,
]);
if (!$file) {
return new HtmlErrorResponse(404, 'File not found');
}
if ($file->fileGroupId && $file->fileGroup->consultationTextId) {
if (!$file->fileGroup->consultationText->getReadPolicy()->checkCurrUser()) {
return new HtmlErrorResponse(403, 'No access to file');
}
}
return new class($file) implements ResponseInterface {
private ConsultationFile $file;
public function __construct(ConsultationFile $file)
{
$this->file = $file;
}
public function renderYii(Layout $layoutParams, Response $response): string
{
$response->format = Response::FORMAT_RAW;
$response->headers->add('Content-Type', $this->file->mimetype);
$response->headers->set('Expires', gmdate('D, d M Y H:i:s \G\M\T', time() + 3600 * 24 * 7));
$response->headers->set('Pragma', 'cache');
$response->headers->set('Cache-Control', 'public, max-age=' . (3600 * 24 * 7));
return $this->file->data;
}
};
}
public function actionCss(): ResponseInterface
{
$stylesheetSettings = $this->site->getSettings()->getStylesheet();
$file = ConsultationFile::findStylesheetCache($this->site, $stylesheetSettings);
if ($file) {
$lines = explode("\n", $file->data);
$firstLine = array_shift($lines);
if ($firstLine === ConsultationFile::getStylesheetCacheTag()) {
return new BinaryFileResponse(BinaryFileResponse::TYPE_CSS, implode("\n", $lines), false, null, false, 3600 * 24 * 7);
} else {
$file->delete();
}
}
$data = $this->renderPartial('css', [
'stylesheetSettings' => $stylesheetSettings,
]);
$toSaveData = ConsultationFile::getStylesheetCacheTag() . "\n" . $data;
ConsultationFile::createStylesheetCache($this->site, $stylesheetSettings, $toSaveData);
return new BinaryFileResponse(BinaryFileResponse::TYPE_CSS, $data, false, null, false, 3600 * 24 * 7);
}
public function actionDocuments(): ResponseInterface
{
$iAmAdmin = User::havePrivilege($this->consultation, Privileges::PRIVILEGE_CONTENT_EDIT, null);
if ($iAmAdmin && $this->isPostSet('createGroup') && $this->getPostValue('name')) {
$group = new ConsultationFileGroup();
$group->title = trim($this->getPostValue('name'));
$group->consultationId = $this->consultation->id;
$group->parentGroupId = null;
$group->position = ConsultationFileGroup::getNextAvailablePosition($this->consultation);
$group->save();
$this->getHttpSession()->setFlash('success', \Yii::t('pages', 'documents_group_added'));
return new RedirectResponse(UrlHelper::createUrl('/pages/documents'));
}
if ($iAmAdmin && $this->isPostSet('deleteGroup') && $this->getPostValue('groupId') > 0) {
$found = false;
foreach ($this->consultation->fileGroups as $fileGroup) {
if ($fileGroup->id === intval($this->getPostValue('groupId'))) {
foreach ($fileGroup->files as $file) {
$file->delete();
}
$fileGroup->delete();
$found = true;
}
}
if ($found) {
$this->getHttpSession()->setFlash('success', \Yii::t('pages', 'documents_group_deleted'));
return new RedirectResponse(UrlHelper::createUrl('/pages/documents'));
}
}
if ($iAmAdmin && $this->isPostSet('uploadFile') && $this->getPostValue('groupId') > 0) {
$group = null;
foreach ($this->consultation->fileGroups as $fileGroup) {
if ($fileGroup->id === (int)$this->getPostValue('groupId')) {
$group = $fileGroup;
}
}
if (isset($_FILES['uploadedFile']) && $_FILES['uploadedFile']['error'] === 0 && $_FILES['uploadedFile']['size'] > 0) {
ConsultationFile::createDownloadableFile(
$this->consultation,
User::getCurrentUser(),
(string)file_get_contents($_FILES['uploadedFile']['tmp_name']),
$_FILES['uploadedFile']['name'],
$this->getPostValue('fileTitle'),
$group
);
$this->getHttpSession()->setFlash('success', \Yii::t('pages', 'documents_uploaded_file'));
return new RedirectResponse(UrlHelper::createUrl('/pages/documents'));
}
}
if ($iAmAdmin && $this->isPostSet('deleteFile')) {
$toDeleteIds = array_map('intval', array_keys($this->getPostValue('deleteFile', [])));
$found = false;
foreach ($this->consultation->files as $file) {
if (in_array($file->id, $toDeleteIds)) {
$file->delete();
$found = true;
}
}
if ($found) {
$this->getHttpSession()->setFlash('success', \Yii::t('pages', 'documents_file_deleted'));
return new RedirectResponse(UrlHelper::createUrl('/pages/documents'));
}
}
if ($iAmAdmin && $this->isPostSet('sortDocuments') && $this->isPostSet('document')) {
$idPosition = [];
foreach ($this->getPostValue('document') as $pos => $documentId) {
$idPosition[(int)$documentId] = $pos;
}
foreach ($this->consultation->fileGroups as $fileGroup) {
if (isset($idPosition[$fileGroup->id])) {
$fileGroup->position = $idPosition[$fileGroup->id];
$fileGroup->save();
}
}
return new RedirectResponse(UrlHelper::createUrl('/pages/documents'));
}
return new HtmlResponse($this->render('documents'));
}
public function actionDocumentsZip(string $groupId): BinaryFileResponse
{
$zip = new ZipWriter();
$zipName = 'documents';
foreach ($this->consultation->fileGroups as $fileGroup) {
if ($fileGroup->consultationTextId) {
continue;
}
$directory = Tools::sanitizeFilename($fileGroup->title, true);
if ($fileGroup->id === intval($groupId)) {
$zipName = Tools::sanitizeFilename($fileGroup->title, false);
}
if ($fileGroup->id === intval($groupId) || $groupId === 'all') {
foreach ($fileGroup->files as $file) {
$filename = $directory . '/' . $file->filename;
$zip->addFile($filename, $file->data);
}
}
}
return new BinaryFileResponse(
BinaryFileResponse::TYPE_ZIP,
$zip->getContentAndFlush(),
true,
$zipName,
false
);
}
}