Process main page (linklist) through Slim controller

Including a bunch of improvements on the container,
and helper used across new controllers.
This commit is contained in:
ArthurHoaro 2020-07-06 08:04:35 +02:00
parent 6132d64748
commit 1a8ac737e5
44 changed files with 1459 additions and 747 deletions

View file

@ -71,7 +71,14 @@ public function __invoke($request, $response, $next)
$response = $e->getApiResponse(); $response = $e->getApiResponse();
} }
return $response; return $response
->withHeader('Access-Control-Allow-Origin', '*')
->withHeader(
'Access-Control-Allow-Headers',
'X-Requested-With, Content-Type, Accept, Origin, Authorization'
)
->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
;
} }
/** /**

View file

@ -93,7 +93,7 @@ public function findByHash($hash)
throw new Exception('Not authorized'); throw new Exception('Not authorized');
} }
return $bookmark; return $first;
} }
/** /**

View file

@ -18,6 +18,8 @@
use Shaarli\Security\LoginManager; use Shaarli\Security\LoginManager;
use Shaarli\Security\SessionManager; use Shaarli\Security\SessionManager;
use Shaarli\Thumbnailer; use Shaarli\Thumbnailer;
use Shaarli\Updater\Updater;
use Shaarli\Updater\UpdaterUtils;
/** /**
* Class ContainerBuilder * Class ContainerBuilder
@ -128,6 +130,15 @@ public function build(): ShaarliContainer
return new NetscapeBookmarkUtils($container->bookmarkService, $container->conf, $container->history); return new NetscapeBookmarkUtils($container->bookmarkService, $container->conf, $container->history);
}; };
$container['updater'] = function (ShaarliContainer $container): Updater {
return new Updater(
UpdaterUtils::read_updates_file($container->conf->get('resource.updates')),
$container->bookmarkService,
$container->conf,
$container->loginManager->isLoggedIn()
);
};
return $container; return $container;
} }
} }

View file

@ -17,15 +17,17 @@
use Shaarli\Security\LoginManager; use Shaarli\Security\LoginManager;
use Shaarli\Security\SessionManager; use Shaarli\Security\SessionManager;
use Shaarli\Thumbnailer; use Shaarli\Thumbnailer;
use Shaarli\Updater\Updater;
use Slim\Container; use Slim\Container;
/** /**
* Extension of Slim container to document the injected objects. * Extension of Slim container to document the injected objects.
* *
* @property string $basePath Shaarli's instance base path (e.g. `/shaarli/`) * @property string $basePath Shaarli's instance base path (e.g. `/shaarli/`)
* @property BookmarkServiceInterface $bookmarkService * @property BookmarkServiceInterface $bookmarkService
* @property ConfigManager $conf * @property ConfigManager $conf
* @property mixed[] $environment $_SERVER automatically injected by Slim * @property mixed[] $environment $_SERVER automatically injected by Slim
* @property callable $errorHandler Overrides default Slim error display
* @property FeedBuilder $feedBuilder * @property FeedBuilder $feedBuilder
* @property FormatterFactory $formatterFactory * @property FormatterFactory $formatterFactory
* @property History $history * @property History $history
@ -37,6 +39,7 @@
* @property PluginManager $pluginManager * @property PluginManager $pluginManager
* @property SessionManager $sessionManager * @property SessionManager $sessionManager
* @property Thumbnailer $thumbnailer * @property Thumbnailer $thumbnailer
* @property Updater $updater
*/ */
class ShaarliContainer extends Container class ShaarliContainer extends Container
{ {

View file

@ -25,6 +25,8 @@ public function __construct(ShaarliContainer $container)
/** /**
* Middleware execution: * Middleware execution:
* - run updates
* - if not logged in open shaarli, redirect to login
* - execute the controller * - execute the controller
* - return the response * - return the response
* *
@ -36,27 +38,82 @@ public function __construct(ShaarliContainer $container)
* *
* @return Response response. * @return Response response.
*/ */
public function __invoke(Request $request, Response $response, callable $next) public function __invoke(Request $request, Response $response, callable $next): Response
{ {
$this->container->basePath = rtrim($request->getUri()->getBasePath(), '/'); $this->container->basePath = rtrim($request->getUri()->getBasePath(), '/');
try { try {
$response = $next($request, $response); $this->runUpdates();
$this->checkOpenShaarli($request, $response, $next);
return $next($request, $response);
} catch (ShaarliFrontException $e) { } catch (ShaarliFrontException $e) {
// Possible functional error
$this->container->pageBuilder->reset();
$this->container->pageBuilder->assign('message', $e->getMessage()); $this->container->pageBuilder->assign('message', $e->getMessage());
if ($this->container->conf->get('dev.debug', false)) {
$this->container->pageBuilder->assign(
'stacktrace',
nl2br(get_class($this) .': '. $e->getTraceAsString())
);
}
$response = $response->withStatus($e->getCode()); $response = $response->withStatus($e->getCode());
$response = $response->write($this->container->pageBuilder->render('error'));
return $response->write($this->container->pageBuilder->render('error'));
} catch (UnauthorizedException $e) { } catch (UnauthorizedException $e) {
return $response->withRedirect($this->container->basePath . '/login'); return $response->withRedirect($this->container->basePath . '/login');
} catch (\Throwable $e) {
// Unknown error encountered
$this->container->pageBuilder->reset();
if ($this->container->conf->get('dev.debug', false)) {
$this->container->pageBuilder->assign('message', $e->getMessage());
$this->container->pageBuilder->assign(
'stacktrace',
nl2br(get_class($e) .': '. PHP_EOL . $e->getTraceAsString())
);
} else {
$this->container->pageBuilder->assign('message', t('An unexpected error occurred.'));
}
$response = $response->withStatus(500);
return $response->write($this->container->pageBuilder->render('error'));
}
}
/**
* Run the updater for every requests processed while logged in.
*/
protected function runUpdates(): void
{
if ($this->container->loginManager->isLoggedIn() !== true) {
return;
} }
return $response; $newUpdates = $this->container->updater->update();
if (!empty($newUpdates)) {
$this->container->updater->writeUpdates(
$this->container->conf->get('resource.updates'),
$this->container->updater->getDoneUpdates()
);
$this->container->pageCacheManager->invalidateCaches();
}
}
/**
* Access is denied to most pages with `hide_public_links` + `force_login` settings.
*/
protected function checkOpenShaarli(Request $request, Response $response, callable $next): bool
{
if (// if the user isn't logged in
!$this->container->loginManager->isLoggedIn()
// and Shaarli doesn't have public content...
&& $this->container->conf->get('privacy.hide_public_links')
// and is configured to enforce the login
&& $this->container->conf->get('privacy.force_login')
// and the current page isn't already the login page
// and the user is not requesting a feed (which would lead to a different content-type as expected)
&& !in_array($next->getName(), ['login', 'atom', 'rss'], true)
) {
throw new UnauthorizedException();
}
return true;
} }
} }

View file

@ -5,6 +5,7 @@
namespace Shaarli\Front\Controller\Admin; namespace Shaarli\Front\Controller\Admin;
use Shaarli\Languages; use Shaarli\Languages;
use Shaarli\Render\TemplatePage;
use Shaarli\Render\ThemeUtils; use Shaarli\Render\ThemeUtils;
use Shaarli\Thumbnailer; use Shaarli\Thumbnailer;
use Slim\Http\Request; use Slim\Http\Request;
@ -52,7 +53,7 @@ public function index(Request $request, Response $response): Response
$this->assignView('thumbnails_mode', $this->container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE)); $this->assignView('thumbnails_mode', $this->container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE));
$this->assignView('pagetitle', t('Configure') .' - '. $this->container->conf->get('general.title', 'Shaarli')); $this->assignView('pagetitle', t('Configure') .' - '. $this->container->conf->get('general.title', 'Shaarli'));
return $response->write($this->render('configure')); return $response->write($this->render(TemplatePage::CONFIGURE));
} }
/** /**

View file

@ -6,6 +6,7 @@
use DateTime; use DateTime;
use Shaarli\Bookmark\Bookmark; use Shaarli\Bookmark\Bookmark;
use Shaarli\Render\TemplatePage;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response; use Slim\Http\Response;
@ -24,7 +25,7 @@ public function index(Request $request, Response $response): Response
{ {
$this->assignView('pagetitle', t('Export') .' - '. $this->container->conf->get('general.title', 'Shaarli')); $this->assignView('pagetitle', t('Export') .' - '. $this->container->conf->get('general.title', 'Shaarli'));
return $response->write($this->render('export')); return $response->write($this->render(TemplatePage::EXPORT));
} }
/** /**
@ -74,6 +75,6 @@ public function export(Request $request, Response $response): Response
$this->assignView('eol', PHP_EOL); $this->assignView('eol', PHP_EOL);
$this->assignView('selection', $selection); $this->assignView('selection', $selection);
return $response->write($this->render('export.bookmarks')); return $response->write($this->render(TemplatePage::NETSCAPE_EXPORT_BOOKMARKS));
} }
} }

View file

@ -5,6 +5,7 @@
namespace Shaarli\Front\Controller\Admin; namespace Shaarli\Front\Controller\Admin;
use Psr\Http\Message\UploadedFileInterface; use Psr\Http\Message\UploadedFileInterface;
use Shaarli\Render\TemplatePage;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response; use Slim\Http\Response;
@ -39,7 +40,7 @@ public function index(Request $request, Response $response): Response
); );
$this->assignView('pagetitle', t('Import') .' - '. $this->container->conf->get('general.title', 'Shaarli')); $this->assignView('pagetitle', t('Import') .' - '. $this->container->conf->get('general.title', 'Shaarli'));
return $response->write($this->render('import')); return $response->write($this->render(TemplatePage::IMPORT));
} }
/** /**

View file

@ -7,6 +7,7 @@
use Shaarli\Bookmark\Bookmark; use Shaarli\Bookmark\Bookmark;
use Shaarli\Bookmark\Exception\BookmarkNotFoundException; use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
use Shaarli\Formatter\BookmarkMarkdownFormatter; use Shaarli\Formatter\BookmarkMarkdownFormatter;
use Shaarli\Render\TemplatePage;
use Shaarli\Thumbnailer; use Shaarli\Thumbnailer;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response; use Slim\Http\Response;
@ -28,7 +29,7 @@ public function addShaare(Request $request, Response $response): Response
t('Shaare a new link') .' - '. $this->container->conf->get('general.title', 'Shaarli') t('Shaare a new link') .' - '. $this->container->conf->get('general.title', 'Shaarli')
); );
return $response->write($this->render('addlink')); return $response->write($this->render(TemplatePage::ADDLINK));
} }
/** /**
@ -365,7 +366,7 @@ protected function displayForm(array $link, bool $isNew, Request $request, Respo
$editLabel . t('Shaare') .' - '. $this->container->conf->get('general.title', 'Shaarli') $editLabel . t('Shaare') .' - '. $this->container->conf->get('general.title', 'Shaarli')
); );
return $response->write($this->render('editlink')); return $response->write($this->render(TemplatePage::EDIT_LINK));
} }
/** /**

View file

@ -5,6 +5,7 @@
namespace Shaarli\Front\Controller\Admin; namespace Shaarli\Front\Controller\Admin;
use Shaarli\Bookmark\BookmarkFilter; use Shaarli\Bookmark\BookmarkFilter;
use Shaarli\Render\TemplatePage;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response; use Slim\Http\Response;
@ -28,7 +29,7 @@ public function index(Request $request, Response $response): Response
t('Manage tags') .' - '. $this->container->conf->get('general.title', 'Shaarli') t('Manage tags') .' - '. $this->container->conf->get('general.title', 'Shaarli')
); );
return $response->write($this->render('changetag')); return $response->write($this->render(TemplatePage::CHANGE_TAG));
} }
/** /**

View file

@ -7,6 +7,7 @@
use Shaarli\Container\ShaarliContainer; use Shaarli\Container\ShaarliContainer;
use Shaarli\Front\Exception\OpenShaarliPasswordException; use Shaarli\Front\Exception\OpenShaarliPasswordException;
use Shaarli\Front\Exception\ShaarliFrontException; use Shaarli\Front\Exception\ShaarliFrontException;
use Shaarli\Render\TemplatePage;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response; use Slim\Http\Response;
use Throwable; use Throwable;
@ -33,7 +34,7 @@ public function __construct(ShaarliContainer $container)
*/ */
public function index(Request $request, Response $response): Response public function index(Request $request, Response $response): Response
{ {
return $response->write($this->render('changepassword')); return $response->write($this->render(TemplatePage::CHANGE_PASSWORD));
} }
/** /**
@ -55,7 +56,7 @@ public function change(Request $request, Response $response): Response
return $response return $response
->withStatus(400) ->withStatus(400)
->write($this->render('changepassword')) ->write($this->render(TemplatePage::CHANGE_PASSWORD))
; ;
} }
@ -71,7 +72,7 @@ public function change(Request $request, Response $response): Response
return $response return $response
->withStatus(400) ->withStatus(400)
->write($this->render('changepassword')) ->write($this->render(TemplatePage::CHANGE_PASSWORD))
; ;
} }
@ -95,6 +96,6 @@ public function change(Request $request, Response $response): Response
$this->saveSuccessMessage(t('Your password has been changed')); $this->saveSuccessMessage(t('Your password has been changed'));
return $response->write($this->render('changepassword')); return $response->write($this->render(TemplatePage::CHANGE_PASSWORD));
} }
} }

View file

@ -5,6 +5,7 @@
namespace Shaarli\Front\Controller\Admin; namespace Shaarli\Front\Controller\Admin;
use Exception; use Exception;
use Shaarli\Render\TemplatePage;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response; use Slim\Http\Response;
@ -44,7 +45,7 @@ function ($a, $b) {
t('Plugin Administration') .' - '. $this->container->conf->get('general.title', 'Shaarli') t('Plugin Administration') .' - '. $this->container->conf->get('general.title', 'Shaarli')
); );
return $response->write($this->render('pluginsadmin')); return $response->write($this->render(TemplatePage::PLUGINS_ADMIN));
} }
/** /**

View file

@ -5,6 +5,7 @@
namespace Shaarli\Front\Controller\Admin; namespace Shaarli\Front\Controller\Admin;
use Shaarli\Bookmark\Exception\BookmarkNotFoundException; use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
use Shaarli\Render\TemplatePage;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response; use Slim\Http\Response;
@ -36,7 +37,7 @@ public function index(Request $request, Response $response): Response
t('Thumbnails update') .' - '. $this->container->conf->get('general.title', 'Shaarli') t('Thumbnails update') .' - '. $this->container->conf->get('general.title', 'Shaarli')
); );
return $response->write($this->render('thumbnails')); return $response->write($this->render(TemplatePage::THUMBNAILS));
} }
/** /**
@ -61,19 +62,4 @@ public function ajaxUpdate(Request $request, Response $response, array $args): R
return $response->withJson($this->container->formatterFactory->getFormatter('raw')->format($bookmark)); return $response->withJson($this->container->formatterFactory->getFormatter('raw')->format($bookmark));
} }
/**
* @param mixed[] $data Variables passed to the template engine
*
* @return mixed[] Template data after active plugins render_picwall hook execution.
*/
protected function executeHooks(array $data): array
{
$this->container->pluginManager->executeHooks(
'render_tools',
$data
);
return $data;
}
} }

