*/
function escape($input)
{
+ if (null === $input) {
+ return null;
+ }
+
if (is_bool($input)) {
return $input;
}
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.
*
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
);
};
+ $container['thumbnailer'] = function (ShaarliContainer $container): Thumbnailer {
+ return new Thumbnailer($container->conf);
+ };
+
+ $container['httpAccess'] = function (): HttpAccess {
+ return new HttpAccess();
+ };
+
return $container;
}
}
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;
/**
* @property FormatterFactory $formatterFactory
* @property PageCacheManager $pageCacheManager
* @property FeedBuilder $feedBuilder
+ * @property Thumbnailer $thumbnailer
+ * @property HttpAccess $httpAccess
*/
class ShaarliContainer extends Container
{
--- /dev/null
+<?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;
+ }
+}
'sslenabled' => is_https($this->container->environment),
];
- $this->executeHooks($data);
+ $data = $this->executeHooks($data);
foreach ($data as $key => $value) {
$this->assignView($key, $value);
];
// 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 = $this->container->feedBuilder->buildData($feedType, $request->getParams());
- $this->executeHooks($data, $feedType);
+ $data = $this->executeHooks($data, $feedType);
$this->assignAllView($data);
$content = $this->render('feed.'. $feedType);
];
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);
}
}
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) {
}
$queryString = count($params) > 0 ? '?'. http_build_query($params) : '';
+ $anchor = $anchor ? '#' . $anchor : '';
- return $response->withRedirect($path . $queryString);
+ return $response->withRedirect($path . $queryString . $anchor);
}
}
--- /dev/null
+<?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
+ );
+ }
+}
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);
+ };
+}
```
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
// -------- 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;
}
// -------- 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;
}
$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);
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.
* @property MockObject|FormatterFactory $formatterFactory
* @property MockObject|PageCacheManager $pageCacheManager
* @property MockObject|FeedBuilder $feedBuilder
+ * @property MockObject|Thumbnailer $thumbnailer
+ * @property MockObject|HttpAccess $httpAccess
*/
class ShaarliTestContainer extends ShaarliContainer
{
--- /dev/null
+<?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);
+ }
+}
<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">
{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>
</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>
<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>
{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}