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,6 +17,7 @@
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;
/** /**
@ -26,6 +27,7 @@
* @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.'));
} }
return $response; $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;
}
$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

@ -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>

532
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:
// 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); $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 {
@ -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}">