View file

@ -4,6 +4,7 @@
namespace Shaarli\Front\Controller\Admin; namespace Shaarli\Front\Controller\Admin;
use Shaarli\Render\TemplatePage;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response; use Slim\Http\Response;
@ -29,7 +30,7 @@ public function index(Request $request, Response $response): Response
$this->assignView('pagetitle', t('Tools') .' - '. $this->container->conf->get('general.title', 'Shaarli')); $this->assignView('pagetitle', t('Tools') .' - '. $this->container->conf->get('general.title', 'Shaarli'));
return $response->write($this->render('tools')); return $response->write($this->render(TemplatePage::TOOLS));
} }
/** /**

View file

@ -0,0 +1,248 @@
<?php
declare(strict_types=1);
namespace Shaarli\Front\Controller\Visitor;
use Shaarli\Bookmark\Bookmark;
use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
use Shaarli\Legacy\LegacyController;
use Shaarli\Legacy\UnknowLegacyRouteException;
use Shaarli\Render\TemplatePage;
use Shaarli\Thumbnailer;
use Slim\Http\Request;
use Slim\Http\Response;
/**
* Class BookmarkListController
*
* Slim controller used to render the bookmark list, the home page of Shaarli.
* It also displays permalinks, and process legacy routes based on GET parameters.
*/
class BookmarkListController extends ShaarliVisitorController
{
/**
* GET / - Displays the bookmark list, with optional filter parameters.
*/
public function index(Request $request, Response $response): Response
{
$legacyResponse = $this->processLegacyController($request, $response);
if (null !== $legacyResponse) {
return $legacyResponse;
}
$formatter = $this->container->formatterFactory->getFormatter();
$searchTags = escape(normalize_spaces($request->getParam('searchtags') ?? ''));
$searchTerm = escape(normalize_spaces($request->getParam('searchterm') ?? ''));;
// Filter bookmarks according search parameters.
$visibility = $this->container->sessionManager->getSessionParameter('visibility');
$search = [
'searchtags' => $searchTags,
'searchterm' => $searchTerm,
];
$linksToDisplay = $this->container->bookmarkService->search(
$search,
$visibility,
false,
!!$this->container->sessionManager->getSessionParameter('untaggedonly')
) ?? [];
// ---- Handle paging.
$keys = [];
foreach ($linksToDisplay as $key => $value) {
$keys[] = $key;
}
$linksPerPage = $this->container->sessionManager->getSessionParameter('LINKS_PER_PAGE', 20) ?: 20;
// Select articles according to paging.
$pageCount = (int) ceil(count($keys) / $linksPerPage) ?: 1;
$page = (int) $request->getParam('page') ?? 1;
$page = $page < 1 ? 1 : $page;
$page = $page > $pageCount ? $pageCount : $page;
// Start index.
$i = ($page - 1) * $linksPerPage;
$end = $i + $linksPerPage;
$linkDisp = [];
$save = false;
while ($i < $end && $i < count($keys)) {
$save = $this->updateThumbnail($linksToDisplay[$keys[$i]], false) || $save;
$link = $formatter->format($linksToDisplay[$keys[$i]]);
$linkDisp[$keys[$i]] = $link;
$i++;
}
if ($save) {
$this->container->bookmarkService->save();
}
// Compute paging navigation
$searchtagsUrl = $searchTags === '' ? '' : '&searchtags=' . urlencode($searchTags);
$searchtermUrl = $searchTerm === '' ? '' : '&searchterm=' . urlencode($searchTerm);
$previous_page_url = '';
if ($i !== count($keys)) {
$previous_page_url = '?page=' . ($page + 1) . $searchtermUrl . $searchtagsUrl;
}
$next_page_url = '';
if ($page > 1) {
$next_page_url = '?page=' . ($page - 1) . $searchtermUrl . $searchtagsUrl;
}
// Fill all template fields.
$data = array_merge(
$this->initializeTemplateVars(),
[
'previous_page_url' => $previous_page_url,
'next_page_url' => $next_page_url,
'page_current' => $page,
'page_max' => $pageCount,
'result_count' => count($linksToDisplay),
'search_term' => $searchTerm,
'search_tags' => $searchTags,
'visibility' => $visibility,
'links' => $linkDisp,
]
);
if (!empty($searchTerm) || !empty($searchTags)) {
$data['pagetitle'] = t('Search: ');
$data['pagetitle'] .= ! empty($searchTerm) ? $searchTerm . ' ' : '';
$bracketWrap = function ($tag) {
return '[' . $tag . ']';
};
$data['pagetitle'] .= ! empty($searchTags)
? implode(' ', array_map($bracketWrap, preg_split('/\s+/', $searchTags))) . ' '
: '';
$data['pagetitle'] .= '- ';
}
$data['pagetitle'] = ($data['pagetitle'] ?? '') . $this->container->conf->get('general.title', 'Shaarli');
$this->executeHooks($data);
$this->assignAllView($data);
return $response->write($this->render(TemplatePage::LINKLIST));
}
/**
* GET /shaare/{hash} - Display a single shaare
*/
public function permalink(Request $request, Response $response, array $args): Response
{
try {
$bookmark = $this->container->bookmarkService->findByHash($args['hash']);
} catch (BookmarkNotFoundException $e) {
$this->assignView('error_message', $e->getMessage());
return $response->write($this->render(TemplatePage::ERROR_404));
}
$this->updateThumbnail($bookmark);
$data = array_merge(
$this->initializeTemplateVars(),
[
'pagetitle' => $bookmark->getTitle() .' - '. $this->container->conf->get('general.title', 'Shaarli'),
'links' => [$this->container->formatterFactory->getFormatter()->format($bookmark)],
]
);
$this->executeHooks($data);
$this->assignAllView($data);
return $response->write($this->render(TemplatePage::LINKLIST));
}
/**
* Update the thumbnail of a single bookmark if necessary.
*/
protected function updateThumbnail(Bookmark $bookmark, bool $writeDatastore = true): bool
{
// Logged in, thumbnails enabled, not a note, is HTTP
// and (never retrieved yet or no valid cache file)
if ($this->container->loginManager->isLoggedIn()
&& $this->container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE
&& false !== $bookmark->getThumbnail()
&& !$bookmark->isNote()
&& (null === $bookmark->getThumbnail() || !is_file($bookmark->getThumbnail()))
&& startsWith(strtolower($bookmark->getUrl()), 'http')
) {
$bookmark->setThumbnail($this->container->thumbnailer->get($bookmark->getUrl()));
$this->container->bookmarkService->set($bookmark, $writeDatastore);
return true;
}
return false;
}
/**
* @param mixed[] $data Template vars to process in plugins, passed as reference.
*/
protected function executeHooks(array &$data): void
{
$this->container->pluginManager->executeHooks(
'render_linklist',
$data,
['loggedin' => $this->container->loginManager->isLoggedIn()]
);
}
/**
* @return string[] Default template variables without values.
*/
protected function initializeTemplateVars(): array
{
return [
'previous_page_url' => '',
'next_page_url' => '',
'page_max' => '',
'search_tags' => '',
'result_count' => '',
];
}
/**
* Process legacy routes if necessary. They used query parameters.
* If no legacy routes is passed, return null.
*/
protected function processLegacyController(Request $request, Response $response): ?Response
{
// Legacy smallhash filter
$queryString = $this->container->environment['QUERY_STRING'] ?? null;
if (null !== $queryString && 1 === preg_match('/^([a-zA-Z0-9-_@]{6})($|&|#)/', $queryString, $match)) {
return $this->redirect($response, '/shaare/' . $match[1]);
}
// Legacy controllers (mostly used for redirections)
if (null !== $request->getQueryParam('do')) {
$legacyController = new LegacyController($this->container);
try {
return $legacyController->process($request, $response, $request->getQueryParam('do'));
} catch (UnknowLegacyRouteException $e) {
// We ignore legacy 404
return null;
}
}
// Legacy GET admin routes
$legacyGetRoutes = array_intersect(
LegacyController::LEGACY_GET_ROUTES,
array_keys($request->getQueryParams() ?? [])
);
if (1 === count($legacyGetRoutes)) {
$legacyController = new LegacyController($this->container);
return $legacyController->process($request, $response, $legacyGetRoutes[0]);
}
return null;
}
}

View file

@ -7,6 +7,7 @@
use DateTime; use DateTime;
use DateTimeImmutable; use DateTimeImmutable;
use Shaarli\Bookmark\Bookmark; use Shaarli\Bookmark\Bookmark;
use Shaarli\Render\TemplatePage;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response; use Slim\Http\Response;
@ -85,7 +86,7 @@ public function index(Request $request, Response $response): Response
t('Daily') .' - '. format_date($dayDate, false) . ' - ' . $mainTitle t('Daily') .' - '. format_date($dayDate, false) . ' - ' . $mainTitle
); );
return $response->write($this->render('daily')); return $response->write($this->render(TemplatePage::DAILY));
} }
/** /**
@ -152,7 +153,7 @@ public function rss(Request $request, Response $response): Response
$this->assignView('hide_timestamps', $this->container->conf->get('privacy.hide_timestamps', false)); $this->assignView('hide_timestamps', $this->container->conf->get('privacy.hide_timestamps', false));
$this->assignView('days', $dataPerDay); $this->assignView('days', $dataPerDay);
$rssContent = $this->render('dailyrss'); $rssContent = $this->render(TemplatePage::DAILY_RSS);
$cache->cache($rssContent); $cache->cache($rssContent);

View file

@ -5,6 +5,7 @@
namespace Shaarli\Front\Controller\Visitor; namespace Shaarli\Front\Controller\Visitor;
use Shaarli\Front\Exception\LoginBannedException; use Shaarli\Front\Exception\LoginBannedException;
use Shaarli\Render\TemplatePage;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response; use Slim\Http\Response;
@ -41,6 +42,6 @@ public function index(Request $request, Response $response): Response
->assignView('pagetitle', t('Login') .' - '. $this->container->conf->get('general.title', 'Shaarli')) ->assignView('pagetitle', t('Login') .' - '. $this->container->conf->get('general.title', 'Shaarli'))
; ;
return $response->write($this->render('loginform')); return $response->write($this->render(TemplatePage::LOGIN));
} }
} }

View file

@ -4,6 +4,7 @@
namespace Shaarli\Front\Controller\Visitor; namespace Shaarli\Front\Controller\Visitor;
use Shaarli\Render\TemplatePage;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response; use Slim\Http\Response;
@ -21,6 +22,6 @@ public function index(Request $request, Response $response): Response
$this->assignView('serverurl', index_url($this->container->environment)); $this->assignView('serverurl', index_url($this->container->environment));
return $response->write($this->render('opensearch')); return $response->write($this->render(TemplatePage::OPEN_SEARCH));
} }
} }

View file

@ -5,6 +5,7 @@
namespace Shaarli\Front\Controller\Visitor; namespace Shaarli\Front\Controller\Visitor;
use Shaarli\Front\Exception\ThumbnailsDisabledException; use Shaarli\Front\Exception\ThumbnailsDisabledException;
use Shaarli\Render\TemplatePage;
use Shaarli\Thumbnailer; use Shaarli\Thumbnailer;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response; use Slim\Http\Response;
@ -46,7 +47,7 @@ public function index(Request $request, Response $response): Response
$this->assignView($key, $value); $this->assignView($key, $value);
} }
return $response->write($this->render('picwall')); return $response->write($this->render(TemplatePage::PICTURE_WALL));
} }
/** /**

View file

@ -0,0 +1,130 @@
<?php
declare(strict_types=1);
namespace Shaarli\Legacy;
use Shaarli\Feed\FeedBuilder;
use Shaarli\Front\Controller\Visitor\ShaarliVisitorController;
use Slim\Http\Request;
use Slim\Http\Response;
/**
* We use this to maintain legacy routes, and redirect requests to the corresponding Slim route.
* Only public routes, and both `?addlink` and `?post` were kept here.
* Other routes will just display the linklist.
*
* @deprecated
*/
class LegacyController extends ShaarliVisitorController
{
/** @var string[] Both `?post` and `?addlink` do not use `?do=` format. */
public const LEGACY_GET_ROUTES = [
'post',
'addlink',
];
/**
* This method will call `$action` method, which will redirect to corresponding Slim route.
*/
public function process(Request $request, Response $response, string $action): Response
{
if (!method_exists($this, $action)) {
throw new UnknowLegacyRouteException();
}
return $this->{$action}($request, $response);
}
/** Legacy route: ?post= */
public function post(Request $request, Response $response): Response
{
$parameters = count($request->getQueryParams()) > 0 ? '?' . http_build_query($request->getQueryParams()) : '';
if (!$this->container->loginManager->isLoggedIn()) {
return $this->redirect($response, '/login' . $parameters);
}
return $this->redirect($response, '/admin/shaare' . $parameters);
}
/** Legacy route: ?addlink= */
protected function addlink(Request $request, Response $response): Response
{
if (!$this->container->loginManager->isLoggedIn()) {
return $this->redirect($response, '/login');
}
return $this->redirect($response, '/admin/add-shaare');
}
/** Legacy route: ?do=login */
protected function login(Request $request, Response $response): Response
{
return $this->redirect($response, '/login');
}
/** Legacy route: ?do=logout */
protected function logout(Request $request, Response $response): Response
{
return $this->redirect($response, '/logout');
}
/** Legacy route: ?do=picwall */
protected function picwall(Request $request, Response $response): Response
{
return $this->redirect($response, '/picture-wall');
}
/** Legacy route: ?do=tagcloud */
protected function tagcloud(Request $request, Response $response): Response
{
return $this->redirect($response, '/tags/cloud');
}
/** Legacy route: ?do=taglist */
protected function taglist(Request $request, Response $response): Response
{
return $this->redirect($response, '/tags/list');
}
/** Legacy route: ?do=daily */
protected function daily(Request $request, Response $response): Response
{
$dayParam = !empty($request->getParam('day')) ? '?day=' . escape($request->getParam('day')) : '';
return $this->redirect($response, '/daily' . $dayParam);
}
/** Legacy route: ?do=rss */
protected function rss(Request $request, Response $response): Response
{
return $this->feed($request, $response, FeedBuilder::$FEED_RSS);
}
/** Legacy route: ?do=atom */
protected function atom(Request $request, Response $response): Response
{
return $this->feed($request, $response, FeedBuilder::$FEED_ATOM);
}
/** Legacy route: ?do=opensearch */
protected function opensearch(Request $request, Response $response): Response
{
return $this->redirect($response, '/open-search');
}
/** Legacy route: ?do=dailyrss */
protected function dailyrss(Request $request, Response $response): Response
{
return $this->redirect($response, '/daily-rss');
}
/** Legacy route: ?do=feed */
protected function feed(Request $request, Response $response, string $feedType): Response
{
$parameters = count($request->getQueryParams()) > 0 ? '?' . http_build_query($request->getQueryParams()) : '';
return $this->redirect($response, '/feed/' . $feedType . $parameters);
}
}

