aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--composer.lock48
-rw-r--r--index.php4
-rw-r--r--tests/front/controller/TagCloudControllerTest.php258
-rw-r--r--tpl/default/page.header.html8
-rw-r--r--tpl/vintage/page.header.html4
5 files changed, 290 insertions, 32 deletions
diff --git a/composer.lock b/composer.lock
index fccc741c..ae7a9269 100644
--- a/composer.lock
+++ b/composer.lock
@@ -1498,12 +1498,12 @@
1498 "source": { 1498 "source": {
1499 "type": "git", 1499 "type": "git",
1500 "url": "https://github.com/Roave/SecurityAdvisories.git", 1500 "url": "https://github.com/Roave/SecurityAdvisories.git",
1501 "reference": "ec1a75b10126327b351fdea7c2b9bfb94e2f6f35" 1501 "reference": "5a342e2dc0408d026b97ee3176b5b406e54e3766"
1502 }, 1502 },
1503 "dist": { 1503 "dist": {
1504 "type": "zip", 1504 "type": "zip",
1505 "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/ec1a75b10126327b351fdea7c2b9bfb94e2f6f35", 1505 "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/5a342e2dc0408d026b97ee3176b5b406e54e3766",
1506 "reference": "ec1a75b10126327b351fdea7c2b9bfb94e2f6f35", 1506 "reference": "5a342e2dc0408d026b97ee3176b5b406e54e3766",
1507 "shasum": "" 1507 "shasum": ""
1508 }, 1508 },
1509 "conflict": { 1509 "conflict": {
@@ -1694,8 +1694,8 @@
1694 "titon/framework": ">=0,<9.9.99", 1694 "titon/framework": ">=0,<9.9.99",
1695 "truckersmp/phpwhois": "<=4.3.1", 1695 "truckersmp/phpwhois": "<=4.3.1",
1696 "twig/twig": "<1.38|>=2,<2.7", 1696 "twig/twig": "<1.38|>=2,<2.7",
1697 "typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.32|>=8,<8.7.30|>=9,<9.5.12|>=10,<10.2.1", 1697 "typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.32|>=8,<8.7.30|>=9,<9.5.17|>=10,<10.4.2",
1698 "typo3/cms-core": ">=8,<8.7.30|>=9,<9.5.12|>=10,<10.2.1", 1698 "typo3/cms-core": ">=8,<8.7.30|>=9,<9.5.17|>=10,<10.4.2",
1699 "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.10|>=3.1,<3.1.7|>=3.2,<3.2.7|>=3.3,<3.3.5", 1699 "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.10|>=3.1,<3.1.7|>=3.2,<3.2.7|>=3.3,<3.3.5",
1700 "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4", 1700 "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4",
1701 "typo3/phar-stream-wrapper": ">=1,<2.1.1|>=3,<3.1.1", 1701 "typo3/phar-stream-wrapper": ">=1,<2.1.1|>=3,<3.1.1",
@@ -1758,7 +1758,7 @@
1758 } 1758 }
1759 ], 1759 ],
1760 "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", 1760 "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it",
1761 "time": "2020-05-09T00:00:21+00:00" 1761 "time": "2020-05-12T11:18:47+00:00"
1762 }, 1762 },
1763 { 1763 {
1764 "name": "sebastian/code-unit-reverse-lookup", 1764 "name": "sebastian/code-unit-reverse-lookup",
@@ -2547,16 +2547,16 @@
2547 }, 2547 },
2548 { 2548 {
2549 "name": "symfony/polyfill-ctype", 2549 "name": "symfony/polyfill-ctype",
2550 "version": "v1.16.0", 2550 "version": "v1.17.0",
2551 "source": { 2551 "source": {
2552 "type": "git", 2552 "type": "git",
2553 "url": "https://github.com/symfony/polyfill-ctype.git", 2553 "url": "https://github.com/symfony/polyfill-ctype.git",
2554 "reference": "1aab00e39cebaef4d8652497f46c15c1b7e45294" 2554 "reference": "e94c8b1bbe2bc77507a1056cdb06451c75b427f9"
2555 }, 2555 },
2556 "dist": { 2556 "dist": {
2557 "type": "zip", 2557 "type": "zip",
2558 "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1aab00e39cebaef4d8652497f46c15c1b7e45294", 2558 "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e94c8b1bbe2bc77507a1056cdb06451c75b427f9",
2559 "reference": "1aab00e39cebaef4d8652497f46c15c1b7e45294", 2559 "reference": "e94c8b1bbe2bc77507a1056cdb06451c75b427f9",
2560 "shasum": "" 2560 "shasum": ""
2561 }, 2561 },
2562 "require": { 2562 "require": {
@@ -2568,7 +2568,7 @@
2568 "type": "library", 2568 "type": "library",
2569 "extra": { 2569 "extra": {
2570 "branch-alias": { 2570 "branch-alias": {
2571 "dev-master": "1.16-dev" 2571 "dev-master": "1.17-dev"
2572 } 2572 }
2573 }, 2573 },
2574 "autoload": { 2574 "autoload": {
@@ -2601,20 +2601,20 @@
2601 "polyfill", 2601 "polyfill",
2602 "portable" 2602 "portable"
2603 ], 2603 ],
2604 "time": "2020-05-08T16:50:20+00:00" 2604 "time": "2020-05-12T16:14:59+00:00"
2605 }, 2605 },
2606 { 2606 {
2607 "name": "symfony/polyfill-mbstring", 2607 "name": "symfony/polyfill-mbstring",
2608 "version": "v1.16.0", 2608 "version": "v1.17.0",
2609 "source": { 2609 "source": {
2610 "type": "git", 2610 "type": "git",
2611 "url": "https://github.com/symfony/polyfill-mbstring.git", 2611 "url": "https://github.com/symfony/polyfill-mbstring.git",
2612 "reference": "a54881ec0ab3b2005c406aed0023c062879031e7" 2612 "reference": "fa79b11539418b02fc5e1897267673ba2c19419c"
2613 }, 2613 },
2614 "dist": { 2614 "dist": {
2615 "type": "zip", 2615 "type": "zip",
2616 "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/a54881ec0ab3b2005c406aed0023c062879031e7", 2616 "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fa79b11539418b02fc5e1897267673ba2c19419c",
2617 "reference": "a54881ec0ab3b2005c406aed0023c062879031e7", 2617 "reference": "fa79b11539418b02fc5e1897267673ba2c19419c",
2618 "shasum": "" 2618 "shasum": ""
2619 }, 2619 },
2620 "require": { 2620 "require": {
@@ -2626,7 +2626,7 @@
2626 "type": "library", 2626 "type": "library",
2627 "extra": { 2627 "extra": {
2628 "branch-alias": { 2628 "branch-alias": {
2629 "dev-master": "1.16-dev" 2629 "dev-master": "1.17-dev"
2630 } 2630 }
2631 }, 2631 },
2632 "autoload": { 2632 "autoload": {
@@ -2660,20 +2660,20 @@
2660 "portable", 2660 "portable",
2661 "shim" 2661 "shim"
2662 ], 2662 ],
2663 "time": "2020-05-08T16:50:20+00:00" 2663 "time": "2020-05-12T16:47:27+00:00"
2664 }, 2664 },
2665 { 2665 {
2666 "name": "symfony/polyfill-php73", 2666 "name": "symfony/polyfill-php73",
2667 "version": "v1.16.0", 2667 "version": "v1.17.0",
2668 "source": { 2668 "source": {
2669 "type": "git", 2669 "type": "git",
2670 "url": "https://github.com/symfony/polyfill-php73.git", 2670 "url": "https://github.com/symfony/polyfill-php73.git",
2671 "reference": "7e95fe59d12169fcf4041487e4bf34fca37ee0ed" 2671 "reference": "a760d8964ff79ab9bf057613a5808284ec852ccc"
2672 }, 2672 },
2673 "dist": { 2673 "dist": {
2674 "type": "zip", 2674 "type": "zip",
2675 "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/7e95fe59d12169fcf4041487e4bf34fca37ee0ed", 2675 "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/a760d8964ff79ab9bf057613a5808284ec852ccc",
2676 "reference": "7e95fe59d12169fcf4041487e4bf34fca37ee0ed", 2676 "reference": "a760d8964ff79ab9bf057613a5808284ec852ccc",
2677 "shasum": "" 2677 "shasum": ""
2678 }, 2678 },
2679 "require": { 2679 "require": {
@@ -2682,7 +2682,7 @@
2682 "type": "library", 2682 "type": "library",
2683 "extra": { 2683 "extra": {
2684 "branch-alias": { 2684 "branch-alias": {
2685 "dev-master": "1.16-dev" 2685 "dev-master": "1.17-dev"
2686 } 2686 }
2687 }, 2687 },
2688 "autoload": { 2688 "autoload": {
@@ -2718,7 +2718,7 @@
2718 "portable", 2718 "portable",
2719 "shim" 2719 "shim"
2720 ], 2720 ],
2721 "time": "2020-05-02T14:56:09+00:00" 2721 "time": "2020-05-12T16:47:27+00:00"
2722 }, 2722 },
2723 { 2723 {
2724 "name": "symfony/service-contracts", 2724 "name": "symfony/service-contracts",
diff --git a/index.php b/index.php
index 83f1264f..423a68f4 100644
--- a/index.php
+++ b/index.php
@@ -852,7 +852,7 @@ function renderPage($conf, $pluginManager, $bookmarkService, $history, $sessionM
852 // Show login screen, then redirect to ?post=... 852 // Show login screen, then redirect to ?post=...
853 if (isset($_GET['post'])) { 853 if (isset($_GET['post'])) {
854 header( // Redirect to login page, then back to post link. 854 header( // Redirect to login page, then back to post link.
855 'Location: /login?post='.urlencode($_GET['post']). 855 'Location: ./login?post='.urlencode($_GET['post']).
856 (!empty($_GET['title'])?'&title='.urlencode($_GET['title']):''). 856 (!empty($_GET['title'])?'&title='.urlencode($_GET['title']):'').
857 (!empty($_GET['description'])?'&description='.urlencode($_GET['description']):''). 857 (!empty($_GET['description'])?'&description='.urlencode($_GET['description']):'').
858 (!empty($_GET['tags'])?'&tags='.urlencode($_GET['tags']):''). 858 (!empty($_GET['tags'])?'&tags='.urlencode($_GET['tags']):'').
@@ -863,7 +863,7 @@ function renderPage($conf, $pluginManager, $bookmarkService, $history, $sessionM
863 863
864 showLinkList($PAGE, $bookmarkService, $conf, $pluginManager, $loginManager); 864 showLinkList($PAGE, $bookmarkService, $conf, $pluginManager, $loginManager);
865 if (isset($_GET['edit_link'])) { 865 if (isset($_GET['edit_link'])) {
866 header('Location: /login?edit_link='. escape($_GET['edit_link'])); 866 header('Location: ./login?edit_link='. escape($_GET['edit_link']));
867 exit; 867 exit;
868 } 868 }
869 869
diff --git a/tests/front/controller/TagCloudControllerTest.php b/tests/front/controller/TagCloudControllerTest.php
new file mode 100644
index 00000000..a76d5835
--- /dev/null
+++ b/tests/front/controller/TagCloudControllerTest.php
@@ -0,0 +1,258 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Shaarli\Front\Controller;
6
7use PHPUnit\Framework\TestCase;
8use Shaarli\Bookmark\BookmarkFilter;
9use Shaarli\Bookmark\BookmarkServiceInterface;
10use Shaarli\Config\ConfigManager;
11use Shaarli\Container\ShaarliContainer;
12use Shaarli\Plugin\PluginManager;
13use Shaarli\Render\PageBuilder;
14use Shaarli\Security\LoginManager;
15use Shaarli\Security\SessionManager;
16use Slim\Http\Request;
17use Slim\Http\Response;
18
19class TagCloudControllerTest extends TestCase
20{
21 /** @var ShaarliContainer */
22 protected $container;
23
24 /** @var TagCloudController */
25 protected $controller;
26
27 public function setUp(): void
28 {
29 $this->container = $this->createMock(ShaarliContainer::class);
30 $this->controller = new TagCloudController($this->container);
31 }
32
33 public function testValidCloudControllerInvokeDefault(): void
34 {
35 $this->createValidContainerMockSet();
36
37 $allTags = [
38 'ghi' => 1,
39 'abc' => 3,
40 'def' => 12,
41 ];
42 $expectedOrder = ['abc', 'def', 'ghi'];
43
44 $request = $this->createMock(Request::class);
45 $request->expects(static::once())->method('getQueryParam')->with('searchtags')->willReturn(null);
46 $response = new Response();
47
48 // Save RainTPL assigned variables
49 $assignedVariables = [];
50 $this->assignTemplateVars($assignedVariables);
51
52 $this->container->bookmarkService
53 ->expects(static::once())
54 ->method('bookmarksCountPerTag')
55 ->with([], null)
56 ->willReturnCallback(function () use ($allTags): array {
57 return $allTags;
58 })
59 ;
60
61 // Make sure that PluginManager hook is triggered
62 $this->container->pluginManager
63 ->expects(static::at(0))
64 ->method('executeHooks')
65 ->willReturnCallback(function (string $hook, array $data, array $param): array {
66 static::assertSame('render_tagcloud', $hook);
67 static::assertSame('', $data['search_tags']);
68 static::assertCount(3, $data['tags']);
69
70 static::assertArrayHasKey('loggedin', $param);
71
72 return $data;
73 })
74 ;
75
76 $result = $this->controller->index($request, $response);
77
78 static::assertSame(200, $result->getStatusCode());
79 static::assertSame('tag.cloud', (string) $result->getBody());
80 static::assertSame('Tag cloud - Shaarli', $assignedVariables['pagetitle']);
81
82 static::assertSame('', $assignedVariables['search_tags']);
83 static::assertCount(3, $assignedVariables['tags']);
84 static::assertSame($expectedOrder, array_keys($assignedVariables['tags']));
85
86 foreach ($allTags as $tag => $count) {
87 static::assertArrayHasKey($tag, $assignedVariables['tags']);
88 static::assertSame($count, $assignedVariables['tags'][$tag]['count']);
89 static::assertGreaterThan(0, $assignedVariables['tags'][$tag]['size']);
90 static::assertLessThan(5, $assignedVariables['tags'][$tag]['size']);
91 }
92 }
93
94 /**
95 * Additional parameters:
96 * - logged in
97 * - visibility private
98 * - search tags: `ghi` and `def` (note that filtered tags are not displayed anymore)
99 */
100 public function testValidCloudControllerInvokeWithParameters(): void
101 {
102 $this->createValidContainerMockSet();
103
104 $allTags = [
105 'ghi' => 1,
106 'abc' => 3,
107 'def' => 12,
108 ];
109
110 $request = $this->createMock(Request::class);
111 $request
112 ->expects(static::once())
113 ->method('getQueryParam')
114 ->with('searchtags')
115 ->willReturn('ghi def')
116 ;
117 $response = new Response();
118
119 // Save RainTPL assigned variables
120 $assignedVariables = [];
121 $this->assignTemplateVars($assignedVariables);
122
123 $this->container->loginManager->method('isLoggedin')->willReturn(true);
124 $this->container->sessionManager->expects(static::once())->method('getSessionParameter')->willReturn('private');
125
126 $this->container->bookmarkService
127 ->expects(static::once())
128 ->method('bookmarksCountPerTag')
129 ->with(['ghi', 'def'], BookmarkFilter::$PRIVATE)
130 ->willReturnCallback(function () use ($allTags): array {
131 return $allTags;
132 })
133 ;
134
135 // Make sure that PluginManager hook is triggered
136 $this->container->pluginManager
137 ->expects(static::at(0))
138 ->method('executeHooks')
139 ->willReturnCallback(function (string $hook, array $data, array $param): array {
140 static::assertSame('render_tagcloud', $hook);
141 static::assertSame('ghi def', $data['search_tags']);
142 static::assertCount(1, $data['tags']);
143
144 static::assertArrayHasKey('loggedin', $param);
145
146 return $data;
147 })
148 ;
149
150 $result = $this->controller->index($request, $response);
151
152 static::assertSame(200, $result->getStatusCode());
153 static::assertSame('tag.cloud', (string) $result->getBody());
154 static::assertSame('ghi def - Tag cloud - Shaarli', $assignedVariables['pagetitle']);
155
156 static::assertSame('ghi def', $assignedVariables['search_tags']);
157 static::assertCount(1, $assignedVariables['tags']);
158
159 static::assertArrayHasKey('abc', $assignedVariables['tags']);
160 static::assertSame(3, $assignedVariables['tags']['abc']['count']);
161 static::assertGreaterThan(0, $assignedVariables['tags']['abc']['size']);
162 static::assertLessThan(5, $assignedVariables['tags']['abc']['size']);
163 }
164
165 public function testEmptyCloud(): void
166 {
167 $this->createValidContainerMockSet();
168
169 $request = $this->createMock(Request::class);
170 $request->expects(static::once())->method('getQueryParam')->with('searchtags')->willReturn(null);
171 $response = new Response();
172
173 // Save RainTPL assigned variables
174 $assignedVariables = [];
175 $this->assignTemplateVars($assignedVariables);
176
177 $this->container->bookmarkService
178 ->expects(static::once())
179 ->method('bookmarksCountPerTag')
180 ->with([], null)
181 ->willReturnCallback(function (array $parameters, ?string $visibility): array {
182 return [];
183 })
184 ;
185
186 // Make sure that PluginManager hook is triggered
187 $this->container->pluginManager
188 ->expects(static::at(0))
189 ->method('executeHooks')
190 ->willReturnCallback(function (string $hook, array $data, array $param): array {
191 static::assertSame('render_tagcloud', $hook);
192 static::assertSame('', $data['search_tags']);
193 static::assertCount(0, $data['tags']);
194
195 static::assertArrayHasKey('loggedin', $param);
196
197 return $data;
198 })
199 ;
200
201 $result = $this->controller->index($request, $response);
202
203 static::assertSame(200, $result->getStatusCode());
204 static::assertSame('tag.cloud', (string) $result->getBody());
205 static::assertSame('Tag cloud - Shaarli', $assignedVariables['pagetitle']);
206
207 static::assertSame('', $assignedVariables['search_tags']);
208 static::assertCount(0, $assignedVariables['tags']);
209 }
210
211 protected function createValidContainerMockSet(): void
212 {
213 $loginManager = $this->createMock(LoginManager::class);
214 $this->container->loginManager = $loginManager;
215
216 $sessionManager = $this->createMock(SessionManager::class);
217 $this->container->sessionManager = $sessionManager;
218
219 // Config
220 $conf = $this->createMock(ConfigManager::class);
221 $this->container->conf = $conf;
222
223 $this->container->conf->method('get')->willReturnCallback(function (string $parameter, $default) {
224 return $default;
225 });
226
227 // PageBuilder
228 $pageBuilder = $this->createMock(PageBuilder::class);
229 $pageBuilder
230 ->method('render')
231 ->willReturnCallback(function (string $template): string {
232 return $template;
233 })
234 ;
235 $this->container->pageBuilder = $pageBuilder;
236
237 // Plugin Manager
238 $pluginManager = $this->createMock(PluginManager::class);
239 $this->container->pluginManager = $pluginManager;
240
241 // BookmarkService
242 $bookmarkService = $this->createMock(BookmarkServiceInterface::class);
243 $this->container->bookmarkService = $bookmarkService;
244 }
245
246 protected function assignTemplateVars(array &$variables): void
247 {
248 $this->container->pageBuilder
249 ->expects(static::atLeastOnce())
250 ->method('assign')
251 ->willReturnCallback(function ($key, $value) use (&$variables) {
252 $variables[$key] = $value;
253
254 return $this;
255 })
256 ;
257 }
258}
diff --git a/tpl/default/page.header.html b/tpl/default/page.header.html
index ea89a209..6b686580 100644
--- a/tpl/default/page.header.html
+++ b/tpl/default/page.header.html
@@ -56,11 +56,11 @@
56 </li> 56 </li>
57 {if="$is_logged_in"} 57 {if="$is_logged_in"}
58 <li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-logout"> 58 <li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-logout">
59 <a href="/logout" class="pure-menu-link">{'Logout'|t}</a> 59 <a href="./logout" class="pure-menu-link">{'Logout'|t}</a>
60 </li> 60 </li>
61 {else} 61 {else}
62 <li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-login"> 62 <li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-login">
63 <a href="/login" class="pure-menu-link">{'Login'|t}</a> 63 <a href="./login" class="pure-menu-link">{'Login'|t}</a>
64 </li> 64 </li>
65 {/if} 65 {/if}
66 </ul> 66 </ul>
@@ -80,7 +80,7 @@
80 </li> 80 </li>
81 {if="!$is_logged_in"} 81 {if="!$is_logged_in"}
82 <li class="pure-menu-item" id="shaarli-menu-desktop-login"> 82 <li class="pure-menu-item" id="shaarli-menu-desktop-login">
83 <a href="/login" class="pure-menu-link" 83 <a href="./login" class="pure-menu-link"
84 data-open-id="header-login-form" 84 data-open-id="header-login-form"
85 id="login-button" aria-label="{'Login'|t}" title="{'Login'|t}"> 85 id="login-button" aria-label="{'Login'|t}" title="{'Login'|t}">
86 <i class="fa fa-user" aria-hidden="true"></i> 86 <i class="fa fa-user" aria-hidden="true"></i>
@@ -88,7 +88,7 @@
88 </li> 88 </li>
89 {else} 89 {else}
90 <li class="pure-menu-item" id="shaarli-menu-desktop-logout"> 90 <li class="pure-menu-item" id="shaarli-menu-desktop-logout">
91 <a href="/logout" class="pure-menu-link" aria-label="{'Logout'|t}" title="{'Logout'|t}"> 91 <a href="./logout" class="pure-menu-link" aria-label="{'Logout'|t}" title="{'Logout'|t}">
92 <i class="fa fa-sign-out" aria-hidden="true"></i> 92 <i class="fa fa-sign-out" aria-hidden="true"></i>
93 </a> 93 </a>
94 </li> 94 </li>
diff --git a/tpl/vintage/page.header.html b/tpl/vintage/page.header.html
index 971fac9a..d9451555 100644
--- a/tpl/vintage/page.header.html
+++ b/tpl/vintage/page.header.html
@@ -18,14 +18,14 @@
18{else} 18{else}
19<li><a href="{$titleLink}" class="nomobile">Home</a></li> 19<li><a href="{$titleLink}" class="nomobile">Home</a></li>
20 {if="$is_logged_in"} 20 {if="$is_logged_in"}
21 <li><a href="/logout">Logout</a></li> 21 <li><a href="./logout">Logout</a></li>
22 <li><a href="?do=tools">Tools</a></li> 22 <li><a href="?do=tools">Tools</a></li>
23 <li><a href="?do=addlink">Add link</a></li> 23 <li><a href="?do=addlink">Add link</a></li>
24 {elseif="$openshaarli"} 24 {elseif="$openshaarli"}
25 <li><a href="./?do=tools">Tools</a></li> 25 <li><a href="./?do=tools">Tools</a></li>
26 <li><a href="./?do=addlink">Add link</a></li> 26 <li><a href="./?do=addlink">Add link</a></li>
27 {else} 27 {else}
28 <li><a href="/login">Login</a></li> 28 <li><a href="./login">Login</a></li>
29 {/if} 29 {/if}
30 <li><a href="{$feedurl}?do=rss{$searchcrits}" class="nomobile">RSS Feed</a></li> 30 <li><a href="{$feedurl}?do=rss{$searchcrits}" class="nomobile">RSS Feed</a></li>
31 {if="$showatom"} 31 {if="$showatom"}