/** @var LoginManager */
protected $login;
- /** @var string */
- protected $webPath;
+ /** @var string|null */
+ protected $basePath = null;
public function __construct(
ConfigManager $conf,
SessionManager $session,
- LoginManager $login,
- string $webPath
+ LoginManager $login
) {
$this->conf = $conf;
$this->session = $session;
$this->login = $login;
- $this->webPath = $webPath;
}
public function build(): ShaarliContainer
$container['conf'] = $this->conf;
$container['sessionManager'] = $this->session;
$container['loginManager'] = $this->login;
- $container['webPath'] = $this->webPath;
+ $container['basePath'] = $this->basePath;
$container['plugins'] = function (ShaarliContainer $container): PluginManager {
return new PluginManager($container->conf);
/**
* Extension of Slim container to document the injected objects.
*
- * @property mixed[] $environment $_SERVER automatically injected by Slim
* @property string $basePath Shaarli's instance base path (e.g. `/shaarli/`)
+ * @property BookmarkServiceInterface $bookmarkService
* @property ConfigManager $conf
- * @property SessionManager $sessionManager
- * @property LoginManager $loginManager
- * @property string $webPath
+ * @property mixed[] $environment $_SERVER automatically injected by Slim
+ * @property FeedBuilder $feedBuilder
+ * @property FormatterFactory $formatterFactory
* @property History $history
- * @property BookmarkServiceInterface $bookmarkService
+ * @property HttpAccess $httpAccess
+ * @property LoginManager $loginManager
* @property PageBuilder $pageBuilder
- * @property PluginManager $pluginManager
- * @property FormatterFactory $formatterFactory
* @property PageCacheManager $pageCacheManager
- * @property FeedBuilder $feedBuilder
+ * @property PluginManager $pluginManager
+ * @property SessionManager $sessionManager
* @property Thumbnailer $thumbnailer
- * @property HttpAccess $httpAccess
*/
class ShaarliContainer extends Container
{
*/
public function __invoke(Request $request, Response $response, callable $next)
{
- try {
- $this->container->basePath = rtrim($request->getUri()->getBasePath(), '/');
+ $this->container->basePath = rtrim($request->getUri()->getBasePath(), '/');
+ try {
$response = $next($request, $response);
} catch (ShaarliFrontException $e) {
$this->container->pageBuilder->assign('message', $e->getMessage());
$response = $response->withStatus($e->getCode());
$response = $response->write($this->container->pageBuilder->render('error'));
} catch (UnauthorizedException $e) {
- return $response->withRedirect($request->getUri()->getBasePath() . '/login');
+ return $response->withRedirect($this->container->basePath . '/login');
}
return $response;
class ConfigureController extends ShaarliAdminController
{
/**
- * GET /configure - Displays the configuration page
+ * GET /admin/configure - Displays the configuration page
*/
public function index(Request $request, Response $response): Response
{
}
/**
- * POST /configure - Update Shaarli's configuration
+ * POST /admin/configure - Update Shaarli's configuration
*/
public function save(Request $request, Response $response): Response
{
$this->saveSuccessMessage(t('Configuration was saved.'));
- return $response->withRedirect('./configure');
+ return $this->redirect($response, '/admin/configure');
}
}
$this->container->sessionManager->logout();
// TODO: switch to a simple Cookie manager allowing to check the session, and create mocks.
- setcookie(LoginManager::$STAY_SIGNED_IN_COOKIE, 'false', 0, $this->container->webPath);
+ setcookie(LoginManager::$STAY_SIGNED_IN_COOKIE, 'false', 0, $this->container->basePath . '/');
- return $response->withRedirect('./');
+ return $this->redirect($response, '/');
}
}
class ManageTagController extends ShaarliAdminController
{
/**
- * GET /manage-tags - Displays the manage tags page
+ * GET /admin/tags - Displays the manage tags page
*/
public function index(Request $request, Response $response): Response
{
}
/**
- * POST /manage-tags - Update or delete provided tag
+ * POST /admin/tags - Update or delete provided tag
*/
public function save(Request $request, Response $response): Response
{
if (0 === strlen($fromTag) || false === $isDelete && 0 === strlen($toTag)) {
$this->saveWarningMessage(t('Invalid tags provided.'));
- return $response->withRedirect('./manage-tags');
+ return $this->redirect($response, '/admin/tags');
}
// TODO: move this to bookmark service
$this->saveSuccessMessage($alert);
- $redirect = true === $isDelete ? './manage-tags' : './?searchtags='. urlencode($toTag);
+ $redirect = true === $isDelete ? '/admin/tags' : '/?searchtags='. urlencode($toTag);
- return $response->withRedirect($redirect);
+ return $this->redirect($response, $redirect);
}
}
}
/**
- * GET /password - Displays the change password template
+ * GET /admin/password - Displays the change password template
*/
public function index(Request $request, Response $response): Response
{
}
/**
- * POST /password - Change admin password - existing and new passwords need to be provided.
+ * POST /admin/password - Change admin password - existing and new passwords need to be provided.
*/
public function change(Request $request, Response $response): Response
{
class PostBookmarkController extends ShaarliAdminController
{
/**
- * GET /add-shaare - Displays the form used to create a new bookmark from an URL
+ * GET /admin/add-shaare - Displays the form used to create a new bookmark from an URL
*/
public function addShaare(Request $request, Response $response): Response
{
}
/**
- * GET /shaare - Displays the bookmark form for creation.
+ * GET /admin/shaare - Displays the bookmark form for creation.
* Note that if the URL is found in existing bookmarks, then it will be in edit mode.
*/
public function displayCreateForm(Request $request, Response $response): Response
}
/**
- * GET /shaare-{id} - Displays the bookmark form in edition mode.
+ * GET /admin/shaare/{id} - Displays the bookmark form in edition mode.
*/
public function displayEditForm(Request $request, Response $response, array $args): Response
{
} catch (BookmarkNotFoundException $e) {
$this->saveErrorMessage(t('Bookmark not found'));
- return $response->withRedirect('./');
+ return $this->redirect($response, '/');
}
$formatter = $this->container->formatterFactory->getFormatter('raw');
}
/**
- * POST /shaare
+ * POST /admin/shaare
*/
public function save(Request $request, Response $response): Response
{
);
}
+ /**
+ * GET /admin/shaare/delete
+ */
public function deleteBookmark(Request $request, Response $response): Response
{
$this->checkToken($request);
- $ids = escape(trim($request->getParam('lf_linkdate')));
+ $ids = escape(trim($request->getParam('id')));
if (strpos($ids, ' ') !== false) {
// multiple, space-separated ids provided
$ids = array_values(array_filter(preg_split('/\s+/', $ids), 'strlen'));
}
// Don't redirect to where we were previously because the datastore has changed.
- return $response->withRedirect('./');
+ return $this->redirect($response, '/');
}
protected function displayForm(array $link, bool $isNew, Request $request, Response $response): Response
if ($this->container->loginManager->isLoggedIn()
|| $this->container->conf->get('security.open_shaarli', false)
) {
- return $response->withRedirect('./');
+ return $this->redirect($response, '/');
}
$userCanLogin = $this->container->loginManager->canLogin($request->getServerParams());
}
}
+ /**
+ * Simple helper which prepend the base path to redirect path.
+ *
+ * @param Response $response
+ * @param string $path Absolute path, e.g.: `/`, or `/admin/shaare/123` regardless of install directory
+ *
+ * @return Response updated
+ */
+ protected function redirect(Response $response, string $path): Response
+ {
+ return $response->withRedirect($this->container->basePath . $path);
+ }
+
/**
* Generates a redirection to the previous page, based on the HTTP_REFERER.
* It fails back to the home page.
* Class TagController
*
* Slim controller handle tags.
+ *
+ * TODO: check redirections with new helper
*/
class TagController extends ShaarliVisitorController
{
// In case browser does not send HTTP_REFERER, we search a single tag
if (null === $referer) {
if (null !== $newTag) {
- return $response->withRedirect('./?searchtags='. urlencode($newTag));
+ return $this->redirect($response, '/?searchtags='. urlencode($newTag));
}
- return $response->withRedirect('./');
+ return $this->redirect($response, '/');
}
$currentUrl = parse_url($referer);
// If the referrer is not provided, we can update the search, so we failback on the bookmark list
if (empty($referer)) {
- return $response->withRedirect('./');
+ return $this->redirect($response, '/');
}
$tagToRemove = $args['tag'] ?? null;
});
if (window.confirm(message)) {
- window.location = `${basePath}/?delete_link&lf_linkdate=${ids.join('+')}&token=${token.value}`;
+ window.location = `${basePath}/admin/shaare/delete?id=${ids.join('+')}&token=${token.value}`;
}
});
}
const refreshedToken = document.getElementById('token').value;
const fromtag = block.getAttribute('data-tag');
const xhr = new XMLHttpRequest();
- xhr.open('POST', `${basePath}/manage-tags`);
+ xhr.open('POST', `${basePath}/admin/tags`);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onload = () => {
if (xhr.status !== 200) {
.setAttribute('href', `${basePath}/?searchtags=${encodeURIComponent(totag)}`);
block
.querySelector('a.rename-tag')
- .setAttribute('href', `${basePath}/manage-tags?fromtag=${encodeURIComponent(totag)}`);
+ .setAttribute('href', `${basePath}/admin/tags?fromtag=${encodeURIComponent(totag)}`);
// Refresh awesomplete values
existingTags = existingTags.map(tag => (tag === fromtag ? totag : tag));
if (confirm(`Are you sure you want to delete the tag "${tag}"?`)) {
const xhr = new XMLHttpRequest();
- xhr.open('POST', `${basePath}/manage-tags`);
+ xhr.open('POST', `${basePath}/admin/tags`);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onload = () => {
block.remove();
### Feeds options
-Feeds are available in ATOM with `/feed-atom` and RSS with `/feed-rss`.
+Feeds are available in ATOM with `/feed/atom` and RSS with `/feed/rss`.
Options:
- You can use `permalinks` in the feed URL to get permalink to Shaares instead of direct link to shaared URL.
- - E.G. `https://my.shaarli.domain/feed-atom?permalinks`.
+ - E.G. `https://my.shaarli.domain/feed/atom?permalinks`.
- You can use `nb` parameter in the feed URL to specify the number of Shaares you want in a feed (default if not specified: `50`). The keyword `all` is available if you want everything.
- - `https://my.shaarli.domain/feed-atom?permalinks&nb=42`
- - `https://my.shaarli.domain/feed-atom?permalinks&nb=all`
+ - `https://my.shaarli.domain/feed/atom?permalinks&nb=42`
+ - `https://my.shaarli.domain/feed/atom?permalinks&nb=all`
### RSS Feeds or Picture Wall for a specific search/tag
```
http://<replace_domain>/
http://<replace_domain>/?nonope
-http://<replace_domain>/add-shaare
-http://<replace_domain>/?do=changepasswd
+http://<replace_domain>/admin/add-shaare
+http://<replace_domain>/admin/password
http://<replace_domain>/?do=changetag
-http://<replace_domain>/configure
-http://<replace_domain>/tools
+http://<replace_domain>/admin/configure
+http://<replace_domain>/admin/tools
http://<replace_domain>/daily
http://<replace_domain>/?post
http://<replace_domain>/?do=export
http://<replace_domain>/login
http://<replace_domain>/picture-wall
http://<replace_domain>/?do=pluginadmin
-http://<replace_domain>/tag-cloud
-http://<replace_domain>/tag-list
+http://<replace_domain>/tags/cloud
+http://<replace_domain>/tags/list
```
#### Improve existing translation
// -------- Tag cloud
if ($targetPage == Router::$PAGE_TAGCLOUD) {
- header('Location: ./tag-cloud');
+ header('Location: ./tags/cloud');
exit;
}
// -------- Tag list
if ($targetPage == Router::$PAGE_TAGLIST) {
- header('Location: ./tag-list');
+ header('Location: ./tags/list');
exit;
}
if ($targetPage == Router::$PAGE_FEED_ATOM || $targetPage == Router::$PAGE_FEED_RSS) {
$feedType = $targetPage == Router::$PAGE_FEED_RSS ? FeedBuilder::$FEED_RSS : FeedBuilder::$FEED_ATOM;
- header('Location: ./feed-'. $feedType .'?'. http_build_query($_GET));
+ header('Location: ./feed/'. $feedType .'?'. http_build_query($_GET));
exit;
}
// -------- Display the Tools menu if requested (import/export/bookmarklet...)
if ($targetPage == Router::$PAGE_TOOLS) {
- header('Location: ./tools');
+ header('Location: ./admin/tools');
exit;
}
// -------- User wants to change his/her password.
if ($targetPage == Router::$PAGE_CHANGEPASSWORD) {
- header('Location: ./password');
+ header('Location: ./admin/password');
exit;
}
// -------- User wants to change configuration
if ($targetPage == Router::$PAGE_CONFIGURE) {
- header('Location: ./configure');
+ header('Location: ./admin/configure');
exit;
}
// -------- User wants to rename a tag or delete it
if ($targetPage == Router::$PAGE_CHANGETAG) {
- header('Location: ./manage-tags');
+ header('Location: ./admin/tags');
exit;
}
// -------- User wants to add a link without using the bookmarklet: Show form.
if ($targetPage == Router::$PAGE_ADDLINK) {
- header('Location: ./shaare');
+ header('Location: ./admin/shaare');
exit;
}
// -------- User clicked the "Delete" button when editing a link: Delete link from database.
if ($targetPage == Router::$PAGE_DELETELINK) {
- if (! $sessionManager->checkToken($_GET['token'])) {
- die(t('Wrong token.'));
- }
-
- $ids = trim($_GET['lf_linkdate']);
- if (strpos($ids, ' ') !== false) {
- // multiple, space-separated ids provided
- $ids = array_values(array_filter(
- preg_split('/\s+/', escape($ids)),
- function ($item) {
- return $item !== '';
- }
- ));
- } else {
- // only a single id provided
- $shortUrl = $bookmarkService->get($ids)->getShortUrl();
- $ids = [$ids];
- }
- // assert at least one id is given
- if (!count($ids)) {
- die('no id provided');
- }
- $factory = new FormatterFactory($conf, $loginManager->isLoggedIn());
- $formatter = $factory->getFormatter('raw');
- foreach ($ids as $id) {
- $id = (int) escape($id);
- $bookmark = $bookmarkService->get($id);
- $data = $formatter->format($bookmark);
- $pluginManager->executeHooks('delete_link', $data);
- $bookmarkService->remove($bookmark, false);
- }
- $bookmarkService->save();
-
- // If we are called from the bookmarklet, we must close the popup:
- if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) {
- echo '<script>self.close();</script>';
- exit;
- }
+ $ids = $_GET['lf_linkdate'] ?? '';
+ $token = $_GET['token'] ?? '';
- $location = '?';
- if (isset($_SERVER['HTTP_REFERER'])) {
- // Don't redirect to where we were previously if it was a permalink or an edit_link, because it would 404.
- $location = generateLocation(
- $_SERVER['HTTP_REFERER'],
- $_SERVER['HTTP_HOST'],
- ['delete_link', 'edit_link', ! empty($shortUrl) ? $shortUrl : null]
- );
- }
-
- header('Location: ' . $location); // After deleting the link, redirect to appropriate location
+ header('Location: ./admin/shaare/delete?id=' . $ids . '&token=' . $token);
exit;
}
// -------- User clicked the "EDIT" button on a link: Display link edit form.
if (isset($_GET['edit_link'])) {
$id = (int) escape($_GET['edit_link']);
- header('Location: ./shaare-' . $id);
+ header('Location: ./admin/shaare/' . $id);
exit;
}
// -------- User want to post a new link: Display link edit form.
if (isset($_GET['post'])) {
- header('Location: ./shaare?' . http_build_query($_GET));
+ header('Location: ./admin/shaare?' . http_build_query($_GET));
exit;
}
exit;
}
-$containerBuilder = new ContainerBuilder($conf, $sessionManager, $loginManager, WEB_PATH);
+$containerBuilder = new ContainerBuilder($conf, $sessionManager, $loginManager);
$container = $containerBuilder->build();
$app = new App($container);
$app->group('', function () {
/* -- PUBLIC --*/
- $this->get('/login', '\Shaarli\Front\Controller\Visitor\LoginController:index')->setName('login');
- $this->get('/picture-wall', '\Shaarli\Front\Controller\Visitor\PictureWallController:index')->setName('picwall');
- $this->get('/tag-cloud', '\Shaarli\Front\Controller\Visitor\TagCloudController:cloud')->setName('tagcloud');
- $this->get('/tag-list', '\Shaarli\Front\Controller\Visitor\TagCloudController:list')->setName('taglist');
- $this->get('/daily', '\Shaarli\Front\Controller\Visitor\DailyController:index')->setName('daily');
- $this->get('/daily-rss', '\Shaarli\Front\Controller\Visitor\DailyController:rss')->setName('dailyrss');
- $this->get('/feed-atom', '\Shaarli\Front\Controller\Visitor\FeedController:atom')->setName('feedatom');
- $this->get('/feed-rss', '\Shaarli\Front\Controller\Visitor\FeedController:rss')->setName('feedrss');
- $this->get('/open-search', '\Shaarli\Front\Controller\Visitor\OpenSearchController:index')->setName('opensearch');
-
- $this->get('/add-tag/{newTag}', '\Shaarli\Front\Controller\Visitor\TagController:addTag')->setName('add-tag');
- $this->get('/remove-tag/{tag}', '\Shaarli\Front\Controller\Visitor\TagController:removeTag')->setName('remove-tag');
+ $this->get('/login', '\Shaarli\Front\Controller\Visitor\LoginController:index');
+ $this->get('/picture-wall', '\Shaarli\Front\Controller\Visitor\PictureWallController:index');
+ $this->get('/tags/cloud', '\Shaarli\Front\Controller\Visitor\TagCloudController:cloud');
+ $this->get('/tags/list', '\Shaarli\Front\Controller\Visitor\TagCloudController:list');
+ $this->get('/daily', '\Shaarli\Front\Controller\Visitor\DailyController:index');
+ $this->get('/daily-rss', '\Shaarli\Front\Controller\Visitor\DailyController:rss');
+ $this->get('/feed/atom', '\Shaarli\Front\Controller\Visitor\FeedController:atom');
+ $this->get('/feed/rss', '\Shaarli\Front\Controller\Visitor\FeedController:rss');
+ $this->get('/open-search', '\Shaarli\Front\Controller\Visitor\OpenSearchController:index');
+
+ $this->get('/add-tag/{newTag}', '\Shaarli\Front\Controller\Visitor\TagController:addTag');
+ $this->get('/remove-tag/{tag}', '\Shaarli\Front\Controller\Visitor\TagController:removeTag');
/* -- LOGGED IN -- */
- $this->get('/logout', '\Shaarli\Front\Controller\Admin\LogoutController:index')->setName('logout');
- $this->get('/tools', '\Shaarli\Front\Controller\Admin\ToolsController:index')->setName('tools');
- $this->get('/password', '\Shaarli\Front\Controller\Admin\PasswordController:index')->setName('password');
- $this->post('/password', '\Shaarli\Front\Controller\Admin\PasswordController:change')->setName('changePassword');
- $this->get('/configure', '\Shaarli\Front\Controller\Admin\ConfigureController:index')->setName('configure');
- $this->post('/configure', '\Shaarli\Front\Controller\Admin\ConfigureController:save')->setName('saveConfigure');
- $this->get('/manage-tags', '\Shaarli\Front\Controller\Admin\ManageTagController:index')->setName('manageTag');
- $this->post('/manage-tags', '\Shaarli\Front\Controller\Admin\ManageTagController:save')->setName('saveManageTag');
- $this->get('/add-shaare', '\Shaarli\Front\Controller\Admin\PostBookmarkController:addShaare')->setName('addShaare');
- $this
- ->get('/shaare', '\Shaarli\Front\Controller\Admin\PostBookmarkController:displayCreateForm')
- ->setName('newShaare');
- $this
- ->get('/shaare-{id}', '\Shaarli\Front\Controller\Admin\PostBookmarkController:displayEditForm')
- ->setName('editShaare');
- $this
- ->post('/shaare', '\Shaarli\Front\Controller\Admin\PostBookmarkController:save')
- ->setName('saveShaare');
- $this
- ->get('/delete-shaare', '\Shaarli\Front\Controller\Admin\PostBookmarkController:deleteBookmark')
- ->setName('deleteShaare');
-
- $this
- ->get('/links-per-page', '\Shaarli\Front\Controller\Admin\SessionFilterController:linksPerPage')
- ->setName('filter-links-per-page');
- $this
- ->get('/visibility/{visibility}', '\Shaarli\Front\Controller\Admin\SessionFilterController:visibility')
- ->setName('visibility');
- $this
- ->get('/untagged-only', '\Shaarli\Front\Controller\Admin\SessionFilterController:untaggedOnly')
- ->setName('untagged-only');
+ $this->get('/logout', '\Shaarli\Front\Controller\Admin\LogoutController:index');
+ $this->get('/admin/tools', '\Shaarli\Front\Controller\Admin\ToolsController:index');
+ $this->get('/admin/password', '\Shaarli\Front\Controller\Admin\PasswordController:index');
+ $this->post('/admin/password', '\Shaarli\Front\Controller\Admin\PasswordController:change');
+ $this->get('/admin/configure', '\Shaarli\Front\Controller\Admin\ConfigureController:index');
+ $this->post('/admin/configure', '\Shaarli\Front\Controller\Admin\ConfigureController:save');
+ $this->get('/admin/tags', '\Shaarli\Front\Controller\Admin\ManageTagController:index');
+ $this->post('/admin/tags', '\Shaarli\Front\Controller\Admin\ManageTagController:save');
+ $this->get('/admin/add-shaare', '\Shaarli\Front\Controller\Admin\PostBookmarkController:addShaare');
+ $this->get('/admin/shaare', '\Shaarli\Front\Controller\Admin\PostBookmarkController:displayCreateForm');
+ $this->get('/admin/shaare/{id:[0-9]+}', '\Shaarli\Front\Controller\Admin\PostBookmarkController:displayEditForm');
+ $this->post('/admin/shaare', '\Shaarli\Front\Controller\Admin\PostBookmarkController:save');
+ $this->get('/admin/shaare/delete', '\Shaarli\Front\Controller\Admin\PostBookmarkController:deleteBookmark');
+
+ $this->get('/links-per-page', '\Shaarli\Front\Controller\Admin\SessionFilterController:linksPerPage');
+ $this->get('/visibility/{visibility}', '\Shaarli\Front\Controller\Admin\SessionFilterController:visibility');
+ $this->get('/untagged-only', '\Shaarli\Front\Controller\Admin\SessionFilterController:untaggedOnly');
})->add('\Shaarli\Front\ShaarliMiddleware');
$response = $app->run(true);
function hook_pubsubhubbub_save_link($data, $conf)
{
$feeds = array(
- index_url($_SERVER) .'feed-atom',
- index_url($_SERVER) .'feed-rss',
+ index_url($_SERVER) .'feed/atom',
+ index_url($_SERVER) .'feed/rss',
);
$httpPost = function_exists('curl_version') ? false : 'nocurl_http_post';
use PHPUnit\Framework\TestCase;
use Shaarli\Bookmark\BookmarkServiceInterface;
use Shaarli\Config\ConfigManager;
+use Shaarli\Feed\FeedBuilder;
use Shaarli\Formatter\FormatterFactory;
use Shaarli\History;
+use Shaarli\Http\HttpAccess;
+use Shaarli\Plugin\PluginManager;
use Shaarli\Render\PageBuilder;
use Shaarli\Render\PageCacheManager;
use Shaarli\Security\LoginManager;
use Shaarli\Security\SessionManager;
+use Shaarli\Thumbnailer;
class ContainerBuilderTest extends TestCase
{
$this->containerBuilder = new ContainerBuilder(
$this->conf,
$this->sessionManager,
- $this->loginManager,
- 'UT web path'
+ $this->loginManager
);
}
static::assertInstanceOf(ConfigManager::class, $container->conf);
static::assertInstanceOf(SessionManager::class, $container->sessionManager);
static::assertInstanceOf(LoginManager::class, $container->loginManager);
- static::assertSame('UT web path', $container->webPath);
static::assertInstanceOf(History::class, $container->history);
static::assertInstanceOf(BookmarkServiceInterface::class, $container->bookmarkService);
static::assertInstanceOf(PageBuilder::class, $container->pageBuilder);
+ static::assertInstanceOf(PluginManager::class, $container->pluginManager);
static::assertInstanceOf(FormatterFactory::class, $container->formatterFactory);
static::assertInstanceOf(PageCacheManager::class, $container->pageCacheManager);
+ static::assertInstanceOf(FeedBuilder::class, $container->feedBuilder);
+ static::assertInstanceOf(Thumbnailer::class, $container->thumbnailer);
+ static::assertInstanceOf(HttpAccess::class, $container->httpAccess);
+
+ // Set by the middleware
+ static::assertNull($container->basePath);
}
}
{
// test cache directory
protected static $testCacheDir = 'sandbox/pagecache';
- protected static $url = 'http://shaar.li/feed-atom';
+ protected static $url = 'http://shaar.li/feed/atom';
protected static $filename;
/**
{
new CachedPage(self::$testCacheDir, '', true);
new CachedPage(self::$testCacheDir, '', false);
- new CachedPage(self::$testCacheDir, 'http://shaar.li/feed-rss', true);
- new CachedPage(self::$testCacheDir, 'http://shaar.li/feed-atom', false);
+ new CachedPage(self::$testCacheDir, 'http://shaar.li/feed/rss', true);
+ new CachedPage(self::$testCacheDir, 'http://shaar.li/feed/atom', false);
$this->addToAssertionCount(1);
}
$result = $this->controller->save($request, $response);
static::assertSame(302, $result->getStatusCode());
- static::assertSame(['./configure'], $result->getHeader('Location'));
+ static::assertSame(['/subfolder/admin/configure'], $result->getHeader('Location'));
static::assertArrayNotHasKey(SessionManager::KEY_WARNING_MESSAGES, $session);
static::assertArrayNotHasKey(SessionManager::KEY_ERROR_MESSAGES, $session);
$result = $this->controller->save($request, $response);
static::assertSame(302, $result->getStatusCode());
- static::assertSame(['./configure'], $result->getHeader('Location'));
+ static::assertSame(['/subfolder/admin/configure'], $result->getHeader('Location'));
static::assertArrayNotHasKey(SessionManager::KEY_ERROR_MESSAGES, $session);
static::assertArrayHasKey(SessionManager::KEY_WARNING_MESSAGES, $session);
$result = $this->controller->save($request, $response);
static::assertSame(302, $result->getStatusCode());
- static::assertSame(['./configure'], $result->getHeader('Location'));
+ static::assertSame(['/subfolder/admin/configure'], $result->getHeader('Location'));
static::assertArrayNotHasKey(SessionManager::KEY_ERROR_MESSAGES, $session);
static::assertArrayNotHasKey(SessionManager::KEY_WARNING_MESSAGES, $session);
static::assertInstanceOf(Response::class, $result);
static::assertSame(302, $result->getStatusCode());
- static::assertContains('./', $result->getHeader('Location'));
+ static::assertSame(['/subfolder/'], $result->getHeader('location'));
static::assertSame('false', $_COOKIE[LoginManager::$STAY_SIGNED_IN_COOKIE]);
}
}
$result = $this->controller->save($request, $response);
static::assertSame(302, $result->getStatusCode());
- static::assertSame(['./?searchtags=new-tag'], $result->getHeader('location'));
+ static::assertSame(['/subfolder/?searchtags=new-tag'], $result->getHeader('location'));
static::assertArrayNotHasKey(SessionManager::KEY_ERROR_MESSAGES, $session);
static::assertArrayNotHasKey(SessionManager::KEY_WARNING_MESSAGES, $session);
$result = $this->controller->save($request, $response);
static::assertSame(302, $result->getStatusCode());
- static::assertSame(['./manage-tags'], $result->getHeader('location'));
+ static::assertSame(['/subfolder/admin/tags'], $result->getHeader('location'));
static::assertArrayNotHasKey(SessionManager::KEY_ERROR_MESSAGES, $session);
static::assertArrayNotHasKey(SessionManager::KEY_WARNING_MESSAGES, $session);
$result = $this->controller->save($request, $response);
static::assertSame(302, $result->getStatusCode());
- static::assertSame(['./manage-tags'], $result->getHeader('location'));
+ static::assertSame(['/subfolder/admin/tags'], $result->getHeader('location'));
static::assertArrayNotHasKey(SessionManager::KEY_ERROR_MESSAGES, $session);
static::assertArrayHasKey(SessionManager::KEY_WARNING_MESSAGES, $session);
$result = $this->controller->save($request, $response);
static::assertSame(302, $result->getStatusCode());
- static::assertSame(['./manage-tags'], $result->getHeader('location'));
+ static::assertSame(['/subfolder/admin/tags'], $result->getHeader('location'));
static::assertArrayNotHasKey(SessionManager::KEY_ERROR_MESSAGES, $session);
static::assertArrayHasKey(SessionManager::KEY_WARNING_MESSAGES, $session);
$result = $this->controller->save($request, $response);
static::assertSame(302, $result->getStatusCode());
- static::assertSame(['./manage-tags'], $result->getHeader('location'));
+ static::assertSame(['/subfolder/admin/tags'], $result->getHeader('location'));
static::assertArrayNotHasKey(SessionManager::KEY_ERROR_MESSAGES, $session);
static::assertArrayHasKey(SessionManager::KEY_WARNING_MESSAGES, $session);
'lf_description' => 'Provided description.',
'lf_tags' => 'abc def',
'lf_private' => '1',
- 'returnurl' => 'http://shaarli.tld/subfolder/add-shaare'
+ 'returnurl' => 'http://shaarli.tld/subfolder/admin/add-shaare'
];
$request = $this->createMock(Request::class);
$result = $this->controller->save($request, $response);
static::assertSame(302, $result->getStatusCode());
- static::assertRegExp('@/subfolder/#\w{6}@', $result->getHeader('location')[0]);
+ static::assertRegExp('@/subfolder/#[\w\-]{6}@', $result->getHeader('location')[0]);
}
$result = $this->controller->save($request, $response);
static::assertSame(302, $result->getStatusCode());
- static::assertRegExp('@/subfolder/\?page=2#\w{6}@', $result->getHeader('location')[0]);
+ static::assertRegExp('@/subfolder/\?page=2#[\w\-]{6}@', $result->getHeader('location')[0]);
}
/**
static::assertInstanceOf(Response::class, $result);
static::assertSame(302, $result->getStatusCode());
- static::assertSame(['./'], $result->getHeader('Location'));
+ static::assertSame(['/subfolder/'], $result->getHeader('Location'));
}
public function testLoginControllerOpenShaarli(): void
static::assertInstanceOf(Response::class, $result);
static::assertSame(302, $result->getStatusCode());
- static::assertSame(['./'], $result->getHeader('Location'));
+ static::assertSame(['/subfolder/'], $result->getHeader('Location'));
}
public function testLoginControllerWhileBanned(): void
static::assertInstanceOf(Response::class, $result);
static::assertSame(302, $result->getStatusCode());
- static::assertSame(['./?searchtags=abc'], $result->getHeader('location'));
+ static::assertSame(['/subfolder/?searchtags=abc'], $result->getHeader('location'));
}
public function testAddTagRemoveLegacyQueryParam(): void
static::assertInstanceOf(Response::class, $result);
static::assertSame(302, $result->getStatusCode());
- static::assertSame(['./'], $result->getHeader('location'));
+ static::assertSame(['/subfolder/'], $result->getHeader('location'));
}
public function testRemoveTagWithoutMatchingTag(): void
static::assertInstanceOf(Response::class, $result);
static::assertSame(302, $result->getStatusCode());
- static::assertSame(['./'], $result->getHeader('location'));
+ static::assertSame(['/subfolder/'], $result->getHeader('location'));
}
public function testRemoveTagWithoutTag(): void
static::assertInstanceOf(Response::class, $result);
static::assertSame(302, $result->getStatusCode());
- static::assertSame(['./'], $result->getHeader('location'));
+ static::assertSame(['/subfolder/'], $result->getHeader('location'));
}
}
<div class="pure-u-lg-1-3 pure-u-1-24"></div>
<div id="addlink-form" class="page-form page-form-light pure-u-lg-1-3 pure-u-22-24">
<h2 class="window-title">{"Shaare a new link"|t}</h2>
- <form method="GET" action="{$base_path}/shaare" name="addform" class="addform">
+ <form method="GET" action="{$base_path}/admin/shaare" name="addform" class="addform">
<div>
<label for="shaare">{'URL or leave empty to post a note'|t}</label>
<input type="text" name="post" id="shaare" class="autofocus">
<div class="pure-u-lg-1-3 pure-u-1-24"></div>
<div id="addlink-form" class="page-form page-form-light pure-u-lg-1-3 pure-u-22-24">
<h2 class="window-title">{"Change password"|t}</h2>
- <form method="POST" action="{$base_path}/password" name="changepasswordform" id="changepasswordform">
+ <form method="POST" action="{$base_path}/admin/password" name="changepasswordform" id="changepasswordform">
<div>
<input type="password" name="oldpassword" aria-label="{'Current password'|t}" placeholder="{'Current password'|t}" class="autofocus">
</div>
<div class="pure-u-lg-1-3 pure-u-1-24"></div>
<div id="addlink-form" class="page-form page-form-light pure-u-lg-1-3 pure-u-22-24">
<h2 class="window-title">{"Manage tags"|t}</h2>
- <form method="POST" action="{$base_path}/manage-tags" name="changetag" id="changetag">
+ <form method="POST" action="{$base_path}/admin/tags" name="changetag" id="changetag">
<div>
<input type="text" name="fromtag" aria-label="{'Tag'|t}" placeholder="{'Tag'|t}" value="{$fromtag}"
list="tagsList" autocomplete="off" class="awesomplete autofocus" data-minChars="1">
</div>
</form>
- <p>{'You can also edit tags in the'|t} <a href="{$base_path}/tag-list?sort=usage">{'tag list'|t}</a>.</p>
+ <p>{'You can also edit tags in the'|t} <a href="{$base_path}/tags/list?sort=usage">{'tag list'|t}</a>.</p>
</div>
</div>
{include="page.footer"}
{$ratioInput='7-12'}
{$ratioInputMobile='1-8'}
-<form method="POST" action="{$base_path}/configure" name="configform" id="configform">
+<form method="POST" action="{$base_path}/admin/configure" name="configform" id="configform">
<div class="pure-g">
<div class="pure-u-lg-1-8 pure-u-1-24"></div>
<div class="pure-u-lg-3-4 pure-u-22-24 page-form page-form-complete">
<div class="pure-u-lg-1-5 pure-u-1-24"></div>
<form method="post"
name="linkform"
- action="{$base_path}/shaare"
+ action="{$base_path}/admin/shaare"
class="page-form pure-u-lg-3-5 pure-u-22-24 page-form page-form-light"
>
<h2 class="window-title">
<input type="submit" name="save_edit" class="" id="button-save-edit"
value="{if="$link_is_new"}{'Save'|t}{else}{'Apply Changes'|t}{/if}">
{if="!$link_is_new"}
- <a href="{$base_path}/?delete_link&lf_linkdate={$link.id}&token={$token}"
+ <a href="{$base_path}/admin/shaare/delete?id={$link.id}&token={$token}"
title="" name="delete_link" class="button button-red confirm-delete">
{'Delete'|t}
</a>
<meta name="format-detection" content="telephone=no" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="referrer" content="same-origin">
-<link rel="alternate" type="application/atom+xml" href="{$feedurl}feed-atom?{$searchcrits}#" title="ATOM Feed" />
-<link rel="alternate" type="application/rss+xml" href="{$feedurl}feed-rss?{$searchcrits}#" title="RSS Feed" />
+<link rel="alternate" type="application/atom+xml" href="{$feedurl}feed/atom?{$searchcrits}#" title="ATOM Feed" />
+<link rel="alternate" type="application/rss+xml" href="{$feedurl}feed/rss?{$searchcrits}#" title="RSS Feed" />
<link href="{$asset_path}/img/favicon.png#" rel="shortcut icon" type="image/png" />
<link href="{$asset_path}/img/apple-touch-icon.png#" rel="apple-touch-icon" sizes="180x180" />
<link type="text/css" rel="stylesheet" href="{$asset_path}/css/shaarli.min.css?v={$version_hash}#" />
<input type="checkbox" class="link-checkbox" value="{$value.id}">
</span>
<span class="linklist-item-infos-controls-item ctrl-edit">
- <a href="{$base_path}/?edit_link={$value.id}" aria-label="{$strEdit}" title="{$strEdit}"><i class="fa fa-pencil-square-o edit-link" aria-hidden="true"></i></a>
+ <a href="{$base_path}/admin/shaare/{$value.id}" aria-label="{$strEdit}" title="{$strEdit}"><i class="fa fa-pencil-square-o edit-link" aria-hidden="true"></i></a>
</span>
<span class="linklist-item-infos-controls-item ctrl-delete">
- <a href="{$base_path}/?delete_link&lf_linkdate={$value.id}&token={$token}" aria-label="{$strDelete}"
+ <a href="{$base_path}/admin/shaare/delete?id={$value.id}&token={$token}" aria-label="{$strDelete}"
title="{$strDelete}" class="delete-link pure-u-0 pure-u-lg-visible confirm-delete">
<i class="fa fa-trash" aria-hidden="true"></i>
</a>
{/if}
{if="$is_logged_in"}
·
- <a href="{$base_path}/?delete_link&lf_linkdate={$value.id}&token={$token}" aria-label="{$strDelete}"
+ <a href="{$base_path}/admin/shaare/delete?id={$value.id}&token={$token}" aria-label="{$strDelete}"
title="{$strDelete}" class="delete-link confirm-delete">
<i class="fa fa-trash" aria-hidden="true"></i>
</a>
·
- <a href="{$base_path}/?edit_link={$value.id}" aria-label="{$strEdit}" title="{$strEdit}"><i class="fa fa-pencil-square-o edit-link" aria-hidden="true"></i></a>
+ <a href="{$base_path}/admin/shaare/{$value.id}" aria-label="{$strEdit}" title="{$strEdit}"><i class="fa fa-pencil-square-o edit-link" aria-hidden="true"></i></a>
{/if}
</div>
</div>
<ShortName>Shaarli search - {$pagetitle}</ShortName>
<Description>Shaarli search - {$pagetitle}</Description>
<Url type="text/html" template="{$serverurl}?searchterm={searchTerms}" />
- <Url type="application/atom+xml" template="{$serverurl}feed-atom?searchterm={searchTerms}"/>
- <Url type="application/rss+xml" template="{$serverurl}feed-rss?searchterm={searchTerms}"/>
+ <Url type="application/atom+xml" template="{$serverurl}feed/atom?searchterm={searchTerms}"/>
+ <Url type="application/rss+xml" template="{$serverurl}feed/rss?searchterm={searchTerms}"/>
<InputEncoding>UTF-8</InputEncoding>
<Developer>Shaarli Community - https://github.com/shaarli/Shaarli/</Developer>
<Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAHRklE
</li>
{if="$is_logged_in || $openshaarli"}
<li class="pure-menu-item">
- <a href="{$base_path}/add-shaare" class="pure-menu-link" id="shaarli-menu-shaare">
+ <a href="{$base_path}/admin/add-shaare" class="pure-menu-link" id="shaarli-menu-shaare">
<i class="fa fa-plus" aria-hidden="true"></i> {'Shaare'|t}
</a>
</li>
<li class="pure-menu-item" id="shaarli-menu-tools">
- <a href="{$base_path}/tools" class="pure-menu-link">{'Tools'|t}</a>
+ <a href="{$base_path}/admin/tools" class="pure-menu-link">{'Tools'|t}</a>
</li>
{/if}
<li class="pure-menu-item" id="shaarli-menu-tags">
- <a href="{$base_path}/tag-cloud" class="pure-menu-link">{'Tag cloud'|t}</a>
+ <a href="{$base_path}/tags/cloud" class="pure-menu-link">{'Tag cloud'|t}</a>
</li>
{if="$thumbnails_enabled"}
<li class="pure-menu-item" id="shaarli-menu-picwall">
</li>
{/loop}
<li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-rss">
- <a href="{$base_path}/feed-{$feed_type}?{$searchcrits}" class="pure-menu-link">{'RSS Feed'|t}</a>
+ <a href="{$base_path}/feed/{$feed_type}?{$searchcrits}" class="pure-menu-link">{'RSS Feed'|t}</a>
</li>
{if="$is_logged_in"}
<li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-logout">
</a>
</li>
<li class="pure-menu-item" id="shaarli-menu-desktop-rss">
- <a href="{$base_path}/feed-{$feed_type}?{$searchcrits}" class="pure-menu-link" title="{'RSS Feed'|t}" aria-label="{'RSS Feed'|t}">
+ <a href="{$base_path}/feed/{$feed_type}?{$searchcrits}" class="pure-menu-link" title="{'RSS Feed'|t}" aria-label="{'RSS Feed'|t}">
<i class="fa fa-rss" aria-hidden="true"></i>
</a>
</li>
<div class="pure-u-1">
{if="$is_logged_in===true"}
<a href="#" class="delete-tag" aria-label="{'Delete'|t}"><i class="fa fa-trash" aria-hidden="true"></i></a>
- <a href="{$base_path}/manage-tags?fromtag={$key|urlencode}" class="rename-tag" aria-label="{'Rename tag'|t}">
+ <a href="{$base_path}/admin/tags?fromtag={$key|urlencode}" class="rename-tag" aria-label="{'Rename tag'|t}">
<i class="fa fa-pencil-square-o {$key}" aria-hidden="true"></i>
</a>
{/if}
<div class="pure-g">
<div class="pure-u-1 pure-alert pure-alert-success tag-sort">
{'Sort by:'|t}
- <a href="{$base_path}/tag-cloud">{'Cloud'|t}</a> ·
- <a href="{$base_path}/tag-list?sort=usage">{'Most used'|t}</a> ·
- <a href="{$base_path}/tag-list?sort=alpha">{'Alphabetical'|t}</a>
+ <a href="{$base_path}/tags/cloud">{'Cloud'|t}</a> ·
+ <a href="{$base_path}/tags/list?sort=usage">{'Most used'|t}</a> ·
+ <a href="{$base_path}/tags/list?sort=alpha">{'Alphabetical'|t}</a>
</div>
</div>
<div class="pure-u-lg-1-3 pure-u-22-24 page-form page-form-light">
<h2 class="window-title">{'Settings'|t}</h2>
<div class="tools-item">
- <a href="{$base_path}/configure" title="{'Change Shaarli settings: title, timezone, etc.'|t}">
+ <a href="{$base_path}/admin/configure" title="{'Change Shaarli settings: title, timezone, etc.'|t}">
<span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Configure your Shaarli'|t}</span>
</a>
</div>
</div>
{if="!$openshaarli"}
<div class="tools-item">
- <a href="{$base_path}/?do=changepasswd" title="{'Change your password'|t}">
+ <a href="{$base_path}/admin/password" title="{'Change your password'|t}">
<span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Change password'|t}</span>
</a>
</div>
{/if}
<div class="tools-item">
- <a href="{$base_path}/manage-tags" title="{'Rename or delete a tag in all links'|t}">
+ <a href="{$base_path}/admin/tags" title="{'Rename or delete a tag in all links'|t}">
<span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Manage tags'|t}</span>
</a>
</div>
<div id="pageheader">
{include="page.header"}
<div id="headerform">
- <form method="GET" action="{$base_path}/shaare" name="addform" class="addform">
+ <form method="GET" action="{$base_path}/admin/shaare" name="addform" class="addform">
<input type="text" name="post" class="linkurl">
<input type="submit" value="Add link" class="bigbutton">
</form>
{/if}
<input type="submit" value="Save" name="save_edit" class="bigbutton">
{if="!$link_is_new && isset($link.id)"}
- <a href="{$base_path}/?delete_link&lf_linkdate={$link.id}&token={$token}"
+ <a href="{$base_path}/admin/shaare/delete?id={$link.id}&token={$token}"
name="delete_link" class="bigbutton"
onClick="return confirmDeleteLink();">
{'Delete'|t}
<meta name="format-detection" content="telephone=no" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<meta name="referrer" content="same-origin">
-<link rel="alternate" type="application/rss+xml" href="{$feedurl}feed-rss?{$searchcrits}#" title="RSS Feed" />
-<link rel="alternate" type="application/atom+xml" href="{$feedurl}feed-atom?{$searchcrits}#" title="ATOM Feed" />
+<link rel="alternate" type="application/rss+xml" href="{$feedurl}feed/rss?{$searchcrits}#" title="RSS Feed" />
+<link rel="alternate" type="application/atom+xml" href="{$feedurl}feed/atom?{$searchcrits}#" title="ATOM Feed" />
<link href="img/favicon.ico" rel="shortcut icon" type="image/x-icon" />
<link type="text/css" rel="stylesheet" href="css/shaarli.min.css" />
{if="$formatter==='markdown'"}
<ShortName>Shaarli search - {$pagetitle}</ShortName>
<Description>Shaarli search - {$pagetitle}</Description>
<Url type="text/html" template="{$serverurl}?searchterm={searchTerms}" />
- <Url type="application/atom+xml" template="{$serverurl}feed-atom?searchterm={searchTerms}"/>
- <Url type="application/rss+xml" template="{$serverurl}feed-rss?searchterm={searchTerms}"/>
+ <Url type="application/atom+xml" template="{$serverurl}feed/atom?searchterm={searchTerms}"/>
+ <Url type="application/rss+xml" template="{$serverurl}feed/rss?searchterm={searchTerms}"/>
<InputEncoding>UTF-8</InputEncoding>
<Developer>Shaarli Community - https://github.com/shaarli/Shaarli/</Developer>
<Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAHRklE
<li><a href="{$titleLink}" class="nomobile">Home</a></li>
{if="$is_logged_in"}
<li><a href="{$base_path}/logout">Logout</a></li>
- <li><a href="{$base_path}/tools">Tools</a></li>
- <li><a href="{$base_path}/add-shaare">Add link</a></li>
+ <li><a href="{$base_path}/admin/tools">Tools</a></li>
+ <li><a href="{$base_path}/admin/add-shaare">Add link</a></li>
{elseif="$openshaarli"}
- <li><a href="{$base_path}/tools">Tools</a></li>
- <li><a href="{$base_path}/add-shaare">Add link</a></li>
+ <li><a href="{$base_path}/admin/tools">Tools</a></li>
+ <li><a href="{$base_path}/admin/add-shaare">Add link</a></li>
{else}
<li><a href="{$base_path}/login">Login</a></li>
{/if}
- <li><a href="{$feedurl}/feed-rss?{$searchcrits}" class="nomobile">RSS Feed</a></li>
+ <li><a href="{$feedurl}/feed/rss?{$searchcrits}" class="nomobile">RSS Feed</a></li>
{if="$showatom"}
- <li><a href="{$feedurl}/feed-atom?{$searchcrits}" class="nomobile">ATOM Feed</a></li>
+ <li><a href="{$feedurl}/feed/atom?{$searchcrits}" class="nomobile">ATOM Feed</a></li>
{/if}
- <li><a href="{$base_path}/tag-cloud">Tag cloud</a></li>
+ <li><a href="{$base_path}/tags/cloud">Tag cloud</a></li>
<li><a href="{$base_path}/picture-wall{function="ltrim($searchcrits, '&')"}">Picture wall</a></li>
<li><a href="{$base_path}/daily">Daily</a></li>
{loop="$plugins_header.buttons_toolbar"}
<div id="pageheader">
{include="page.header"}
<div id="toolsdiv">
- <a href="{$base_path}/configure"><b>Configure your Shaarli</b><span>: Change Title, timezone...</span></a>
+ <a href="{$base_path}/admin/configure"><b>Configure your Shaarli</b><span>: Change Title, timezone...</span></a>
<br><br>
<a href="{$base_path}/?do=pluginadmin"><b>Plugin administration</b><span>: Enable, disable and configure plugins.</span></a>
<br><br>
- {if="!$openshaarli"}<a href="{$base_path}/?do=changepasswd"><b>Change password</b><span>: Change your password.</span></a>
+ {if="!$openshaarli"}<a href="{$base_path}/admin/password"><b>Change password</b><span>: Change your password.</span></a>
<br><br>{/if}
- <a href="{$base_path}/manage-tags"><b>Rename/delete tags</b><span>: Rename or delete a tag in all links</span></a>
+ <a href="{$base_path}/admin/tags"><b>Rename/delete tags</b><span>: Rename or delete a tag in all links</span></a>
<br><br>
<a href="{$base_path}/?do=import"><b>Import</b><span>: Import Netscape html bookmarks (as exported from Firefox, Chrome, Opera, delicious...)</span></a>
<br><br>