diff --git a/nginx.conf b/nginx.conf index 800c4f34c1..f30c231489 100644 --- a/nginx.conf +++ b/nginx.conf @@ -129,7 +129,7 @@ server { rewrite admin/api/(.*) /admin/api/index.php last; # Administration pages - rewrite admin/(attachments|backup|configuration|elasticsearch|export|group|import|instance|instances|password|session-keep-alive|statistics|stopwords|system|update|user) /admin/front.php last; + rewrite admin/(attachments|backup|configuration|elasticsearch|export|group|import|instance|instances|password|session-keep-alive|statistics|stopwords|system|tags|update|user) /admin/front.php last; # REST API v3.0 and v3.1 rewrite ^api/v3\.[01]/(.*) /api/index.php last; diff --git a/package.json b/package.json index fc0f3fd51a..212508491d 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "type": "module", "scripts": { "build": "vite build", - "build:watch": "vite build --watch", + "build:watch": "vite build --watch -d", "build:prod": "vite build", "lint": "prettier --check .", "lint:fix": "prettier --write .", diff --git a/phpmyfaq/.htaccess b/phpmyfaq/.htaccess index 07ce5fd43b..dc878ee15e 100644 --- a/phpmyfaq/.htaccess +++ b/phpmyfaq/.htaccess @@ -143,7 +143,7 @@ Header set Access-Control-Allow-Headers "Content-Type, Authorization" # Administration API RewriteRule ^admin/api/(.*) admin/api/index.php [L,QSA] # Administration pages - RewriteRule ^admin/(attachments|backup|configuration|elasticsearch|export|group|import|instance|instances|password|session-keep-alive|statistics|stopwords|system|update|user) admin/front.php [L,QSA] + RewriteRule ^admin/(attachments|backup|configuration|elasticsearch|export|group|import|instance|instances|password|session-keep-alive|statistics|stopwords|system|tags|update|user) admin/front.php [L,QSA] # Private APIs RewriteRule ^api/(autocomplete|bookmark/delete|bookmark/create|user/data/update|user/password/update|user/request-removal|user/remove-twofactor|contact|voting|register|captcha|share|comment/create|faq/create|question/create|webauthn/prepare|webauthn/register|webauthn/prepare-login|webauthn/login) api/index.php [L,QSA] # Setup APIs diff --git a/phpmyfaq/admin/assets/src/api/tags.js b/phpmyfaq/admin/assets/src/api/tags.js index df1064c70d..b50d7c659e 100644 --- a/phpmyfaq/admin/assets/src/api/tags.js +++ b/phpmyfaq/admin/assets/src/api/tags.js @@ -39,3 +39,30 @@ export const fetchTags = async (searchString) => { throw error; } }; + +export const deleteTag = async (tagId) => { + try { + const response = await fetch(`./api/content/tags/${tagId}`, { + method: 'DELETE', + cache: 'no-cache', + headers: { + 'Content-Type': 'application/json', + }, + redirect: 'follow', + referrerPolicy: 'no-referrer', + }); + + if (response.ok) { + return await response.json(); + } else { + throw new Error('Network response was not ok: ', { cause: { response } }); + } + } catch (error) { + console.error('Error deleting tag:', error); + if (error.cause && error.cause.response) { + const errorMessage = await error.cause.response.json(); + console.error(errorMessage); + } + throw error; + } +}; diff --git a/phpmyfaq/admin/assets/src/content/tags.js b/phpmyfaq/admin/assets/src/content/tags.js index d421d01f6d..525708dd93 100644 --- a/phpmyfaq/admin/assets/src/content/tags.js +++ b/phpmyfaq/admin/assets/src/content/tags.js @@ -15,10 +15,12 @@ import autocomplete from 'autocompleter'; import { addElement } from '../../../../assets/src/utils'; -import { fetchTags } from '../api'; +import { deleteTag, fetchTags } from '../api'; +import { pushNotification } from '../utils/index.js'; export const handleTags = () => { const editTagButtons = document.querySelectorAll('.btn-edit'); + const deleteButtons = document.querySelectorAll('.btn-delete'); const tagForm = document.getElementById('tag-form'); const tagsAutocomplete = document.querySelector('.pmf-tags-autocomplete'); @@ -51,6 +53,23 @@ export const handleTags = () => { }); } + if (deleteButtons) { + deleteButtons.forEach((element) => { + element.addEventListener('click', async (event) => { + const tagId = event.target.getAttribute('data-pmf-id'); + + const response = await deleteTag(tagId); + if (response.success) { + pushNotification(response.success); + const row = document.getElementById(`pmf-row-tag-id-${tagId}`); + row.remove(); + } else { + throw new Error('Network response was not ok: ' + JSON.stringify(response.error)); + } + }); + }); + } + if (tagForm) { tagForm.addEventListener('submit', (event) => { event.preventDefault(); diff --git a/phpmyfaq/admin/header.php b/phpmyfaq/admin/header.php index 04fc0475d6..ca921381e0 100644 --- a/phpmyfaq/admin/header.php +++ b/phpmyfaq/admin/header.php @@ -90,7 +90,8 @@ $secLevelEntries['content'] .= $adminHelper->addMenuEntry( PermissionType::FAQ_EDIT->value, 'tags', - 'ad_entry_tags' + 'ad_entry_tags', + 'tags' ); $secLevelEntries['content'] .= $adminHelper->addMenuEntry( 'addglossary+editglossary+delglossary', @@ -201,8 +202,6 @@ case 'takequestion': case 'comments': case 'attachments': - case 'tags': - case 'delete-tag': case 'stickyfaqs': $contentPage = true; break; diff --git a/phpmyfaq/admin/index.php b/phpmyfaq/admin/index.php index 617b9ca019..ecaee48ed1 100755 --- a/phpmyfaq/admin/index.php +++ b/phpmyfaq/admin/index.php @@ -276,11 +276,6 @@ case 'stickyfaqs': require 'stickyfaqs.php'; break; - // functions for tags - case 'tags': - case 'delete-tag': - require 'tags.php'; - break; // news administration case 'news': case 'add-news': diff --git a/phpmyfaq/admin/tags.php b/phpmyfaq/admin/tags.php deleted file mode 100644 index d759419485..0000000000 --- a/phpmyfaq/admin/tags.php +++ /dev/null @@ -1,72 +0,0 @@ - - * @copyright 2003-2024 phpMyFAQ Team - * @license https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0 - * @link https://www.phpmyfaq.de - * @since 2003-02-24 - */ - -use phpMyFAQ\Configuration; -use phpMyFAQ\Enums\PermissionType; -use phpMyFAQ\Filter; -use phpMyFAQ\Session\Token; -use phpMyFAQ\Tags; -use phpMyFAQ\Template\TwigWrapper; -use phpMyFAQ\Translation; -use phpMyFAQ\User\CurrentUser; - -if (!defined('IS_VALID_PHPMYFAQ')) { - http_response_code(400); - exit(); -} - -$faqConfig = Configuration::getConfigurationInstance(); -$user = CurrentUser::getCurrentUser($faqConfig); - -$tagId = Filter::filterInput(INPUT_GET, 'id', FILTER_VALIDATE_INT); - -$twig = new TwigWrapper(PMF_ROOT_DIR . '/assets/templates'); -$template = $twig->loadTemplate('@admin/content/tags.twig'); - -$tags = new Tags($faqConfig); - -if ('delete-tag' === $action) { - $tagId = Filter::filterInput(INPUT_GET, 'id', FILTER_VALIDATE_INT); - if ($tags->delete($tagId)) { - $deleteSuccess = true; - } else { - $deleteSuccess = false; - } -} - -$tagData = $tags->getAllTags(); - -$templateVars = [ - 'adminHeaderTags' => Translation::get('ad_entry_tags'), - 'csrfToken' => Token::getInstance($container->get('session'))->getTokenInput('tags'), - 'isDelete' => 'delete-tag' === $action, - 'isDeleteSuccess' => $deleteSuccess ?? false, - 'msgDeleteSuccess' => Translation::get('ad_tag_delete_success'), - 'msgDeleteError' => Translation::get('ad_tag_delete_error'), - 'tags' => $tagData, - 'noTags' => Translation::get('ad_news_nodata'), - 'buttonEdit' => Translation::get('ad_user_edit'), - 'msgConfirm' => Translation::get('ad_user_del_3'), - 'buttonDelete' => Translation::get('msgDelete'), -]; - -echo $template->render($templateVars); - -if (!$user->perm->hasPermission($user->getUserId(), PermissionType::FAQ_EDIT->value)) { - require __DIR__ . '/no-permission.php'; -} - diff --git a/phpmyfaq/assets/templates/admin/content/tags.twig b/phpmyfaq/assets/templates/admin/content/tags.twig index 6cffd16009..1fdb6ce2fa 100644 --- a/phpmyfaq/assets/templates/admin/content/tags.twig +++ b/phpmyfaq/assets/templates/admin/content/tags.twig @@ -1,58 +1,61 @@ -
-

- - {{ adminHeaderTags }} -

-
- - -
-
-
- {{ csrfToken | raw }} - - {% if isDelete %} - {% if isDeleteSuccess %} - - {% else %} -
+{% endblock %} diff --git a/phpmyfaq/src/admin-api-routes.php b/phpmyfaq/src/admin-api-routes.php index 97b11f2a0d..b3d6fa7e1e 100644 --- a/phpmyfaq/src/admin-api-routes.php +++ b/phpmyfaq/src/admin-api-routes.php @@ -362,6 +362,11 @@ 'controller' => [TagController::class, 'search'], 'methods' => 'GET' ], + 'admin.api.content.tags.id' => [ + 'path' => '/content/tags/{tagId}', + 'controller' => [TagController::class, 'delete'], + 'methods' => 'GET' + ], // Update API 'admin.api.health-check' => [ 'path' => '/health-check', diff --git a/phpmyfaq/src/admin-routes.php b/phpmyfaq/src/admin-routes.php index 19cbcf6a9c..0b2b929599 100644 --- a/phpmyfaq/src/admin-routes.php +++ b/phpmyfaq/src/admin-routes.php @@ -32,6 +32,7 @@ use phpMyFAQ\Controller\Administration\StatisticsSessionsController; use phpMyFAQ\Controller\Administration\StopWordsController; use phpMyFAQ\Controller\Administration\SystemInformationController; +use phpMyFAQ\Controller\Administration\TagController; use phpMyFAQ\Controller\Administration\UpdateController; use phpMyFAQ\Controller\Administration\UserController; use Symfony\Component\Routing\Route; @@ -195,6 +196,11 @@ 'controller' => [SystemInformationController::class, 'index'], 'methods' => 'GET' ], + 'admin.tags' => [ + 'path' => '/tags', + 'controller' => [TagController::class, 'index'], + 'methods' => 'GET' + ], 'admin.update' => [ 'path' => '/update', 'controller' => [UpdateController::class, 'index'], diff --git a/phpmyfaq/src/phpMyFAQ/Controller/Administration/AbstractAdministrationController.php b/phpmyfaq/src/phpMyFAQ/Controller/Administration/AbstractAdministrationController.php index f55665834d..6734f5e25d 100644 --- a/phpmyfaq/src/phpMyFAQ/Controller/Administration/AbstractAdministrationController.php +++ b/phpmyfaq/src/phpMyFAQ/Controller/Administration/AbstractAdministrationController.php @@ -260,6 +260,7 @@ protected function getHeader(Request $request): array $userPage = true; break; case 'admin.attachments': + case 'admin.tags': $contentPage = true; break; case 'admin.statistics.admin-log': diff --git a/phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/FaqController.php b/phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/FaqController.php index f7557e6729..5a7c662556 100644 --- a/phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/FaqController.php +++ b/phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/FaqController.php @@ -278,7 +278,7 @@ public function update(Request $request): JsonResponse $data = json_decode($request->getContent())->data; - if (!Token::getInstance()->verifyToken('edit-faq', $data->{'pmf-csrf-token'})) { + if (!Token::getInstance($this->container->get('session'))->verifyToken('edit-faq', $data->{'pmf-csrf-token'})) { return $this->json(['error' => Translation::get('msgNoPermission')], Response::HTTP_UNAUTHORIZED); } diff --git a/phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/TagController.php b/phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/TagController.php index d71db00918..49a38c93de 100644 --- a/phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/TagController.php +++ b/phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/TagController.php @@ -25,7 +25,6 @@ use phpMyFAQ\Session\Token; use phpMyFAQ\Tags; use phpMyFAQ\Translation; -use phpMyFAQ\User\CurrentUser; use stdClass; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; @@ -42,8 +41,6 @@ public function update(Request $request): JsonResponse { $this->userHasPermission(PermissionType::FAQ_EDIT); - $tags = new Tags($this->configuration); - $postData = json_decode($request->getContent()); if (!Token::getInstance($this->container->get('session'))->verifyToken('tags', $postData->csrf)) { @@ -57,7 +54,7 @@ public function update(Request $request): JsonResponse $tagEntity->setId($id); $tagEntity->setName($newTag); - if ($tags->update($tagEntity)) { + if ($this->container->get('phpmyfaq.tags')->update($tagEntity)) { return $this->json(['updated' => Translation::get('ad_entryins_suc')], Response::HTTP_OK); } else { return $this->json(['error' => Translation::get('msgErrorOccurred')], Response::HTTP_BAD_REQUEST); @@ -72,8 +69,7 @@ public function search(Request $request): JsonResponse { $this->userIsAuthenticated(); - $user = CurrentUser::getCurrentUser($this->configuration); - $tag = new Tags($this->configuration); + $tag = $this->container->get('phpmyfaq.tags'); $autoCompleteValue = Filter::filterVar($request->query->get('search'), FILTER_SANITIZE_SPECIAL_CHARS); @@ -92,7 +88,7 @@ public function search(Request $request): JsonResponse $tags = $tag->getAllTags(); } - if ($user->perm->hasPermission($user->getUserId(), PermissionType::FAQ_EDIT)) { + if ($this->currentUser->perm->hasPermission($this->currentUser->getUserId(), PermissionType::FAQ_EDIT)) { $numTags = 0; $tagNames = []; foreach ($tags as $tag) { @@ -109,4 +105,21 @@ public function search(Request $request): JsonResponse return $this->json([], Response::HTTP_OK); } + + /** + * @throws \Exception + */ + #[Route('admin/api/content/tag/:tagId')] + public function delete(Request $request): JsonResponse + { + $this->userIsAuthenticated(); + + $tagId = Filter::filterVar($request->get('tagId'), FILTER_VALIDATE_INT); + + if ($this->container->get('phpmyfaq.tags')->delete($tagId)) { + return $this->json(['success' => Translation::get('ad_tag_delete_success')], Response::HTTP_OK); + } else { + return $this->json(['error' => Translation::get('ad_tag_delete_error')], Response::HTTP_BAD_REQUEST); + } + } } diff --git a/phpmyfaq/src/phpMyFAQ/Controller/Administration/TagController.php b/phpmyfaq/src/phpMyFAQ/Controller/Administration/TagController.php new file mode 100644 index 0000000000..daa93bed98 --- /dev/null +++ b/phpmyfaq/src/phpMyFAQ/Controller/Administration/TagController.php @@ -0,0 +1,44 @@ +userIsAuthenticated(); + + $tagData = $this->container->get('phpmyfaq.tags')->getAllTags(); + + return $this->render( + '@admin/content/tags.twig', + [ + ... $this->getHeader($request), + ... $this->getFooter(), + 'adminHeaderTags' => Translation::get('ad_entry_tags'), + 'csrfToken' => Token::getInstance($this->container->get('session'))->getTokenInput('tags'), + 'tags' => $tagData, + 'noTags' => Translation::get('ad_news_nodata'), + 'buttonEdit' => Translation::get('ad_user_edit'), + 'msgConfirm' => Translation::get('ad_user_del_3'), + 'buttonDelete' => Translation::get('msgDelete'), + ] + ); + } +}