Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.24% covered (success)
90.24%
37 / 41
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
BookmarkController
90.24% covered (success)
90.24%
37 / 41
0.00% covered (danger)
0.00%
0 / 3
13.16
0.00% covered (danger)
0.00%
0 / 1
 create
93.75% covered (success)
93.75%
15 / 16
0.00% covered (danger)
0.00%
0 / 1
5.01
 delete
87.50% covered (success)
87.50%
14 / 16
0.00% covered (danger)
0.00%
0 / 1
5.05
 deleteAll
88.89% covered (success)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
3.01
1<?php
2
3/**
4 * The Bookmark Controller
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public License,
7 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
8 * obtain one at https://mozilla.org/MPL/2.0/.
9 *
10 * @package   phpMyFAQ
11 * @author    Thorsten Rinne <thorsten@phpmyfaq.de>
12 * @copyright 2023-2026 phpMyFAQ Team
13 * @license   https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
14 * @link      https://www.phpmyfaq.de
15 * @since     2023-09-17
16 */
17
18declare(strict_types=1);
19
20namespace phpMyFAQ\Controller\Frontend\Api;
21
22use Exception;
23use JsonException;
24use phpMyFAQ\Bookmark;
25use phpMyFAQ\Controller\AbstractController;
26use phpMyFAQ\Filter;
27use phpMyFAQ\Session\Token;
28use phpMyFAQ\Translation;
29use Symfony\Component\HttpFoundation\JsonResponse;
30use Symfony\Component\HttpFoundation\Request;
31use Symfony\Component\HttpFoundation\Response;
32use Symfony\Component\Routing\Attribute\Route;
33
34final class BookmarkController extends AbstractController
35{
36    /**
37     * @throws JsonException
38     * @throws Exception
39     */
40    #[Route(path: 'bookmark/create', name: 'api.private.bookmark.create', methods: ['POST'])]
41    public function create(Request $request): JsonResponse
42    {
43        $this->userIsAuthenticated();
44
45        $data = json_decode($request->getContent(), associative: false, depth: 512, flags: JSON_THROW_ON_ERROR);
46        $bookmarkId = Filter::filterVar($data->id ?? null, FILTER_VALIDATE_INT);
47        $csrfToken = Filter::filterVar($data->csrfToken ?? null, FILTER_SANITIZE_SPECIAL_CHARS);
48
49        if (!Token::getInstance($this->session)->verifyToken('add-bookmark', $csrfToken)) {
50            return $this->json(['error' => Translation::get(key: 'ad_msg_noauth')], Response::HTTP_UNAUTHORIZED);
51        }
52
53        if ($bookmarkId === null || $bookmarkId < 1) {
54            return $this->json(['error' => Translation::get(key: 'msgError')], Response::HTTP_BAD_REQUEST);
55        }
56
57        $bookmark = new Bookmark($this->configuration, $this->currentUser);
58
59        if ($bookmark->add($bookmarkId)) {
60            return $this->json([
61                'success' => Translation::get(key: 'msgBookmarkAdded'),
62                'linkText' => Translation::get(key: 'removeBookmark'),
63                'csrfToken' => Token::getInstance($this->session)->getTokenString('delete-bookmark'),
64            ], Response::HTTP_OK);
65        }
66
67        return $this->json(['error' => Translation::get(key: 'msgError')], Response::HTTP_BAD_REQUEST);
68    }
69
70    /**
71     * @throws JsonException
72     * @throws Exception
73     */
74    #[Route(path: 'api/bookmark/delete', name: 'api.private.bookmark.delete', methods: ['DELETE'])]
75    public function delete(Request $request): JsonResponse
76    {
77        $this->userIsAuthenticated();
78
79        $data = json_decode($request->getContent(), associative: false, depth: 512, flags: JSON_THROW_ON_ERROR);
80        $bookmarkId = Filter::filterVar($data->id ?? null, FILTER_VALIDATE_INT);
81        $csrfToken = Filter::filterVar($data->csrfToken ?? null, FILTER_SANITIZE_SPECIAL_CHARS);
82
83        if (!Token::getInstance($this->session)->verifyToken('delete-bookmark', $csrfToken)) {
84            return $this->json(['error' => Translation::get(key: 'ad_msg_noauth')], Response::HTTP_UNAUTHORIZED);
85        }
86
87        if ($bookmarkId === null || $bookmarkId < 1) {
88            return $this->json(['error' => Translation::get(key: 'msgError')], Response::HTTP_BAD_REQUEST);
89        }
90
91        $bookmark = new Bookmark($this->configuration, $this->currentUser);
92
93        if ($bookmark->remove($bookmarkId)) {
94            return $this->json([
95                'success' => Translation::get(key: 'msgBookmarkRemoved'),
96                'linkText' => Translation::get(key: 'msgAddBookmark'),
97                'csrfToken' => Token::getInstance($this->session)->getTokenString('add-bookmark'),
98            ], Response::HTTP_OK);
99        }
100
101        return $this->json(['error' => Translation::get(key: 'msgError')], Response::HTTP_BAD_REQUEST);
102    }
103
104    /**
105     * @throws JsonException
106     * @throws Exception
107     */
108    #[Route(path: 'api/bookmark/delete-all', name: 'api.private.bookmark.delete-all', methods: ['DELETE'])]
109    public function deleteAll(Request $request): JsonResponse
110    {
111        $this->userIsAuthenticated();
112
113        $data = json_decode($request->getContent(), associative: false, depth: 512, flags: JSON_THROW_ON_ERROR);
114        $csrfToken = Filter::filterVar($data->csrfToken ?? null, FILTER_SANITIZE_SPECIAL_CHARS);
115
116        if (!Token::getInstance($this->session)->verifyToken('delete-all-bookmarks', $csrfToken)) {
117            return $this->json(['error' => Translation::get(key: 'ad_msg_noauth')], Response::HTTP_UNAUTHORIZED);
118        }
119
120        $bookmark = new Bookmark($this->configuration, $this->currentUser);
121
122        if ($bookmark->removeAll()) {
123            return $this->json(['success' => Translation::get(key: 'msgBookmarkRemoved')], Response::HTTP_OK);
124        }
125
126        return $this->json(['error' => Translation::get(key: 'msgError')], Response::HTTP_BAD_REQUEST);
127    }
128}