Handle shaare creation/edition/deletion through Slim controllers
This commit is contained in:
parent
8eac2e5488
commit
c22fa57a55
20 changed files with 1121 additions and 280 deletions
|
@ -91,6 +91,10 @@ function endsWith($haystack, $needle, $case = true)
|
||||||
*/
|
*/
|
||||||
function escape($input)
|
function escape($input)
|
||||||
{
|
{
|
||||||
|
if (null === $input) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (is_bool($input)) {
|
if (is_bool($input)) {
|
||||||
return $input;
|
return $input;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,112 +2,6 @@
|
||||||
|
|
||||||
use Shaarli\Bookmark\Bookmark;
|
use Shaarli\Bookmark\Bookmark;
|
||||||
|
|
||||||
/**
|
|
||||||
* Get cURL callback function for CURLOPT_WRITEFUNCTION
|
|
||||||
*
|
|
||||||
* @param string $charset to extract from the downloaded page (reference)
|
|
||||||
* @param string $title to extract from the downloaded page (reference)
|
|
||||||
* @param string $description to extract from the downloaded page (reference)
|
|
||||||
* @param string $keywords to extract from the downloaded page (reference)
|
|
||||||
* @param bool $retrieveDescription Automatically tries to retrieve description and keywords from HTML content
|
|
||||||
* @param string $curlGetInfo Optionally overrides curl_getinfo function
|
|
||||||
*
|
|
||||||
* @return Closure
|
|
||||||
*/
|
|
||||||
function get_curl_download_callback(
|
|
||||||
&$charset,
|
|
||||||
&$title,
|
|
||||||
&$description,
|
|
||||||
&$keywords,
|
|
||||||
$retrieveDescription,
|
|
||||||
$curlGetInfo = 'curl_getinfo'
|
|
||||||
) {
|
|
||||||
$isRedirected = false;
|
|
||||||
$currentChunk = 0;
|
|
||||||
$foundChunk = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* cURL callback function for CURLOPT_WRITEFUNCTION (called during the download).
|
|
||||||
*
|
|
||||||
* While downloading the remote page, we check that the HTTP code is 200 and content type is 'html/text'
|
|
||||||
* Then we extract the title and the charset and stop the download when it's done.
|
|
||||||
*
|
|
||||||
* @param resource $ch cURL resource
|
|
||||||
* @param string $data chunk of data being downloaded
|
|
||||||
*
|
|
||||||
* @return int|bool length of $data or false if we need to stop the download
|
|
||||||
*/
|
|
||||||
return function (&$ch, $data) use (
|
|
||||||
$retrieveDescription,
|
|
||||||
$curlGetInfo,
|
|
||||||
&$charset,
|
|
||||||
&$title,
|
|
||||||
&$description,
|
|
||||||
&$keywords,
|
|
||||||
&$isRedirected,
|
|
||||||
&$currentChunk,
|
|
||||||
&$foundChunk
|
|
||||||
) {
|
|
||||||
$currentChunk++;
|
|
||||||
$responseCode = $curlGetInfo($ch, CURLINFO_RESPONSE_CODE);
|
|
||||||
if (!empty($responseCode) && in_array($responseCode, [301, 302])) {
|
|
||||||
$isRedirected = true;
|
|
||||||
return strlen($data);
|
|
||||||
}
|
|
||||||
if (!empty($responseCode) && $responseCode !== 200) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// After a redirection, the content type will keep the previous request value
|
|
||||||
// until it finds the next content-type header.
|
|
||||||
if (! $isRedirected || strpos(strtolower($data), 'content-type') !== false) {
|
|
||||||
$contentType = $curlGetInfo($ch, CURLINFO_CONTENT_TYPE);
|
|
||||||
}
|
|
||||||
if (!empty($contentType) && strpos($contentType, 'text/html') === false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!empty($contentType) && empty($charset)) {
|
|
||||||
$charset = header_extract_charset($contentType);
|
|
||||||
}
|
|
||||||
if (empty($charset)) {
|
|
||||||
$charset = html_extract_charset($data);
|
|
||||||
}
|
|
||||||
if (empty($title)) {
|
|
||||||
$title = html_extract_title($data);
|
|
||||||
$foundChunk = ! empty($title) ? $currentChunk : $foundChunk;
|
|
||||||
}
|
|
||||||
if ($retrieveDescription && empty($description)) {
|
|
||||||
$description = html_extract_tag('description', $data);
|
|
||||||
$foundChunk = ! empty($description) ? $currentChunk : $foundChunk;
|
|
||||||
}
|
|
||||||
if ($retrieveDescription && empty($keywords)) {
|
|
||||||
$keywords = html_extract_tag('keywords', $data);
|
|
||||||
if (! empty($keywords)) {
|
|
||||||
$foundChunk = $currentChunk;
|
|
||||||
// Keywords use the format tag1, tag2 multiple words, tag
|
|
||||||
// So we format them to match Shaarli's separator and glue multiple words with '-'
|
|
||||||
$keywords = implode(' ', array_map(function($keyword) {
|
|
||||||
return implode('-', preg_split('/\s+/', trim($keyword)));
|
|
||||||
}, explode(',', $keywords)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We got everything we want, stop the download.
|
|
||||||
// If we already found either the title, description or keywords,
|
|
||||||
// it's highly unlikely that we'll found the other metas further than
|
|
||||||
// in the same chunk of data or the next one. So we also stop the download after that.
|
|
||||||
if ((!empty($responseCode) && !empty($contentType) && !empty($charset)) && $foundChunk !== null
|
|
||||||
&& (! $retrieveDescription
|
|
||||||
|| $foundChunk < $currentChunk
|
|
||||||
|| (!empty($title) && !empty($description) && !empty($keywords))
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return strlen($data);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract title from an HTML document.
|
* Extract title from an HTML document.
|
||||||
*
|
*
|
||||||
|
|
|
@ -10,11 +10,13 @@
|
||||||
use Shaarli\Feed\FeedBuilder;
|
use Shaarli\Feed\FeedBuilder;
|
||||||
use Shaarli\Formatter\FormatterFactory;
|
use Shaarli\Formatter\FormatterFactory;
|
||||||
use Shaarli\History;
|
use Shaarli\History;
|
||||||
|
use Shaarli\Http\HttpAccess;
|
||||||
use Shaarli\Plugin\PluginManager;
|
use Shaarli\Plugin\PluginManager;
|
||||||
use Shaarli\Render\PageBuilder;
|
use Shaarli\Render\PageBuilder;
|
||||||
use Shaarli\Render\PageCacheManager;
|
use Shaarli\Render\PageCacheManager;
|
||||||
use Shaarli\Security\LoginManager;
|
use Shaarli\Security\LoginManager;
|
||||||
use Shaarli\Security\SessionManager;
|
use Shaarli\Security\SessionManager;
|
||||||
|
use Shaarli\Thumbnailer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class ContainerBuilder
|
* Class ContainerBuilder
|
||||||
|
@ -110,6 +112,14 @@ public function build(): ShaarliContainer
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$container['thumbnailer'] = function (ShaarliContainer $container): Thumbnailer {
|
||||||
|
return new Thumbnailer($container->conf);
|
||||||
|
};
|
||||||
|
|
||||||
|
$container['httpAccess'] = function (): HttpAccess {
|
||||||
|
return new HttpAccess();
|
||||||
|
};
|
||||||
|
|
||||||
return $container;
|
return $container;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,13 @@
|
||||||
use Shaarli\Feed\FeedBuilder;
|
use Shaarli\Feed\FeedBuilder;
|
||||||
use Shaarli\Formatter\FormatterFactory;
|
use Shaarli\Formatter\FormatterFactory;
|
||||||
use Shaarli\History;
|
use Shaarli\History;
|
||||||
|
use Shaarli\Http\HttpAccess;
|
||||||
use Shaarli\Plugin\PluginManager;
|
use Shaarli\Plugin\PluginManager;
|
||||||
use Shaarli\Render\PageBuilder;
|
use Shaarli\Render\PageBuilder;
|
||||||
use Shaarli\Render\PageCacheManager;
|
use Shaarli\Render\PageCacheManager;
|
||||||
use Shaarli\Security\LoginManager;
|
use Shaarli\Security\LoginManager;
|
||||||
use Shaarli\Security\SessionManager;
|
use Shaarli\Security\SessionManager;
|
||||||
|
use Shaarli\Thumbnailer;
|
||||||
use Slim\Container;
|
use Slim\Container;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -31,6 +33,8 @@
|
||||||
* @property FormatterFactory $formatterFactory
|
* @property FormatterFactory $formatterFactory
|
||||||
* @property PageCacheManager $pageCacheManager
|
* @property PageCacheManager $pageCacheManager
|
||||||
* @property FeedBuilder $feedBuilder
|
* @property FeedBuilder $feedBuilder
|
||||||
|
* @property Thumbnailer $thumbnailer
|
||||||
|
* @property HttpAccess $httpAccess
|
||||||
*/
|
*/
|
||||||
class ShaarliContainer extends Container
|
class ShaarliContainer extends Container
|
||||||
{
|
{
|
||||||
|
|
258
application/front/controller/admin/PostBookmarkController.php
Normal file
258
application/front/controller/admin/PostBookmarkController.php
Normal file
|
@ -0,0 +1,258 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shaarli\Front\Controller\Admin;
|
||||||
|
|
||||||
|
use Shaarli\Bookmark\Bookmark;
|
||||||
|
use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
|
||||||
|
use Shaarli\Formatter\BookmarkMarkdownFormatter;
|
||||||
|
use Shaarli\Thumbnailer;
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class PostBookmarkController
|
||||||
|
*
|
||||||
|
* Slim controller used to handle Shaarli create or edit bookmarks.
|
||||||
|
*/
|
||||||
|
class PostBookmarkController extends ShaarliAdminController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* GET /add-shaare - Displays the form used to create a new bookmark from an URL
|
||||||
|
*/
|
||||||
|
public function addShaare(Request $request, Response $response): Response
|
||||||
|
{
|
||||||
|
$this->assignView(
|
||||||
|
'pagetitle',
|
||||||
|
t('Shaare a new link') .' - '. $this->container->conf->get('general.title', 'Shaarli')
|
||||||
|
);
|
||||||
|
|
||||||
|
return $response->write($this->render('addlink'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /shaare - Displays the bookmark form for creation.
|
||||||
|
* Note that if the URL is found in existing bookmarks, then it will be in edit mode.
|
||||||
|
*/
|
||||||
|
public function displayCreateForm(Request $request, Response $response): Response
|
||||||
|
{
|
||||||
|
$url = cleanup_url($request->getParam('post'));
|
||||||
|
|
||||||
|
$linkIsNew = false;
|
||||||
|
// Check if URL is not already in database (in this case, we will edit the existing link)
|
||||||
|
$bookmark = $this->container->bookmarkService->findByUrl($url);
|
||||||
|
if (null === $bookmark) {
|
||||||
|
$linkIsNew = true;
|
||||||
|
// Get shaare data if it was provided in URL (e.g.: by the bookmarklet).
|
||||||
|
$title = $request->getParam('title');
|
||||||
|
$description = $request->getParam('description');
|
||||||
|
$tags = $request->getParam('tags');
|
||||||
|
$private = filter_var($request->getParam('private'), FILTER_VALIDATE_BOOLEAN);
|
||||||
|
|
||||||
|
// If this is an HTTP(S) link, we try go get the page to extract
|
||||||
|
// the title (otherwise we will to straight to the edit form.)
|
||||||
|
if (empty($title) && strpos(get_url_scheme($url) ?: '', 'http') !== false) {
|
||||||
|
$retrieveDescription = $this->container->conf->get('general.retrieve_description');
|
||||||
|
// Short timeout to keep the application responsive
|
||||||
|
// The callback will fill $charset and $title with data from the downloaded page.
|
||||||
|
$this->container->httpAccess->getHttpResponse(
|
||||||
|
$url,
|
||||||
|
$this->container->conf->get('general.download_timeout', 30),
|
||||||
|
$this->container->conf->get('general.download_max_size', 4194304),
|
||||||
|
$this->container->httpAccess->getCurlDownloadCallback(
|
||||||
|
$charset,
|
||||||
|
$title,
|
||||||
|
$description,
|
||||||
|
$tags,
|
||||||
|
$retrieveDescription
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (! empty($title) && strtolower($charset) !== 'utf-8') {
|
||||||
|
$title = mb_convert_encoding($title, 'utf-8', $charset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($url) && empty($title)) {
|
||||||
|
$title = $this->container->conf->get('general.default_note_title', t('Note: '));
|
||||||
|
}
|
||||||
|
|
||||||
|
$link = escape([
|
||||||
|
'title' => $title,
|
||||||
|
'url' => $url ?? '',
|
||||||
|
'description' => $description ?? '',
|
||||||
|
'tags' => $tags ?? '',
|
||||||
|
'private' => $private,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$formatter = $this->container->formatterFactory->getFormatter('raw');
|
||||||
|
$link = $formatter->format($bookmark);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->displayForm($link, $linkIsNew, $request, $response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /shaare-{id} - Displays the bookmark form in edition mode.
|
||||||
|
*/
|
||||||
|
public function displayEditForm(Request $request, Response $response, array $args): Response
|
||||||
|
{
|
||||||
|
$id = $args['id'];
|
||||||
|
try {
|
||||||
|
if (false === ctype_digit($id)) {
|
||||||
|
throw new BookmarkNotFoundException();
|
||||||
|
}
|
||||||
|
$bookmark = $this->container->bookmarkService->get($id); // Read database
|
||||||
|
} catch (BookmarkNotFoundException $e) {
|
||||||
|
$this->saveErrorMessage(t('Bookmark not found'));
|
||||||
|
|
||||||
|
return $response->withRedirect('./');
|
||||||
|
}
|
||||||
|
|
||||||
|
$formatter = $this->container->formatterFactory->getFormatter('raw');
|
||||||
|
$link = $formatter->format($bookmark);
|
||||||
|
|
||||||
|
return $this->displayForm($link, false, $request, $response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /shaare
|
||||||
|
*/
|
||||||
|
public function save(Request $request, Response $response): Response
|
||||||
|
{
|
||||||
|
$this->checkToken($request);
|
||||||
|
|
||||||
|
// lf_id should only be present if the link exists.
|
||||||
|
$id = $request->getParam('lf_id') ? intval(escape($request->getParam('lf_id'))) : null;
|
||||||
|
if (null !== $id && true === $this->container->bookmarkService->exists($id)) {
|
||||||
|
// Edit
|
||||||
|
$bookmark = $this->container->bookmarkService->get($id);
|
||||||
|
} else {
|
||||||
|
// New link
|
||||||
|
$bookmark = new Bookmark();
|
||||||
|
}
|
||||||
|
|
||||||
|
$bookmark->setTitle($request->getParam('lf_title'));
|
||||||
|
$bookmark->setDescription($request->getParam('lf_description'));
|
||||||
|
$bookmark->setUrl($request->getParam('lf_url'), $this->container->conf->get('security.allowed_protocols', []));
|
||||||
|
$bookmark->setPrivate(filter_var($request->getParam('lf_private'), FILTER_VALIDATE_BOOLEAN));
|
||||||
|
$bookmark->setTagsString($request->getParam('lf_tags'));
|
||||||
|
|
||||||
|
if ($this->container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE
|
||||||
|
&& false === $bookmark->isNote()
|
||||||
|
) {
|
||||||
|
$bookmark->setThumbnail($this->container->thumbnailer->get($bookmark->getUrl()));
|
||||||
|
}
|
||||||
|
$this->container->bookmarkService->addOrSet($bookmark, false);
|
||||||
|
|
||||||
|
// To preserve backward compatibility with 3rd parties, plugins still use arrays
|
||||||
|
$formatter = $this->container->formatterFactory->getFormatter('raw');
|
||||||
|
$data = $formatter->format($bookmark);
|
||||||
|
$data = $this->executeHooks('save_link', $data);
|
||||||
|
|
||||||
|
$bookmark->fromArray($data);
|
||||||
|
$this->container->bookmarkService->set($bookmark);
|
||||||
|
|
||||||
|
// If we are called from the bookmarklet, we must close the popup:
|
||||||
|
if ($request->getParam('source') === 'bookmarklet') {
|
||||||
|
return $response->write('<script>self.close();</script>');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($request->getParam('returnurl'))) {
|
||||||
|
$this->container->environment['HTTP_REFERER'] = escape($request->getParam('returnurl'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->redirectFromReferer(
|
||||||
|
$request,
|
||||||
|
$response,
|
||||||
|
['add-shaare', 'shaare'], ['addlink', 'post', 'edit_link'],
|
||||||
|
$bookmark->getShortUrl()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteBookmark(Request $request, Response $response): Response
|
||||||
|
{
|
||||||
|
$this->checkToken($request);
|
||||||
|
|
||||||
|
$ids = escape(trim($request->getParam('lf_linkdate')));
|
||||||
|
if (strpos($ids, ' ') !== false) {
|
||||||
|
// multiple, space-separated ids provided
|
||||||
|
$ids = array_values(array_filter(preg_split('/\s+/', $ids), 'strlen'));
|
||||||
|
} else {
|
||||||
|
$ids = [$ids];
|
||||||
|
}
|
||||||
|
|
||||||
|
// assert at least one id is given
|
||||||
|
if (0 === count($ids)) {
|
||||||
|
$this->saveErrorMessage(t('Invalid bookmark ID provided.'));
|
||||||
|
|
||||||
|
return $this->redirectFromReferer($request, $response, [], ['delete-shaare']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$formatter = $this->container->formatterFactory->getFormatter('raw');
|
||||||
|
foreach ($ids as $id) {
|
||||||
|
$id = (int) $id;
|
||||||
|
// TODO: check if it exists
|
||||||
|
$bookmark = $this->container->bookmarkService->get($id);
|
||||||
|
$data = $formatter->format($bookmark);
|
||||||
|
$this->container->pluginManager->executeHooks('delete_link', $data);
|
||||||
|
$this->container->bookmarkService->remove($bookmark, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->container->bookmarkService->save();
|
||||||
|
|
||||||
|
// If we are called from the bookmarklet, we must close the popup:
|
||||||
|
if ($request->getParam('source') === 'bookmarklet') {
|
||||||
|
return $response->write('<script>self.close();</script>');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't redirect to where we were previously because the datastore has changed.
|
||||||
|
return $response->withRedirect('./');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function displayForm(array $link, bool $isNew, Request $request, Response $response): Response
|
||||||
|
{
|
||||||
|
$tags = $this->container->bookmarkService->bookmarksCountPerTag();
|
||||||
|
if ($this->container->conf->get('formatter') === 'markdown') {
|
||||||
|
$tags[BookmarkMarkdownFormatter::NO_MD_TAG] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'link' => $link,
|
||||||
|
'link_is_new' => $isNew,
|
||||||
|
'http_referer' => escape($this->container->environment['HTTP_REFERER'] ?? ''),
|
||||||
|
'source' => $request->getParam('source') ?? '',
|
||||||
|
'tags' => $tags,
|
||||||
|
'default_private_links' => $this->container->conf->get('privacy.default_private_links', false),
|
||||||
|
];
|
||||||
|
|
||||||
|
$data = $this->executeHooks('render_editlink', $data);
|
||||||
|
|
||||||
|
foreach ($data as $key => $value) {
|
||||||
|
$this->assignView($key, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
$editLabel = false === $isNew ? t('Edit') .' ' : '';
|
||||||
|
$this->assignView(
|
||||||
|
'pagetitle',
|
||||||
|
$editLabel . t('Shaare') .' - '. $this->container->conf->get('general.title', 'Shaarli')
|
||||||
|
);
|
||||||
|
|
||||||
|
return $response->write($this->render('editlink'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed[] $data Variables passed to the template engine
|
||||||
|
*
|
||||||
|
* @return mixed[] Template data after active plugins render_picwall hook execution.
|
||||||
|
*/
|
||||||
|
protected function executeHooks(string $hook, array $data): array
|
||||||
|
{
|
||||||
|
$this->container->pluginManager->executeHooks(
|
||||||
|
$hook,
|
||||||
|
$data
|
||||||
|
);
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,7 +21,7 @@ public function index(Request $request, Response $response): Response
|
||||||
'sslenabled' => is_https($this->container->environment),
|
'sslenabled' => is_https($this->container->environment),
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->executeHooks($data);
|
$data = $this->executeHooks($data);
|
||||||
|
|
||||||
foreach ($data as $key => $value) {
|
foreach ($data as $key => $value) {
|
||||||
$this->assignView($key, $value);
|
$this->assignView($key, $value);
|
||||||
|
|
|
@ -71,7 +71,7 @@ public function index(Request $request, Response $response): Response
|
||||||
];
|
];
|
||||||
|
|
||||||
// Hooks are called before column construction so that plugins don't have to deal with columns.
|
// Hooks are called before column construction so that plugins don't have to deal with columns.
|
||||||
$this->executeHooks($data);
|
$data = $this->executeHooks($data);
|
||||||
|
|
||||||
$data['cols'] = $this->calculateColumns($data['linksToDisplay']);
|
$data['cols'] = $this->calculateColumns($data['linksToDisplay']);
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ protected function processRequest(string $feedType, Request $request, Response $
|
||||||
|
|
||||||
$data = $this->container->feedBuilder->buildData($feedType, $request->getParams());
|
$data = $this->container->feedBuilder->buildData($feedType, $request->getParams());
|
||||||
|
|
||||||
$this->executeHooks($data, $feedType);
|
$data = $this->executeHooks($data, $feedType);
|
||||||
$this->assignAllView($data);
|
$this->assignAllView($data);
|
||||||
|
|
||||||
$content = $this->render('feed.'. $feedType);
|
$content = $this->render('feed.'. $feedType);
|
||||||
|
|
|
@ -78,16 +78,16 @@ protected function executeDefaultHooks(string $template): void
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($common_hooks as $name) {
|
foreach ($common_hooks as $name) {
|
||||||
$plugin_data = [];
|
$pluginData = [];
|
||||||
$this->container->pluginManager->executeHooks(
|
$this->container->pluginManager->executeHooks(
|
||||||
'render_' . $name,
|
'render_' . $name,
|
||||||
$plugin_data,
|
$pluginData,
|
||||||
[
|
[
|
||||||
'target' => $template,
|
'target' => $template,
|
||||||
'loggedin' => $this->container->loginManager->isLoggedIn()
|
'loggedin' => $this->container->loginManager->isLoggedIn()
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
$this->assignView('plugins_' . $name, $plugin_data);
|
$this->assignView('plugins_' . $name, $pluginData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,9 +102,10 @@ protected function redirectFromReferer(
|
||||||
Request $request,
|
Request $request,
|
||||||
Response $response,
|
Response $response,
|
||||||
array $loopTerms = [],
|
array $loopTerms = [],
|
||||||
array $clearParams = []
|
array $clearParams = [],
|
||||||
|
string $anchor = null
|
||||||
): Response {
|
): Response {
|
||||||
$defaultPath = $request->getUri()->getBasePath();
|
$defaultPath = rtrim($request->getUri()->getBasePath(), '/') . '/';
|
||||||
$referer = $this->container->environment['HTTP_REFERER'] ?? null;
|
$referer = $this->container->environment['HTTP_REFERER'] ?? null;
|
||||||
|
|
||||||
if (null !== $referer) {
|
if (null !== $referer) {
|
||||||
|
@ -133,7 +134,8 @@ protected function redirectFromReferer(
|
||||||
}
|
}
|
||||||
|
|
||||||
$queryString = count($params) > 0 ? '?'. http_build_query($params) : '';
|
$queryString = count($params) > 0 ? '?'. http_build_query($params) : '';
|
||||||
|
$anchor = $anchor ? '#' . $anchor : '';
|
||||||
|
|
||||||
return $response->withRedirect($path . $queryString);
|
return $response->withRedirect($path . $queryString . $anchor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
39
application/http/HttpAccess.php
Normal file
39
application/http/HttpAccess.php
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shaarli\Http;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class HttpAccess
|
||||||
|
*
|
||||||
|
* This is mostly an OOP wrapper for HTTP functions defined in `HttpUtils`.
|
||||||
|
* It is used as dependency injection in Shaarli's container.
|
||||||
|
*
|
||||||
|
* @package Shaarli\Http
|
||||||
|
*/
|
||||||
|
class HttpAccess
|
||||||
|
{
|
||||||
|
public function getHttpResponse($url, $timeout = 30, $maxBytes = 4194304, $curlWriteFunction = null)
|
||||||
|
{
|
||||||
|
return get_http_response($url, $timeout, $maxBytes, $curlWriteFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCurlDownloadCallback(
|
||||||
|
&$charset,
|
||||||
|
&$title,
|
||||||
|
&$description,
|
||||||
|
&$keywords,
|
||||||
|
$retrieveDescription,
|
||||||
|
$curlGetInfo = 'curl_getinfo'
|
||||||
|
) {
|
||||||
|
return get_curl_download_callback(
|
||||||
|
$charset,
|
||||||
|
$title,
|
||||||
|
$description,
|
||||||
|
$keywords,
|
||||||
|
$retrieveDescription,
|
||||||
|
$curlGetInfo
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -484,3 +484,109 @@ function is_https($server)
|
||||||
|
|
||||||
return ! empty($server['HTTPS']);
|
return ! empty($server['HTTPS']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cURL callback function for CURLOPT_WRITEFUNCTION
|
||||||
|
*
|
||||||
|
* @param string $charset to extract from the downloaded page (reference)
|
||||||
|
* @param string $title to extract from the downloaded page (reference)
|
||||||
|
* @param string $description to extract from the downloaded page (reference)
|
||||||
|
* @param string $keywords to extract from the downloaded page (reference)
|
||||||
|
* @param bool $retrieveDescription Automatically tries to retrieve description and keywords from HTML content
|
||||||
|
* @param string $curlGetInfo Optionally overrides curl_getinfo function
|
||||||
|
*
|
||||||
|
* @return Closure
|
||||||
|
*/
|
||||||
|
function get_curl_download_callback(
|
||||||
|
&$charset,
|
||||||
|
&$title,
|
||||||
|
&$description,
|
||||||
|
&$keywords,
|
||||||
|
$retrieveDescription,
|
||||||
|
$curlGetInfo = 'curl_getinfo'
|
||||||
|
) {
|
||||||
|
$isRedirected = false;
|
||||||
|
$currentChunk = 0;
|
||||||
|
$foundChunk = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cURL callback function for CURLOPT_WRITEFUNCTION (called during the download).
|
||||||
|
*
|
||||||
|
* While downloading the remote page, we check that the HTTP code is 200 and content type is 'html/text'
|
||||||
|
* Then we extract the title and the charset and stop the download when it's done.
|
||||||
|
*
|
||||||
|
* @param resource $ch cURL resource
|
||||||
|
* @param string $data chunk of data being downloaded
|
||||||
|
*
|
||||||
|
* @return int|bool length of $data or false if we need to stop the download
|
||||||
|
*/
|
||||||
|
return function (&$ch, $data) use (
|
||||||
|
$retrieveDescription,
|
||||||
|
$curlGetInfo,
|
||||||
|
&$charset,
|
||||||
|
&$title,
|
||||||
|
&$description,
|
||||||
|
&$keywords,
|
||||||
|
&$isRedirected,
|
||||||
|
&$currentChunk,
|
||||||
|
&$foundChunk
|
||||||
|
) {
|
||||||
|
$currentChunk++;
|
||||||
|
$responseCode = $curlGetInfo($ch, CURLINFO_RESPONSE_CODE);
|
||||||
|
if (!empty($responseCode) && in_array($responseCode, [301, 302])) {
|
||||||
|
$isRedirected = true;
|
||||||
|
return strlen($data);
|
||||||
|
}
|
||||||
|
if (!empty($responseCode) && $responseCode !== 200) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// After a redirection, the content type will keep the previous request value
|
||||||
|
// until it finds the next content-type header.
|
||||||
|
if (! $isRedirected || strpos(strtolower($data), 'content-type') !== false) {
|
||||||
|
$contentType = $curlGetInfo($ch, CURLINFO_CONTENT_TYPE);
|
||||||
|
}
|
||||||
|
if (!empty($contentType) && strpos($contentType, 'text/html') === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!empty($contentType) && empty($charset)) {
|
||||||
|
$charset = header_extract_charset($contentType);
|
||||||
|
}
|
||||||
|
if (empty($charset)) {
|
||||||
|
$charset = html_extract_charset($data);
|
||||||
|
}
|
||||||
|
if (empty($title)) {
|
||||||
|
$title = html_extract_title($data);
|
||||||
|
$foundChunk = ! empty($title) ? $currentChunk : $foundChunk;
|
||||||
|
}
|
||||||
|
if ($retrieveDescription && empty($description)) {
|
||||||
|
$description = html_extract_tag('description', $data);
|
||||||
|
$foundChunk = ! empty($description) ? $currentChunk : $foundChunk;
|
||||||
|
}
|
||||||
|
if ($retrieveDescription && empty($keywords)) {
|
||||||
|
$keywords = html_extract_tag('keywords', $data);
|
||||||
|
if (! empty($keywords)) {
|
||||||
|
$foundChunk = $currentChunk;
|
||||||
|
// Keywords use the format tag1, tag2 multiple words, tag
|
||||||
|
// So we format them to match Shaarli's separator and glue multiple words with '-'
|
||||||
|
$keywords = implode(' ', array_map(function($keyword) {
|
||||||
|
return implode('-', preg_split('/\s+/', trim($keyword)));
|
||||||
|
}, explode(',', $keywords)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We got everything we want, stop the download.
|
||||||
|
// If we already found either the title, description or keywords,
|
||||||
|
// it's highly unlikely that we'll found the other metas further than
|
||||||
|
// in the same chunk of data or the next one. So we also stop the download after that.
|
||||||
|
if ((!empty($responseCode) && !empty($contentType) && !empty($charset)) && $foundChunk !== null
|
||||||
|
&& (! $retrieveDescription
|
||||||
|
|| $foundChunk < $currentChunk
|
||||||
|
|| (!empty($title) && !empty($description) && !empty($keywords))
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return strlen($data);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ Here is a list :
|
||||||
```
|
```
|
||||||
http://<replace_domain>/
|
http://<replace_domain>/
|
||||||
http://<replace_domain>/?nonope
|
http://<replace_domain>/?nonope
|
||||||
http://<replace_domain>/?do=addlink
|
http://<replace_domain>/add-shaare
|
||||||
http://<replace_domain>/?do=changepasswd
|
http://<replace_domain>/?do=changepasswd
|
||||||
http://<replace_domain>/?do=changetag
|
http://<replace_domain>/?do=changetag
|
||||||
http://<replace_domain>/configure
|
http://<replace_domain>/configure
|
||||||
|
|
180
index.php
180
index.php
|
@ -519,69 +519,20 @@ function renderPage($conf, $pluginManager, $bookmarkService, $history, $sessionM
|
||||||
|
|
||||||
// -------- User wants to rename a tag or delete it
|
// -------- User wants to rename a tag or delete it
|
||||||
if ($targetPage == Router::$PAGE_CHANGETAG) {
|
if ($targetPage == Router::$PAGE_CHANGETAG) {
|
||||||
header('./manage-tags');
|
header('Location: ./manage-tags');
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------- User wants to add a link without using the bookmarklet: Show form.
|
// -------- User wants to add a link without using the bookmarklet: Show form.
|
||||||
if ($targetPage == Router::$PAGE_ADDLINK) {
|
if ($targetPage == Router::$PAGE_ADDLINK) {
|
||||||
$PAGE->assign('pagetitle', t('Shaare a new link') .' - '. $conf->get('general.title', 'Shaarli'));
|
header('Location: ./shaare');
|
||||||
$PAGE->renderPage('addlink');
|
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------- User clicked the "Save" button when editing a link: Save link to database.
|
// -------- User clicked the "Save" button when editing a link: Save link to database.
|
||||||
if (isset($_POST['save_edit'])) {
|
if (isset($_POST['save_edit'])) {
|
||||||
// Go away!
|
// This route is no longer supported in legacy mode
|
||||||
if (! $sessionManager->checkToken($_POST['token'])) {
|
header('Location: ./');
|
||||||
die(t('Wrong token.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// lf_id should only be present if the link exists.
|
|
||||||
$id = isset($_POST['lf_id']) ? intval(escape($_POST['lf_id'])) : null;
|
|
||||||
if ($id && $bookmarkService->exists($id)) {
|
|
||||||
// Edit
|
|
||||||
$bookmark = $bookmarkService->get($id);
|
|
||||||
} else {
|
|
||||||
// New link
|
|
||||||
$bookmark = new Bookmark();
|
|
||||||
}
|
|
||||||
|
|
||||||
$bookmark->setTitle($_POST['lf_title']);
|
|
||||||
$bookmark->setDescription($_POST['lf_description']);
|
|
||||||
$bookmark->setUrl($_POST['lf_url'], $conf->get('security.allowed_protocols'));
|
|
||||||
$bookmark->setPrivate(isset($_POST['lf_private']));
|
|
||||||
$bookmark->setTagsString($_POST['lf_tags']);
|
|
||||||
|
|
||||||
if ($conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE
|
|
||||||
&& ! $bookmark->isNote()
|
|
||||||
) {
|
|
||||||
$thumbnailer = new Thumbnailer($conf);
|
|
||||||
$bookmark->setThumbnail($thumbnailer->get($bookmark->getUrl()));
|
|
||||||
}
|
|
||||||
$bookmarkService->addOrSet($bookmark, false);
|
|
||||||
|
|
||||||
// To preserve backward compatibility with 3rd parties, plugins still use arrays
|
|
||||||
$factory = new FormatterFactory($conf, $loginManager->isLoggedIn());
|
|
||||||
$formatter = $factory->getFormatter('raw');
|
|
||||||
$data = $formatter->format($bookmark);
|
|
||||||
$pluginManager->executeHooks('save_link', $data);
|
|
||||||
|
|
||||||
$bookmark->fromArray($data);
|
|
||||||
$bookmarkService->set($bookmark);
|
|
||||||
|
|
||||||
// If we are called from the bookmarklet, we must close the popup:
|
|
||||||
if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) {
|
|
||||||
echo '<script>self.close();</script>';
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$returnurl = !empty($_POST['returnurl']) ? $_POST['returnurl'] : '?';
|
|
||||||
$location = generateLocation($returnurl, $_SERVER['HTTP_HOST'], array('addlink', 'post', 'edit_link'));
|
|
||||||
// Scroll to the link which has been edited.
|
|
||||||
$location .= '#' . $bookmark->getShortUrl();
|
|
||||||
// After saving the link, redirect to the page the user was on.
|
|
||||||
header('Location: '. $location);
|
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -695,110 +646,13 @@ function ($item) {
|
||||||
// -------- User clicked the "EDIT" button on a link: Display link edit form.
|
// -------- User clicked the "EDIT" button on a link: Display link edit form.
|
||||||
if (isset($_GET['edit_link'])) {
|
if (isset($_GET['edit_link'])) {
|
||||||
$id = (int) escape($_GET['edit_link']);
|
$id = (int) escape($_GET['edit_link']);
|
||||||
try {
|
header('Location: ./shaare-' . $id);
|
||||||
$link = $bookmarkService->get($id); // Read database
|
|
||||||
} catch (BookmarkNotFoundException $e) {
|
|
||||||
// Link not found in database.
|
|
||||||
header('Location: ?');
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$factory = new FormatterFactory($conf, $loginManager->isLoggedIn());
|
|
||||||
$formatter = $factory->getFormatter('raw');
|
|
||||||
$formattedLink = $formatter->format($link);
|
|
||||||
$tags = $bookmarkService->bookmarksCountPerTag();
|
|
||||||
if ($conf->get('formatter') === 'markdown') {
|
|
||||||
$tags[BookmarkMarkdownFormatter::NO_MD_TAG] = 1;
|
|
||||||
}
|
|
||||||
$data = array(
|
|
||||||
'link' => $formattedLink,
|
|
||||||
'link_is_new' => false,
|
|
||||||
'http_referer' => (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''),
|
|
||||||
'tags' => $tags,
|
|
||||||
);
|
|
||||||
$pluginManager->executeHooks('render_editlink', $data);
|
|
||||||
|
|
||||||
foreach ($data as $key => $value) {
|
|
||||||
$PAGE->assign($key, $value);
|
|
||||||
}
|
|
||||||
|
|
||||||
$PAGE->assign('pagetitle', t('Edit') .' '. t('Shaare') .' - '. $conf->get('general.title', 'Shaarli'));
|
|
||||||
$PAGE->renderPage('editlink');
|
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------- User want to post a new link: Display link edit form.
|
// -------- User want to post a new link: Display link edit form.
|
||||||
if (isset($_GET['post'])) {
|
if (isset($_GET['post'])) {
|
||||||
$url = cleanup_url($_GET['post']);
|
header('Location: ./shaare?' . http_build_query($_GET));
|
||||||
|
|
||||||
$link_is_new = false;
|
|
||||||
// Check if URL is not already in database (in this case, we will edit the existing link)
|
|
||||||
$bookmark = $bookmarkService->findByUrl($url);
|
|
||||||
if (! $bookmark) {
|
|
||||||
$link_is_new = true;
|
|
||||||
// Get title if it was provided in URL (by the bookmarklet).
|
|
||||||
$title = empty($_GET['title']) ? '' : escape($_GET['title']);
|
|
||||||
// Get description if it was provided in URL (by the bookmarklet). [Bronco added that]
|
|
||||||
$description = empty($_GET['description']) ? '' : escape($_GET['description']);
|
|
||||||
$tags = empty($_GET['tags']) ? '' : escape($_GET['tags']);
|
|
||||||
$private = !empty($_GET['private']) && $_GET['private'] === "1" ? 1 : 0;
|
|
||||||
|
|
||||||
// If this is an HTTP(S) link, we try go get the page to extract
|
|
||||||
// the title (otherwise we will to straight to the edit form.)
|
|
||||||
if (empty($title) && strpos(get_url_scheme($url), 'http') !== false) {
|
|
||||||
$retrieveDescription = $conf->get('general.retrieve_description');
|
|
||||||
// Short timeout to keep the application responsive
|
|
||||||
// The callback will fill $charset and $title with data from the downloaded page.
|
|
||||||
get_http_response(
|
|
||||||
$url,
|
|
||||||
$conf->get('general.download_timeout', 30),
|
|
||||||
$conf->get('general.download_max_size', 4194304),
|
|
||||||
get_curl_download_callback($charset, $title, $description, $tags, $retrieveDescription)
|
|
||||||
);
|
|
||||||
if (! empty($title) && strtolower($charset) != 'utf-8') {
|
|
||||||
$title = mb_convert_encoding($title, 'utf-8', $charset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($url == '') {
|
|
||||||
$title = $conf->get('general.default_note_title', t('Note: '));
|
|
||||||
}
|
|
||||||
$url = escape($url);
|
|
||||||
$title = escape($title);
|
|
||||||
|
|
||||||
$link = [
|
|
||||||
'title' => $title,
|
|
||||||
'url' => $url,
|
|
||||||
'description' => $description,
|
|
||||||
'tags' => $tags,
|
|
||||||
'private' => $private,
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
$factory = new FormatterFactory($conf, $loginManager->isLoggedIn());
|
|
||||||
$formatter = $factory->getFormatter('raw');
|
|
||||||
$link = $formatter->format($bookmark);
|
|
||||||
}
|
|
||||||
|
|
||||||
$tags = $bookmarkService->bookmarksCountPerTag();
|
|
||||||
if ($conf->get('formatter') === 'markdown') {
|
|
||||||
$tags[BookmarkMarkdownFormatter::NO_MD_TAG] = 1;
|
|
||||||
}
|
|
||||||
$data = [
|
|
||||||
'link' => $link,
|
|
||||||
'link_is_new' => $link_is_new,
|
|
||||||
'http_referer' => (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''),
|
|
||||||
'source' => (isset($_GET['source']) ? $_GET['source'] : ''),
|
|
||||||
'tags' => $tags,
|
|
||||||
'default_private_links' => $conf->get('privacy.default_private_links', false),
|
|
||||||
];
|
|
||||||
$pluginManager->executeHooks('render_editlink', $data);
|
|
||||||
|
|
||||||
foreach ($data as $key => $value) {
|
|
||||||
$PAGE->assign($key, $value);
|
|
||||||
}
|
|
||||||
|
|
||||||
$PAGE->assign('pagetitle', t('Shaare') .' - '. $conf->get('general.title', 'Shaarli'));
|
|
||||||
$PAGE->renderPage('editlink');
|
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1351,19 +1205,29 @@ function install($conf, $sessionManager, $loginManager)
|
||||||
$this->post('/configure', '\Shaarli\Front\Controller\Admin\ConfigureController:save')->setName('saveConfigure');
|
$this->post('/configure', '\Shaarli\Front\Controller\Admin\ConfigureController:save')->setName('saveConfigure');
|
||||||
$this->get('/manage-tags', '\Shaarli\Front\Controller\Admin\ManageTagController:index')->setName('manageTag');
|
$this->get('/manage-tags', '\Shaarli\Front\Controller\Admin\ManageTagController:index')->setName('manageTag');
|
||||||
$this->post('/manage-tags', '\Shaarli\Front\Controller\Admin\ManageTagController:save')->setName('saveManageTag');
|
$this->post('/manage-tags', '\Shaarli\Front\Controller\Admin\ManageTagController:save')->setName('saveManageTag');
|
||||||
|
$this->get('/add-shaare', '\Shaarli\Front\Controller\Admin\PostBookmarkController:addShaare')->setName('addShaare');
|
||||||
|
$this
|
||||||
|
->get('/shaare', '\Shaarli\Front\Controller\Admin\PostBookmarkController:displayCreateForm')
|
||||||
|
->setName('newShaare');
|
||||||
|
$this
|
||||||
|
->get('/shaare-{id}', '\Shaarli\Front\Controller\Admin\PostBookmarkController:displayEditForm')
|
||||||
|
->setName('editShaare');
|
||||||
|
$this
|
||||||
|
->post('/shaare', '\Shaarli\Front\Controller\Admin\PostBookmarkController:save')
|
||||||
|
->setName('saveShaare');
|
||||||
|
$this
|
||||||
|
->get('/delete-shaare', '\Shaarli\Front\Controller\Admin\PostBookmarkController:deleteBookmark')
|
||||||
|
->setName('deleteShaare');
|
||||||
|
|
||||||
$this
|
$this
|
||||||
->get('/links-per-page', '\Shaarli\Front\Controller\Admin\SessionFilterController:linksPerPage')
|
->get('/links-per-page', '\Shaarli\Front\Controller\Admin\SessionFilterController:linksPerPage')
|
||||||
->setName('filter-links-per-page')
|
->setName('filter-links-per-page');
|
||||||
;
|
|
||||||
$this
|
$this
|
||||||
->get('/visibility/{visibility}', '\Shaarli\Front\Controller\Admin\SessionFilterController:visibility')
|
->get('/visibility/{visibility}', '\Shaarli\Front\Controller\Admin\SessionFilterController:visibility')
|
||||||
->setName('visibility')
|
->setName('visibility');
|
||||||
;
|
|
||||||
$this
|
$this
|
||||||
->get('/untagged-only', '\Shaarli\Front\Controller\Admin\SessionFilterController:untaggedOnly')
|
->get('/untagged-only', '\Shaarli\Front\Controller\Admin\SessionFilterController:untaggedOnly')
|
||||||
->setName('untagged-only')
|
->setName('untagged-only');
|
||||||
;
|
|
||||||
})->add('\Shaarli\Front\ShaarliMiddleware');
|
})->add('\Shaarli\Front\ShaarliMiddleware');
|
||||||
|
|
||||||
$response = $app->run(true);
|
$response = $app->run(true);
|
||||||
|
|
|
@ -10,11 +10,13 @@
|
||||||
use Shaarli\Feed\FeedBuilder;
|
use Shaarli\Feed\FeedBuilder;
|
||||||
use Shaarli\Formatter\FormatterFactory;
|
use Shaarli\Formatter\FormatterFactory;
|
||||||
use Shaarli\History;
|
use Shaarli\History;
|
||||||
|
use Shaarli\Http\HttpAccess;
|
||||||
use Shaarli\Plugin\PluginManager;
|
use Shaarli\Plugin\PluginManager;
|
||||||
use Shaarli\Render\PageBuilder;
|
use Shaarli\Render\PageBuilder;
|
||||||
use Shaarli\Render\PageCacheManager;
|
use Shaarli\Render\PageCacheManager;
|
||||||
use Shaarli\Security\LoginManager;
|
use Shaarli\Security\LoginManager;
|
||||||
use Shaarli\Security\SessionManager;
|
use Shaarli\Security\SessionManager;
|
||||||
|
use Shaarli\Thumbnailer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test helper allowing auto-completion for MockObjects.
|
* Test helper allowing auto-completion for MockObjects.
|
||||||
|
@ -31,6 +33,8 @@
|
||||||
* @property MockObject|FormatterFactory $formatterFactory
|
* @property MockObject|FormatterFactory $formatterFactory
|
||||||
* @property MockObject|PageCacheManager $pageCacheManager
|
* @property MockObject|PageCacheManager $pageCacheManager
|
||||||
* @property MockObject|FeedBuilder $feedBuilder
|
* @property MockObject|FeedBuilder $feedBuilder
|
||||||
|
* @property MockObject|Thumbnailer $thumbnailer
|
||||||
|
* @property MockObject|HttpAccess $httpAccess
|
||||||
*/
|
*/
|
||||||
class ShaarliTestContainer extends ShaarliContainer
|
class ShaarliTestContainer extends ShaarliContainer
|
||||||
{
|
{
|
||||||
|
|
652
tests/front/controller/admin/PostBookmarkControllerTest.php
Normal file
652
tests/front/controller/admin/PostBookmarkControllerTest.php
Normal file
|
@ -0,0 +1,652 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shaarli\Front\Controller\Admin;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Shaarli\Bookmark\Bookmark;
|
||||||
|
use Shaarli\Config\ConfigManager;
|
||||||
|
use Shaarli\Front\Exception\WrongTokenException;
|
||||||
|
use Shaarli\Http\HttpAccess;
|
||||||
|
use Shaarli\Security\SessionManager;
|
||||||
|
use Shaarli\Thumbnailer;
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
use Slim\Http\Uri;
|
||||||
|
|
||||||
|
class PostBookmarkControllerTest extends TestCase
|
||||||
|
{
|
||||||
|
use FrontAdminControllerMockHelper;
|
||||||
|
|
||||||
|
/** @var PostBookmarkController */
|
||||||
|
protected $controller;
|
||||||
|
|
||||||
|
public function setUp(): void
|
||||||
|
{
|
||||||
|
$this->createContainer();
|
||||||
|
|
||||||
|
$this->container->httpAccess = $this->createMock(HttpAccess::class);
|
||||||
|
$this->controller = new PostBookmarkController($this->container);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test displaying add link page
|
||||||
|
*/
|
||||||
|
public function testAddShaare(): void
|
||||||
|
{
|
||||||
|
$assignedVariables = [];
|
||||||
|
$this->assignTemplateVars($assignedVariables);
|
||||||
|
|
||||||
|
$request = $this->createMock(Request::class);
|
||||||
|
$response = new Response();
|
||||||
|
|
||||||
|
$result = $this->controller->addShaare($request, $response);
|
||||||
|
|
||||||
|
static::assertSame(200, $result->getStatusCode());
|
||||||
|
static::assertSame('addlink', (string) $result->getBody());
|
||||||
|
|
||||||
|
static::assertSame('Shaare a new link - Shaarli', $assignedVariables['pagetitle']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test displaying bookmark create form
|
||||||
|
* Ensure that every step of the standard workflow works properly.
|
||||||
|
*/
|
||||||
|
public function testDisplayCreateFormWithUrl(): void
|
||||||
|
{
|
||||||
|
$this->container->environment = [
|
||||||
|
'HTTP_REFERER' => $referer = 'http://shaarli/subfolder/controller/?searchtag=abc'
|
||||||
|
];
|
||||||
|
|
||||||
|
$assignedVariables = [];
|
||||||
|
$this->assignTemplateVars($assignedVariables);
|
||||||
|
|
||||||
|
$url = 'http://url.tld/other?part=3&utm_ad=pay#hash';
|
||||||
|
$expectedUrl = str_replace('&utm_ad=pay', '', $url);
|
||||||
|
$remoteTitle = 'Remote Title';
|
||||||
|
$remoteDesc = 'Sometimes the meta description is relevant.';
|
||||||
|
$remoteTags = 'abc def';
|
||||||
|
|
||||||
|
$request = $this->createMock(Request::class);
|
||||||
|
$request->method('getParam')->willReturnCallback(function (string $key) use ($url): ?string {
|
||||||
|
return $key === 'post' ? $url : null;
|
||||||
|
});
|
||||||
|
$response = new Response();
|
||||||
|
|
||||||
|
$this->container->httpAccess
|
||||||
|
->expects(static::once())
|
||||||
|
->method('getCurlDownloadCallback')
|
||||||
|
->willReturnCallback(
|
||||||
|
function (&$charset, &$title, &$description, &$tags) use (
|
||||||
|
$remoteTitle,
|
||||||
|
$remoteDesc,
|
||||||
|
$remoteTags
|
||||||
|
): callable {
|
||||||
|
return function () use (
|
||||||
|
&$charset,
|
||||||
|
&$title,
|
||||||
|
&$description,
|
||||||
|
&$tags,
|
||||||
|
$remoteTitle,
|
||||||
|
$remoteDesc,
|
||||||
|
$remoteTags
|
||||||
|
): void {
|
||||||
|
$charset = 'ISO-8859-1';
|
||||||
|
$title = $remoteTitle;
|
||||||
|
$description = $remoteDesc;
|
||||||
|
$tags = $remoteTags;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
;
|
||||||
|
$this->container->httpAccess
|
||||||
|
->expects(static::once())
|
||||||
|
->method('getHttpResponse')
|
||||||
|
->with($expectedUrl, 30, 4194304)
|
||||||
|
->willReturnCallback(function($url, $timeout, $maxBytes, $callback): void {
|
||||||
|
$callback();
|
||||||
|
})
|
||||||
|
;
|
||||||
|
|
||||||
|
$this->container->bookmarkService
|
||||||
|
->expects(static::once())
|
||||||
|
->method('bookmarksCountPerTag')
|
||||||
|
->willReturn($tags = ['tag1' => 2, 'tag2' => 1])
|
||||||
|
;
|
||||||
|
|
||||||
|
// Make sure that PluginManager hook is triggered
|
||||||
|
$this->container->pluginManager
|
||||||
|
->expects(static::at(0))
|
||||||
|
->method('executeHooks')
|
||||||
|
->willReturnCallback(function (string $hook, array $data) use ($remoteTitle, $remoteDesc): array {
|
||||||
|
static::assertSame('render_editlink', $hook);
|
||||||
|
static::assertSame($remoteTitle, $data['link']['title']);
|
||||||
|
static::assertSame($remoteDesc, $data['link']['description']);
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
})
|
||||||
|
;
|
||||||
|
|
||||||
|
$result = $this->controller->displayCreateForm($request, $response);
|
||||||
|
|
||||||
|
static::assertSame(200, $result->getStatusCode());
|
||||||
|
static::assertSame('editlink', (string) $result->getBody());
|
||||||
|
|
||||||
|
static::assertSame('Shaare - Shaarli', $assignedVariables['pagetitle']);
|
||||||
|
|
||||||
|
static::assertSame($expectedUrl, $assignedVariables['link']['url']);
|
||||||
|
static::assertSame($remoteTitle, $assignedVariables['link']['title']);
|
||||||
|
static::assertSame($remoteDesc, $assignedVariables['link']['description']);
|
||||||
|
static::assertSame($remoteTags, $assignedVariables['link']['tags']);
|
||||||
|
static::assertFalse($assignedVariables['link']['private']);
|
||||||
|
|
||||||
|
static::assertTrue($assignedVariables['link_is_new']);
|
||||||
|
static::assertSame($referer, $assignedVariables['http_referer']);
|
||||||
|
static::assertSame($tags, $assignedVariables['tags']);
|
||||||
|
static::assertArrayHasKey('source', $assignedVariables);
|
||||||
|
static::assertArrayHasKey('default_private_links', $assignedVariables);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test displaying bookmark create form
|
||||||
|
* Ensure all available query parameters are handled properly.
|
||||||
|
*/
|
||||||
|
public function testDisplayCreateFormWithFullParameters(): void
|
||||||
|
{
|
||||||
|
$assignedVariables = [];
|
||||||
|
$this->assignTemplateVars($assignedVariables);
|
||||||
|
|
||||||
|
$parameters = [
|
||||||
|
'post' => 'http://url.tld/other?part=3&utm_ad=pay#hash',
|
||||||
|
'title' => 'Provided Title',
|
||||||
|
'description' => 'Provided description.',
|
||||||
|
'tags' => 'abc def',
|
||||||
|
'private' => '1',
|
||||||
|
'source' => 'apps',
|
||||||
|
];
|
||||||
|
$expectedUrl = str_replace('&utm_ad=pay', '', $parameters['post']);
|
||||||
|
|
||||||
|
$request = $this->createMock(Request::class);
|
||||||
|
$request
|
||||||
|
->method('getParam')
|
||||||
|
->willReturnCallback(function (string $key) use ($parameters): ?string {
|
||||||
|
return $parameters[$key] ?? null;
|
||||||
|
});
|
||||||
|
$response = new Response();
|
||||||
|
|
||||||
|
$result = $this->controller->displayCreateForm($request, $response);
|
||||||
|
|
||||||
|
static::assertSame(200, $result->getStatusCode());
|
||||||
|
static::assertSame('editlink', (string) $result->getBody());
|
||||||
|
|
||||||
|
static::assertSame('Shaare - Shaarli', $assignedVariables['pagetitle']);
|
||||||
|
|
||||||
|
static::assertSame($expectedUrl, $assignedVariables['link']['url']);
|
||||||
|
static::assertSame($parameters['title'], $assignedVariables['link']['title']);
|
||||||
|
static::assertSame($parameters['description'], $assignedVariables['link']['description']);
|
||||||
|
static::assertSame($parameters['tags'], $assignedVariables['link']['tags']);
|
||||||
|
static::assertTrue($assignedVariables['link']['private']);
|
||||||
|
static::assertTrue($assignedVariables['link_is_new']);
|
||||||
|
static::assertSame($parameters['source'], $assignedVariables['source']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test displaying bookmark create form
|
||||||
|
* Without any parameter.
|
||||||
|
*/
|
||||||
|
public function testDisplayCreateFormEmpty(): void
|
||||||
|
{
|
||||||
|
$assignedVariables = [];
|
||||||
|
$this->assignTemplateVars($assignedVariables);
|
||||||
|
|
||||||
|
$request = $this->createMock(Request::class);
|
||||||
|
$response = new Response();
|
||||||
|
|
||||||
|
$this->container->httpAccess->expects(static::never())->method('getHttpResponse');
|
||||||
|
$this->container->httpAccess->expects(static::never())->method('getCurlDownloadCallback');
|
||||||
|
|
||||||
|
$result = $this->controller->displayCreateForm($request, $response);
|
||||||
|
|
||||||
|
static::assertSame(200, $result->getStatusCode());
|
||||||
|
static::assertSame('editlink', (string) $result->getBody());
|
||||||
|
static::assertSame('', $assignedVariables['link']['url']);
|
||||||
|
static::assertSame('Note: ', $assignedVariables['link']['title']);
|
||||||
|
static::assertSame('', $assignedVariables['link']['description']);
|
||||||
|
static::assertSame('', $assignedVariables['link']['tags']);
|
||||||
|
static::assertFalse($assignedVariables['link']['private']);
|
||||||
|
static::assertTrue($assignedVariables['link_is_new']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test displaying bookmark create form
|
||||||
|
* URL not using HTTP protocol: do not try to retrieve the title
|
||||||
|
*/
|
||||||
|
public function testDisplayCreateFormNotHttp(): void
|
||||||
|
{
|
||||||
|
$assignedVariables = [];
|
||||||
|
$this->assignTemplateVars($assignedVariables);
|
||||||
|
|
||||||
|
$url = 'magnet://kubuntu.torrent';
|
||||||
|
$request = $this->createMock(Request::class);
|
||||||
|
$request
|
||||||
|
->method('getParam')
|
||||||
|
->willReturnCallback(function (string $key) use ($url): ?string {
|
||||||
|
return $key === 'post' ? $url : null;
|
||||||
|
});
|
||||||
|
$response = new Response();
|
||||||
|
|
||||||
|
$this->container->httpAccess->expects(static::never())->method('getHttpResponse');
|
||||||
|
$this->container->httpAccess->expects(static::never())->method('getCurlDownloadCallback');
|
||||||
|
|
||||||
|
$result = $this->controller->displayCreateForm($request, $response);
|
||||||
|
|
||||||
|
static::assertSame(200, $result->getStatusCode());
|
||||||
|
static::assertSame('editlink', (string) $result->getBody());
|
||||||
|
static::assertSame($url, $assignedVariables['link']['url']);
|
||||||
|
static::assertTrue($assignedVariables['link_is_new']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test displaying bookmark create form
|
||||||
|
* When markdown formatter is enabled, the no markdown tag should be added to existing tags.
|
||||||
|
*/
|
||||||
|
public function testDisplayCreateFormWithMarkdownEnabled(): void
|
||||||
|
{
|
||||||
|
$assignedVariables = [];
|
||||||
|
$this->assignTemplateVars($assignedVariables);
|
||||||
|
|
||||||
|
$this->container->conf = $this->createMock(ConfigManager::class);
|
||||||
|
$this->container->conf
|
||||||
|
->expects(static::atLeastOnce())
|
||||||
|
->method('get')->willReturnCallback(function (string $key): ?string {
|
||||||
|
if ($key === 'formatter') {
|
||||||
|
return 'markdown';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $key;
|
||||||
|
})
|
||||||
|
;
|
||||||
|
|
||||||
|
$request = $this->createMock(Request::class);
|
||||||
|
$response = new Response();
|
||||||
|
|
||||||
|
$result = $this->controller->displayCreateForm($request, $response);
|
||||||
|
|
||||||
|
static::assertSame(200, $result->getStatusCode());
|
||||||
|
static::assertSame('editlink', (string) $result->getBody());
|
||||||
|
static::assertSame(['nomarkdown' => 1], $assignedVariables['tags']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test displaying bookmark create form
|
||||||
|
* When an existing URL is submitted, we want to edit the existing link.
|
||||||
|
*/
|
||||||
|
public function testDisplayCreateFormWithExistingUrl(): void
|
||||||
|
{
|
||||||
|
$assignedVariables = [];
|
||||||
|
$this->assignTemplateVars($assignedVariables);
|
||||||
|
|
||||||
|
$url = 'http://url.tld/other?part=3&utm_ad=pay#hash';
|
||||||
|
$expectedUrl = str_replace('&utm_ad=pay', '', $url);
|
||||||
|
|
||||||
|
$request = $this->createMock(Request::class);
|
||||||
|
$request
|
||||||
|
->method('getParam')
|
||||||
|
->willReturnCallback(function (string $key) use ($url): ?string {
|
||||||
|
return $key === 'post' ? $url : null;
|
||||||
|
});
|
||||||
|
$response = new Response();
|
||||||
|
|
||||||
|
$this->container->httpAccess->expects(static::never())->method('getHttpResponse');
|
||||||
|
$this->container->httpAccess->expects(static::never())->method('getCurlDownloadCallback');
|
||||||
|
|
||||||
|
$this->container->bookmarkService
|
||||||
|
->expects(static::once())
|
||||||
|
->method('findByUrl')
|
||||||
|
->with($expectedUrl)
|
||||||
|
->willReturn(
|
||||||
|
(new Bookmark())
|
||||||
|
->setId($id = 23)
|
||||||
|
->setUrl($expectedUrl)
|
||||||
|
->setTitle($title = 'Bookmark Title')
|
||||||
|
->setDescription($description = 'Bookmark description.')
|
||||||
|
->setTags($tags = ['abc', 'def'])
|
||||||
|
->setPrivate(true)
|
||||||
|
->setCreated($createdAt = new \DateTime('2020-06-10 18:45:44'))
|
||||||
|
)
|
||||||
|
;
|
||||||
|
|
||||||
|
$result = $this->controller->displayCreateForm($request, $response);
|
||||||
|
|
||||||
|
static::assertSame(200, $result->getStatusCode());
|
||||||
|
static::assertSame('editlink', (string) $result->getBody());
|
||||||
|
|
||||||
|
static::assertSame('Edit Shaare - Shaarli', $assignedVariables['pagetitle']);
|
||||||
|
static::assertFalse($assignedVariables['link_is_new']);
|
||||||
|
|
||||||
|
static::assertSame($id, $assignedVariables['link']['id']);
|
||||||
|
static::assertSame($expectedUrl, $assignedVariables['link']['url']);
|
||||||
|
static::assertSame($title, $assignedVariables['link']['title']);
|
||||||
|
static::assertSame($description, $assignedVariables['link']['description']);
|
||||||
|
static::assertSame(implode(' ', $tags), $assignedVariables['link']['tags']);
|
||||||
|
static::assertTrue($assignedVariables['link']['private']);
|
||||||
|
static::assertSame($createdAt, $assignedVariables['link']['created']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test displaying bookmark edit form
|
||||||
|
* When an existing ID is provided, ensure that default workflow works properly.
|
||||||
|
*/
|
||||||
|
public function testDisplayEditFormDefault(): void
|
||||||
|
{
|
||||||
|
$assignedVariables = [];
|
||||||
|
$this->assignTemplateVars($assignedVariables);
|
||||||
|
|
||||||
|
$id = 11;
|
||||||
|
|
||||||
|
$request = $this->createMock(Request::class);
|
||||||
|
$response = new Response();
|
||||||
|
|
||||||
|
$this->container->httpAccess->expects(static::never())->method('getHttpResponse');
|
||||||
|
$this->container->httpAccess->expects(static::never())->method('getCurlDownloadCallback');
|
||||||
|
|
||||||
|
$this->container->bookmarkService
|
||||||
|
->expects(static::once())
|
||||||
|
->method('get')
|
||||||
|
->with($id)
|
||||||
|
->willReturn(
|
||||||
|
(new Bookmark())
|
||||||
|
->setId($id)
|
||||||
|
->setUrl($url = 'http://domain.tld')
|
||||||
|
->setTitle($title = 'Bookmark Title')
|
||||||
|
->setDescription($description = 'Bookmark description.')
|
||||||
|
->setTags($tags = ['abc', 'def'])
|
||||||
|
->setPrivate(true)
|
||||||
|
->setCreated($createdAt = new \DateTime('2020-06-10 18:45:44'))
|
||||||
|
)
|
||||||
|
;
|
||||||
|
|
||||||
|
$result = $this->controller->displayEditForm($request, $response, ['id' => (string) $id]);
|
||||||
|
|
||||||
|
static::assertSame(200, $result->getStatusCode());
|
||||||
|
static::assertSame('editlink', (string) $result->getBody());
|
||||||
|
|
||||||
|
static::assertSame('Edit Shaare - Shaarli', $assignedVariables['pagetitle']);
|
||||||
|
static::assertFalse($assignedVariables['link_is_new']);
|
||||||
|
|
||||||
|
static::assertSame($id, $assignedVariables['link']['id']);
|
||||||
|
static::assertSame($url, $assignedVariables['link']['url']);
|
||||||
|
static::assertSame($title, $assignedVariables['link']['title']);
|
||||||
|
static::assertSame($description, $assignedVariables['link']['description']);
|
||||||
|
static::assertSame(implode(' ', $tags), $assignedVariables['link']['tags']);
|
||||||
|
static::assertTrue($assignedVariables['link']['private']);
|
||||||
|
static::assertSame($createdAt, $assignedVariables['link']['created']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test save a new bookmark
|
||||||
|
*/
|
||||||
|
public function testSaveBookmark(): void
|
||||||
|
{
|
||||||
|
$id = 21;
|
||||||
|
$parameters = [
|
||||||
|
'lf_url' => 'http://url.tld/other?part=3#hash',
|
||||||
|
'lf_title' => 'Provided Title',
|
||||||
|
'lf_description' => 'Provided description.',
|
||||||
|
'lf_tags' => 'abc def',
|
||||||
|
'lf_private' => '1',
|
||||||
|
'returnurl' => 'http://shaarli.tld/subfolder/add-shaare'
|
||||||
|
];
|
||||||
|
|
||||||
|
$request = $this->createMock(Request::class);
|
||||||
|
$request
|
||||||
|
->method('getParam')
|
||||||
|
->willReturnCallback(function (string $key) use ($parameters): ?string {
|
||||||
|
return $parameters[$key] ?? null;
|
||||||
|
})
|
||||||
|
;
|
||||||
|
$request->method('getUri')->willReturnCallback(function (): Uri {
|
||||||
|
$uri = $this->createMock(Uri::class);
|
||||||
|
$uri->method('getBasePath')->willReturn('/subfolder');
|
||||||
|
|
||||||
|
return $uri;
|
||||||
|
});
|
||||||
|
$response = new Response();
|
||||||
|
|
||||||
|
$checkBookmark = function (Bookmark $bookmark) use ($parameters) {
|
||||||
|
static::assertSame($parameters['lf_url'], $bookmark->getUrl());
|
||||||
|
static::assertSame($parameters['lf_title'], $bookmark->getTitle());
|
||||||
|
static::assertSame($parameters['lf_description'], $bookmark->getDescription());
|
||||||
|
static::assertSame($parameters['lf_tags'], $bookmark->getTagsString());
|
||||||
|
static::assertTrue($bookmark->isPrivate());
|
||||||
|
};
|
||||||
|
|
||||||
|
$this->container->bookmarkService
|
||||||
|
->expects(static::once())
|
||||||
|
->method('addOrSet')
|
||||||
|
->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($checkBookmark, $id): void {
|
||||||
|
static::assertFalse($save);
|
||||||
|
|
||||||
|
$checkBookmark($bookmark);
|
||||||
|
|
||||||
|
$bookmark->setId($id);
|
||||||
|
})
|
||||||
|
;
|
||||||
|
$this->container->bookmarkService
|
||||||
|
->expects(static::once())
|
||||||
|
->method('set')
|
||||||
|
->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($checkBookmark, $id): void {
|
||||||
|
static::assertTrue($save);
|
||||||
|
|
||||||
|
$checkBookmark($bookmark);
|
||||||
|
|
||||||
|
static::assertSame($id, $bookmark->getId());
|
||||||
|
})
|
||||||
|
;
|
||||||
|
|
||||||
|
// Make sure that PluginManager hook is triggered
|
||||||
|
$this->container->pluginManager
|
||||||
|
->expects(static::at(0))
|
||||||
|
->method('executeHooks')
|
||||||
|
->willReturnCallback(function (string $hook, array $data) use ($parameters, $id): array {
|
||||||
|
static::assertSame('save_link', $hook);
|
||||||
|
|
||||||
|
static::assertSame($id, $data['id']);
|
||||||
|
static::assertSame($parameters['lf_url'], $data['url']);
|
||||||
|
static::assertSame($parameters['lf_title'], $data['title']);
|
||||||
|
static::assertSame($parameters['lf_description'], $data['description']);
|
||||||
|
static::assertSame($parameters['lf_tags'], $data['tags']);
|
||||||
|
static::assertTrue($data['private']);
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
})
|
||||||
|
;
|
||||||
|
|
||||||
|
$result = $this->controller->save($request, $response);
|
||||||
|
|
||||||
|
static::assertSame(302, $result->getStatusCode());
|
||||||
|
static::assertRegExp('@/subfolder/#\w{6}@', $result->getHeader('location')[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test save an existing bookmark
|
||||||
|
*/
|
||||||
|
public function testSaveExistingBookmark(): void
|
||||||
|
{
|
||||||
|
$id = 21;
|
||||||
|
$parameters = [
|
||||||
|
'lf_id' => (string) $id,
|
||||||
|
'lf_url' => 'http://url.tld/other?part=3#hash',
|
||||||
|
'lf_title' => 'Provided Title',
|
||||||
|
'lf_description' => 'Provided description.',
|
||||||
|
'lf_tags' => 'abc def',
|
||||||
|
'lf_private' => '1',
|
||||||
|
'returnurl' => 'http://shaarli.tld/subfolder/?page=2'
|
||||||
|
];
|
||||||
|
|
||||||
|
$request = $this->createMock(Request::class);
|
||||||
|
$request
|
||||||
|
->method('getParam')
|
||||||
|
->willReturnCallback(function (string $key) use ($parameters): ?string {
|
||||||
|
return $parameters[$key] ?? null;
|
||||||
|
})
|
||||||
|
;
|
||||||
|
$request->method('getUri')->willReturnCallback(function (): Uri {
|
||||||
|
$uri = $this->createMock(Uri::class);
|
||||||
|
$uri->method('getBasePath')->willReturn('/subfolder');
|
||||||
|
|
||||||
|
return $uri;
|
||||||
|
});
|
||||||
|
$response = new Response();
|
||||||
|
|
||||||
|
$checkBookmark = function (Bookmark $bookmark) use ($parameters, $id) {
|
||||||
|
static::assertSame($id, $bookmark->getId());
|
||||||
|
static::assertSame($parameters['lf_url'], $bookmark->getUrl());
|
||||||
|
static::assertSame($parameters['lf_title'], $bookmark->getTitle());
|
||||||
|
static::assertSame($parameters['lf_description'], $bookmark->getDescription());
|
||||||
|
static::assertSame($parameters['lf_tags'], $bookmark->getTagsString());
|
||||||
|
static::assertTrue($bookmark->isPrivate());
|
||||||
|
};
|
||||||
|
|
||||||
|
$this->container->bookmarkService->expects(static::atLeastOnce())->method('exists')->willReturn(true);
|
||||||
|
$this->container->bookmarkService
|
||||||
|
->expects(static::once())
|
||||||
|
->method('get')
|
||||||
|
->willReturn((new Bookmark())->setId($id)->setUrl('http://other.url'))
|
||||||
|
;
|
||||||
|
$this->container->bookmarkService
|
||||||
|
->expects(static::once())
|
||||||
|
->method('addOrSet')
|
||||||
|
->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($checkBookmark, $id): void {
|
||||||
|
static::assertFalse($save);
|
||||||
|
|
||||||
|
$checkBookmark($bookmark);
|
||||||
|
})
|
||||||
|
;
|
||||||
|
$this->container->bookmarkService
|
||||||
|
->expects(static::once())
|
||||||
|
->method('set')
|
||||||
|
->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($checkBookmark, $id): void {
|
||||||
|
static::assertTrue($save);
|
||||||
|
|
||||||
|
$checkBookmark($bookmark);
|
||||||
|
|
||||||
|
static::assertSame($id, $bookmark->getId());
|
||||||
|
})
|
||||||
|
;
|
||||||
|
|
||||||
|
// Make sure that PluginManager hook is triggered
|
||||||
|
$this->container->pluginManager
|
||||||
|
->expects(static::at(0))
|
||||||
|
->method('executeHooks')
|
||||||
|
->willReturnCallback(function (string $hook, array $data) use ($parameters, $id): array {
|
||||||
|
static::assertSame('save_link', $hook);
|
||||||
|
|
||||||
|
static::assertSame($id, $data['id']);
|
||||||
|
static::assertSame($parameters['lf_url'], $data['url']);
|
||||||
|
static::assertSame($parameters['lf_title'], $data['title']);
|
||||||
|
static::assertSame($parameters['lf_description'], $data['description']);
|
||||||
|
static::assertSame($parameters['lf_tags'], $data['tags']);
|
||||||
|
static::assertTrue($data['private']);
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
})
|
||||||
|
;
|
||||||
|
|
||||||
|
$result = $this->controller->save($request, $response);
|
||||||
|
|
||||||
|
static::assertSame(302, $result->getStatusCode());
|
||||||
|
static::assertRegExp('@/subfolder/\?page=2#\w{6}@', $result->getHeader('location')[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test save a bookmark - try to retrieve the thumbnail
|
||||||
|
*/
|
||||||
|
public function testSaveBookmarkWithThumbnail(): void
|
||||||
|
{
|
||||||
|
$parameters = ['lf_url' => 'http://url.tld/other?part=3#hash'];
|
||||||
|
|
||||||
|
$request = $this->createMock(Request::class);
|
||||||
|
$request
|
||||||
|
->method('getParam')
|
||||||
|
->willReturnCallback(function (string $key) use ($parameters): ?string {
|
||||||
|
return $parameters[$key] ?? null;
|
||||||
|
})
|
||||||
|
;
|
||||||
|
$request->method('getUri')->willReturnCallback(function (): Uri {
|
||||||
|
$uri = $this->createMock(Uri::class);
|
||||||
|
$uri->method('getBasePath')->willReturn('/subfolder');
|
||||||
|
|
||||||
|
return $uri;
|
||||||
|
});
|
||||||
|
$response = new Response();
|
||||||
|
|
||||||
|
$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')
|
||||||
|
->with($parameters['lf_url'])
|
||||||
|
->willReturn($thumb = 'http://thumb.url')
|
||||||
|
;
|
||||||
|
|
||||||
|
$this->container->bookmarkService
|
||||||
|
->expects(static::once())
|
||||||
|
->method('addOrSet')
|
||||||
|
->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($thumb): void {
|
||||||
|
static::assertSame($thumb, $bookmark->getThumbnail());
|
||||||
|
})
|
||||||
|
;
|
||||||
|
|
||||||
|
$result = $this->controller->save($request, $response);
|
||||||
|
|
||||||
|
static::assertSame(302, $result->getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the password with a wrong existing password
|
||||||
|
*/
|
||||||
|
public function testSaveBookmarkFromBookmarklet(): void
|
||||||
|
{
|
||||||
|
$parameters = ['source' => 'bookmarklet'];
|
||||||
|
|
||||||
|
$request = $this->createMock(Request::class);
|
||||||
|
$request
|
||||||
|
->method('getParam')
|
||||||
|
->willReturnCallback(function (string $key) use ($parameters): ?string {
|
||||||
|
return $parameters[$key] ?? null;
|
||||||
|
})
|
||||||
|
;
|
||||||
|
$response = new Response();
|
||||||
|
|
||||||
|
$result = $this->controller->save($request, $response);
|
||||||
|
|
||||||
|
static::assertSame(200, $result->getStatusCode());
|
||||||
|
static::assertSame('<script>self.close();</script>', (string) $result->getBody());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the password with a wrong existing password
|
||||||
|
*/
|
||||||
|
public function testSaveBookmarkWrongToken(): void
|
||||||
|
{
|
||||||
|
$this->container->sessionManager = $this->createMock(SessionManager::class);
|
||||||
|
$this->container->sessionManager->method('checkToken')->willReturn(false);
|
||||||
|
|
||||||
|
$this->container->bookmarkService->expects(static::never())->method('addOrSet');
|
||||||
|
$this->container->bookmarkService->expects(static::never())->method('set');
|
||||||
|
|
||||||
|
$request = $this->createMock(Request::class);
|
||||||
|
$response = new Response();
|
||||||
|
|
||||||
|
$this->expectException(WrongTokenException::class);
|
||||||
|
|
||||||
|
$this->controller->save($request, $response);
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,7 +9,7 @@
|
||||||
<div class="pure-u-lg-1-3 pure-u-1-24"></div>
|
<div class="pure-u-lg-1-3 pure-u-1-24"></div>
|
||||||
<div id="addlink-form" class="page-form page-form-light pure-u-lg-1-3 pure-u-22-24">
|
<div id="addlink-form" class="page-form page-form-light pure-u-lg-1-3 pure-u-22-24">
|
||||||
<h2 class="window-title">{"Shaare a new link"|t}</h2>
|
<h2 class="window-title">{"Shaare a new link"|t}</h2>
|
||||||
<form method="GET" action="#" name="addform" class="addform">
|
<form method="GET" action="./shaare" name="addform" class="addform">
|
||||||
<div>
|
<div>
|
||||||
<label for="shaare">{'URL or leave empty to post a note'|t}</label>
|
<label for="shaare">{'URL or leave empty to post a note'|t}</label>
|
||||||
<input type="text" name="post" id="shaare" class="autofocus">
|
<input type="text" name="post" id="shaare" class="autofocus">
|
||||||
|
|
|
@ -7,7 +7,11 @@
|
||||||
{include="page.header"}
|
{include="page.header"}
|
||||||
<div id="editlinkform" class="edit-link-container" class="pure-g">
|
<div id="editlinkform" class="edit-link-container" class="pure-g">
|
||||||
<div class="pure-u-lg-1-5 pure-u-1-24"></div>
|
<div class="pure-u-lg-1-5 pure-u-1-24"></div>
|
||||||
<form method="post" name="linkform" class="page-form pure-u-lg-3-5 pure-u-22-24 page-form page-form-light">
|
<form method="post"
|
||||||
|
name="linkform"
|
||||||
|
action="./shaare"
|
||||||
|
class="page-form pure-u-lg-3-5 pure-u-22-24 page-form page-form-light"
|
||||||
|
>
|
||||||
<h2 class="window-title">
|
<h2 class="window-title">
|
||||||
{if="!$link_is_new"}{'Edit Shaare'|t}{else}{'New Shaare'|t}{/if}
|
{if="!$link_is_new"}{'Edit Shaare'|t}{else}{'New Shaare'|t}{/if}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
</li>
|
</li>
|
||||||
{if="$is_logged_in || $openshaarli"}
|
{if="$is_logged_in || $openshaarli"}
|
||||||
<li class="pure-menu-item">
|
<li class="pure-menu-item">
|
||||||
<a href="./?do=addlink" class="pure-menu-link" id="shaarli-menu-shaare">
|
<a href="./add-shaare" class="pure-menu-link" id="shaarli-menu-shaare">
|
||||||
<i class="fa fa-plus" aria-hidden="true"></i> {'Shaare'|t}
|
<i class="fa fa-plus" aria-hidden="true"></i> {'Shaare'|t}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<div id="pageheader">
|
<div id="pageheader">
|
||||||
{include="page.header"}
|
{include="page.header"}
|
||||||
<div id="headerform">
|
<div id="headerform">
|
||||||
<form method="GET" action="" name="addform" class="addform">
|
<form method="GET" action="./shaare" name="addform" class="addform">
|
||||||
<input type="text" name="post" class="linkurl">
|
<input type="text" name="post" class="linkurl">
|
||||||
<input type="submit" value="Add link" class="bigbutton">
|
<input type="submit" value="Add link" class="bigbutton">
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -20,10 +20,10 @@
|
||||||
{if="$is_logged_in"}
|
{if="$is_logged_in"}
|
||||||
<li><a href="./logout">Logout</a></li>
|
<li><a href="./logout">Logout</a></li>
|
||||||
<li><a href="./tools">Tools</a></li>
|
<li><a href="./tools">Tools</a></li>
|
||||||
<li><a href="?do=addlink">Add link</a></li>
|
<li><a href="./add-shaare">Add link</a></li>
|
||||||
{elseif="$openshaarli"}
|
{elseif="$openshaarli"}
|
||||||
<li><a href="./tools">Tools</a></li>
|
<li><a href="./tools">Tools</a></li>
|
||||||
<li><a href="./?do=addlink">Add link</a></li>
|
<li><a href="./add-shaare">Add link</a></li>
|
||||||
{else}
|
{else}
|
||||||
<li><a href="./login">Login</a></li>
|
<li><a href="./login">Login</a></li>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
Loading…
Reference in a new issue