diff options
-rw-r--r-- | application/front/controller/admin/ConfigureController.php | 2 | ||||
-rw-r--r-- | application/front/controller/admin/ManageTagController.php | 87 | ||||
-rw-r--r-- | assets/default/js/base.js | 6 | ||||
-rw-r--r-- | assets/default/scss/shaarli.scss | 4 | ||||
-rw-r--r-- | index.php | 35 | ||||
-rw-r--r-- | tests/front/controller/admin/ManageTagControllerTest.php | 272 | ||||
-rw-r--r-- | tpl/default/page.header.html | 6 | ||||
-rw-r--r-- | tpl/default/tag.list.html | 2 | ||||
-rw-r--r-- | tpl/default/tools.html | 2 | ||||
-rw-r--r-- | tpl/vintage/tools.html | 2 |
10 files changed, 376 insertions, 42 deletions
diff --git a/application/front/controller/admin/ConfigureController.php b/application/front/controller/admin/ConfigureController.php index b1d32270..5a482d8e 100644 --- a/application/front/controller/admin/ConfigureController.php +++ b/application/front/controller/admin/ConfigureController.php | |||
@@ -12,7 +12,7 @@ use Slim\Http\Response; | |||
12 | use Throwable; | 12 | use Throwable; |
13 | 13 | ||
14 | /** | 14 | /** |
15 | * Class PasswordController | 15 | * Class ConfigureController |
16 | * | 16 | * |
17 | * Slim controller used to handle Shaarli configuration page (display + save new config). | 17 | * Slim controller used to handle Shaarli configuration page (display + save new config). |
18 | */ | 18 | */ |
diff --git a/application/front/controller/admin/ManageTagController.php b/application/front/controller/admin/ManageTagController.php new file mode 100644 index 00000000..e015e613 --- /dev/null +++ b/application/front/controller/admin/ManageTagController.php | |||
@@ -0,0 +1,87 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Shaarli\Bookmark\BookmarkFilter; | ||
8 | use Slim\Http\Request; | ||
9 | use Slim\Http\Response; | ||
10 | |||
11 | /** | ||
12 | * Class ManageTagController | ||
13 | * | ||
14 | * Slim controller used to handle Shaarli manage tags page (rename and delete tags). | ||
15 | */ | ||
16 | class ManageTagController extends ShaarliAdminController | ||
17 | { | ||
18 | /** | ||
19 | * GET /manage-tags - Displays the manage tags page | ||
20 | */ | ||
21 | public function index(Request $request, Response $response): Response | ||
22 | { | ||
23 | $fromTag = $request->getParam('fromtag') ?? ''; | ||
24 | |||
25 | $this->assignView('fromtag', escape($fromTag)); | ||
26 | $this->assignView( | ||
27 | 'pagetitle', | ||
28 | t('Manage tags') .' - '. $this->container->conf->get('general.title', 'Shaarli') | ||
29 | ); | ||
30 | |||
31 | return $response->write($this->render('changetag')); | ||
32 | } | ||
33 | |||
34 | /** | ||
35 | * POST /manage-tags - Update or delete provided tag | ||
36 | */ | ||
37 | public function save(Request $request, Response $response): Response | ||
38 | { | ||
39 | $this->checkToken($request); | ||
40 | |||
41 | $isDelete = null !== $request->getParam('deletetag') && null === $request->getParam('renametag'); | ||
42 | |||
43 | $fromTag = escape(trim($request->getParam('fromtag') ?? '')); | ||
44 | $toTag = escape(trim($request->getParam('totag') ?? '')); | ||
45 | |||
46 | if (0 === strlen($fromTag) || false === $isDelete && 0 === strlen($toTag)) { | ||
47 | $this->saveWarningMessage(t('Invalid tags provided.')); | ||
48 | |||
49 | return $response->withRedirect('./manage-tags'); | ||
50 | } | ||
51 | |||
52 | // TODO: move this to bookmark service | ||
53 | $count = 0; | ||
54 | $bookmarks = $this->container->bookmarkService->search(['searchtags' => $fromTag], BookmarkFilter::$ALL, true); | ||
55 | foreach ($bookmarks as $bookmark) { | ||
56 | if (false === $isDelete) { | ||
57 | $bookmark->renameTag($fromTag, $toTag); | ||
58 | } else { | ||
59 | $bookmark->deleteTag($fromTag); | ||
60 | } | ||
61 | |||
62 | $this->container->bookmarkService->set($bookmark, false); | ||
63 | $this->container->history->updateLink($bookmark); | ||
64 | $count++; | ||
65 | } | ||
66 | |||
67 | $this->container->bookmarkService->save(); | ||
68 | |||
69 | if (true === $isDelete) { | ||
70 | $alert = sprintf( | ||
71 | t('The tag was removed from %d bookmark.', 'The tag was removed from %d bookmarks.', $count), | ||
72 | $count | ||
73 | ); | ||
74 | } else { | ||
75 | $alert = sprintf( | ||
76 | t('The tag was renamed in %d bookmark.', 'The tag was renamed in %d bookmarks.', $count), | ||
77 | $count | ||
78 | ); | ||
79 | } | ||
80 | |||
81 | $this->saveSuccessMessage($alert); | ||
82 | |||
83 | $redirect = true === $isDelete ? './manage-tags' : './?searchtags='. urlencode($toTag); | ||
84 | |||
85 | return $response->withRedirect($redirect); | ||
86 | } | ||
87 | } | ||
diff --git a/assets/default/js/base.js b/assets/default/js/base.js index f61cfa92..8cc7eed5 100644 --- a/assets/default/js/base.js +++ b/assets/default/js/base.js | |||
@@ -546,7 +546,7 @@ function init(description) { | |||
546 | const refreshedToken = document.getElementById('token').value; | 546 | const refreshedToken = document.getElementById('token').value; |
547 | const fromtag = block.getAttribute('data-tag'); | 547 | const fromtag = block.getAttribute('data-tag'); |
548 | const xhr = new XMLHttpRequest(); | 548 | const xhr = new XMLHttpRequest(); |
549 | xhr.open('POST', './?do=changetag'); | 549 | xhr.open('POST', './manage-tags'); |
550 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); | 550 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); |
551 | xhr.onload = () => { | 551 | xhr.onload = () => { |
552 | if (xhr.status !== 200) { | 552 | if (xhr.status !== 200) { |
@@ -559,7 +559,7 @@ function init(description) { | |||
559 | findParent(input, 'div', { class: 'rename-tag-form' }).style.display = 'none'; | 559 | findParent(input, 'div', { class: 'rename-tag-form' }).style.display = 'none'; |
560 | block.querySelector('a.tag-link').innerHTML = htmlEntities(totag); | 560 | block.querySelector('a.tag-link').innerHTML = htmlEntities(totag); |
561 | block.querySelector('a.tag-link').setAttribute('href', `./?searchtags=${encodeURIComponent(totag)}`); | 561 | block.querySelector('a.tag-link').setAttribute('href', `./?searchtags=${encodeURIComponent(totag)}`); |
562 | block.querySelector('a.rename-tag').setAttribute('href', `./?do=changetag&fromtag=${encodeURIComponent(totag)}`); | 562 | block.querySelector('a.rename-tag').setAttribute('href', `./manage-tags?fromtag=${encodeURIComponent(totag)}`); |
563 | 563 | ||
564 | // Refresh awesomplete values | 564 | // Refresh awesomplete values |
565 | existingTags = existingTags.map(tag => (tag === fromtag ? totag : tag)); | 565 | existingTags = existingTags.map(tag => (tag === fromtag ? totag : tag)); |
@@ -593,7 +593,7 @@ function init(description) { | |||
593 | 593 | ||
594 | if (confirm(`Are you sure you want to delete the tag "${tag}"?`)) { | 594 | if (confirm(`Are you sure you want to delete the tag "${tag}"?`)) { |
595 | const xhr = new XMLHttpRequest(); | 595 | const xhr = new XMLHttpRequest(); |
596 | xhr.open('POST', './?do=changetag'); | 596 | xhr.open('POST', './manage-tags'); |
597 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); | 597 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); |
598 | xhr.onload = () => { | 598 | xhr.onload = () => { |
599 | block.remove(); | 599 | block.remove(); |
diff --git a/assets/default/scss/shaarli.scss b/assets/default/scss/shaarli.scss index 243ab1b2..759dff29 100644 --- a/assets/default/scss/shaarli.scss +++ b/assets/default/scss/shaarli.scss | |||
@@ -490,6 +490,10 @@ body, | |||
490 | } | 490 | } |
491 | } | 491 | } |
492 | 492 | ||
493 | .header-alert-message { | ||
494 | text-align: center; | ||
495 | } | ||
496 | |||
493 | // CONTENT - GENERAL | 497 | // CONTENT - GENERAL |
494 | .container { | 498 | .container { |
495 | position: relative; | 499 | position: relative; |
@@ -519,38 +519,7 @@ function renderPage($conf, $pluginManager, $bookmarkService, $history, $sessionM | |||
519 | 519 | ||
520 | // -------- User wants to rename a tag or delete it | 520 | // -------- User wants to rename a tag or delete it |
521 | if ($targetPage == Router::$PAGE_CHANGETAG) { | 521 | if ($targetPage == Router::$PAGE_CHANGETAG) { |
522 | if (empty($_POST['fromtag']) || (empty($_POST['totag']) && isset($_POST['renametag']))) { | 522 | header('./manage-tags'); |
523 | $PAGE->assign('fromtag', ! empty($_GET['fromtag']) ? escape($_GET['fromtag']) : ''); | ||
524 | $PAGE->assign('pagetitle', t('Manage tags') .' - '. $conf->get('general.title', 'Shaarli')); | ||
525 | $PAGE->renderPage('changetag'); | ||
526 | exit; | ||
527 | } | ||
528 | |||
529 | if (!$sessionManager->checkToken($_POST['token'])) { | ||
530 | die(t('Wrong token.')); | ||
531 | } | ||
532 | |||
533 | $toTag = isset($_POST['totag']) ? escape($_POST['totag']) : null; | ||
534 | $fromTag = escape($_POST['fromtag']); | ||
535 | $count = 0; | ||
536 | $bookmarks = $bookmarkService->search(['searchtags' => $fromTag], BookmarkFilter::$ALL, true); | ||
537 | foreach ($bookmarks as $bookmark) { | ||
538 | if ($toTag) { | ||
539 | $bookmark->renameTag($fromTag, $toTag); | ||
540 | } else { | ||
541 | $bookmark->deleteTag($fromTag); | ||
542 | } | ||
543 | $bookmarkService->set($bookmark, false); | ||
544 | $history->updateLink($bookmark); | ||
545 | $count++; | ||
546 | } | ||
547 | $bookmarkService->save(); | ||
548 | $delete = empty($_POST['totag']); | ||
549 | $redirect = $delete ? './do=changetag' : 'searchtags='. urlencode(escape($_POST['totag'])); | ||
550 | $alert = $delete | ||
551 | ? sprintf(t('The tag was removed from %d link.', 'The tag was removed from %d bookmarks.', $count), $count) | ||
552 | : sprintf(t('The tag was renamed in %d link.', 'The tag was renamed in %d bookmarks.', $count), $count); | ||
553 | echo '<script>alert("'. $alert .'");document.location=\'?'. $redirect .'\';</script>'; | ||
554 | exit; | 523 | exit; |
555 | } | 524 | } |
556 | 525 | ||
@@ -1380,6 +1349,8 @@ $app->group('', function () { | |||
1380 | $this->post('/password', '\Shaarli\Front\Controller\Admin\PasswordController:change')->setName('changePassword'); | 1349 | $this->post('/password', '\Shaarli\Front\Controller\Admin\PasswordController:change')->setName('changePassword'); |
1381 | $this->get('/configure', '\Shaarli\Front\Controller\Admin\ConfigureController:index')->setName('configure'); | 1350 | $this->get('/configure', '\Shaarli\Front\Controller\Admin\ConfigureController:index')->setName('configure'); |
1382 | $this->post('/configure', '\Shaarli\Front\Controller\Admin\ConfigureController:save')->setName('saveConfigure'); | 1351 | $this->post('/configure', '\Shaarli\Front\Controller\Admin\ConfigureController:save')->setName('saveConfigure'); |
1352 | $this->get('/manage-tags', '\Shaarli\Front\Controller\Admin\ManageTagController:index')->setName('manageTag'); | ||
1353 | $this->post('/manage-tags', '\Shaarli\Front\Controller\Admin\ManageTagController:save')->setName('saveManageTag'); | ||
1383 | 1354 | ||
1384 | $this | 1355 | $this |
1385 | ->get('/links-per-page', '\Shaarli\Front\Controller\Admin\SessionFilterController:linksPerPage') | 1356 | ->get('/links-per-page', '\Shaarli\Front\Controller\Admin\SessionFilterController:linksPerPage') |
diff --git a/tests/front/controller/admin/ManageTagControllerTest.php b/tests/front/controller/admin/ManageTagControllerTest.php new file mode 100644 index 00000000..eed99231 --- /dev/null +++ b/tests/front/controller/admin/ManageTagControllerTest.php | |||
@@ -0,0 +1,272 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use PHPUnit\Framework\TestCase; | ||
8 | use Shaarli\Bookmark\Bookmark; | ||
9 | use Shaarli\Bookmark\BookmarkFilter; | ||
10 | use Shaarli\Front\Exception\WrongTokenException; | ||
11 | use Shaarli\Security\SessionManager; | ||
12 | use Slim\Http\Request; | ||
13 | use Slim\Http\Response; | ||
14 | |||
15 | class ManageTagControllerTest extends TestCase | ||
16 | { | ||
17 | use FrontAdminControllerMockHelper; | ||
18 | |||
19 | /** @var ManageTagController */ | ||
20 | protected $controller; | ||
21 | |||
22 | public function setUp(): void | ||
23 | { | ||
24 | $this->createContainer(); | ||
25 | |||
26 | $this->controller = new ManageTagController($this->container); | ||
27 | } | ||
28 | |||
29 | /** | ||
30 | * Test displaying manage tag page | ||
31 | */ | ||
32 | public function testIndex(): void | ||
33 | { | ||
34 | $assignedVariables = []; | ||
35 | $this->assignTemplateVars($assignedVariables); | ||
36 | |||
37 | $request = $this->createMock(Request::class); | ||
38 | $request->method('getParam')->with('fromtag')->willReturn('fromtag'); | ||
39 | $response = new Response(); | ||
40 | |||
41 | $result = $this->controller->index($request, $response); | ||
42 | |||
43 | static::assertSame(200, $result->getStatusCode()); | ||
44 | static::assertSame('changetag', (string) $result->getBody()); | ||
45 | |||
46 | static::assertSame('fromtag', $assignedVariables['fromtag']); | ||
47 | static::assertSame('Manage tags - Shaarli', $assignedVariables['pagetitle']); | ||
48 | } | ||
49 | |||
50 | /** | ||
51 | * Test posting a tag update - rename tag - valid info provided. | ||
52 | */ | ||
53 | public function testSaveRenameTagValid(): void | ||
54 | { | ||
55 | $session = []; | ||
56 | $this->assignSessionVars($session); | ||
57 | |||
58 | $requestParameters = [ | ||
59 | 'renametag' => 'rename', | ||
60 | 'fromtag' => 'old-tag', | ||
61 | 'totag' => 'new-tag', | ||
62 | ]; | ||
63 | $request = $this->createMock(Request::class); | ||
64 | $request | ||
65 | ->expects(static::atLeastOnce()) | ||
66 | ->method('getParam') | ||
67 | ->willReturnCallback(function (string $key) use ($requestParameters): ?string { | ||
68 | return $requestParameters[$key] ?? null; | ||
69 | }) | ||
70 | ; | ||
71 | $response = new Response(); | ||
72 | |||
73 | $bookmark1 = $this->createMock(Bookmark::class); | ||
74 | $bookmark2 = $this->createMock(Bookmark::class); | ||
75 | $this->container->bookmarkService | ||
76 | ->expects(static::once()) | ||
77 | ->method('search') | ||
78 | ->with(['searchtags' => 'old-tag'], BookmarkFilter::$ALL, true) | ||
79 | ->willReturnCallback(function () use ($bookmark1, $bookmark2): array { | ||
80 | $bookmark1->expects(static::once())->method('renameTag')->with('old-tag', 'new-tag'); | ||
81 | $bookmark2->expects(static::once())->method('renameTag')->with('old-tag', 'new-tag'); | ||
82 | |||
83 | return [$bookmark1, $bookmark2]; | ||
84 | }) | ||
85 | ; | ||
86 | $this->container->bookmarkService | ||
87 | ->expects(static::exactly(2)) | ||
88 | ->method('set') | ||
89 | ->withConsecutive([$bookmark1, false], [$bookmark2, false]) | ||
90 | ; | ||
91 | $this->container->bookmarkService->expects(static::once())->method('save'); | ||
92 | |||
93 | $result = $this->controller->save($request, $response); | ||
94 | |||
95 | static::assertSame(302, $result->getStatusCode()); | ||
96 | static::assertSame(['./?searchtags=new-tag'], $result->getHeader('location')); | ||
97 | |||
98 | static::assertArrayNotHasKey(SessionManager::KEY_ERROR_MESSAGES, $session); | ||
99 | static::assertArrayNotHasKey(SessionManager::KEY_WARNING_MESSAGES, $session); | ||
100 | static::assertArrayHasKey(SessionManager::KEY_SUCCESS_MESSAGES, $session); | ||
101 | static::assertSame(['The tag was renamed in 2 bookmarks.'], $session[SessionManager::KEY_SUCCESS_MESSAGES]); | ||
102 | } | ||
103 | |||
104 | /** | ||
105 | * Test posting a tag update - delete tag - valid info provided. | ||
106 | */ | ||
107 | public function testSaveDeleteTagValid(): void | ||
108 | { | ||
109 | $session = []; | ||
110 | $this->assignSessionVars($session); | ||
111 | |||
112 | $requestParameters = [ | ||
113 | 'deletetag' => 'delete', | ||
114 | 'fromtag' => 'old-tag', | ||
115 | ]; | ||
116 | $request = $this->createMock(Request::class); | ||
117 | $request | ||
118 | ->expects(static::atLeastOnce()) | ||
119 | ->method('getParam') | ||
120 | ->willReturnCallback(function (string $key) use ($requestParameters): ?string { | ||
121 | return $requestParameters[$key] ?? null; | ||
122 | }) | ||
123 | ; | ||
124 | $response = new Response(); | ||
125 | |||
126 | $bookmark1 = $this->createMock(Bookmark::class); | ||
127 | $bookmark2 = $this->createMock(Bookmark::class); | ||
128 | $this->container->bookmarkService | ||
129 | ->expects(static::once()) | ||
130 | ->method('search') | ||
131 | ->with(['searchtags' => 'old-tag'], BookmarkFilter::$ALL, true) | ||
132 | ->willReturnCallback(function () use ($bookmark1, $bookmark2): array { | ||
133 | $bookmark1->expects(static::once())->method('deleteTag')->with('old-tag'); | ||
134 | $bookmark2->expects(static::once())->method('deleteTag')->with('old-tag'); | ||
135 | |||
136 | return [$bookmark1, $bookmark2]; | ||
137 | }) | ||
138 | ; | ||
139 | $this->container->bookmarkService | ||
140 | ->expects(static::exactly(2)) | ||
141 | ->method('set') | ||
142 | ->withConsecutive([$bookmark1, false], [$bookmark2, false]) | ||
143 | ; | ||
144 | $this->container->bookmarkService->expects(static::once())->method('save'); | ||
145 | |||
146 | $result = $this->controller->save($request, $response); | ||
147 | |||
148 | static::assertSame(302, $result->getStatusCode()); | ||
149 | static::assertSame(['./manage-tags'], $result->getHeader('location')); | ||
150 | |||
151 | static::assertArrayNotHasKey(SessionManager::KEY_ERROR_MESSAGES, $session); | ||
152 | static::assertArrayNotHasKey(SessionManager::KEY_WARNING_MESSAGES, $session); | ||
153 | static::assertArrayHasKey(SessionManager::KEY_SUCCESS_MESSAGES, $session); | ||
154 | static::assertSame(['The tag was removed from 2 bookmarks.'], $session[SessionManager::KEY_SUCCESS_MESSAGES]); | ||
155 | } | ||
156 | |||
157 | /** | ||
158 | * Test posting a tag update - wrong token. | ||
159 | */ | ||
160 | public function testSaveWrongToken(): void | ||
161 | { | ||
162 | $this->container->sessionManager = $this->createMock(SessionManager::class); | ||
163 | $this->container->sessionManager->method('checkToken')->willReturn(false); | ||
164 | |||
165 | $this->container->conf->expects(static::never())->method('set'); | ||
166 | $this->container->conf->expects(static::never())->method('write'); | ||
167 | |||
168 | $request = $this->createMock(Request::class); | ||
169 | $response = new Response(); | ||
170 | |||
171 | $this->expectException(WrongTokenException::class); | ||
172 | |||
173 | $this->controller->save($request, $response); | ||
174 | } | ||
175 | |||
176 | /** | ||
177 | * Test posting a tag update - rename tag - missing "FROM" tag. | ||
178 | */ | ||
179 | public function testSaveRenameTagMissingFrom(): void | ||
180 | { | ||
181 | $session = []; | ||
182 | $this->assignSessionVars($session); | ||
183 | |||
184 | $requestParameters = [ | ||
185 | 'renametag' => 'rename', | ||
186 | ]; | ||
187 | $request = $this->createMock(Request::class); | ||
188 | $request | ||
189 | ->expects(static::atLeastOnce()) | ||
190 | ->method('getParam') | ||
191 | ->willReturnCallback(function (string $key) use ($requestParameters): ?string { | ||
192 | return $requestParameters[$key] ?? null; | ||
193 | }) | ||
194 | ; | ||
195 | $response = new Response(); | ||
196 | |||
197 | $result = $this->controller->save($request, $response); | ||
198 | |||
199 | static::assertSame(302, $result->getStatusCode()); | ||
200 | static::assertSame(['./manage-tags'], $result->getHeader('location')); | ||
201 | |||
202 | static::assertArrayNotHasKey(SessionManager::KEY_ERROR_MESSAGES, $session); | ||
203 | static::assertArrayHasKey(SessionManager::KEY_WARNING_MESSAGES, $session); | ||
204 | static::assertArrayNotHasKey(SessionManager::KEY_SUCCESS_MESSAGES, $session); | ||
205 | static::assertSame(['Invalid tags provided.'], $session[SessionManager::KEY_WARNING_MESSAGES]); | ||
206 | } | ||
207 | |||
208 | /** | ||
209 | * Test posting a tag update - delete tag - missing "FROM" tag. | ||
210 | */ | ||
211 | public function testSaveDeleteTagMissingFrom(): void | ||
212 | { | ||
213 | $session = []; | ||
214 | $this->assignSessionVars($session); | ||
215 | |||
216 | $requestParameters = [ | ||
217 | 'deletetag' => 'delete', | ||
218 | ]; | ||
219 | $request = $this->createMock(Request::class); | ||
220 | $request | ||
221 | ->expects(static::atLeastOnce()) | ||
222 | ->method('getParam') | ||
223 | ->willReturnCallback(function (string $key) use ($requestParameters): ?string { | ||
224 | return $requestParameters[$key] ?? null; | ||
225 | }) | ||
226 | ; | ||
227 | $response = new Response(); | ||
228 | |||
229 | $result = $this->controller->save($request, $response); | ||
230 | |||
231 | static::assertSame(302, $result->getStatusCode()); | ||
232 | static::assertSame(['./manage-tags'], $result->getHeader('location')); | ||
233 | |||
234 | static::assertArrayNotHasKey(SessionManager::KEY_ERROR_MESSAGES, $session); | ||
235 | static::assertArrayHasKey(SessionManager::KEY_WARNING_MESSAGES, $session); | ||
236 | static::assertArrayNotHasKey(SessionManager::KEY_SUCCESS_MESSAGES, $session); | ||
237 | static::assertSame(['Invalid tags provided.'], $session[SessionManager::KEY_WARNING_MESSAGES]); | ||
238 | } | ||
239 | |||
240 | /** | ||
241 | * Test posting a tag update - rename tag - missing "TO" tag. | ||
242 | */ | ||
243 | public function testSaveRenameTagMissingTo(): void | ||
244 | { | ||
245 | $session = []; | ||
246 | $this->assignSessionVars($session); | ||
247 | |||
248 | $requestParameters = [ | ||
249 | 'renametag' => 'rename', | ||
250 | 'fromtag' => 'old-tag' | ||
251 | ]; | ||
252 | $request = $this->createMock(Request::class); | ||
253 | $request | ||
254 | ->expects(static::atLeastOnce()) | ||
255 | ->method('getParam') | ||
256 | ->willReturnCallback(function (string $key) use ($requestParameters): ?string { | ||
257 | return $requestParameters[$key] ?? null; | ||
258 | }) | ||
259 | ; | ||
260 | $response = new Response(); | ||
261 | |||
262 | $result = $this->controller->save($request, $response); | ||
263 | |||
264 | static::assertSame(302, $result->getStatusCode()); | ||
265 | static::assertSame(['./manage-tags'], $result->getHeader('location')); | ||
266 | |||
267 | static::assertArrayNotHasKey(SessionManager::KEY_ERROR_MESSAGES, $session); | ||
268 | static::assertArrayHasKey(SessionManager::KEY_WARNING_MESSAGES, $session); | ||
269 | static::assertArrayNotHasKey(SessionManager::KEY_SUCCESS_MESSAGES, $session); | ||
270 | static::assertSame(['Invalid tags provided.'], $session[SessionManager::KEY_WARNING_MESSAGES]); | ||
271 | } | ||
272 | } | ||
diff --git a/tpl/default/page.header.html b/tpl/default/page.header.html index 4afcca73..bde5036d 100644 --- a/tpl/default/page.header.html +++ b/tpl/default/page.header.html | |||
@@ -185,7 +185,7 @@ | |||
185 | {/if} | 185 | {/if} |
186 | 186 | ||
187 | {if="!empty($global_errors) && $is_logged_in"} | 187 | {if="!empty($global_errors) && $is_logged_in"} |
188 | <div class="pure-g new-version-message pure-alert pure-alert-error pure-alert-closable" id="shaarli-errors-alert"> | 188 | <div class="pure-g header-alert-message pure-alert pure-alert-error pure-alert-closable" id="shaarli-errors-alert"> |
189 | <div class="pure-u-2-24"></div> | 189 | <div class="pure-u-2-24"></div> |
190 | <div class="pure-u-20-24"> | 190 | <div class="pure-u-20-24"> |
191 | {loop="$global_errors"} | 191 | {loop="$global_errors"} |
@@ -199,7 +199,7 @@ | |||
199 | {/if} | 199 | {/if} |
200 | 200 | ||
201 | {if="!empty($global_warnings) && $is_logged_in"} | 201 | {if="!empty($global_warnings) && $is_logged_in"} |
202 | <div class="pure-g pure-alert pure-alert-warning pure-alert-closable" id="shaarli-warnings-alert"> | 202 | <div class="pure-g header-alert-message pure-alert pure-alert-warning pure-alert-closable" id="shaarli-warnings-alert"> |
203 | <div class="pure-u-2-24"></div> | 203 | <div class="pure-u-2-24"></div> |
204 | <div class="pure-u-20-24"> | 204 | <div class="pure-u-20-24"> |
205 | {loop="global_warnings"} | 205 | {loop="global_warnings"} |
@@ -213,7 +213,7 @@ | |||
213 | {/if} | 213 | {/if} |
214 | 214 | ||
215 | {if="!empty($global_successes) && $is_logged_in"} | 215 | {if="!empty($global_successes) && $is_logged_in"} |
216 | <div class="pure-g new-version-message pure-alert pure-alert-success pure-alert-closable" id="shaarli-success-alert"> | 216 | <div class="pure-g header-alert-message new-version-message pure-alert pure-alert-success pure-alert-closable" id="shaarli-success-alert"> |
217 | <div class="pure-u-2-24"></div> | 217 | <div class="pure-u-2-24"></div> |
218 | <div class="pure-u-20-24"> | 218 | <div class="pure-u-20-24"> |
219 | {loop="$global_successes"} | 219 | {loop="$global_successes"} |
diff --git a/tpl/default/tag.list.html b/tpl/default/tag.list.html index 3e498f89..3adcfd1f 100644 --- a/tpl/default/tag.list.html +++ b/tpl/default/tag.list.html | |||
@@ -51,7 +51,7 @@ | |||
51 | <div class="pure-u-1"> | 51 | <div class="pure-u-1"> |
52 | {if="$is_logged_in===true"} | 52 | {if="$is_logged_in===true"} |
53 | <a href="#" class="delete-tag" aria-label="{'Delete'|t}"><i class="fa fa-trash" aria-hidden="true"></i></a> | 53 | <a href="#" class="delete-tag" aria-label="{'Delete'|t}"><i class="fa fa-trash" aria-hidden="true"></i></a> |
54 | <a href="./?do=changetag&fromtag={$key|urlencode}" class="rename-tag" aria-label="{'Rename tag'|t}"> | 54 | <a href="./manage-tags?fromtag={$key|urlencode}" class="rename-tag" aria-label="{'Rename tag'|t}"> |
55 | <i class="fa fa-pencil-square-o {$key}" aria-hidden="true"></i> | 55 | <i class="fa fa-pencil-square-o {$key}" aria-hidden="true"></i> |
56 | </a> | 56 | </a> |
57 | {/if} | 57 | {/if} |
diff --git a/tpl/default/tools.html b/tpl/default/tools.html index 0135c480..6e432e00 100644 --- a/tpl/default/tools.html +++ b/tpl/default/tools.html | |||
@@ -28,7 +28,7 @@ | |||
28 | </div> | 28 | </div> |
29 | {/if} | 29 | {/if} |
30 | <div class="tools-item"> | 30 | <div class="tools-item"> |
31 | <a href="./?do=changetag" title="{'Rename or delete a tag in all links'|t}"> | 31 | <a href="./manage-tags" title="{'Rename or delete a tag in all links'|t}"> |
32 | <span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Manage tags'|t}</span> | 32 | <span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Manage tags'|t}</span> |
33 | </a> | 33 | </a> |
34 | </div> | 34 | </div> |
diff --git a/tpl/vintage/tools.html b/tpl/vintage/tools.html index 0d8fcdec..8f606efb 100644 --- a/tpl/vintage/tools.html +++ b/tpl/vintage/tools.html | |||
@@ -11,7 +11,7 @@ | |||
11 | <br><br> | 11 | <br><br> |
12 | {if="!$openshaarli"}<a href="?do=changepasswd"><b>Change password</b><span>: Change your password.</span></a> | 12 | {if="!$openshaarli"}<a href="?do=changepasswd"><b>Change password</b><span>: Change your password.</span></a> |
13 | <br><br>{/if} | 13 | <br><br>{/if} |
14 | <a href="./?do=changetag"><b>Rename/delete tags</b><span>: Rename or delete a tag in all links</span></a> | 14 | <a href="./manage-tags"><b>Rename/delete tags</b><span>: Rename or delete a tag in all links</span></a> |
15 | <br><br> | 15 | <br><br> |
16 | <a href="./?do=import"><b>Import</b><span>: Import Netscape html bookmarks (as exported from Firefox, Chrome, Opera, delicious...)</span></a> | 16 | <a href="./?do=import"><b>Import</b><span>: Import Netscape html bookmarks (as exported from Firefox, Chrome, Opera, delicious...)</span></a> |
17 | <br><br> | 17 | <br><br> |