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)
|
||||
{
|
||||
if (null === $input) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_bool($input)) {
|
||||
return $input;
|
||||
}
|
||||
|
|
|
@ -2,112 +2,6 @@
|
|||
|
||||
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.
|
||||
*
|
||||
|
|
|
@ -10,11 +10,13 @@
|
|||
use Shaarli\Feed\FeedBuilder;
|
||||
use Shaarli\Formatter\FormatterFactory;
|
||||
use Shaarli\History;
|
||||
use Shaarli\Http\HttpAccess;
|
||||
use Shaarli\Plugin\PluginManager;
|
||||
use Shaarli\Render\PageBuilder;
|
||||
use Shaarli\Render\PageCacheManager;
|
||||
use Shaarli\Security\LoginManager;
|
||||
use Shaarli\Security\SessionManager;
|
||||
use Shaarli\Thumbnailer;
|
||||
|
||||
/**
|
||||
* Class 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,11 +9,13 @@
|
|||
use Shaarli\Feed\FeedBuilder;
|
||||
use Shaarli\Formatter\FormatterFactory;
|
||||
use Shaarli\History;
|
||||
use Shaarli\Http\HttpAccess;
|
||||
use Shaarli\Plugin\PluginManager;
|
||||
use Shaarli\Render\PageBuilder;
|
||||
use Shaarli\Render\PageCacheManager;
|
||||
use Shaarli\Security\LoginManager;
|
||||
use Shaarli\Security\SessionManager;
|
||||
use Shaarli\Thumbnailer;
|
||||
use Slim\Container;
|
||||
|
||||
/**
|
||||
|
@ -31,6 +33,8 @@
|
|||
* @property FormatterFactory $formatterFactory
|
||||
* @property PageCacheManager $pageCacheManager
|
||||
* @property FeedBuilder $feedBuilder
|
||||
* @property Thumbnailer $thumbnailer
|
||||
* @property HttpAccess $httpAccess
|
||||
*/
|
||||
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),
|
||||
];
|
||||
|
||||
$this->executeHooks($data);
|
||||
$data = $this->executeHooks($data);
|
||||
|
||||
foreach ($data as $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.
|
||||
$this->executeHooks($data);
|
||||
$data = $this->executeHooks($data);
|
||||
|
||||
$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());
|
||||
|
||||
$this->executeHooks($data, $feedType);
|
||||
$data = $this->executeHooks($data, $feedType);
|
||||
$this->assignAllView($data);
|
||||
|
||||
$content = $this->render('feed.'. $feedType);
|
||||
|
|
|
@ -78,16 +78,16 @@ protected function executeDefaultHooks(string $template): void
|
|||
];
|
||||
|
||||
foreach ($common_hooks as $name) {
|
||||
$plugin_data = [];
|
||||
$pluginData = [];
|
||||
$this->container->pluginManager->executeHooks(
|
||||
'render_' . $name,
|
||||
$plugin_data,
|
||||
$pluginData,
|
||||
[
|
||||
'target' => $template,
|
||||
'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,
|
||||
Response $response,
|
||||
array $loopTerms = [],
|
||||
array $clearParams = []
|
||||
array $clearParams = [],
|
||||
string $anchor = null
|
||||
): Response {
|
||||
$defaultPath = $request->getUri()->getBasePath();
|
||||
$defaultPath = rtrim($request->getUri()->getBasePath(), '/') . '/';
|
||||
$referer = $this->container->environment['HTTP_REFERER'] ?? null;
|
||||
|
||||
if (null !== $referer) {
|
||||
|
@ -133,7 +134,8 @@ protected function redirectFromReferer(
|
|||
}
|
||||
|
||||
$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']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>/?nonope
|
||||
http://<replace_domain>/?do=addlink
|
||||
http://<replace_domain>/add-shaare
|
||||
http://<replace_domain>/?do=changepasswd
|
||||
http://<replace_domain>/?do=changetag
|
||||
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
|
||||
if ($targetPage == Router::$PAGE_CHANGETAG) {
|
||||
header('./manage-tags');
|
||||
header('Location: ./manage-tags');
|
||||
exit;
|
||||
}
|
||||
|
||||
// -------- User wants to add a link without using the bookmarklet: Show form.
|
||||
if ($targetPage == Router::$PAGE_ADDLINK) {
|
||||
$PAGE->assign('pagetitle', t('Shaare a new link') .' - '. $conf->get('general.title', 'Shaarli'));
|
||||
$PAGE->renderPage('addlink');
|
||||
header('Location: ./shaare');
|
||||
exit;
|
||||
}
|
||||
|
||||
// -------- User clicked the "Save" button when editing a link: Save link to database.
|
||||
if (isset($_POST['save_edit'])) {
|
||||
// Go away!
|
||||
if (! $sessionManager->checkToken($_POST['token'])) {
|
||||
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);
|
||||
// This route is no longer supported in legacy mode
|
||||
header('Location: ./');
|
||||
exit;
|
||||
}
|
||||
|
||||
|
@ -695,110 +646,13 @@ function ($item) {
|
|||
// -------- User clicked the "EDIT" button on a link: Display link edit form.
|
||||
if (isset($_GET['edit_link'])) {
|
||||
$id = (int) escape($_GET['edit_link']);
|
||||
try {
|
||||
$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');
|
||||
header('Location: ./shaare-' . $id);
|
||||
exit;
|
||||
}
|
||||
|
||||
// -------- User want to post a new link: Display link edit form.
|
||||
if (isset($_GET['post'])) {
|
||||
$url = cleanup_url($_GET['post']);
|
||||
|
||||
$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');
|
||||
header('Location: ./shaare?' . http_build_query($_GET));
|
||||
exit;
|
||||
}
|
||||
|
||||
|
@ -1351,19 +1205,29 @@ function install($conf, $sessionManager, $loginManager)
|
|||
$this->post('/configure', '\Shaarli\Front\Controller\Admin\ConfigureController:save')->setName('saveConfigure');
|
||||
$this->get('/manage-tags', '\Shaarli\Front\Controller\Admin\ManageTagController:index')->setName('manageTag');
|
||||
$this->post('/manage-tags', '\Shaarli\Front\Controller\Admin\ManageTagController:save')->setName('saveManageTag');
|
||||
$this->get('/add-shaare', '\Shaarli\Front\Controller\Admin\PostBookmarkController:addShaare')->setName('addShaare');
|
||||
$this
|
||||
->get('/shaare', '\Shaarli\Front\Controller\Admin\PostBookmarkController:displayCreateForm')
|
||||
->setName('newShaare');
|
||||
$this
|
||||
->get('/shaare-{id}', '\Shaarli\Front\Controller\Admin\PostBookmarkController:displayEditForm')
|
||||
->setName('editShaare');
|
||||
$this
|
||||
->post('/shaare', '\Shaarli\Front\Controller\Admin\PostBookmarkController:save')
|
||||
->setName('saveShaare');
|
||||
$this
|
||||
->get('/delete-shaare', '\Shaarli\Front\Controller\Admin\PostBookmarkController:deleteBookmark')
|
||||
->setName('deleteShaare');
|
||||
|
||||
$this
|
||||
->get('/links-per-page', '\Shaarli\Front\Controller\Admin\SessionFilterController:linksPerPage')
|
||||
->setName('filter-links-per-page')
|
||||
;
|
||||
->setName('filter-links-per-page');
|
||||
$this
|
||||
->get('/visibility/{visibility}', '\Shaarli\Front\Controller\Admin\SessionFilterController:visibility')
|
||||
->setName('visibility')
|
||||
;
|
||||
->setName('visibility');
|
||||
$this
|
||||
->get('/untagged-only', '\Shaarli\Front\Controller\Admin\SessionFilterController:untaggedOnly')
|
||||
->setName('untagged-only')
|
||||
;
|
||||
->setName('untagged-only');
|
||||
})->add('\Shaarli\Front\ShaarliMiddleware');
|
||||
|
||||
$response = $app->run(true);
|
||||
|
|
|
@ -10,11 +10,13 @@
|
|||
use Shaarli\Feed\FeedBuilder;
|
||||
use Shaarli\Formatter\FormatterFactory;
|
||||
use Shaarli\History;
|
||||
use Shaarli\Http\HttpAccess;
|
||||
use Shaarli\Plugin\PluginManager;
|
||||
use Shaarli\Render\PageBuilder;
|
||||
use Shaarli\Render\PageCacheManager;
|
||||
use Shaarli\Security\LoginManager;
|
||||
use Shaarli\Security\SessionManager;
|
||||
use Shaarli\Thumbnailer;
|
||||
|
||||
/**
|
||||
* Test helper allowing auto-completion for MockObjects.
|
||||
|
@ -31,6 +33,8 @@
|
|||
* @property MockObject|FormatterFactory $formatterFactory
|
||||
* @property MockObject|PageCacheManager $pageCacheManager
|
||||
* @property MockObject|FeedBuilder $feedBuilder
|
||||
* @property MockObject|Thumbnailer $thumbnailer
|
||||
* @property MockObject|HttpAccess $httpAccess
|
||||
*/
|
||||
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 id="addlink-form" class="page-form page-form-light pure-u-lg-1-3 pure-u-22-24">
|
||||
<h2 class="window-title">{"Shaare a new link"|t}</h2>
|
||||
<form method="GET" action="#" name="addform" class="addform">
|
||||
<form method="GET" action="./shaare" name="addform" class="addform">
|
||||
<div>
|
||||
<label for="shaare">{'URL or leave empty to post a note'|t}</label>
|
||||
<input type="text" name="post" id="shaare" class="autofocus">
|
||||
|
|
|
@ -7,7 +7,11 @@
|
|||
{include="page.header"}
|
||||
<div id="editlinkform" class="edit-link-container" class="pure-g">
|
||||
<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">
|
||||
{if="!$link_is_new"}{'Edit Shaare'|t}{else}{'New Shaare'|t}{/if}
|
||||
</h2>
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
</li>
|
||||
{if="$is_logged_in || $openshaarli"}
|
||||
<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}
|
||||
</a>
|
||||
</li>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<div id="pageheader">
|
||||
{include="page.header"}
|
||||
<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="submit" value="Add link" class="bigbutton">
|
||||
</form>
|
||||
|
|
|
@ -20,10 +20,10 @@
|
|||
{if="$is_logged_in"}
|
||||
<li><a href="./logout">Logout</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"}
|
||||
<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}
|
||||
<li><a href="./login">Login</a></li>
|
||||
{/if}
|
||||
|
|
Loading…
Reference in a new issue