View file

@ -1,12 +1,15 @@
<?php <?php
namespace Shaarli;
namespace Shaarli\Legacy;
/** /**
* Class Router * Class Router
* *
* (only displayable pages here) * (only displayable pages here)
*
* @deprecated
*/ */
class Router class LegacyRouter
{ {
public static $AJAX_THUMB_UPDATE = 'ajax_thumb_update'; public static $AJAX_THUMB_UPDATE = 'ajax_thumb_update';

View file

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Shaarli\Legacy;
class UnknowLegacyRouteException extends \Exception
{
}

View file

@ -69,6 +69,15 @@ public function __construct(&$conf, $session, $linkDB = null, $token = null, $is
$this->isLoggedIn = $isLoggedIn; $this->isLoggedIn = $isLoggedIn;
} }
/**
* Reset current state of template rendering.
* Mostly useful for error handling. We remove everything, and display the error template.
*/
public function reset(): void
{
$this->tpl = false;
}
/** /**
* Initialize all default tpl tags. * Initialize all default tpl tags.
*/ */

View file

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Shaarli\Render;
interface TemplatePage
{
public const ERROR_404 = '404';
public const ADDLINK = 'addlink';
public const CHANGE_PASSWORD = 'changepassword';
public const CHANGE_TAG = 'changetag';
public const CONFIGURE = 'configure';
public const DAILY = 'daily';
public const DAILY_RSS = 'dailyrss';
public const EDIT_LINK = 'editlink';
public const ERROR = 'error';
public const EXPORT = 'export';
public const NETSCAPE_EXPORT_BOOKMARKS = 'export.bookmarks';
public const FEED_ATOM = 'feed.atom';
public const FEED_RSS = 'feed.rss';
public const IMPORT = 'import';
public const INSTALL = 'install';
public const LINKLIST = 'linklist';
public const LOGIN = 'loginform';
public const OPEN_SEARCH = 'opensearch';
public const PICTURE_WALL = 'picwall';
public const PLUGINS_ADMIN = 'pluginsadmin';
public const TAG_CLOUD = 'tag.cloud';
public const TAG_LIST = 'tag.list';
public const THUMBNAILS = 'thumbnails';
public const TOOLS = 'tools';
}

View file

@ -21,7 +21,7 @@ class Updater
/** /**
* @var BookmarkServiceInterface instance. * @var BookmarkServiceInterface instance.
*/ */
protected $linkServices; protected $bookmarkService;
/** /**
* @var ConfigManager $conf Configuration Manager instance. * @var ConfigManager $conf Configuration Manager instance.
@ -49,7 +49,7 @@ class Updater
public function __construct($doneUpdates, $linkDB, $conf, $isLoggedIn) public function __construct($doneUpdates, $linkDB, $conf, $isLoggedIn)
{ {
$this->doneUpdates = $doneUpdates; $this->doneUpdates = $doneUpdates;
$this->linkServices = $linkDB; $this->bookmarkService = $linkDB;
$this->conf = $conf; $this->conf = $conf;
$this->isLoggedIn = $isLoggedIn; $this->isLoggedIn = $isLoggedIn;
@ -68,7 +68,7 @@ public function __construct($doneUpdates, $linkDB, $conf, $isLoggedIn)
*/ */
public function update() public function update()
{ {
$updatesRan = array(); $updatesRan = [];
// If the user isn't logged in, exit without updating. // If the user isn't logged in, exit without updating.
if ($this->isLoggedIn !== true) { if ($this->isLoggedIn !== true) {
@ -112,6 +112,16 @@ public function getDoneUpdates()
return $this->doneUpdates; return $this->doneUpdates;
} }
public function readUpdates(string $updatesFilepath): array
{
return UpdaterUtils::read_updates_file($updatesFilepath);
}
public function writeUpdates(string $updatesFilepath, array $updates): void
{
UpdaterUtils::write_updates_file($updatesFilepath, $updates);
}
/** /**
* With the Slim routing system, default header link should be `./` instead of `?`. * With the Slim routing system, default header link should be `./` instead of `?`.
* Otherwise you can not go back to the home page. Example: `/picture-wall` -> `/picture-wall?` instead of `/`. * Otherwise you can not go back to the home page. Example: `/picture-wall` -> `/picture-wall?` instead of `/`.
@ -127,4 +137,31 @@ public function updateMethodRelativeHomeLink(): bool
return true; return true;
} }
/**
* With the Slim routing system, note bookmarks URL formatted `?abcdef`
* should be replaced with `/shaare/abcdef`
*/
public function updateMethodMigrateExistingNotesUrl(): bool
{
$updated = false;
foreach ($this->bookmarkService->search() as $bookmark) {
if ($bookmark->isNote()
&& startsWith($bookmark->getUrl(), '?')
&& 1 === preg_match('/^\?([a-zA-Z0-9-_@]{6})($|&|#)/', $bookmark->getUrl(), $match)
) {
$updated = true;
$bookmark = $bookmark->setUrl('/shaare/' . $match[1]);
$this->bookmarkService->set($bookmark, false);
}
}
if ($updated) {
$this->bookmarkService->save();
}
return true;
}
} }

View file

@ -131,7 +131,7 @@ If it's still not working, please [open an issue](https://github.com/shaarli/Sha
| ------------- |:-------------:| | ------------- |:-------------:|
| [render_header](#render_header) | Allow plugin to add content in page headers. | | [render_header](#render_header) | Allow plugin to add content in page headers. |
| [render_includes](#render_includes) | Allow plugin to include their own CSS files. | | [render_includes](#render_includes) | Allow plugin to include their own CSS files. |
| [render_footer](#render_footer) | Allow plugin to add content in page footer and include their own JS files. | | [render_footer](#render_footer) | Allow plugin to add content in page footer and include their own JS files. |
| [render_linklist](#render_linklist) | It allows to add content at the begining and end of the page, after every link displayed and to alter link data. | | [render_linklist](#render_linklist) | It allows to add content at the begining and end of the page, after every link displayed and to alter link data. |
| [render_editlink](#render_editlink) | Allow to add fields in the form, or display elements. | | [render_editlink](#render_editlink) | Allow to add fields in the form, or display elements. |
| [render_tools](#render_tools) | Allow to add content at the end of the page. | | [render_tools](#render_tools) | Allow to add content at the end of the page. |
@ -515,7 +515,7 @@ Otherwise, you can use your own JS as long as this field is send by the form:
### Placeholder system ### Placeholder system
In order to make plugins work with every custom themes, you need to add variable placeholder in your templates. In order to make plugins work with every custom themes, you need to add variable placeholder in your templates.
It's a RainTPL loop like this: It's a RainTPL loop like this:
@ -537,7 +537,7 @@ At the end of the menu:
At the end of file, before clearing floating blocks: At the end of file, before clearing floating blocks:
{if="!empty($plugin_errors) && isLoggedIn()"} {if="!empty($plugin_errors) && $is_logged_in"}
<ul class="errors"> <ul class="errors">
{loop="plugin_errors"} {loop="plugin_errors"}
<li>{$value}</li> <li>{$value}</li>

534
index.php
View file

@ -61,30 +61,15 @@
require_once 'application/Utils.php'; require_once 'application/Utils.php';
use Shaarli\ApplicationUtils; use Shaarli\ApplicationUtils;
use Shaarli\Bookmark\Bookmark;
use Shaarli\Bookmark\BookmarkFileService; use Shaarli\Bookmark\BookmarkFileService;
use Shaarli\Bookmark\BookmarkFilter;
use Shaarli\Bookmark\BookmarkServiceInterface;
use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Shaarli\Container\ContainerBuilder; use Shaarli\Container\ContainerBuilder;
use Shaarli\Feed\CachedPage;
use Shaarli\Feed\FeedBuilder;
use Shaarli\Formatter\BookmarkMarkdownFormatter;
use Shaarli\Formatter\FormatterFactory;
use Shaarli\History; use Shaarli\History;
use Shaarli\Languages; use Shaarli\Languages;
use Shaarli\Netscape\NetscapeBookmarkUtils;
use Shaarli\Plugin\PluginManager; use Shaarli\Plugin\PluginManager;
use Shaarli\Render\PageBuilder; use Shaarli\Render\PageBuilder;
use Shaarli\Render\PageCacheManager;
use Shaarli\Render\ThemeUtils;
use Shaarli\Router;
use Shaarli\Security\LoginManager; use Shaarli\Security\LoginManager;
use Shaarli\Security\SessionManager; use Shaarli\Security\SessionManager;
use Shaarli\Thumbnailer;
use Shaarli\Updater\Updater;
use Shaarli\Updater\UpdaterUtils;
use Slim\App; use Slim\App;
// Ensure the PHP version is supported // Ensure the PHP version is supported
@ -196,20 +181,6 @@
$loginManager->checkLoginState($_COOKIE, $clientIpId); $loginManager->checkLoginState($_COOKIE, $clientIpId);
/**
* Adapter function to ensure compatibility with third-party templates
*
* @see https://github.com/shaarli/Shaarli/pull/1086
*
* @return bool true when the user is logged in, false otherwise
*/
function isLoggedIn()
{
global $loginManager;
return $loginManager->isLoggedIn();
}
// ------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------
// Process login form: Check if login/password is correct. // Process login form: Check if login/password is correct.
if (isset($_POST['login'])) { if (isset($_POST['login'])) {
@ -300,473 +271,6 @@ function isLoggedIn()
$_SESSION['tokens']=array(); // Token are attached to the session. $_SESSION['tokens']=array(); // Token are attached to the session.
} }
/**
* Renders the linklist
*
* @param pageBuilder $PAGE pageBuilder instance.
* @param BookmarkServiceInterface $linkDb instance.
* @param ConfigManager $conf Configuration Manager instance.
* @param PluginManager $pluginManager Plugin Manager instance.
*/
function showLinkList($PAGE, $linkDb, $conf, $pluginManager, $loginManager)
{
buildLinkList($PAGE, $linkDb, $conf, $pluginManager, $loginManager);
$PAGE->renderPage('linklist');
}
/**
* Render HTML page (according to URL parameters and user rights)
*
* @param ConfigManager $conf Configuration Manager instance.
* @param PluginManager $pluginManager Plugin Manager instance,
* @param BookmarkServiceInterface $bookmarkService
* @param History $history instance
* @param SessionManager $sessionManager SessionManager instance
* @param LoginManager $loginManager LoginManager instance
*/
function renderPage($conf, $pluginManager, $bookmarkService, $history, $sessionManager, $loginManager)
{
$pageCacheManager = new PageCacheManager($conf->get('resource.page_cache'), $loginManager->isLoggedIn());
$updater = new Updater(
UpdaterUtils::read_updates_file($conf->get('resource.updates')),
$bookmarkService,
$conf,
$loginManager->isLoggedIn()
);
try {
$newUpdates = $updater->update();
if (! empty($newUpdates)) {
UpdaterUtils::write_updates_file(
$conf->get('resource.updates'),
$updater->getDoneUpdates()
);
$pageCacheManager->invalidateCaches();
}
} catch (Exception $e) {
die($e->getMessage());
}
$PAGE = new PageBuilder($conf, $_SESSION, $bookmarkService, $sessionManager->generateToken(), $loginManager->isLoggedIn());
$PAGE->assign('linkcount', $bookmarkService->count(BookmarkFilter::$ALL));
$PAGE->assign('privateLinkcount', $bookmarkService->count(BookmarkFilter::$PRIVATE));
$PAGE->assign('plugin_errors', $pluginManager->getErrors());
// Determine which page will be rendered.
$query = (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : '';
$targetPage = Router::findPage($query, $_GET, $loginManager->isLoggedIn());
if (// if the user isn't logged in
!$loginManager->isLoggedIn() &&
// and Shaarli doesn't have public content...
$conf->get('privacy.hide_public_links') &&
// and is configured to enforce the login
$conf->get('privacy.force_login') &&
// and the current page isn't already the login page
$targetPage !== Router::$PAGE_LOGIN &&
// and the user is not requesting a feed (which would lead to a different content-type as expected)
$targetPage !== Router::$PAGE_FEED_ATOM &&
$targetPage !== Router::$PAGE_FEED_RSS
) {
// force current page to be the login page
$targetPage = Router::$PAGE_LOGIN;
}
// Call plugin hooks for header, footer and includes, specifying which page will be rendered.
// Then assign generated data to RainTPL.
$common_hooks = array(
'includes',
'header',
'footer',
);
foreach ($common_hooks as $name) {
$plugin_data = array();
$pluginManager->executeHooks(
'render_' . $name,
$plugin_data,
array(
'target' => $targetPage,
'loggedin' => $loginManager->isLoggedIn()
)
);
$PAGE->assign('plugins_' . $name, $plugin_data);
}
// -------- Display login form.
if ($targetPage == Router::$PAGE_LOGIN) {
header('Location: ./login');
exit;
}
// -------- User wants to logout.
if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=logout')) {
header('Location: ./logout');
exit;
}
// -------- Picture wall
if ($targetPage == Router::$PAGE_PICWALL) {
header('Location: ./picture-wall');
exit;
}
// -------- Tag cloud
if ($targetPage == Router::$PAGE_TAGCLOUD) {
header('Location: ./tags/cloud');
exit;
}
// -------- Tag list
if ($targetPage == Router::$PAGE_TAGLIST) {
header('Location: ./tags/list');
exit;
}
// Daily page.
if ($targetPage == Router::$PAGE_DAILY) {
$dayParam = !empty($_GET['day']) ? '?day=' . escape($_GET['day']) : '';
header('Location: ./daily'. $dayParam);
exit;
}
// ATOM and RSS feed.
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));
exit;
}
// Display opensearch plugin (XML)
if ($targetPage == Router::$PAGE_OPENSEARCH) {
header('Location: ./open-search');
exit;
}
// -------- User clicks on a tag in a link: The tag is added to the list of searched tags (searchtags=...)
if (isset($_GET['addtag'])) {
header('Location: ./add-tag/'. $_GET['addtag']);
exit;
}
// -------- User clicks on a tag in result count: Remove the tag from the list of searched tags (searchtags=...)
if (isset($_GET['removetag'])) {
header('Location: ./remove-tag/'. $_GET['removetag']);
exit;
}
// -------- User wants to change the number of bookmarks per page (linksperpage=...)
if (isset($_GET['linksperpage'])) {
header('Location: ./links-per-page?nb='. $_GET['linksperpage']);
exit;
}
// -------- User wants to see only private bookmarks (toggle)
if (isset($_GET['visibility'])) {
header('Location: ./visibility/'. $_GET['visibility']);
exit;
}
// -------- User wants to see only untagged bookmarks (toggle)
if (isset($_GET['untaggedonly'])) {
header('Location: ./untagged-only');
exit;
}
// -------- Handle other actions allowed for non-logged in users:
if (!$loginManager->isLoggedIn()) {
// User tries to post new link but is not logged in:
// Show login screen, then redirect to ?post=...
if (isset($_GET['post'])) {
header( // Redirect to login page, then back to post link.
'Location: ./login?post='.urlencode($_GET['post']).
(!empty($_GET['title'])?'&title='.urlencode($_GET['title']):'').
(!empty($_GET['description'])?'&description='.urlencode($_GET['description']):'').
(!empty($_GET['tags'])?'&tags='.urlencode($_GET['tags']):'').
(!empty($_GET['source'])?'&source='.urlencode($_GET['source']):'')
);
exit;
}
showLinkList($PAGE, $bookmarkService, $conf, $pluginManager, $loginManager);
if (isset($_GET['edit_link'])) {
header('Location: ./login?edit_link='. escape($_GET['edit_link']));
exit;
}
exit; // Never remove this one! All operations below are reserved for logged in user.
}
// -------- All other functions are reserved for the registered user:
// TODO: Remove legacy admin route redirections. We'll only keep public URL.
// -------- Display the Tools menu if requested (import/export/bookmarklet...)
if ($targetPage == Router::$PAGE_TOOLS) {
header('Location: ./admin/tools');
exit;
}
// -------- User wants to change his/her password.
if ($targetPage == Router::$PAGE_CHANGEPASSWORD) {
header('Location: ./admin/password');
exit;
}
// -------- User wants to change configuration
if ($targetPage == Router::$PAGE_CONFIGURE) {
header('Location: ./admin/configure');
exit;
}
// -------- User wants to rename a tag or delete it
if ($targetPage == Router::$PAGE_CHANGETAG) {
header('Location: ./admin/tags');
exit;
}
// -------- User wants to add a link without using the bookmarklet: Show form.
if ($targetPage == Router::$PAGE_ADDLINK) {
header('Location: ./admin/shaare');
exit;
}
// -------- User clicked the "Save" button when editing a link: Save link to database.
if (isset($_POST['save_edit'])) {
// This route is no longer supported in legacy mode
header('Location: ./');
exit;
}
// -------- User clicked the "Delete" button when editing a link: Delete link from database.
if ($targetPage == Router::$PAGE_DELETELINK) {
$ids = $_GET['lf_linkdate'] ?? '';
$token = $_GET['token'] ?? '';
header('Location: ./admin/shaare/delete?id=' . $ids . '&token=' . $token);
exit;
}
// -------- User clicked either "Set public" or "Set private" bulk operation
if ($targetPage == Router::$PAGE_CHANGE_VISIBILITY) {
header('Location: ./admin/shaare/visibility?id=' . $_GET['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: ./admin/shaare/' . $id);
exit;
}
// -------- User want to post a new link: Display link edit form.
if (isset($_GET['post'])) {
header('Location: ./admin/shaare?' . http_build_query($_GET));
exit;
}
if ($targetPage == Router::$PAGE_PINLINK) {
// This route is no longer supported in legacy mode
header('Location: ./');
exit;
}
if ($targetPage == Router::$PAGE_EXPORT) {
header('Location: ./admin/export');
exit;
}
if ($targetPage == Router::$PAGE_IMPORT) {
header('Location: ./admin/import');
exit;
}
// Plugin administration page
if ($targetPage == Router::$PAGE_PLUGINSADMIN) {
header('Location: ./admin/plugins');
exit;
}
// Plugin administration form action
if ($targetPage == Router::$PAGE_SAVE_PLUGINSADMIN) {
// This route is no longer supported in legacy mode
header('Location: ./admin/plugins');
exit;
}
// Get a fresh token
if ($targetPage == Router::$GET_TOKEN) {
header('Location: ./admin/token');
exit;
}
// -------- Thumbnails Update
if ($targetPage == Router::$PAGE_THUMBS_UPDATE) {
header('Location: ./admin/thumbnails');
exit;
}
// -------- Single Thumbnail Update
if ($targetPage == Router::$AJAX_THUMB_UPDATE) {
// This route is no longer supported in legacy mode
http_response_code(404);
exit;
}
// -------- Otherwise, simply display search form and bookmarks:
showLinkList($PAGE, $bookmarkService, $conf, $pluginManager, $loginManager);
exit;
}
/**
* Template for the list of bookmarks (<div id="linklist">)
* This function fills all the necessary fields in the $PAGE for the template 'linklist.html'
*
* @param pageBuilder $PAGE pageBuilder instance.
* @param BookmarkServiceInterface $linkDb LinkDB instance.
* @param ConfigManager $conf Configuration Manager instance.
* @param PluginManager $pluginManager Plugin Manager instance.
* @param LoginManager $loginManager LoginManager instance
*/
function buildLinkList($PAGE, $linkDb, $conf, $pluginManager, $loginManager)
{
$factory = new FormatterFactory($conf, $loginManager->isLoggedIn());
$formatter = $factory->getFormatter();
// Used in templates
if (isset($_GET['searchtags'])) {
if (! empty($_GET['searchtags'])) {
$searchtags = escape(normalize_spaces($_GET['searchtags']));
} else {
$searchtags = false;
}
} else {
$searchtags = '';
}
$searchterm = !empty($_GET['searchterm']) ? escape(normalize_spaces($_GET['searchterm'])) : '';
// Smallhash filter
if (! empty($_SERVER['QUERY_STRING'])
&& preg_match('/^[a-zA-Z0-9-_@]{6}($|&|#)/', $_SERVER['QUERY_STRING'])) {
try {
$linksToDisplay = $linkDb->findByHash($_SERVER['QUERY_STRING']);
} catch (BookmarkNotFoundException $e) {
$PAGE->render404($e->getMessage());
exit;
}
} else {
// Filter bookmarks according search parameters.
$visibility = ! empty($_SESSION['visibility']) ? $_SESSION['visibility'] : null;
$request = [
'searchtags' => $searchtags,
'searchterm' => $searchterm,
];
$linksToDisplay = $linkDb->search($request, $visibility, false, !empty($_SESSION['untaggedonly']));
}
// ---- Handle paging.
$keys = array();
foreach ($linksToDisplay as $key => $value) {
$keys[] = $key;
}
// Select articles according to paging.
$pagecount = ceil(count($keys) / $_SESSION['LINKS_PER_PAGE']);
$pagecount = $pagecount == 0 ? 1 : $pagecount;
$page= empty($_GET['page']) ? 1 : intval($_GET['page']);
$page = $page < 1 ? 1 : $page;
$page = $page > $pagecount ? $pagecount : $page;
// Start index.
$i = ($page-1) * $_SESSION['LINKS_PER_PAGE'];
$end = $i + $_SESSION['LINKS_PER_PAGE'];
$thumbnailsEnabled = $conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE;
if ($thumbnailsEnabled) {
$thumbnailer = new Thumbnailer($conf);
}
$linkDisp = array();
while ($i<$end && $i<count($keys)) {
$link = $formatter->format($linksToDisplay[$keys[$i]]);
// Logged in, thumbnails enabled, not a note,
// and (never retrieved yet or no valid cache file)
if ($loginManager->isLoggedIn()
&& $thumbnailsEnabled
&& !$linksToDisplay[$keys[$i]]->isNote()
&& $linksToDisplay[$keys[$i]]->getThumbnail() !== false
&& ! is_file($linksToDisplay[$keys[$i]]->getThumbnail())
) {
$linksToDisplay[$keys[$i]]->setThumbnail($thumbnailer->get($link['url']));
$linkDb->set($linksToDisplay[$keys[$i]], false);
$updateDB = true;
$link['thumbnail'] = $linksToDisplay[$keys[$i]]->getThumbnail();
}
// Check for both signs of a note: starting with ? and 7 chars long.
// if ($link['url'][0] === '?' && strlen($link['url']) === 7) {
// $link['url'] = index_url($_SERVER) . $link['url'];
// }
$linkDisp[$keys[$i]] = $link;
$i++;
}
// If we retrieved new thumbnails, we update the database.
if (!empty($updateDB)) {
$linkDb->save();
}
// Compute paging navigation
$searchtagsUrl = $searchtags === '' ? '' : '&searchtags=' . urlencode($searchtags);
$searchtermUrl = empty($searchterm) ? '' : '&searchterm=' . urlencode($searchterm);
$previous_page_url = '';
if ($i != count($keys)) {
$previous_page_url = '?page=' . ($page+1) . $searchtermUrl . $searchtagsUrl;
}
$next_page_url='';
if ($page>1) {
$next_page_url = '?page=' . ($page-1) . $searchtermUrl . $searchtagsUrl;
}
// Fill all template fields.
$data = array(
'previous_page_url' => $previous_page_url,
'next_page_url' => $next_page_url,
'page_current' => $page,
'page_max' => $pagecount,
'result_count' => count($linksToDisplay),
'search_term' => $searchterm,
'search_tags' => $searchtags,
'visibility' => ! empty($_SESSION['visibility']) ? $_SESSION['visibility'] : '',
'links' => $linkDisp,
);
// If there is only a single link, we change on-the-fly the title of the page.
if (count($linksToDisplay) == 1) {
$data['pagetitle'] = $linksToDisplay[$keys[0]]->getTitle() .' - '. $conf->get('general.title');
} elseif (! empty($searchterm) || ! empty($searchtags)) {
$data['pagetitle'] = t('Search: ');
$data['pagetitle'] .= ! empty($searchterm) ? $searchterm .' ' : '';
$bracketWrap = function ($tag) {
return '['. $tag .']';
};
$data['pagetitle'] .= ! empty($searchtags)
? implode(' ', array_map($bracketWrap, preg_split('/\s+/', $searchtags))).' '
: '';
$data['pagetitle'] .= '- '. $conf->get('general.title');
}
$pluginManager->executeHooks('render_linklist', $data, array('loggedin' => $loginManager->isLoggedIn()));
foreach ($data as $key => $value) {
$PAGE->assign($key, $value);
}
return;
}
/** /**
* Installation * Installation
* This function should NEVER be called if the file data/config.php exists. * This function should NEVER be called if the file data/config.php exists.
@ -882,19 +386,6 @@ function install($conf, $sessionManager, $loginManager)
$_SESSION['LINKS_PER_PAGE'] = $conf->get('general.links_per_page', 20); $_SESSION['LINKS_PER_PAGE'] = $conf->get('general.links_per_page', 20);
} }
try {
$history = new History($conf->get('resource.history'));
} catch (Exception $e) {
die($e->getMessage());
}
$linkDb = new BookmarkFileService($conf, $history, $loginManager->isLoggedIn());
if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=dailyrss')) {
header('Location: ./daily-rss');
exit;
}
$containerBuilder = new ContainerBuilder($conf, $sessionManager, $loginManager); $containerBuilder = new ContainerBuilder($conf, $sessionManager, $loginManager);
$container = $containerBuilder->build(); $container = $containerBuilder->build();
$app = new App($container); $app = new App($container);
@ -918,13 +409,15 @@ function install($conf, $sessionManager, $loginManager)
$app->group('', function () { $app->group('', function () {
/* -- PUBLIC --*/ /* -- PUBLIC --*/
$this->get('/login', '\Shaarli\Front\Controller\Visitor\LoginController:index'); $this->get('/', '\Shaarli\Front\Controller\Visitor\BookmarkListController:index');
$this->get('/shaare/{hash}', '\Shaarli\Front\Controller\Visitor\BookmarkListController:permalink');
$this->get('/login', '\Shaarli\Front\Controller\Visitor\LoginController:index')->setName('login');
$this->get('/picture-wall', '\Shaarli\Front\Controller\Visitor\PictureWallController:index'); $this->get('/picture-wall', '\Shaarli\Front\Controller\Visitor\PictureWallController:index');
$this->get('/tags/cloud', '\Shaarli\Front\Controller\Visitor\TagCloudController:cloud'); $this->get('/tags/cloud', '\Shaarli\Front\Controller\Visitor\TagCloudController:cloud');
$this->get('/tags/list', '\Shaarli\Front\Controller\Visitor\TagCloudController:list'); $this->get('/tags/list', '\Shaarli\Front\Controller\Visitor\TagCloudController:list');
$this->get('/daily', '\Shaarli\Front\Controller\Visitor\DailyController:index'); $this->get('/daily', '\Shaarli\Front\Controller\Visitor\DailyController:index');
$this->get('/daily-rss', '\Shaarli\Front\Controller\Visitor\DailyController:rss'); $this->get('/daily-rss', '\Shaarli\Front\Controller\Visitor\DailyController:rss')->setName('rss');
$this->get('/feed/atom', '\Shaarli\Front\Controller\Visitor\FeedController:atom'); $this->get('/feed/atom', '\Shaarli\Front\Controller\Visitor\FeedController:atom')->setName('atom');
$this->get('/feed/rss', '\Shaarli\Front\Controller\Visitor\FeedController:rss'); $this->get('/feed/rss', '\Shaarli\Front\Controller\Visitor\FeedController:rss');
$this->get('/open-search', '\Shaarli\Front\Controller\Visitor\OpenSearchController:index'); $this->get('/open-search', '\Shaarli\Front\Controller\Visitor\OpenSearchController:index');
@ -967,19 +460,4 @@ function install($conf, $sessionManager, $loginManager)
$response = $app->run(true); $response = $app->run(true);
// Hack to make Slim and Shaarli router work together: $app->respond($response);
// If a Slim route isn't found and NOT API call, we call renderPage().
if ($response->getStatusCode() == 404 && strpos($_SERVER['REQUEST_URI'], '/api/v1') === false) {
// We use UTF-8 for proper international characters handling.
header('Content-Type: text/html; charset=utf-8');
renderPage($conf, $pluginManager, $linkDb, $history, $sessionManager, $loginManager);
} else {
$response = $response
->withHeader('Access-Control-Allow-Origin', '*')
->withHeader(
'Access-Control-Allow-Headers',
'X-Requested-With, Content-Type, Accept, Origin, Authorization'
)
->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
$app->respond($response);
}

View file

@ -5,7 +5,7 @@
* Adds the addlink input on the linklist page. * Adds the addlink input on the linklist page.
*/ */
use Shaarli\Router; use Shaarli\Render\TemplatePage;
/** /**
* When linklist is displayed, add play videos to header's toolbar. * When linklist is displayed, add play videos to header's toolbar.
@ -16,7 +16,7 @@
*/ */
function hook_addlink_toolbar_render_header($data) function hook_addlink_toolbar_render_header($data)
{ {
if ($data['_PAGE_'] == Router::$PAGE_LINKLIST && $data['_LOGGEDIN_'] === true) { if ($data['_PAGE_'] == TemplatePage::LINKLIST && $data['_LOGGEDIN_'] === true) {
$form = array( $form = array(
'attr' => array( 'attr' => array(
'method' => 'GET', 'method' => 'GET',

View file

@ -16,7 +16,7 @@
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Shaarli\Plugin\PluginManager; use Shaarli\Plugin\PluginManager;
use Shaarli\Router; use Shaarli\Render\TemplatePage;
/** /**
* In the footer hook, there is a working example of a translation extension for Shaarli. * In the footer hook, there is a working example of a translation extension for Shaarli.
@ -74,7 +74,7 @@ function demo_plugin_init($conf)
function hook_demo_plugin_render_header($data) function hook_demo_plugin_render_header($data)
{ {
// Only execute when linklist is rendered. // Only execute when linklist is rendered.
if ($data['_PAGE_'] == Router::$PAGE_LINKLIST) { if ($data['_PAGE_'] == TemplatePage::LINKLIST) {
// If loggedin // If loggedin
if ($data['_LOGGEDIN_'] === true) { if ($data['_LOGGEDIN_'] === true) {
/* /*
@ -441,9 +441,9 @@ function hook_demo_plugin_delete_link($data)
function hook_demo_plugin_render_feed($data) function hook_demo_plugin_render_feed($data)
{ {
foreach ($data['links'] as &$link) { foreach ($data['links'] as &$link) {
if ($data['_PAGE_'] == Router::$PAGE_FEED_ATOM) { if ($data['_PAGE_'] == TemplatePage::FEED_ATOM) {
$link['description'] .= ' - ATOM Feed' ; $link['description'] .= ' - ATOM Feed' ;
} elseif ($data['_PAGE_'] == Router::$PAGE_FEED_RSS) { } elseif ($data['_PAGE_'] == TemplatePage::FEED_RSS) {
$link['description'] .= ' - RSS Feed'; $link['description'] .= ' - RSS Feed';
} }
} }

View file

@ -6,7 +6,7 @@
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Shaarli\Plugin\PluginManager; use Shaarli\Plugin\PluginManager;
use Shaarli\Router; use Shaarli\Render\TemplatePage;
/** /**
* Display an error everywhere if the plugin is enabled without configuration. * Display an error everywhere if the plugin is enabled without configuration.
@ -76,7 +76,7 @@ function hook_isso_render_linklist($data, $conf)
*/ */
function hook_isso_render_includes($data) function hook_isso_render_includes($data)
{ {
if ($data['_PAGE_'] == Router::$PAGE_LINKLIST) { if ($data['_PAGE_'] == TemplatePage::LINKLIST) {
$data['css_files'][] = PluginManager::$PLUGINS_PATH . '/isso/isso.css'; $data['css_files'][] = PluginManager::$PLUGINS_PATH . '/isso/isso.css';
} }

View file

@ -7,7 +7,7 @@
*/ */
use Shaarli\Plugin\PluginManager; use Shaarli\Plugin\PluginManager;
use Shaarli\Router; use Shaarli\Render\TemplatePage;
/** /**
* When linklist is displayed, add play videos to header's toolbar. * When linklist is displayed, add play videos to header's toolbar.
@ -18,7 +18,7 @@
*/ */
function hook_playvideos_render_header($data) function hook_playvideos_render_header($data)
{ {
if ($data['_PAGE_'] == Router::$PAGE_LINKLIST) { if ($data['_PAGE_'] == TemplatePage::LINKLIST) {
$playvideo = array( $playvideo = array(
'attr' => array( 'attr' => array(
'href' => '#', 'href' => '#',
@ -42,7 +42,7 @@ function hook_playvideos_render_header($data)
*/ */
function hook_playvideos_render_footer($data) function hook_playvideos_render_footer($data)
{ {
if ($data['_PAGE_'] == Router::$PAGE_LINKLIST) { if ($data['_PAGE_'] == TemplatePage::LINKLIST) {
$data['js_files'][] = PluginManager::$PLUGINS_PATH . '/playvideos/jquery-1.11.2.min.js'; $data['js_files'][] = PluginManager::$PLUGINS_PATH . '/playvideos/jquery-1.11.2.min.js';
$data['js_files'][] = PluginManager::$PLUGINS_PATH . '/playvideos/youtube_playlist.js'; $data['js_files'][] = PluginManager::$PLUGINS_PATH . '/playvideos/youtube_playlist.js';
} }

View file

@ -13,7 +13,7 @@
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Shaarli\Feed\FeedBuilder; use Shaarli\Feed\FeedBuilder;
use Shaarli\Plugin\PluginManager; use Shaarli\Plugin\PluginManager;
use Shaarli\Router; use Shaarli\Render\TemplatePage;
/** /**
* Plugin init function - set the hub to the default appspot one. * Plugin init function - set the hub to the default appspot one.
@ -41,7 +41,7 @@ function pubsubhubbub_init($conf)
*/ */
function hook_pubsubhubbub_render_feed($data, $conf) function hook_pubsubhubbub_render_feed($data, $conf)
{ {
$feedType = $data['_PAGE_'] == Router::$PAGE_FEED_RSS ? FeedBuilder::$FEED_RSS : FeedBuilder::$FEED_ATOM; $feedType = $data['_PAGE_'] == TemplatePage::FEED_RSS ? FeedBuilder::$FEED_RSS : FeedBuilder::$FEED_ATOM;
$template = file_get_contents(PluginManager::$PLUGINS_PATH . '/pubsubhubbub/hub.'. $feedType .'.xml'); $template = file_get_contents(PluginManager::$PLUGINS_PATH . '/pubsubhubbub/hub.'. $feedType .'.xml');
$data['feed_plugins_header'][] = sprintf($template, $conf->get('plugins.PUBSUBHUB_URL')); $data['feed_plugins_header'][] = sprintf($template, $conf->get('plugins.PUBSUBHUB_URL'));

View file

@ -6,7 +6,7 @@
*/ */
use Shaarli\Plugin\PluginManager; use Shaarli\Plugin\PluginManager;
use Shaarli\Router; use Shaarli\Render\TemplatePage;
/** /**
* Add qrcode icon to link_plugin when rendering linklist. * Add qrcode icon to link_plugin when rendering linklist.
@ -40,7 +40,7 @@ function hook_qrcode_render_linklist($data)
*/ */
function hook_qrcode_render_footer($data) function hook_qrcode_render_footer($data)
{ {
if ($data['_PAGE_'] == Router::$PAGE_LINKLIST) { if ($data['_PAGE_'] == TemplatePage::LINKLIST) {
$data['js_files'][] = PluginManager::$PLUGINS_PATH . '/qrcode/shaarli-qrcode.js'; $data['js_files'][] = PluginManager::$PLUGINS_PATH . '/qrcode/shaarli-qrcode.js';
} }
@ -56,7 +56,7 @@ function hook_qrcode_render_footer($data)
*/ */
function hook_qrcode_render_includes($data) function hook_qrcode_render_includes($data)
{ {
if ($data['_PAGE_'] == Router::$PAGE_LINKLIST) { if ($data['_PAGE_'] == TemplatePage::LINKLIST) {
$data['css_files'][] = PluginManager::$PLUGINS_PATH . '/qrcode/qrcode.css'; $data['css_files'][] = PluginManager::$PLUGINS_PATH . '/qrcode/qrcode.css';
} }

View file

@ -892,35 +892,35 @@ public function testHiddenTags()
public function testFilterHashValid() public function testFilterHashValid()
{ {
$request = smallHash('20150310_114651'); $request = smallHash('20150310_114651');
$this->assertEquals( $this->assertSame(
1, $request,
count($this->publicLinkDB->findByHash($request)) $this->publicLinkDB->findByHash($request)->getShortUrl()
); );
$request = smallHash('20150310_114633' . 8); $request = smallHash('20150310_114633' . 8);
$this->assertEquals( $this->assertSame(
1, $request,
count($this->publicLinkDB->findByHash($request)) $this->publicLinkDB->findByHash($request)->getShortUrl()
); );
} }
/** /**
* Test filterHash() with an invalid smallhash. * Test filterHash() with an invalid smallhash.
*
* @expectedException \Shaarli\Bookmark\Exception\BookmarkNotFoundException
*/ */
public function testFilterHashInValid1() public function testFilterHashInValid1()
{ {
$this->expectException(BookmarkNotFoundException::class);
$request = 'blabla'; $request = 'blabla';
$this->publicLinkDB->findByHash($request); $this->publicLinkDB->findByHash($request);
} }
/** /**
* Test filterHash() with an empty smallhash. * Test filterHash() with an empty smallhash.
*
* @expectedException \Shaarli\Bookmark\Exception\BookmarkNotFoundException
*/ */
public function testFilterHashInValid() public function testFilterHashInValid()
{ {
$this->expectException(BookmarkNotFoundException::class);
$this->publicLinkDB->findByHash(''); $this->publicLinkDB->findByHash('');
} }

View file

@ -8,7 +8,11 @@
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Shaarli\Container\ShaarliContainer; use Shaarli\Container\ShaarliContainer;
use Shaarli\Front\Exception\LoginBannedException; use Shaarli\Front\Exception\LoginBannedException;
use Shaarli\Front\Exception\UnauthorizedException;
use Shaarli\Render\PageBuilder; use Shaarli\Render\PageBuilder;
use Shaarli\Render\PageCacheManager;
use Shaarli\Security\LoginManager;
use Shaarli\Updater\Updater;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response; use Slim\Http\Response;
use Slim\Http\Uri; use Slim\Http\Uri;
@ -24,9 +28,16 @@ class ShaarliMiddlewareTest extends TestCase
public function setUp(): void public function setUp(): void
{ {
$this->container = $this->createMock(ShaarliContainer::class); $this->container = $this->createMock(ShaarliContainer::class);
$this->container->conf = $this->createMock(ConfigManager::class);
$this->container->loginManager = $this->createMock(LoginManager::class);
$this->middleware = new ShaarliMiddleware($this->container); $this->middleware = new ShaarliMiddleware($this->container);
} }
/**
* Test middleware execution with valid controller call
*/
public function testMiddlewareExecution(): void public function testMiddlewareExecution(): void
{ {
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
@ -49,7 +60,10 @@ public function testMiddlewareExecution(): void
static::assertSame(418, $result->getStatusCode()); static::assertSame(418, $result->getStatusCode());
} }
public function testMiddlewareExecutionWithException(): void /**
* Test middleware execution with controller throwing a known front exception
*/
public function testMiddlewareExecutionWithFrontException(): void
{ {
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
$request->method('getUri')->willReturnCallback(function (): Uri { $request->method('getUri')->willReturnCallback(function (): Uri {
@ -58,7 +72,7 @@ public function testMiddlewareExecutionWithException(): void
return $uri; return $uri;
}); });
$response = new Response(); $response = new Response();
$controller = function (): void { $controller = function (): void {
$exception = new LoginBannedException(); $exception = new LoginBannedException();
@ -72,9 +86,6 @@ public function testMiddlewareExecutionWithException(): void
}); });
$this->container->pageBuilder = $pageBuilder; $this->container->pageBuilder = $pageBuilder;
$conf = $this->createMock(ConfigManager::class);
$this->container->conf = $conf;
/** @var Response $result */ /** @var Response $result */
$result = $this->middleware->__invoke($request, $response, $controller); $result = $this->middleware->__invoke($request, $response, $controller);
@ -82,4 +93,113 @@ public function testMiddlewareExecutionWithException(): void
static::assertSame(401, $result->getStatusCode()); static::assertSame(401, $result->getStatusCode());
static::assertContains('error', (string) $result->getBody()); static::assertContains('error', (string) $result->getBody());
} }
/**
* Test middleware execution with controller throwing a not authorized exception
*/
public function testMiddlewareExecutionWithUnauthorizedException(): void
{
$request = $this->createMock(Request::class);
$request->method('getUri')->willReturnCallback(function (): Uri {
$uri = $this->createMock(Uri::class);
$uri->method('getBasePath')->willReturn('/subfolder');
return $uri;
});
$response = new Response();
$controller = function (): void {
throw new UnauthorizedException();
};
/** @var Response $result */
$result = $this->middleware->__invoke($request, $response, $controller);
static::assertSame(302, $result->getStatusCode());
static::assertSame('/subfolder/login', $result->getHeader('location')[0]);
}
/**
* Test middleware execution with controller throwing a not authorized exception
*/
public function testMiddlewareExecutionWithServerExceptionWith(): void
{
$request = $this->createMock(Request::class);
$request->method('getUri')->willReturnCallback(function (): Uri {
$uri = $this->createMock(Uri::class);
$uri->method('getBasePath')->willReturn('/subfolder');
return $uri;
});
$response = new Response();
$controller = function (): void {
throw new \Exception();
};
$parameters = [];
$this->container->pageBuilder = $this->createMock(PageBuilder::class);
$this->container->pageBuilder->method('render')->willReturnCallback(function (string $message): string {
return $message;
});
$this->container->pageBuilder
->method('assign')
->willReturnCallback(function (string $key, string $value) use (&$parameters): void {
$parameters[$key] = $value;
})
;
/** @var Response $result */
$result = $this->middleware->__invoke($request, $response, $controller);
static::assertSame(500, $result->getStatusCode());
static::assertContains('error', (string) $result->getBody());
static::assertSame('An unexpected error occurred.', $parameters['message']);
}
public function testMiddlewareExecutionWithUpdates(): void
{
$request = $this->createMock(Request::class);
$request->method('getUri')->willReturnCallback(function (): Uri {
$uri = $this->createMock(Uri::class);
$uri->method('getBasePath')->willReturn('/subfolder');
return $uri;
});
$response = new Response();
$controller = function (Request $request, Response $response): Response {
return $response->withStatus(418); // I'm a tea pot
};
$this->container->loginManager = $this->createMock(LoginManager::class);
$this->container->loginManager->method('isLoggedIn')->willReturn(true);
$this->container->conf = $this->createMock(ConfigManager::class);
$this->container->conf->method('get')->willReturnCallback(function (string $key): string {
return $key;
});
$this->container->pageCacheManager = $this->createMock(PageCacheManager::class);
$this->container->pageCacheManager->expects(static::once())->method('invalidateCaches');
$this->container->updater = $this->createMock(Updater::class);
$this->container->updater
->expects(static::once())
->method('update')
->willReturn(['update123'])
;
$this->container->updater->method('getDoneUpdates')->willReturn($updates = ['update123', 'other']);
$this->container->updater
->expects(static::once())
->method('writeUpdates')
->with('resource.updates', $updates)
;
/** @var Response $result */
$result = $this->middleware->__invoke($request, $response, $controller);
static::assertInstanceOf(Response::class, $result);
static::assertSame(418, $result->getStatusCode());
}
} }

View file

@ -0,0 +1,448 @@
<?php
declare(strict_types=1);
namespace Shaarli\Front\Controller\Visitor;
use PHPUnit\Framework\TestCase;
use Shaarli\Bookmark\Bookmark;
use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
use Shaarli\Config\ConfigManager;
use Shaarli\Security\LoginManager;
use Shaarli\Thumbnailer;
use Slim\Http\Request;
use Slim\Http\Response;
class BookmarkListControllerTest extends TestCase
{
use FrontControllerMockHelper;
/** @var BookmarkListController */
protected $controller;
public function setUp(): void
{
$this->createContainer();
$this->controller = new BookmarkListController($this->container);
}
/**
* Test rendering list of bookmarks with default parameters (first page).
*/
public function testIndexDefaultFirstPage(): void
{
$assignedVariables = [];
$this->assignTemplateVars($assignedVariables);
$request = $this->createMock(Request::class);
$response = new Response();
$this->container->bookmarkService
->expects(static::once())
->method('search')
->with(
['searchtags' => '', 'searchterm' => ''],
null,
false,
false
)
->willReturn([
(new Bookmark())->setId(1)->setUrl('http://url1.tld')->setTitle('Title 1'),
(new Bookmark())->setId(2)->setUrl('http://url2.tld')->setTitle('Title 2'),
(new Bookmark())->setId(3)->setUrl('http://url3.tld')->setTitle('Title 3'),
]
);
$this->container->sessionManager
->method('getSessionParameter')
->willReturnCallback(function (string $parameter, $default = null) {
if ('LINKS_PER_PAGE' === $parameter) {
return 2;
}
return $default;
})
;
$result = $this->controller->index($request, $response);
static::assertSame(200, $result->getStatusCode());
static::assertSame('linklist', (string) $result->getBody());
static::assertSame('Shaarli', $assignedVariables['pagetitle']);
static::assertSame('?page=2', $assignedVariables['previous_page_url']);
static::assertSame('', $assignedVariables['next_page_url']);
static::assertSame(2, $assignedVariables['page_max']);
static::assertSame('', $assignedVariables['search_tags']);
static::assertSame(3, $assignedVariables['result_count']);
static::assertSame(1, $assignedVariables['page_current']);
static::assertSame('', $assignedVariables['search_term']);
static::assertNull($assignedVariables['visibility']);
static::assertCount(2, $assignedVariables['links']);
$link = $assignedVariables['links'][0];
static::assertSame(1, $link['id']);
static::assertSame('http://url1.tld', $link['url']);
static::assertSame('Title 1', $link['title']);
$link = $assignedVariables['links'][1];
static::assertSame(2, $link['id']);
static::assertSame('http://url2.tld', $link['url']);
static::assertSame('Title 2', $link['title']);
}
/**
* Test rendering list of bookmarks with default parameters (second page).
*/
public function testIndexDefaultSecondPage(): void
{
$assignedVariables = [];
$this->assignTemplateVars($assignedVariables);
$request = $this->createMock(Request::class);
$request->method('getParam')->willReturnCallback(function (string $key) {
if ('page' === $key) {
return '2';
}
return null;
});
$response = new Response();
$this->container->bookmarkService
->expects(static::once())
->method('search')
->with(
['searchtags' => '', 'searchterm' => ''],
null,
false,
false
)
->willReturn([
(new Bookmark())->setId(1)->setUrl('http://url1.tld')->setTitle('Title 1'),
(new Bookmark())->setId(2)->setUrl('http://url2.tld')->setTitle('Title 2'),
(new Bookmark())->setId(3)->setUrl('http://url3.tld')->setTitle('Title 3'),
])
;
$this->container->sessionManager
->method('getSessionParameter')
->willReturnCallback(function (string $parameter, $default = null) {
if ('LINKS_PER_PAGE' === $parameter) {
return 2;
}
return $default;
})
;
$result = $this->controller->index($request, $response);
static::assertSame(200, $result->getStatusCode());
static::assertSame('linklist', (string) $result->getBody());
static::assertSame('Shaarli', $assignedVariables['pagetitle']);
static::assertSame('', $assignedVariables['previous_page_url']);
static::assertSame('?page=1', $assignedVariables['next_page_url']);
static::assertSame(2, $assignedVariables['page_max']);
static::assertSame('', $assignedVariables['search_tags']);
static::assertSame(3, $assignedVariables['result_count']);
static::assertSame(2, $assignedVariables['page_current']);
static::assertSame('', $assignedVariables['search_term']);
static::assertNull($assignedVariables['visibility']);
static::assertCount(1, $assignedVariables['links']);
$link = $assignedVariables['links'][2];
static::assertSame(3, $link['id']);
static::assertSame('http://url3.tld', $link['url']);
static::assertSame('Title 3', $link['title']);
}
/**
* Test rendering list of bookmarks with filters.
*/
public function testIndexDefaultWithFilters(): void
{
$assignedVariables = [];
$this->assignTemplateVars($assignedVariables);
$request = $this->createMock(Request::class);
$request->method('getParam')->willReturnCallback(function (string $key) {
if ('searchtags' === $key) {
return 'abc def';
}
if ('searchterm' === $key) {
return 'ghi jkl';
}
return null;
});
$response = new Response();
$this->container->sessionManager
->method('getSessionParameter')
->willReturnCallback(function (string $key, $default) {
if ('LINKS_PER_PAGE' === $key) {
return 2;
}
if ('visibility' === $key) {
return 'private';
}
if ('untaggedonly' === $key) {
return true;
}
return $default;
})
;
$this->container->bookmarkService
->expects(static::once())
->method('search')
->with(
['searchtags' => 'abc def', 'searchterm' => 'ghi jkl'],
'private',
false,
true
)
->willReturn([
(new Bookmark())->setId(1)->setUrl('http://url1.tld')->setTitle('Title 1'),
(new Bookmark())->setId(2)->setUrl('http://url2.tld')->setTitle('Title 2'),
(new Bookmark())->setId(3)->setUrl('http://url3.tld')->setTitle('Title 3'),
])
;
$result = $this->controller->index($request, $response);
static::assertSame(200, $result->getStatusCode());
static::assertSame('linklist', (string) $result->getBody());
static::assertSame('Search: ghi jkl [abc] [def] - Shaarli', $assignedVariables['pagetitle']);
static::assertSame('?page=2&searchterm=ghi+jkl&searchtags=abc+def', $assignedVariables['previous_page_url']);
}
/**
* Test displaying a permalink with valid parameters
*/
public function testPermalinkValid(): void
{
$hash = 'abcdef';
$assignedVariables = [];
$this->assignTemplateVars($assignedVariables);
$request = $this->createMock(Request::class);
$response = new Response();
$this->container->bookmarkService
->expects(static::once())
->method('findByHash')
->with($hash)
->willReturn((new Bookmark())->setId(123)->setTitle('Title 1')->setUrl('http://url1.tld'))
;
$result = $this->controller->permalink($request, $response, ['hash' => $hash]);
static::assertSame(200, $result->getStatusCode());
static::assertSame('linklist', (string) $result->getBody());
static::assertSame('Title 1 - Shaarli', $assignedVariables['pagetitle']);
static::assertCount(1, $assignedVariables['links']);
$link = $assignedVariables['links'][0];
static::assertSame(123, $link['id']);
static::assertSame('http://url1.tld', $link['url']);
static::assertSame('Title 1', $link['title']);
}
/**
* Test displaying a permalink with an unknown small hash : renders a 404 template error
*/
public function testPermalinkNotFound(): void
{
$hash = 'abcdef';
$assignedVariables = [];
$this->assignTemplateVars($assignedVariables);
$request = $this->createMock(Request::class);
$response = new Response();
$this->container->bookmarkService
->expects(static::once())
->method('findByHash')
->with($hash)
->willThrowException(new BookmarkNotFoundException())
;
$result = $this->controller->permalink($request, $response, ['hash' => $hash]);
static::assertSame(200, $result->getStatusCode());
static::assertSame('404', (string) $result->getBody());
static::assertSame(
'The link you are trying to reach does not exist or has been deleted.',
$assignedVariables['error_message']
);
}
/**
* Test getting link list with thumbnail updates.
* -> 2 thumbnails update, only 1 datastore write
*/
public function testThumbnailUpdateFromLinkList(): void
{
$request = $this->createMock(Request::class);
$response = new Response();
$this->container->loginManager = $this->createMock(LoginManager::class);
$this->container->loginManager->method('isLoggedIn')->willReturn(true);
$this->container->conf = $this->createMock(ConfigManager::class);
$this->container->conf
->method('get')
->willReturnCallback(function (string $key, $default) {
return $key === 'thumbnails.mode' ? Thumbnailer::MODE_ALL : $default;
})
;
$this->container->thumbnailer = $this->createMock(Thumbnailer::class);
$this->container->thumbnailer
->expects(static::exactly(2))
->method('get')
->withConsecutive(['https://url2.tld'], ['https://url4.tld'])
;
$this->container->bookmarkService
->expects(static::once())
->method('search')
->willReturn([
(new Bookmark())->setId(1)->setUrl('https://url1.tld')->setTitle('Title 1')->setThumbnail(false),
$b1 = (new Bookmark())->setId(2)->setUrl('https://url2.tld')->setTitle('Title 2'),
(new Bookmark())->setId(3)->setUrl('https://url3.tld')->setTitle('Title 3')->setThumbnail(false),
$b2 = (new Bookmark())->setId(2)->setUrl('https://url4.tld')->setTitle('Title 4'),
(new Bookmark())->setId(2)->setUrl('ftp://url5.tld', ['ftp'])->setTitle('Title 5'),
])
;
$this->container->bookmarkService
->expects(static::exactly(2))
->method('set')
->withConsecutive([$b1, false], [$b2, false])
;
$this->container->bookmarkService->expects(static::once())->method('save');
$result = $this->controller->index($request, $response);
static::assertSame(200, $result->getStatusCode());
static::assertSame('linklist', (string) $result->getBody());
}
/**
* Test getting a permalink with thumbnail update.
*/
public function testThumbnailUpdateFromPermalink(): void
{
$request = $this->createMock(Request::class);
$response = new Response();
$this->container->loginManager = $this->createMock(LoginManager::class);
$this->container->loginManager->method('isLoggedIn')->willReturn(true);
$this->container->conf = $this->createMock(ConfigManager::class);
$this->container->conf
->method('get')
->willReturnCallback(function (string $key, $default) {
return $key === 'thumbnails.mode' ? Thumbnailer::MODE_ALL : $default;
})
;
$this->container->thumbnailer = $this->createMock(Thumbnailer::class);
$this->container->thumbnailer->expects(static::once())->method('get')->withConsecutive(['https://url.tld']);
$this->container->bookmarkService
->expects(static::once())
->method('findByHash')
->willReturn($bookmark = (new Bookmark())->setId(2)->setUrl('https://url.tld')->setTitle('Title 1'))
;
$this->container->bookmarkService->expects(static::once())->method('set')->with($bookmark, true);
$this->container->bookmarkService->expects(static::never())->method('save');
$result = $this->controller->permalink($request, $response, ['hash' => 'abc']);
static::assertSame(200, $result->getStatusCode());
static::assertSame('linklist', (string) $result->getBody());
}
/**
* Trigger legacy controller in link list controller: permalink
*/
public function testLegacyControllerPermalink(): void
{
$hash = 'abcdef';
$this->container->environment['QUERY_STRING'] = $hash;
$request = $this->createMock(Request::class);
$response = new Response();
$result = $this->controller->index($request, $response);
static::assertSame(302, $result->getStatusCode());
static::assertSame('/subfolder/shaare/' . $hash, $result->getHeader('location')[0]);
}
/**
* Trigger legacy controller in link list controller: ?do= query parameter
*/
public function testLegacyControllerDoPage(): void
{
$request = $this->createMock(Request::class);
$request->method('getQueryParam')->with('do')->willReturn('picwall');
$response = new Response();
$result = $this->controller->index($request, $response);
static::assertSame(302, $result->getStatusCode());
static::assertSame('/subfolder/picture-wall', $result->getHeader('location')[0]);
}
/**
* Trigger legacy controller in link list controller: ?do= query parameter with unknown legacy route
*/
public function testLegacyControllerUnknownDoPage(): void
{
$request = $this->createMock(Request::class);
$request->method('getQueryParam')->with('do')->willReturn('nope');
$response = new Response();
$result = $this->controller->index($request, $response);
static::assertSame(200, $result->getStatusCode());
static::assertSame('linklist', (string) $result->getBody());
}
/**
* Trigger legacy controller in link list controller: other GET route (e.g. ?post)
*/
public function testLegacyControllerGetParameter(): void
{
$request = $this->createMock(Request::class);
$request->method('getQueryParams')->willReturn(['post' => $url = 'http://url.tld']);
$response = new Response();
$this->container->loginManager = $this->createMock(LoginManager::class);
$this->container->loginManager->method('isLoggedIn')->willReturn(true);
$result = $this->controller->index($request, $response);
static::assertSame(302, $result->getStatusCode());
static::assertSame(
'/subfolder/admin/shaare?post=' . urlencode($url),
$result->getHeader('location')[0]
);
}
}

View file

@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
namespace Shaarli\Legacy;
use PHPUnit\Framework\TestCase;
use Shaarli\Front\Controller\Visitor\FrontControllerMockHelper;
use Slim\Http\Request;
use Slim\Http\Response;
class LegacyControllerTest extends TestCase
{
use FrontControllerMockHelper;
/** @var LegacyController */
protected $controller;
public function setUp(): void
{
$this->createContainer();
$this->controller = new LegacyController($this->container);
}
/**
* @dataProvider getProcessProvider
*/
public function testProcess(string $legacyRoute, array $queryParameters, string $slimRoute, bool $isLoggedIn): void
{
$request = $this->createMock(Request::class);
$request->method('getQueryParams')->willReturn($queryParameters);
$request
->method('getParam')
->willReturnCallback(function (string $key) use ($queryParameters): ?string {
return $queryParameters[$key] ?? null;
})
;
$response = new Response();
$this->container->loginManager->method('isLoggedIn')->willReturn($isLoggedIn);
$result = $this->controller->process($request, $response, $legacyRoute);
static::assertSame('/subfolder' . $slimRoute, $result->getHeader('location')[0]);
}
public function testProcessNotFound(): void
{
$request = $this->createMock(Request::class);
$response = new Response();
$this->expectException(UnknowLegacyRouteException::class);
$this->controller->process($request, $response, 'nope');
}
/**
* @return array[] Parameters:
* - string legacyRoute
* - array queryParameters
* - string slimRoute
* - bool isLoggedIn
*/
public function getProcessProvider(): array
{
return [
['post', [], '/admin/shaare', true],
['post', [], '/login', false],
['post', ['title' => 'test'], '/admin/shaare?title=test', true],
['post', ['title' => 'test'], '/login?title=test', false],
['addlink', [], '/admin/add-shaare', true],
['addlink', [], '/login', false],
['login', [], '/login', true],
['login', [], '/login', false],
['logout', [], '/logout', true],
['logout', [], '/logout', false],
['picwall', [], '/picture-wall', false],
['picwall', [], '/picture-wall', true],
['tagcloud', [], '/tags/cloud', false],
['tagcloud', [], '/tags/cloud', true],
['taglist', [], '/tags/list', false],
['taglist', [], '/tags/list', true],
['daily', [], '/daily', false],
['daily', [], '/daily', true],
['daily', ['day' => '123456789', 'discard' => '1'], '/daily?day=123456789', false],
['rss', [], '/feed/rss', false],
['rss', [], '/feed/rss', true],
['rss', ['search' => 'filter123', 'other' => 'param'], '/feed/rss?search=filter123&other=param', false],
['atom', [], '/feed/atom', false],
['atom', [], '/feed/atom', true],
['atom', ['search' => 'filter123', 'other' => 'param'], '/feed/atom?search=filter123&other=param', false],
['opensearch', [], '/open-search', false],
['opensearch', [], '/open-search', true],
['dailyrss', [], '/daily-rss', false],
['dailyrss', [], '/daily-rss', true],
];
}
}

View file

@ -1,10 +1,13 @@
<?php <?php
namespace Shaarli;
namespace Shaarli\Legacy;
use PHPUnit\Framework\TestCase;
/** /**
* Unit tests for Router * Unit tests for Router
*/ */
class RouterTest extends \PHPUnit\Framework\TestCase class LegacyRouterTest extends TestCase
{ {
/** /**
* Test findPage: login page output. * Test findPage: login page output.
@ -15,18 +18,18 @@ class RouterTest extends \PHPUnit\Framework\TestCase
public function testFindPageLoginValid() public function testFindPageLoginValid()
{ {
$this->assertEquals( $this->assertEquals(
Router::$PAGE_LOGIN, LegacyRouter::$PAGE_LOGIN,
Router::findPage('do=login', array(), false) LegacyRouter::findPage('do=login', array(), false)
); );
$this->assertEquals( $this->assertEquals(
Router::$PAGE_LOGIN, LegacyRouter::$PAGE_LOGIN,
Router::findPage('do=login', array(), 1) LegacyRouter::findPage('do=login', array(), 1)
); );
$this->assertEquals( $this->assertEquals(
Router::$PAGE_LOGIN, LegacyRouter::$PAGE_LOGIN,
Router::findPage('do=login&stuff', array(), false) LegacyRouter::findPage('do=login&stuff', array(), false)
); );
} }
@ -39,13 +42,13 @@ public function testFindPageLoginValid()
public function testFindPageLoginInvalid() public function testFindPageLoginInvalid()
{ {
$this->assertNotEquals( $this->assertNotEquals(
Router::$PAGE_LOGIN, LegacyRouter::$PAGE_LOGIN,
Router::findPage('do=login', array(), true) LegacyRouter::findPage('do=login', array(), true)
); );
$this->assertNotEquals( $this->assertNotEquals(
Router::$PAGE_LOGIN, LegacyRouter::$PAGE_LOGIN,
Router::findPage('do=other', array(), false) LegacyRouter::findPage('do=other', array(), false)
); );
} }
@ -58,13 +61,13 @@ public function testFindPageLoginInvalid()
public function testFindPagePicwallValid() public function testFindPagePicwallValid()
{ {
$this->assertEquals( $this->assertEquals(
Router::$PAGE_PICWALL, LegacyRouter::$PAGE_PICWALL,
Router::findPage('do=picwall', array(), false) LegacyRouter::findPage('do=picwall', array(), false)
); );
$this->assertEquals( $this->assertEquals(
Router::$PAGE_PICWALL, LegacyRouter::$PAGE_PICWALL,
Router::findPage('do=picwall', array(), true) LegacyRouter::findPage('do=picwall', array(), true)
); );
} }
@ -77,13 +80,13 @@ public function testFindPagePicwallValid()
public function testFindPagePicwallInvalid() public function testFindPagePicwallInvalid()
{ {
$this->assertEquals( $this->assertEquals(
Router::$PAGE_PICWALL, LegacyRouter::$PAGE_PICWALL,
Router::findPage('do=picwall&stuff', array(), false) LegacyRouter::findPage('do=picwall&stuff', array(), false)
); );
$this->assertNotEquals( $this->assertNotEquals(
Router::$PAGE_PICWALL, LegacyRouter::$PAGE_PICWALL,
Router::findPage('do=other', array(), false) LegacyRouter::findPage('do=other', array(), false)
); );
} }
@ -96,18 +99,18 @@ public function testFindPagePicwallInvalid()
public function testFindPageTagcloudValid() public function testFindPageTagcloudValid()
{ {
$this->assertEquals( $this->assertEquals(
Router::$PAGE_TAGCLOUD, LegacyRouter::$PAGE_TAGCLOUD,
Router::findPage('do=tagcloud', array(), false) LegacyRouter::findPage('do=tagcloud', array(), false)
); );
$this->assertEquals( $this->assertEquals(
Router::$PAGE_TAGCLOUD, LegacyRouter::$PAGE_TAGCLOUD,
Router::findPage('do=tagcloud', array(), true) LegacyRouter::findPage('do=tagcloud', array(), true)
); );
$this->assertEquals( $this->assertEquals(
Router::$PAGE_TAGCLOUD, LegacyRouter::$PAGE_TAGCLOUD,
Router::findPage('do=tagcloud&stuff', array(), false) LegacyRouter::findPage('do=tagcloud&stuff', array(), false)
); );
} }
@ -120,8 +123,8 @@ public function testFindPageTagcloudValid()
public function testFindPageTagcloudInvalid() public function testFindPageTagcloudInvalid()
{ {
$this->assertNotEquals( $this->assertNotEquals(
Router::$PAGE_TAGCLOUD, LegacyRouter::$PAGE_TAGCLOUD,
Router::findPage('do=other', array(), false) LegacyRouter::findPage('do=other', array(), false)
); );
} }
@ -134,23 +137,23 @@ public function testFindPageTagcloudInvalid()
public function testFindPageLinklistValid() public function testFindPageLinklistValid()
{ {
$this->assertEquals( $this->assertEquals(
Router::$PAGE_LINKLIST, LegacyRouter::$PAGE_LINKLIST,
Router::findPage('', array(), true) LegacyRouter::findPage('', array(), true)
); );
$this->assertEquals( $this->assertEquals(
Router::$PAGE_LINKLIST, LegacyRouter::$PAGE_LINKLIST,
Router::findPage('whatever', array(), true) LegacyRouter::findPage('whatever', array(), true)
); );
$this->assertEquals( $this->assertEquals(
Router::$PAGE_LINKLIST, LegacyRouter::$PAGE_LINKLIST,
Router::findPage('whatever', array(), false) LegacyRouter::findPage('whatever', array(), false)
); );
$this->assertEquals( $this->assertEquals(
Router::$PAGE_LINKLIST, LegacyRouter::$PAGE_LINKLIST,
Router::findPage('do=tools', array(), false) LegacyRouter::findPage('do=tools', array(), false)
); );
} }
@ -163,13 +166,13 @@ public function testFindPageLinklistValid()
public function testFindPageToolsValid() public function testFindPageToolsValid()
{ {
$this->assertEquals( $this->assertEquals(
Router::$PAGE_TOOLS, LegacyRouter::$PAGE_TOOLS,
Router::findPage('do=tools', array(), true) LegacyRouter::findPage('do=tools', array(), true)
); );
$this->assertEquals( $this->assertEquals(
Router::$PAGE_TOOLS, LegacyRouter::$PAGE_TOOLS,
Router::findPage('do=tools&stuff', array(), true) LegacyRouter::findPage('do=tools&stuff', array(), true)
); );
} }
@ -182,18 +185,18 @@ public function testFindPageToolsValid()
public function testFindPageToolsInvalid() public function testFindPageToolsInvalid()
{ {
$this->assertNotEquals( $this->assertNotEquals(
Router::$PAGE_TOOLS, LegacyRouter::$PAGE_TOOLS,
Router::findPage('do=tools', array(), 1) LegacyRouter::findPage('do=tools', array(), 1)
); );
$this->assertNotEquals( $this->assertNotEquals(
Router::$PAGE_TOOLS, LegacyRouter::$PAGE_TOOLS,
Router::findPage('do=tools', array(), false) LegacyRouter::findPage('do=tools', array(), false)
); );
$this->assertNotEquals( $this->assertNotEquals(
Router::$PAGE_TOOLS, LegacyRouter::$PAGE_TOOLS,
Router::findPage('do=other', array(), true) LegacyRouter::findPage('do=other', array(), true)
); );
} }
@ -206,12 +209,12 @@ public function testFindPageToolsInvalid()
public function testFindPageChangepasswdValid() public function testFindPageChangepasswdValid()
{ {
$this->assertEquals( $this->assertEquals(
Router::$PAGE_CHANGEPASSWORD, LegacyRouter::$PAGE_CHANGEPASSWORD,
Router::findPage('do=changepasswd', array(), true) LegacyRouter::findPage('do=changepasswd', array(), true)
); );
$this->assertEquals( $this->assertEquals(
Router::$PAGE_CHANGEPASSWORD, LegacyRouter::$PAGE_CHANGEPASSWORD,
Router::findPage('do=changepasswd&stuff', array(), true) LegacyRouter::findPage('do=changepasswd&stuff', array(), true)
); );
} }
@ -224,18 +227,18 @@ public function testFindPageChangepasswdValid()
public function testFindPageChangepasswdInvalid() public function testFindPageChangepasswdInvalid()
{ {
$this->assertNotEquals( $this->assertNotEquals(
Router::$PAGE_CHANGEPASSWORD, LegacyRouter::$PAGE_CHANGEPASSWORD,
Router::findPage('do=changepasswd', array(), 1) LegacyRouter::findPage('do=changepasswd', array(), 1)
); );
$this->assertNotEquals( $this->assertNotEquals(
Router::$PAGE_CHANGEPASSWORD, LegacyRouter::$PAGE_CHANGEPASSWORD,
Router::findPage('do=changepasswd', array(), false) LegacyRouter::findPage('do=changepasswd', array(), false)
); );
$this->assertNotEquals( $this->assertNotEquals(
Router::$PAGE_CHANGEPASSWORD, LegacyRouter::$PAGE_CHANGEPASSWORD,
Router::findPage('do=other', array(), true) LegacyRouter::findPage('do=other', array(), true)
); );
} }
/** /**
@ -247,13 +250,13 @@ public function testFindPageChangepasswdInvalid()
public function testFindPageConfigureValid() public function testFindPageConfigureValid()
{ {
$this->assertEquals( $this->assertEquals(
Router::$PAGE_CONFIGURE, LegacyRouter::$PAGE_CONFIGURE,
Router::findPage('do=configure', array(), true) LegacyRouter::findPage('do=configure', array(), true)
); );
$this->assertEquals( $this->assertEquals(
Router::$PAGE_CONFIGURE, LegacyRouter::$PAGE_CONFIGURE,
Router::findPage('do=configure&stuff', array(), true) LegacyRouter::findPage('do=configure&stuff', array(), true)
); );
} }
@ -266,18 +269,18 @@ public function testFindPageConfigureValid()
public function testFindPageConfigureInvalid() public function testFindPageConfigureInvalid()
{ {
$this->assertNotEquals( $this->assertNotEquals(
Router::$PAGE_CONFIGURE, LegacyRouter::$PAGE_CONFIGURE,
Router::findPage('do=configure', array(), 1) LegacyRouter::findPage('do=configure', array(), 1)
); );
$this->assertNotEquals( $this->assertNotEquals(
Router::$PAGE_CONFIGURE, LegacyRouter::$PAGE_CONFIGURE,
Router::findPage('do=configure', array(), false) LegacyRouter::findPage('do=configure', array(), false)
); );
$this->assertNotEquals( $this->assertNotEquals(
Router::$PAGE_CONFIGURE, LegacyRouter::$PAGE_CONFIGURE,
Router::findPage('do=other', array(), true) LegacyRouter::findPage('do=other', array(), true)
); );
} }
@ -290,13 +293,13 @@ public function testFindPageConfigureInvalid()
public function testFindPageChangetagValid() public function testFindPageChangetagValid()
{ {
$this->assertEquals( $this->assertEquals(
Router::$PAGE_CHANGETAG, LegacyRouter::$PAGE_CHANGETAG,
Router::findPage('do=changetag', array(), true) LegacyRouter::findPage('do=changetag', array(), true)
); );
$this->assertEquals( $this->assertEquals(
Router::$PAGE_CHANGETAG, LegacyRouter::$PAGE_CHANGETAG,
Router::findPage('do=changetag&stuff', array(), true) LegacyRouter::findPage('do=changetag&stuff', array(), true)
); );
} }
@ -309,18 +312,18 @@ public function testFindPageChangetagValid()
public function testFindPageChangetagInvalid() public function testFindPageChangetagInvalid()
{ {
$this->assertNotEquals( $this->assertNotEquals(
Router::$PAGE_CHANGETAG, LegacyRouter::$PAGE_CHANGETAG,
Router::findPage('do=changetag', array(), 1) LegacyRouter::findPage('do=changetag', array(), 1)
); );
$this->assertNotEquals( $this->assertNotEquals(
Router::$PAGE_CHANGETAG, LegacyRouter::$PAGE_CHANGETAG,
Router::findPage('do=changetag', array(), false) LegacyRouter::findPage('do=changetag', array(), false)
); );
$this->assertNotEquals( $this->assertNotEquals(
Router::$PAGE_CHANGETAG, LegacyRouter::$PAGE_CHANGETAG,
Router::findPage('do=other', array(), true) LegacyRouter::findPage('do=other', array(), true)
); );
} }
@ -333,13 +336,13 @@ public function testFindPageChangetagInvalid()
public function testFindPageAddlinkValid() public function testFindPageAddlinkValid()
{ {
$this->assertEquals( $this->assertEquals(
Router::$PAGE_ADDLINK, LegacyRouter::$PAGE_ADDLINK,
Router::findPage('do=addlink', array(), true) LegacyRouter::findPage('do=addlink', array(), true)
); );
$this->assertEquals( $this->assertEquals(
Router::$PAGE_ADDLINK, LegacyRouter::$PAGE_ADDLINK,
Router::findPage('do=addlink&stuff', array(), true) LegacyRouter::findPage('do=addlink&stuff', array(), true)
); );
} }
@ -352,18 +355,18 @@ public function testFindPageAddlinkValid()
public function testFindPageAddlinkInvalid() public function testFindPageAddlinkInvalid()
{ {
$this->assertNotEquals( $this->assertNotEquals(
Router::$PAGE_ADDLINK, LegacyRouter::$PAGE_ADDLINK,
Router::findPage('do=addlink', array(), 1) LegacyRouter::findPage('do=addlink', array(), 1)
); );
$this->assertNotEquals( $this->assertNotEquals(
Router::$PAGE_ADDLINK, LegacyRouter::$PAGE_ADDLINK,
Router::findPage('do=addlink', array(), false) LegacyRouter::findPage('do=addlink', array(), false)
); );
$this->assertNotEquals( $this->assertNotEquals(
Router::$PAGE_ADDLINK, LegacyRouter::$PAGE_ADDLINK,
Router::findPage('do=other', array(), true) LegacyRouter::findPage('do=other', array(), true)
); );
} }
@ -376,13 +379,13 @@ public function testFindPageAddlinkInvalid()
public function testFindPageExportValid() public function testFindPageExportValid()
{ {
$this->assertEquals( $this->assertEquals(
Router::$PAGE_EXPORT, LegacyRouter::$PAGE_EXPORT,
Router::findPage('do=export', array(), true) LegacyRouter::findPage('do=export', array(), true)
); );
$this->assertEquals( $this->assertEquals(
Router::$PAGE_EXPORT, LegacyRouter::$PAGE_EXPORT,
Router::findPage('do=export&stuff', array(), true) LegacyRouter::findPage('do=export&stuff', array(), true)
); );
} }
@ -395,18 +398,18 @@ public function testFindPageExportValid()
public function testFindPageExportInvalid() public function testFindPageExportInvalid()
{ {
$this->assertNotEquals( $this->assertNotEquals(
Router::$PAGE_EXPORT, LegacyRouter::$PAGE_EXPORT,
Router::findPage('do=export', array(), 1) LegacyRouter::findPage('do=export', array(), 1)
); );
$this->assertNotEquals( $this->assertNotEquals(
Router::$PAGE_EXPORT, LegacyRouter::$PAGE_EXPORT,
Router::findPage('do=export', array(), false) LegacyRouter::findPage('do=export', array(), false)
); );
$this->assertNotEquals( $this->assertNotEquals(
Router::$PAGE_EXPORT, LegacyRouter::$PAGE_EXPORT,
Router::findPage('do=other', array(), true) LegacyRouter::findPage('do=other', array(), true)
); );
} }
@ -419,13 +422,13 @@ public function testFindPageExportInvalid()
public function testFindPageImportValid() public function testFindPageImportValid()
{ {
$this->assertEquals( $this->assertEquals(
Router::$PAGE_IMPORT, LegacyRouter::$PAGE_IMPORT,
Router::findPage('do=import', array(), true) LegacyRouter::findPage('do=import', array(), true)
); );
$this->assertEquals( $this->assertEquals(
Router::$PAGE_IMPORT, LegacyRouter::$PAGE_IMPORT,
Router::findPage('do=import&stuff', array(), true) LegacyRouter::findPage('do=import&stuff', array(), true)
); );
} }
@ -438,18 +441,18 @@ public function testFindPageImportValid()
public function testFindPageImportInvalid() public function testFindPageImportInvalid()
{ {
$this->assertNotEquals( $this->assertNotEquals(
Router::$PAGE_IMPORT, LegacyRouter::$PAGE_IMPORT,
Router::findPage('do=import', array(), 1) LegacyRouter::findPage('do=import', array(), 1)
); );
$this->assertNotEquals( $this->assertNotEquals(
Router::$PAGE_IMPORT, LegacyRouter::$PAGE_IMPORT,
Router::findPage('do=import', array(), false) LegacyRouter::findPage('do=import', array(), false)
); );
$this->assertNotEquals( $this->assertNotEquals(
Router::$PAGE_IMPORT, LegacyRouter::$PAGE_IMPORT,
Router::findPage('do=other', array(), true) LegacyRouter::findPage('do=other', array(), true)
); );
} }
@ -462,24 +465,24 @@ public function testFindPageImportInvalid()
public function testFindPageEditlinkValid() public function testFindPageEditlinkValid()
{ {
$this->assertEquals( $this->assertEquals(
Router::$PAGE_EDITLINK, LegacyRouter::$PAGE_EDITLINK,
Router::findPage('whatever', array('edit_link' => 1), true) LegacyRouter::findPage('whatever', array('edit_link' => 1), true)
); );
$this->assertEquals( $this->assertEquals(
Router::$PAGE_EDITLINK, LegacyRouter::$PAGE_EDITLINK,
Router::findPage('', array('edit_link' => 1), true) LegacyRouter::findPage('', array('edit_link' => 1), true)
); );
$this->assertEquals( $this->assertEquals(
Router::$PAGE_EDITLINK, LegacyRouter::$PAGE_EDITLINK,
Router::findPage('whatever', array('post' => 1), true) LegacyRouter::findPage('whatever', array('post' => 1), true)
); );
$this->assertEquals( $this->assertEquals(
Router::$PAGE_EDITLINK, LegacyRouter::$PAGE_EDITLINK,
Router::findPage('whatever', array('post' => 1, 'edit_link' => 1), true) LegacyRouter::findPage('whatever', array('post' => 1, 'edit_link' => 1), true)
); );
} }
@ -492,18 +495,18 @@ public function testFindPageEditlinkValid()
public function testFindPageEditlinkInvalid() public function testFindPageEditlinkInvalid()
{ {
$this->assertNotEquals( $this->assertNotEquals(
Router::$PAGE_EDITLINK, LegacyRouter::$PAGE_EDITLINK,
Router::findPage('whatever', array('edit_link' => 1), false) LegacyRouter::findPage('whatever', array('edit_link' => 1), false)
); );
$this->assertNotEquals( $this->assertNotEquals(
Router::$PAGE_EDITLINK, LegacyRouter::$PAGE_EDITLINK,
Router::findPage('whatever', array('edit_link' => 1), 1) LegacyRouter::findPage('whatever', array('edit_link' => 1), 1)
); );
$this->assertNotEquals( $this->assertNotEquals(
Router::$PAGE_EDITLINK, LegacyRouter::$PAGE_EDITLINK,
Router::findPage('whatever', array(), true) LegacyRouter::findPage('whatever', array(), true)
); );
} }
} }

View file

@ -2,7 +2,7 @@
namespace Shaarli\Plugin\Addlink; namespace Shaarli\Plugin\Addlink;
use Shaarli\Plugin\PluginManager; use Shaarli\Plugin\PluginManager;
use Shaarli\Router; use Shaarli\Render\TemplatePage;
require_once 'plugins/addlink_toolbar/addlink_toolbar.php'; require_once 'plugins/addlink_toolbar/addlink_toolbar.php';
@ -26,7 +26,7 @@ public function testAddlinkHeaderLoggedIn()
{ {
$str = 'stuff'; $str = 'stuff';
$data = array($str => $str); $data = array($str => $str);
$data['_PAGE_'] = Router::$PAGE_LINKLIST; $data['_PAGE_'] = TemplatePage::LINKLIST;
$data['_LOGGEDIN_'] = true; $data['_LOGGEDIN_'] = true;
$data = hook_addlink_toolbar_render_header($data); $data = hook_addlink_toolbar_render_header($data);
@ -48,7 +48,7 @@ public function testAddlinkHeaderLoggedOut()
{ {
$str = 'stuff'; $str = 'stuff';
$data = array($str => $str); $data = array($str => $str);
$data['_PAGE_'] = Router::$PAGE_LINKLIST; $data['_PAGE_'] = TemplatePage::LINKLIST;
$data['_LOGGEDIN_'] = false; $data['_LOGGEDIN_'] = false;
$data = hook_addlink_toolbar_render_header($data); $data = hook_addlink_toolbar_render_header($data);

View file

@ -6,7 +6,7 @@
*/ */
use Shaarli\Plugin\PluginManager; use Shaarli\Plugin\PluginManager;
use Shaarli\Router; use Shaarli\Render\TemplatePage;
require_once 'plugins/playvideos/playvideos.php'; require_once 'plugins/playvideos/playvideos.php';
@ -31,7 +31,7 @@ public function testPlayvideosHeader()
{ {
$str = 'stuff'; $str = 'stuff';
$data = array($str => $str); $data = array($str => $str);
$data['_PAGE_'] = Router::$PAGE_LINKLIST; $data['_PAGE_'] = TemplatePage::LINKLIST;
$data = hook_playvideos_render_header($data); $data = hook_playvideos_render_header($data);
$this->assertEquals($str, $data[$str]); $this->assertEquals($str, $data[$str]);
@ -50,7 +50,7 @@ public function testPlayvideosFooter()
{ {
$str = 'stuff'; $str = 'stuff';
$data = array($str => $str); $data = array($str => $str);
$data['_PAGE_'] = Router::$PAGE_LINKLIST; $data['_PAGE_'] = TemplatePage::LINKLIST;
$data = hook_playvideos_render_footer($data); $data = hook_playvideos_render_footer($data);
$this->assertEquals($str, $data[$str]); $this->assertEquals($str, $data[$str]);

View file

@ -3,7 +3,7 @@
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Shaarli\Plugin\PluginManager; use Shaarli\Plugin\PluginManager;
use Shaarli\Router; use Shaarli\Render\TemplatePage;
require_once 'plugins/pubsubhubbub/pubsubhubbub.php'; require_once 'plugins/pubsubhubbub/pubsubhubbub.php';
@ -34,7 +34,7 @@ public function testPubSubRssRenderFeed()
$hub = 'http://domain.hub'; $hub = 'http://domain.hub';
$conf = new ConfigManager(self::$configFile); $conf = new ConfigManager(self::$configFile);
$conf->set('plugins.PUBSUBHUB_URL', $hub); $conf->set('plugins.PUBSUBHUB_URL', $hub);
$data['_PAGE_'] = Router::$PAGE_FEED_RSS; $data['_PAGE_'] = TemplatePage::FEED_RSS;
$data = hook_pubsubhubbub_render_feed($data, $conf); $data = hook_pubsubhubbub_render_feed($data, $conf);
$expected = '<atom:link rel="hub" href="'. $hub .'" />'; $expected = '<atom:link rel="hub" href="'. $hub .'" />';
@ -49,7 +49,7 @@ public function testPubSubAtomRenderFeed()
$hub = 'http://domain.hub'; $hub = 'http://domain.hub';
$conf = new ConfigManager(self::$configFile); $conf = new ConfigManager(self::$configFile);
$conf->set('plugins.PUBSUBHUB_URL', $hub); $conf->set('plugins.PUBSUBHUB_URL', $hub);
$data['_PAGE_'] = Router::$PAGE_FEED_ATOM; $data['_PAGE_'] = TemplatePage::FEED_ATOM;
$data = hook_pubsubhubbub_render_feed($data, $conf); $data = hook_pubsubhubbub_render_feed($data, $conf);
$expected = '<link rel="hub" href="'. $hub .'" />'; $expected = '<link rel="hub" href="'. $hub .'" />';

View file

@ -6,7 +6,7 @@
*/ */
use Shaarli\Plugin\PluginManager; use Shaarli\Plugin\PluginManager;
use Shaarli\Router; use Shaarli\Render\TemplatePage;
require_once 'plugins/qrcode/qrcode.php'; require_once 'plugins/qrcode/qrcode.php';
@ -57,7 +57,7 @@ public function testQrcodeFooter()
{ {
$str = 'stuff'; $str = 'stuff';
$data = array($str => $str); $data = array($str => $str);
$data['_PAGE_'] = Router::$PAGE_LINKLIST; $data['_PAGE_'] = TemplatePage::LINKLIST;
$data = hook_qrcode_render_footer($data); $data = hook_qrcode_render_footer($data);
$this->assertEquals($str, $data[$str]); $this->assertEquals($str, $data[$str]);

View file

@ -2,7 +2,10 @@
namespace Shaarli\Updater; namespace Shaarli\Updater;
use Exception; use Exception;
use Shaarli\Bookmark\BookmarkFileService;
use Shaarli\Bookmark\BookmarkServiceInterface;
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Shaarli\History;
require_once 'tests/updater/DummyUpdater.php'; require_once 'tests/updater/DummyUpdater.php';
require_once 'tests/utils/ReferenceLinkDB.php'; require_once 'tests/utils/ReferenceLinkDB.php';
@ -29,6 +32,12 @@ class UpdaterTest extends \PHPUnit\Framework\TestCase
*/ */
protected $conf; protected $conf;
/** @var BookmarkServiceInterface */
protected $bookmarkService;
/** @var Updater */
protected $updater;
/** /**
* Executed before each test. * Executed before each test.
*/ */
@ -36,6 +45,8 @@ public function setUp()
{ {
copy('tests/utils/config/configJson.json.php', self::$configFile .'.json.php'); copy('tests/utils/config/configJson.json.php', self::$configFile .'.json.php');
$this->conf = new ConfigManager(self::$configFile); $this->conf = new ConfigManager(self::$configFile);
$this->bookmarkService = new BookmarkFileService($this->conf, $this->createMock(History::class), true);
$this->updater = new Updater([], $this->bookmarkService, $this->conf, true);
} }
/** /**
@ -167,4 +178,12 @@ public function testUpdateFailed()
$updater = new DummyUpdater($updates, array(), $this->conf, true); $updater = new DummyUpdater($updates, array(), $this->conf, true);
$updater->update(); $updater->update();
} }
public function testUpdateMethodRelativeHomeLinkRename(): void
{
$this->conf->set('general.header_link', '?');
$this->updater->updateMethodRelativeHomeLink();
static::assertSame();
}
} }

View file

@ -224,7 +224,7 @@ <h2>
</div> </div>
{/if} {/if}
{/if} {/if}
<a href="{$base_path}/?{$value.shorturl}" title="{$strPermalink}"> <a href="{$base_path}/shaare/{$value.shorturl}" title="{$strPermalink}">
{if="!$hide_timestamps || $is_logged_in"} {if="!$hide_timestamps || $is_logged_in"}
{$updated=$value.updated_timestamp ? $strEdited. format_date($value.updated) : $strPermalink} {$updated=$value.updated_timestamp ? $strEdited. format_date($value.updated) : $strPermalink}
<span class="linkdate" title="{$updated}"> <span class="linkdate" title="{$updated}">