Merge branch 'master' of github.com:Shaarli/Shaarli into upstream
This commit is contained in:
commit
755c094bdd
100 changed files with 3492 additions and 1230 deletions
|
@ -26,7 +26,7 @@ RUN cd shaarli \
|
|||
|
||||
# Stage 4:
|
||||
# - Shaarli image
|
||||
FROM alpine:3.8
|
||||
FROM alpine:3.12
|
||||
LABEL maintainer="Shaarli Community"
|
||||
|
||||
RUN apk --update --no-cache add \
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Stage 1:
|
||||
# - Copy Shaarli sources
|
||||
# - Build documentation
|
||||
FROM arm32v6/alpine:3.8 as docs
|
||||
FROM arm32v6/alpine:3.10 as docs
|
||||
ADD . /usr/src/app/shaarli
|
||||
RUN apk --update --no-cache add py2-pip \
|
||||
&& cd /usr/src/app/shaarli \
|
||||
|
@ -10,7 +10,7 @@ RUN apk --update --no-cache add py2-pip \
|
|||
|
||||
# Stage 2:
|
||||
# - Resolve PHP dependencies with Composer
|
||||
FROM arm32v6/alpine:3.8 as composer
|
||||
FROM arm32v6/alpine:3.10 as composer
|
||||
COPY --from=docs /usr/src/app/shaarli /app/shaarli
|
||||
RUN apk --update --no-cache add php7-curl php7-mbstring php7-simplexml composer \
|
||||
&& cd /app/shaarli \
|
||||
|
@ -18,7 +18,7 @@ RUN apk --update --no-cache add php7-curl php7-mbstring php7-simplexml composer
|
|||
|
||||
# Stage 3:
|
||||
# - Frontend dependencies
|
||||
FROM arm32v6/alpine:3.8 as node
|
||||
FROM arm32v6/alpine:3.10 as node
|
||||
COPY --from=composer /app/shaarli /shaarli
|
||||
RUN apk --update --no-cache add yarn nodejs-current python2 build-base \
|
||||
&& cd /shaarli \
|
||||
|
@ -28,7 +28,7 @@ RUN apk --update --no-cache add yarn nodejs-current python2 build-base \
|
|||
|
||||
# Stage 4:
|
||||
# - Shaarli image
|
||||
FROM arm32v6/alpine:3.8
|
||||
FROM arm32v6/alpine:3.10
|
||||
LABEL maintainer="Shaarli Community"
|
||||
|
||||
RUN apk --update --no-cache add \
|
||||
|
|
|
@ -323,6 +323,7 @@ function format_date($date, $time = true, $intl = true)
|
|||
IntlDateFormatter::LONG,
|
||||
$time ? IntlDateFormatter::LONG : IntlDateFormatter::NONE
|
||||
);
|
||||
$formatter->setTimeZone($date->getTimezone());
|
||||
|
||||
return $formatter->format($date);
|
||||
}
|
||||
|
|
|
@ -145,6 +145,7 @@ protected function setLinkDb($conf)
|
|||
{
|
||||
$linkDb = new BookmarkFileService(
|
||||
$conf,
|
||||
$this->container->get('pluginManager'),
|
||||
$this->container->get('history'),
|
||||
new FlockMutex(fopen(SHAARLI_MUTEX_FILE, 'r'), 2),
|
||||
true
|
||||
|
|
|
@ -91,13 +91,17 @@ public static function formatLink($bookmark, $indexUrl)
|
|||
* If no URL is provided, it will generate a local note URL.
|
||||
* If no title is provided, it will use the URL as title.
|
||||
*
|
||||
* @param array|null $input Request Link.
|
||||
* @param bool $defaultPrivate Setting defined if a bookmark is private by default.
|
||||
* @param array|null $input Request Link.
|
||||
* @param bool $defaultPrivate Setting defined if a bookmark is private by default.
|
||||
* @param string $tagsSeparator Tags separator loaded from the config file.
|
||||
*
|
||||
* @return Bookmark instance.
|
||||
*/
|
||||
public static function buildBookmarkFromRequest(?array $input, bool $defaultPrivate): Bookmark
|
||||
{
|
||||
public static function buildBookmarkFromRequest(
|
||||
?array $input,
|
||||
bool $defaultPrivate,
|
||||
string $tagsSeparator
|
||||
): Bookmark {
|
||||
$bookmark = new Bookmark();
|
||||
$url = ! empty($input['url']) ? cleanup_url($input['url']) : '';
|
||||
if (isset($input['private'])) {
|
||||
|
@ -109,6 +113,15 @@ public static function buildBookmarkFromRequest(?array $input, bool $defaultPriv
|
|||
$bookmark->setTitle(! empty($input['title']) ? $input['title'] : '');
|
||||
$bookmark->setUrl($url);
|
||||
$bookmark->setDescription(! empty($input['description']) ? $input['description'] : '');
|
||||
|
||||
// Be permissive with provided tags format
|
||||
if (is_string($input['tags'] ?? null)) {
|
||||
$input['tags'] = tags_str2array($input['tags'], $tagsSeparator);
|
||||
}
|
||||
if (is_array($input['tags'] ?? null) && count($input['tags']) === 1 && is_string($input['tags'][0])) {
|
||||
$input['tags'] = tags_str2array($input['tags'][0], $tagsSeparator);
|
||||
}
|
||||
|
||||
$bookmark->setTags(! empty($input['tags']) ? $input['tags'] : []);
|
||||
$bookmark->setPrivate($private);
|
||||
|
||||
|
|
|
@ -36,13 +36,6 @@ class Links extends ApiController
|
|||
public function getLinks($request, $response)
|
||||
{
|
||||
$private = $request->getParam('visibility');
|
||||
$bookmarks = $this->bookmarkService->search(
|
||||
[
|
||||
'searchtags' => $request->getParam('searchtags', ''),
|
||||
'searchterm' => $request->getParam('searchterm', ''),
|
||||
],
|
||||
$private
|
||||
);
|
||||
|
||||
// Return bookmarks from the {offset}th link, starting from 0.
|
||||
$offset = $request->getParam('offset');
|
||||
|
@ -50,9 +43,6 @@ public function getLinks($request, $response)
|
|||
throw new ApiBadParametersException('Invalid offset');
|
||||
}
|
||||
$offset = ! empty($offset) ? intval($offset) : 0;
|
||||
if ($offset > count($bookmarks)) {
|
||||
return $response->withJson([], 200, $this->jsonStyle);
|
||||
}
|
||||
|
||||
// limit parameter is either a number of bookmarks or 'all' for everything.
|
||||
$limit = $request->getParam('limit');
|
||||
|
@ -61,23 +51,33 @@ public function getLinks($request, $response)
|
|||
} elseif (ctype_digit($limit)) {
|
||||
$limit = intval($limit);
|
||||
} elseif ($limit === 'all') {
|
||||
$limit = count($bookmarks);
|
||||
$limit = null;
|
||||
} else {
|
||||
throw new ApiBadParametersException('Invalid limit');
|
||||
}
|
||||
|
||||
$searchResult = $this->bookmarkService->search(
|
||||
[
|
||||
'searchtags' => $request->getParam('searchtags', ''),
|
||||
'searchterm' => $request->getParam('searchterm', ''),
|
||||
],
|
||||
$private,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
[
|
||||
'limit' => $limit,
|
||||
'offset' => $offset,
|
||||
'allowOutOfBounds' => true,
|
||||
]
|
||||
);
|
||||
|
||||
// 'environment' is set by Slim and encapsulate $_SERVER.
|
||||
$indexUrl = index_url($this->ci['environment']);
|
||||
|
||||
$out = [];
|
||||
$index = 0;
|
||||
foreach ($bookmarks as $bookmark) {
|
||||
if (count($out) >= $limit) {
|
||||
break;
|
||||
}
|
||||
if ($index++ >= $offset) {
|
||||
$out[] = ApiUtils::formatLink($bookmark, $indexUrl);
|
||||
}
|
||||
foreach ($searchResult->getBookmarks() as $bookmark) {
|
||||
$out[] = ApiUtils::formatLink($bookmark, $indexUrl);
|
||||
}
|
||||
|
||||
return $response->withJson($out, 200, $this->jsonStyle);
|
||||
|
@ -117,7 +117,11 @@ public function getLink($request, $response, $args)
|
|||
public function postLink($request, $response)
|
||||
{
|
||||
$data = (array) ($request->getParsedBody() ?? []);
|
||||
$bookmark = ApiUtils::buildBookmarkFromRequest($data, $this->conf->get('privacy.default_private_links'));
|
||||
$bookmark = ApiUtils::buildBookmarkFromRequest(
|
||||
$data,
|
||||
$this->conf->get('privacy.default_private_links'),
|
||||
$this->conf->get('general.tags_separator', ' ')
|
||||
);
|
||||
// duplicate by URL, return 409 Conflict
|
||||
if (
|
||||
! empty($bookmark->getUrl())
|
||||
|
@ -158,7 +162,11 @@ public function putLink($request, $response, $args)
|
|||
$index = index_url($this->ci['environment']);
|
||||
$data = $request->getParsedBody();
|
||||
|
||||
$requestBookmark = ApiUtils::buildBookmarkFromRequest($data, $this->conf->get('privacy.default_private_links'));
|
||||
$requestBookmark = ApiUtils::buildBookmarkFromRequest(
|
||||
$data,
|
||||
$this->conf->get('privacy.default_private_links'),
|
||||
$this->conf->get('general.tags_separator', ' ')
|
||||
);
|
||||
// duplicate URL on a different link, return 409 Conflict
|
||||
if (
|
||||
! empty($requestBookmark->getUrl())
|
||||
|
|
|
@ -122,12 +122,12 @@ public function putTag($request, $response, $args)
|
|||
throw new ApiBadParametersException('New tag name is required in the request body');
|
||||
}
|
||||
|
||||
$bookmarks = $this->bookmarkService->search(
|
||||
$searchResult = $this->bookmarkService->search(
|
||||
['searchtags' => $args['tagName']],
|
||||
BookmarkFilter::$ALL,
|
||||
true
|
||||
);
|
||||
foreach ($bookmarks as $bookmark) {
|
||||
foreach ($searchResult->getBookmarks() as $bookmark) {
|
||||
$bookmark->renameTag($args['tagName'], $data['name']);
|
||||
$this->bookmarkService->set($bookmark, false);
|
||||
$this->history->updateLink($bookmark);
|
||||
|
@ -157,12 +157,12 @@ public function deleteTag($request, $response, $args)
|
|||
throw new ApiTagNotFoundException();
|
||||
}
|
||||
|
||||
$bookmarks = $this->bookmarkService->search(
|
||||
$searchResult = $this->bookmarkService->search(
|
||||
['searchtags' => $args['tagName']],
|
||||
BookmarkFilter::$ALL,
|
||||
true
|
||||
);
|
||||
foreach ($bookmarks as $bookmark) {
|
||||
foreach ($searchResult->getBookmarks() as $bookmark) {
|
||||
$bookmark->deleteTag($args['tagName']);
|
||||
$this->bookmarkService->set($bookmark, false);
|
||||
$this->history->updateLink($bookmark);
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
use Shaarli\History;
|
||||
use Shaarli\Legacy\LegacyLinkDB;
|
||||
use Shaarli\Legacy\LegacyUpdater;
|
||||
use Shaarli\Plugin\PluginManager;
|
||||
use Shaarli\Render\PageCacheManager;
|
||||
use Shaarli\Updater\UpdaterUtils;
|
||||
|
||||
|
@ -40,6 +41,9 @@ class BookmarkFileService implements BookmarkServiceInterface
|
|||
/** @var ConfigManager instance */
|
||||
protected $conf;
|
||||
|
||||
/** @var PluginManager */
|
||||
protected $pluginManager;
|
||||
|
||||
/** @var History instance */
|
||||
protected $history;
|
||||
|
||||
|
@ -55,8 +59,13 @@ class BookmarkFileService implements BookmarkServiceInterface
|
|||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function __construct(ConfigManager $conf, History $history, Mutex $mutex, bool $isLoggedIn)
|
||||
{
|
||||
public function __construct(
|
||||
ConfigManager $conf,
|
||||
PluginManager $pluginManager,
|
||||
History $history,
|
||||
Mutex $mutex,
|
||||
bool $isLoggedIn
|
||||
) {
|
||||
$this->conf = $conf;
|
||||
$this->history = $history;
|
||||
$this->mutex = $mutex;
|
||||
|
@ -91,7 +100,8 @@ public function __construct(ConfigManager $conf, History $history, Mutex $mutex,
|
|||
}
|
||||
}
|
||||
|
||||
$this->bookmarkFilter = new BookmarkFilter($this->bookmarks, $this->conf);
|
||||
$this->pluginManager = $pluginManager;
|
||||
$this->bookmarkFilter = new BookmarkFilter($this->bookmarks, $this->conf, $this->pluginManager);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -129,8 +139,9 @@ public function search(
|
|||
string $visibility = null,
|
||||
bool $caseSensitive = false,
|
||||
bool $untaggedOnly = false,
|
||||
bool $ignoreSticky = false
|
||||
) {
|
||||
bool $ignoreSticky = false,
|
||||
array $pagination = []
|
||||
): SearchResult {
|
||||
if ($visibility === null) {
|
||||
$visibility = $this->isLoggedIn ? BookmarkFilter::$ALL : BookmarkFilter::$PUBLIC;
|
||||
}
|
||||
|
@ -143,13 +154,20 @@ public function search(
|
|||
$this->bookmarks->reorder('DESC', true);
|
||||
}
|
||||
|
||||
return $this->bookmarkFilter->filter(
|
||||
$bookmarks = $this->bookmarkFilter->filter(
|
||||
BookmarkFilter::$FILTER_TAG | BookmarkFilter::$FILTER_TEXT,
|
||||
[$searchTags, $searchTerm],
|
||||
$caseSensitive,
|
||||
$visibility,
|
||||
$untaggedOnly
|
||||
);
|
||||
|
||||
return SearchResult::getSearchResult(
|
||||
$bookmarks,
|
||||
$pagination['offset'] ?? 0,
|
||||
$pagination['limit'] ?? null,
|
||||
$pagination['allowOutOfBounds'] ?? false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -282,7 +300,7 @@ public function exists(int $id, string $visibility = null): bool
|
|||
*/
|
||||
public function count(string $visibility = null): int
|
||||
{
|
||||
return count($this->search([], $visibility));
|
||||
return $this->search([], $visibility)->getResultCount();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -305,10 +323,10 @@ public function save(): void
|
|||
*/
|
||||
public function bookmarksCountPerTag(array $filteringTags = [], string $visibility = null): array
|
||||
{
|
||||
$bookmarks = $this->search(['searchtags' => $filteringTags], $visibility);
|
||||
$searchResult = $this->search(['searchtags' => $filteringTags], $visibility);
|
||||
$tags = [];
|
||||
$caseMapping = [];
|
||||
foreach ($bookmarks as $bookmark) {
|
||||
foreach ($searchResult->getBookmarks() as $bookmark) {
|
||||
foreach ($bookmark->getTags() as $tag) {
|
||||
if (
|
||||
empty($tag)
|
||||
|
@ -357,7 +375,7 @@ public function findByDate(
|
|||
$previous = null;
|
||||
$next = null;
|
||||
|
||||
foreach ($this->search([], null, false, false, true) as $bookmark) {
|
||||
foreach ($this->search([], null, false, false, true)->getBookmarks() as $bookmark) {
|
||||
if ($to < $bookmark->getCreated()) {
|
||||
$next = $bookmark->getCreated();
|
||||
} elseif ($from < $bookmark->getCreated() && $to > $bookmark->getCreated()) {
|
||||
|
@ -378,7 +396,7 @@ public function findByDate(
|
|||
*/
|
||||
public function getLatest(): ?Bookmark
|
||||
{
|
||||
foreach ($this->search([], null, false, false, true) as $bookmark) {
|
||||
foreach ($this->search([], null, false, false, true)->getBookmarks() as $bookmark) {
|
||||
return $bookmark;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
|
||||
namespace Shaarli\Bookmark;
|
||||
|
||||
use Exception;
|
||||
use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
|
||||
use Shaarli\Config\ConfigManager;
|
||||
use Shaarli\Plugin\PluginManager;
|
||||
|
||||
/**
|
||||
* Class LinkFilter.
|
||||
|
@ -30,11 +30,6 @@ class BookmarkFilter
|
|||
*/
|
||||
public static $FILTER_TAG = 'tags';
|
||||
|
||||
/**
|
||||
* @var string filter by day.
|
||||
*/
|
||||
public static $FILTER_DAY = 'FILTER_DAY';
|
||||
|
||||
/**
|
||||
* @var string filter by day.
|
||||
*/
|
||||
|
@ -62,13 +57,17 @@ class BookmarkFilter
|
|||
/** @var ConfigManager */
|
||||
protected $conf;
|
||||
|
||||
/** @var PluginManager */
|
||||
protected $pluginManager;
|
||||
|
||||
/**
|
||||
* @param Bookmark[] $bookmarks initialization.
|
||||
*/
|
||||
public function __construct($bookmarks, ConfigManager $conf)
|
||||
public function __construct($bookmarks, ConfigManager $conf, PluginManager $pluginManager)
|
||||
{
|
||||
$this->bookmarks = $bookmarks;
|
||||
$this->conf = $conf;
|
||||
$this->pluginManager = $pluginManager;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -112,12 +111,12 @@ public function filter(
|
|||
$filtered = $this->bookmarks;
|
||||
}
|
||||
if (!empty($request[0])) {
|
||||
$filtered = (new BookmarkFilter($filtered, $this->conf))
|
||||
$filtered = (new BookmarkFilter($filtered, $this->conf, $this->pluginManager))
|
||||
->filterTags($request[0], $casesensitive, $visibility)
|
||||
;
|
||||
}
|
||||
if (!empty($request[1])) {
|
||||
$filtered = (new BookmarkFilter($filtered, $this->conf))
|
||||
$filtered = (new BookmarkFilter($filtered, $this->conf, $this->pluginManager))
|
||||
->filterFulltext($request[1], $visibility)
|
||||
;
|
||||
}
|
||||
|
@ -130,8 +129,6 @@ public function filter(
|
|||
} else {
|
||||
return $this->filterTags($request, $casesensitive, $visibility);
|
||||
}
|
||||
case self::$FILTER_DAY:
|
||||
return $this->filterDay($request, $visibility);
|
||||
default:
|
||||
return $this->noFilter($visibility);
|
||||
}
|
||||
|
@ -146,13 +143,20 @@ public function filter(
|
|||
*/
|
||||
private function noFilter(string $visibility = 'all')
|
||||
{
|
||||
if ($visibility === 'all') {
|
||||
return $this->bookmarks;
|
||||
}
|
||||
|
||||
$out = [];
|
||||
foreach ($this->bookmarks as $key => $value) {
|
||||
if ($value->isPrivate() && $visibility === 'private') {
|
||||
if (
|
||||
!$this->pluginManager->filterSearchEntry(
|
||||
$value,
|
||||
['source' => 'no_filter', 'visibility' => $visibility]
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($visibility === 'all') {
|
||||
$out[$key] = $value;
|
||||
} elseif ($value->isPrivate() && $visibility === 'private') {
|
||||
$out[$key] = $value;
|
||||
} elseif (!$value->isPrivate() && $visibility === 'public') {
|
||||
$out[$key] = $value;
|
||||
|
@ -233,18 +237,34 @@ private function filterFulltext(string $searchterms, string $visibility = 'all')
|
|||
}
|
||||
|
||||
// Iterate over every stored link.
|
||||
foreach ($this->bookmarks as $id => $link) {
|
||||
foreach ($this->bookmarks as $id => $bookmark) {
|
||||
if (
|
||||
!$this->pluginManager->filterSearchEntry(
|
||||
$bookmark,
|
||||
[
|
||||
'source' => 'fulltext',
|
||||
'searchterms' => $searchterms,
|
||||
'andSearch' => $andSearch,
|
||||
'exactSearch' => $exactSearch,
|
||||
'excludeSearch' => $excludeSearch,
|
||||
'visibility' => $visibility
|
||||
]
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// ignore non private bookmarks when 'privatonly' is on.
|
||||
if ($visibility !== 'all') {
|
||||
if (!$link->isPrivate() && $visibility === 'private') {
|
||||
if (!$bookmark->isPrivate() && $visibility === 'private') {
|
||||
continue;
|
||||
} elseif ($link->isPrivate() && $visibility === 'public') {
|
||||
} elseif ($bookmark->isPrivate() && $visibility === 'public') {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$lengths = [];
|
||||
$content = $this->buildFullTextSearchableLink($link, $lengths);
|
||||
$content = $this->buildFullTextSearchableLink($bookmark, $lengths);
|
||||
|
||||
// Be optimistic
|
||||
$found = true;
|
||||
|
@ -270,68 +290,18 @@ private function filterFulltext(string $searchterms, string $visibility = 'all')
|
|||
}
|
||||
|
||||
if ($found !== false) {
|
||||
$link->addAdditionalContentEntry(
|
||||
$bookmark->addAdditionalContentEntry(
|
||||
'search_highlight',
|
||||
$this->postProcessFoundPositions($lengths, $foundPositions)
|
||||
);
|
||||
|
||||
$filtered[$id] = $link;
|
||||
$filtered[$id] = $bookmark;
|
||||
}
|
||||
}
|
||||
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* generate a regex fragment out of a tag
|
||||
*
|
||||
* @param string $tag to to generate regexs from. may start with '-' to negate, contain '*' as wildcard
|
||||
*
|
||||
* @return string generated regex fragment
|
||||
*/
|
||||
protected function tag2regex(string $tag): string
|
||||
{
|
||||
$tagsSeparator = $this->conf->get('general.tags_separator', ' ');
|
||||
$len = strlen($tag);
|
||||
if (!$len || $tag === "-" || $tag === "*") {
|
||||
// nothing to search, return empty regex
|
||||
return '';
|
||||
}
|
||||
if ($tag[0] === "-") {
|
||||
// query is negated
|
||||
$i = 1; // use offset to start after '-' character
|
||||
$regex = '(?!'; // create negative lookahead
|
||||
} else {
|
||||
$i = 0; // start at first character
|
||||
$regex = '(?='; // use positive lookahead
|
||||
}
|
||||
// before tag may only be the separator or the beginning
|
||||
$regex .= '.*(?:^|' . $tagsSeparator . ')';
|
||||
// iterate over string, separating it into placeholder and content
|
||||
for (; $i < $len; $i++) {
|
||||
if ($tag[$i] === '*') {
|
||||
// placeholder found
|
||||
$regex .= '[^' . $tagsSeparator . ']*?';
|
||||
} else {
|
||||
// regular characters
|
||||
$offset = strpos($tag, '*', $i);
|
||||
if ($offset === false) {
|
||||
// no placeholder found, set offset to end of string
|
||||
$offset = $len;
|
||||
}
|
||||
// subtract one, as we want to get before the placeholder or end of string
|
||||
$offset -= 1;
|
||||
// we got a tag name that we want to search for. escape any regex characters to prevent conflicts.
|
||||
$regex .= preg_quote(substr($tag, $i, $offset - $i + 1), '/');
|
||||
// move $i on
|
||||
$i = $offset;
|
||||
}
|
||||
}
|
||||
// after the tag may only be the separator or the end
|
||||
$regex .= '(?:$|' . $tagsSeparator . '))';
|
||||
return $regex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of bookmarks associated with a given list of tags
|
||||
*
|
||||
|
@ -381,25 +351,39 @@ public function filterTags($tags, bool $casesensitive = false, string $visibilit
|
|||
$filtered = [];
|
||||
|
||||
// iterate over each link
|
||||
foreach ($this->bookmarks as $key => $link) {
|
||||
foreach ($this->bookmarks as $key => $bookmark) {
|
||||
if (
|
||||
!$this->pluginManager->filterSearchEntry(
|
||||
$bookmark,
|
||||
[
|
||||
'source' => 'tags',
|
||||
'tags' => $tags,
|
||||
'casesensitive' => $casesensitive,
|
||||
'visibility' => $visibility
|
||||
]
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// check level of visibility
|
||||
// ignore non private bookmarks when 'privateonly' is on.
|
||||
if ($visibility !== 'all') {
|
||||
if (!$link->isPrivate() && $visibility === 'private') {
|
||||
if (!$bookmark->isPrivate() && $visibility === 'private') {
|
||||
continue;
|
||||
} elseif ($link->isPrivate() && $visibility === 'public') {
|
||||
} elseif ($bookmark->isPrivate() && $visibility === 'public') {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// build search string, start with tags of current link
|
||||
$search = $link->getTagsString($tagsSeparator);
|
||||
if (strlen(trim($link->getDescription())) && strpos($link->getDescription(), '#') !== false) {
|
||||
$search = $bookmark->getTagsString($tagsSeparator);
|
||||
if (strlen(trim($bookmark->getDescription())) && strpos($bookmark->getDescription(), '#') !== false) {
|
||||
// description given and at least one possible tag found
|
||||
$descTags = [];
|
||||
// find all tags in the form of #tag in the description
|
||||
preg_match_all(
|
||||
'/(?<![' . self::$HASHTAG_CHARS . '])#([' . self::$HASHTAG_CHARS . ']+?)\b/sm',
|
||||
$link->getDescription(),
|
||||
$bookmark->getDescription(),
|
||||
$descTags
|
||||
);
|
||||
if (count($descTags[1])) {
|
||||
|
@ -412,8 +396,9 @@ public function filterTags($tags, bool $casesensitive = false, string $visibilit
|
|||
// this entry does _not_ match our regex
|
||||
continue;
|
||||
}
|
||||
$filtered[$key] = $link;
|
||||
$filtered[$key] = $bookmark;
|
||||
}
|
||||
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
|
@ -427,55 +412,30 @@ public function filterTags($tags, bool $casesensitive = false, string $visibilit
|
|||
public function filterUntagged(string $visibility)
|
||||
{
|
||||
$filtered = [];
|
||||
foreach ($this->bookmarks as $key => $link) {
|
||||
foreach ($this->bookmarks as $key => $bookmark) {
|
||||
if (
|
||||
!$this->pluginManager->filterSearchEntry(
|
||||
$bookmark,
|
||||
['source' => 'untagged', 'visibility' => $visibility]
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($visibility !== 'all') {
|
||||
if (!$link->isPrivate() && $visibility === 'private') {
|
||||
if (!$bookmark->isPrivate() && $visibility === 'private') {
|
||||
continue;
|
||||
} elseif ($link->isPrivate() && $visibility === 'public') {
|
||||
} elseif ($bookmark->isPrivate() && $visibility === 'public') {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($link->getTags())) {
|
||||
$filtered[$key] = $link;
|
||||
}
|
||||
}
|
||||
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of articles for a given day, chronologically sorted
|
||||
*
|
||||
* Day must be in the form 'YYYYMMDD' (e.g. '20120125'), e.g.
|
||||
* print_r($mydb->filterDay('20120125'));
|
||||
*
|
||||
* @param string $day day to filter.
|
||||
* @param string $visibility return only all/private/public bookmarks.
|
||||
|
||||
* @return Bookmark[] all link matching given day.
|
||||
*
|
||||
* @throws Exception if date format is invalid.
|
||||
*/
|
||||
public function filterDay(string $day, string $visibility)
|
||||
{
|
||||
if (!checkDateFormat('Ymd', $day)) {
|
||||
throw new Exception('Invalid date format');
|
||||
}
|
||||
|
||||
$filtered = [];
|
||||
foreach ($this->bookmarks as $key => $bookmark) {
|
||||
if ($visibility === static::$PUBLIC && $bookmark->isPrivate()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($bookmark->getCreated()->format('Ymd') == $day) {
|
||||
if (empty($bookmark->getTags())) {
|
||||
$filtered[$key] = $bookmark;
|
||||
}
|
||||
}
|
||||
|
||||
// sort by date ASC
|
||||
return array_reverse($filtered, true);
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -497,6 +457,56 @@ public static function tagsStrToArray(string $tags, bool $casesensitive): array
|
|||
return preg_split('/\s+/', $tagsOut, -1, PREG_SPLIT_NO_EMPTY);
|
||||
}
|
||||
|
||||
/**
|
||||
* generate a regex fragment out of a tag
|
||||
*
|
||||
* @param string $tag to to generate regexs from. may start with '-' to negate, contain '*' as wildcard
|
||||
*
|
||||
* @return string generated regex fragment
|
||||
*/
|
||||
protected function tag2regex(string $tag): string
|
||||
{
|
||||
$tagsSeparator = $this->conf->get('general.tags_separator', ' ');
|
||||
$len = strlen($tag);
|
||||
if (!$len || $tag === "-" || $tag === "*") {
|
||||
// nothing to search, return empty regex
|
||||
return '';
|
||||
}
|
||||
if ($tag[0] === "-") {
|
||||
// query is negated
|
||||
$i = 1; // use offset to start after '-' character
|
||||
$regex = '(?!'; // create negative lookahead
|
||||
} else {
|
||||
$i = 0; // start at first character
|
||||
$regex = '(?='; // use positive lookahead
|
||||
}
|
||||
// before tag may only be the separator or the beginning
|
||||
$regex .= '.*(?:^|' . $tagsSeparator . ')';
|
||||
// iterate over string, separating it into placeholder and content
|
||||
for (; $i < $len; $i++) {
|
||||
if ($tag[$i] === '*') {
|
||||
// placeholder found
|
||||
$regex .= '[^' . $tagsSeparator . ']*?';
|
||||
} else {
|
||||
// regular characters
|
||||
$offset = strpos($tag, '*', $i);
|
||||
if ($offset === false) {
|
||||
// no placeholder found, set offset to end of string
|
||||
$offset = $len;
|
||||
}
|
||||
// subtract one, as we want to get before the placeholder or end of string
|
||||
$offset -= 1;
|
||||
// we got a tag name that we want to search for. escape any regex characters to prevent conflicts.
|
||||
$regex .= preg_quote(substr($tag, $i, $offset - $i + 1), '/');
|
||||
// move $i on
|
||||
$i = $offset;
|
||||
}
|
||||
}
|
||||
// after the tag may only be the separator or the end
|
||||
$regex .= '(?:$|' . $tagsSeparator . '))';
|
||||
return $regex;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method finalize the content of the foundPositions array,
|
||||
* by associated all search results to their associated bookmark field,
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
namespace Shaarli\Bookmark;
|
||||
|
||||
use malkusch\lock\exception\LockAcquireException;
|
||||
use malkusch\lock\mutex\Mutex;
|
||||
use malkusch\lock\mutex\NoMutex;
|
||||
use Shaarli\Bookmark\Exception\DatastoreNotInitializedException;
|
||||
|
@ -80,7 +81,7 @@ public function read()
|
|||
}
|
||||
|
||||
$content = null;
|
||||
$this->mutex->synchronized(function () use (&$content) {
|
||||
$this->synchronized(function () use (&$content) {
|
||||
$content = file_get_contents($this->datastore);
|
||||
});
|
||||
|
||||
|
@ -119,11 +120,28 @@ public function write($links)
|
|||
|
||||
$data = self::$phpPrefix . base64_encode(gzdeflate(serialize($links))) . self::$phpSuffix;
|
||||
|
||||
$this->mutex->synchronized(function () use ($data) {
|
||||
$this->synchronized(function () use ($data) {
|
||||
file_put_contents(
|
||||
$this->datastore,
|
||||
$data
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper applying mutex to provided function.
|
||||
* If the lock can't be acquired (e.g. some shared hosting provider), we execute the function without mutex.
|
||||
*
|
||||
* @see https://github.com/shaarli/Shaarli/issues/1650
|
||||
*
|
||||
* @param callable $function
|
||||
*/
|
||||
protected function synchronized(callable $function): void
|
||||
{
|
||||
try {
|
||||
$this->mutex->synchronized($function);
|
||||
} catch (LockAcquireException $exception) {
|
||||
$function();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,16 +44,18 @@ public function findByUrl(string $url): ?Bookmark;
|
|||
* @param bool $caseSensitive
|
||||
* @param bool $untaggedOnly
|
||||
* @param bool $ignoreSticky
|
||||
* @param array $pagination This array can contain the following keys for pagination: limit, offset.
|
||||
*
|
||||
* @return Bookmark[]
|
||||
* @return SearchResult
|
||||
*/
|
||||
public function search(
|
||||
array $request = [],
|
||||
string $visibility = null,
|
||||
bool $caseSensitive = false,
|
||||
bool $untaggedOnly = false,
|
||||
bool $ignoreSticky = false
|
||||
);
|
||||
bool $ignoreSticky = false,
|
||||
array $pagination = []
|
||||
): SearchResult;
|
||||
|
||||
/**
|
||||
* Get a single bookmark by its ID.
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
|
||||
use Shaarli\Bookmark\Bookmark;
|
||||
use Shaarli\Formatter\BookmarkDefaultFormatter;
|
||||
|
||||
/**
|
||||
* Extract title from an HTML document.
|
||||
|
@ -68,11 +69,13 @@ function html_extract_tag($tag, $html)
|
|||
$properties = implode('|', $propertiesKey);
|
||||
// We need a OR here to accept either 'property=og:noquote' or 'property="og:unrelated og:my-tag"'
|
||||
$orCondition = '["\']?(?:og:)?' . $tag . '["\']?|["\'][^\'"]*?(?:og:)?' . $tag . '[^\'"]*?[\'"]';
|
||||
// Support quotes in double quoted content, and the other way around
|
||||
$content = 'content=(["\'])((?:(?!\1).)*)\1';
|
||||
// Try to retrieve OpenGraph tag.
|
||||
$ogRegex = '#<meta[^>]+(?:' . $properties . ')=(?:' . $orCondition . ')[^>]*content=(["\'])([^\1]*?)\1.*?>#';
|
||||
$ogRegex = '#<meta[^>]+(?:' . $properties . ')=(?:' . $orCondition . ')[^>]*' . $content . '.*?>#';
|
||||
// If the attributes are not in the order property => content (e.g. Github)
|
||||
// New regex to keep this readable... more or less.
|
||||
$ogRegexReverse = '#<meta[^>]+content=(["\'])([^\1]*?)\1[^>]+(?:' . $properties . ')=(?:' . $orCondition . ').*?>#';
|
||||
$ogRegexReverse = '#<meta[^>]+' . $content . '[^>]+(?:' . $properties . ')=(?:' . $orCondition . ').*?>#';
|
||||
|
||||
if (
|
||||
preg_match($ogRegex, $html, $matches) > 0
|
||||
|
@ -96,7 +99,18 @@ function html_extract_tag($tag, $html)
|
|||
function text2clickable($text)
|
||||
{
|
||||
$regex = '!(((?:https?|ftp|file)://|apt:|magnet:)\S+[a-z0-9\(\)]/?)!si';
|
||||
return preg_replace($regex, '<a href="$1">$1</a>', $text);
|
||||
$format = function (array $match): string {
|
||||
return '<a href="' .
|
||||
str_replace(
|
||||
BookmarkDefaultFormatter::SEARCH_HIGHLIGHT_OPEN,
|
||||
'',
|
||||
str_replace(BookmarkDefaultFormatter::SEARCH_HIGHLIGHT_CLOSE, '', $match[1])
|
||||
) .
|
||||
'">' . $match[1] . '</a>'
|
||||
;
|
||||
};
|
||||
|
||||
return preg_replace_callback($regex, $format, $text);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -109,6 +123,9 @@ function text2clickable($text)
|
|||
*/
|
||||
function hashtag_autolink($description, $indexUrl = '')
|
||||
{
|
||||
$tokens = '(?:' . BookmarkDefaultFormatter::SEARCH_HIGHLIGHT_OPEN . ')' .
|
||||
'(?:' . BookmarkDefaultFormatter::SEARCH_HIGHLIGHT_CLOSE . ')'
|
||||
;
|
||||
/*
|
||||
* To support unicode: http://stackoverflow.com/a/35498078/1484919
|
||||
* \p{Pc} - to match underscore
|
||||
|
@ -116,9 +133,20 @@ function hashtag_autolink($description, $indexUrl = '')
|
|||
* \p{L} - letter from any language
|
||||
* \p{Mn} - any non marking space (accents, umlauts, etc)
|
||||
*/
|
||||
$regex = '/(^|\s)#([\p{Pc}\p{N}\p{L}\p{Mn}]+)/mui';
|
||||
$replacement = '$1<a href="' . $indexUrl . './add-tag/$2" title="Hashtag $2">#$2</a>';
|
||||
return preg_replace($regex, $replacement, $description);
|
||||
$regex = '/(^|\s)#([\p{Pc}\p{N}\p{L}\p{Mn}' . $tokens . ']+)/mui';
|
||||
$format = function (array $match) use ($indexUrl): string {
|
||||
$cleanMatch = str_replace(
|
||||
BookmarkDefaultFormatter::SEARCH_HIGHLIGHT_OPEN,
|
||||
'',
|
||||
str_replace(BookmarkDefaultFormatter::SEARCH_HIGHLIGHT_CLOSE, '', $match[2])
|
||||
);
|
||||
return $match[1] . '<a href="' . $indexUrl . './add-tag/' . $cleanMatch . '"' .
|
||||
' title="Hashtag ' . $cleanMatch . '">' .
|
||||
'#' . $match[2] .
|
||||
'</a>';
|
||||
};
|
||||
|
||||
return preg_replace_callback($regex, $format, $description);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
136
application/bookmark/SearchResult.php
Normal file
136
application/bookmark/SearchResult.php
Normal file
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shaarli\Bookmark;
|
||||
|
||||
/**
|
||||
* Read-only class used to represent search result, including pagination.
|
||||
*/
|
||||
class SearchResult
|
||||
{
|
||||
/** @var Bookmark[] List of result bookmarks with pagination applied */
|
||||
protected $bookmarks;
|
||||
|
||||
/** @var int number of Bookmarks found, with pagination applied */
|
||||
protected $resultCount;
|
||||
|
||||
/** @var int total number of result found */
|
||||
protected $totalCount;
|
||||
|
||||
/** @var int pagination: limit number of result bookmarks */
|
||||
protected $limit;
|
||||
|
||||
/** @var int pagination: offset to apply to complete result list */
|
||||
protected $offset;
|
||||
|
||||
public function __construct(array $bookmarks, int $totalCount, int $offset, ?int $limit)
|
||||
{
|
||||
$this->bookmarks = $bookmarks;
|
||||
$this->resultCount = count($bookmarks);
|
||||
$this->totalCount = $totalCount;
|
||||
$this->limit = $limit;
|
||||
$this->offset = $offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a SearchResult from provided full result set and pagination settings.
|
||||
*
|
||||
* @param Bookmark[] $bookmarks Full set of result which will be filtered
|
||||
* @param int $offset Start recording results from $offset
|
||||
* @param int|null $limit End recording results after $limit bookmarks is reached
|
||||
* @param bool $allowOutOfBounds Set to false to display the last page if the offset is out of bound,
|
||||
* return empty result set otherwise (default: false)
|
||||
*
|
||||
* @return SearchResult
|
||||
*/
|
||||
public static function getSearchResult(
|
||||
$bookmarks,
|
||||
int $offset = 0,
|
||||
?int $limit = null,
|
||||
bool $allowOutOfBounds = false
|
||||
): self {
|
||||
$totalCount = count($bookmarks);
|
||||
if (!$allowOutOfBounds && $offset > $totalCount) {
|
||||
$offset = $limit === null ? 0 : $limit * -1;
|
||||
}
|
||||
|
||||
if ($bookmarks instanceof BookmarkArray) {
|
||||
$buffer = [];
|
||||
foreach ($bookmarks as $key => $value) {
|
||||
$buffer[$key] = $value;
|
||||
}
|
||||
$bookmarks = $buffer;
|
||||
}
|
||||
|
||||
return new static(
|
||||
array_slice($bookmarks, $offset, $limit, true),
|
||||
$totalCount,
|
||||
$offset,
|
||||
$limit
|
||||
);
|
||||
}
|
||||
|
||||
/** @return Bookmark[] List of result bookmarks with pagination applied */
|
||||
public function getBookmarks(): array
|
||||
{
|
||||
return $this->bookmarks;
|
||||
}
|
||||
|
||||
/** @return int number of Bookmarks found, with pagination applied */
|
||||
public function getResultCount(): int
|
||||
{
|
||||
return $this->resultCount;
|
||||
}
|
||||
|
||||
/** @return int total number of result found */
|
||||
public function getTotalCount(): int
|
||||
{
|
||||
return $this->totalCount;
|
||||
}
|
||||
|
||||
/** @return int pagination: limit number of result bookmarks */
|
||||
public function getLimit(): ?int
|
||||
{
|
||||
return $this->limit;
|
||||
}
|
||||
|
||||
/** @return int pagination: offset to apply to complete result list */
|
||||
public function getOffset(): int
|
||||
{
|
||||
return $this->offset;
|
||||
}
|
||||
|
||||
/** @return int Current page of result set in complete results */
|
||||
public function getPage(): int
|
||||
{
|
||||
if (empty($this->limit)) {
|
||||
return $this->offset === 0 ? 1 : 2;
|
||||
}
|
||||
$base = $this->offset >= 0 ? $this->offset : $this->totalCount + $this->offset;
|
||||
|
||||
return (int) ceil($base / $this->limit) + 1;
|
||||
}
|
||||
|
||||
/** @return int Get the # of the last page */
|
||||
public function getLastPage(): int
|
||||
{
|
||||
if (empty($this->limit)) {
|
||||
return $this->offset === 0 ? 1 : 2;
|
||||
}
|
||||
|
||||
return (int) ceil($this->totalCount / $this->limit);
|
||||
}
|
||||
|
||||
/** @return bool Either the current page is the last one or not */
|
||||
public function isLastPage(): bool
|
||||
{
|
||||
return $this->getPage() === $this->getLastPage();
|
||||
}
|
||||
|
||||
/** @return bool Either the current page is the first one or not */
|
||||
public function isFirstPage(): bool
|
||||
{
|
||||
return $this->offset === 0;
|
||||
}
|
||||
}
|
|
@ -50,6 +50,9 @@ class ContainerBuilder
|
|||
/** @var LoginManager */
|
||||
protected $login;
|
||||
|
||||
/** @var PluginManager */
|
||||
protected $pluginManager;
|
||||
|
||||
/** @var LoggerInterface */
|
||||
protected $logger;
|
||||
|
||||
|
@ -61,12 +64,14 @@ public function __construct(
|
|||
SessionManager $session,
|
||||
CookieManager $cookieManager,
|
||||
LoginManager $login,
|
||||
PluginManager $pluginManager,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
$this->conf = $conf;
|
||||
$this->session = $session;
|
||||
$this->login = $login;
|
||||
$this->cookieManager = $cookieManager;
|
||||
$this->pluginManager = $pluginManager;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
|
@ -78,12 +83,10 @@ public function build(): ShaarliContainer
|
|||
$container['sessionManager'] = $this->session;
|
||||
$container['cookieManager'] = $this->cookieManager;
|
||||
$container['loginManager'] = $this->login;
|
||||
$container['pluginManager'] = $this->pluginManager;
|
||||
$container['logger'] = $this->logger;
|
||||
$container['basePath'] = $this->basePath;
|
||||
|
||||
$container['plugins'] = function (ShaarliContainer $container): PluginManager {
|
||||
return new PluginManager($container->conf);
|
||||
};
|
||||
|
||||
$container['history'] = function (ShaarliContainer $container): History {
|
||||
return new History($container->conf->get('resource.history'));
|
||||
|
@ -92,6 +95,7 @@ public function build(): ShaarliContainer
|
|||
$container['bookmarkService'] = function (ShaarliContainer $container): BookmarkServiceInterface {
|
||||
return new BookmarkFileService(
|
||||
$container->conf,
|
||||
$container->pluginManager,
|
||||
$container->history,
|
||||
new FlockMutex(fopen(SHAARLI_MUTEX_FILE, 'r'), 2),
|
||||
$container->loginManager->isLoggedIn()
|
||||
|
@ -113,14 +117,6 @@ public function build(): ShaarliContainer
|
|||
);
|
||||
};
|
||||
|
||||
$container['pluginManager'] = function (ShaarliContainer $container): PluginManager {
|
||||
$pluginManager = new PluginManager($container->conf);
|
||||
|
||||
$pluginManager->load($container->conf->get('general.enabled_plugins'));
|
||||
|
||||
return $pluginManager;
|
||||
};
|
||||
|
||||
$container['formatterFactory'] = function (ShaarliContainer $container): FormatterFactory {
|
||||
return new FormatterFactory(
|
||||
$container->conf,
|
||||
|
|
|
@ -1,34 +1,43 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shaarli\Feed;
|
||||
|
||||
use DatePeriod;
|
||||
|
||||
/**
|
||||
* Simple cache system, mainly for the RSS/ATOM feeds
|
||||
*/
|
||||
class CachedPage
|
||||
{
|
||||
// Directory containing page caches
|
||||
private $cacheDir;
|
||||
/** Directory containing page caches */
|
||||
protected $cacheDir;
|
||||
|
||||
// Should this URL be cached (boolean)?
|
||||
private $shouldBeCached;
|
||||
/** Should this URL be cached (boolean)? */
|
||||
protected $shouldBeCached;
|
||||
|
||||
// Name of the cache file for this URL
|
||||
private $filename;
|
||||
/** Name of the cache file for this URL */
|
||||
protected $filename;
|
||||
|
||||
/** @var DatePeriod|null Optionally specify a period of time for cache validity */
|
||||
protected $validityPeriod;
|
||||
|
||||
/**
|
||||
* Creates a new CachedPage
|
||||
*
|
||||
* @param string $cacheDir page cache directory
|
||||
* @param string $url page URL
|
||||
* @param bool $shouldBeCached whether this page needs to be cached
|
||||
* @param string $cacheDir page cache directory
|
||||
* @param string $url page URL
|
||||
* @param bool $shouldBeCached whether this page needs to be cached
|
||||
* @param ?DatePeriod $validityPeriod Optionally specify a time limit on requested cache
|
||||
*/
|
||||
public function __construct($cacheDir, $url, $shouldBeCached)
|
||||
public function __construct($cacheDir, $url, $shouldBeCached, ?DatePeriod $validityPeriod)
|
||||
{
|
||||
// TODO: check write access to the cache directory
|
||||
$this->cacheDir = $cacheDir;
|
||||
$this->filename = $this->cacheDir . '/' . sha1($url) . '.cache';
|
||||
$this->shouldBeCached = $shouldBeCached;
|
||||
$this->validityPeriod = $validityPeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -41,10 +50,20 @@ public function cachedVersion()
|
|||
if (!$this->shouldBeCached) {
|
||||
return null;
|
||||
}
|
||||
if (is_file($this->filename)) {
|
||||
return file_get_contents($this->filename);
|
||||
if (!is_file($this->filename)) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
if ($this->validityPeriod !== null) {
|
||||
$cacheDate = \DateTime::createFromFormat('U', (string) filemtime($this->filename));
|
||||
if (
|
||||
$cacheDate < $this->validityPeriod->getStartDate()
|
||||
|| $cacheDate > $this->validityPeriod->getEndDate()
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return file_get_contents($this->filename);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -102,22 +102,16 @@ public function buildData(string $feedType, ?array $userInput)
|
|||
$userInput['searchtags'] = false;
|
||||
}
|
||||
|
||||
$limit = $this->getLimit($userInput);
|
||||
|
||||
// Optionally filter the results:
|
||||
$linksToDisplay = $this->linkDB->search($userInput ?? [], null, false, false, true);
|
||||
|
||||
$nblinksToDisplay = $this->getNbLinks(count($linksToDisplay), $userInput);
|
||||
|
||||
// Can't use array_keys() because $link is a LinkDB instance and not a real array.
|
||||
$keys = [];
|
||||
foreach ($linksToDisplay as $key => $value) {
|
||||
$keys[] = $key;
|
||||
}
|
||||
$searchResult = $this->linkDB->search($userInput ?? [], null, false, false, true, ['limit' => $limit]);
|
||||
|
||||
$pageaddr = escape(index_url($this->serverInfo));
|
||||
$this->formatter->addContextData('index_url', $pageaddr);
|
||||
$linkDisplayed = [];
|
||||
for ($i = 0; $i < $nblinksToDisplay && $i < count($keys); $i++) {
|
||||
$linkDisplayed[$keys[$i]] = $this->buildItem($feedType, $linksToDisplay[$keys[$i]], $pageaddr);
|
||||
$links = [];
|
||||
foreach ($searchResult->getBookmarks() as $key => $bookmark) {
|
||||
$links[$key] = $this->buildItem($feedType, $bookmark, $pageaddr);
|
||||
}
|
||||
|
||||
$data['language'] = $this->getTypeLanguage($feedType);
|
||||
|
@ -128,7 +122,7 @@ public function buildData(string $feedType, ?array $userInput)
|
|||
$data['self_link'] = $pageaddr . $requestUri;
|
||||
$data['index_url'] = $pageaddr;
|
||||
$data['usepermalinks'] = $this->usePermalinks === true;
|
||||
$data['links'] = $linkDisplayed;
|
||||
$data['links'] = $links;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
@ -268,19 +262,18 @@ protected function getIsoDate(string $feedType, DateTime $date, $format = false)
|
|||
* If 'nb' not set or invalid, default value: $DEFAULT_NB_LINKS.
|
||||
* If 'nb' is set to 'all', display all filtered bookmarks (max parameter).
|
||||
*
|
||||
* @param int $max maximum number of bookmarks to display.
|
||||
* @param array $userInput $_GET.
|
||||
*
|
||||
* @return int number of bookmarks to display.
|
||||
*/
|
||||
protected function getNbLinks($max, ?array $userInput)
|
||||
protected function getLimit(?array $userInput)
|
||||
{
|
||||
if (empty($userInput['nb'])) {
|
||||
return self::$DEFAULT_NB_LINKS;
|
||||
}
|
||||
|
||||
if ($userInput['nb'] == 'all') {
|
||||
return $max;
|
||||
return null;
|
||||
}
|
||||
|
||||
$intNb = intval($userInput['nb']);
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
*/
|
||||
class BookmarkDefaultFormatter extends BookmarkFormatter
|
||||
{
|
||||
protected const SEARCH_HIGHLIGHT_OPEN = '|@@HIGHLIGHT';
|
||||
protected const SEARCH_HIGHLIGHT_CLOSE = 'HIGHLIGHT@@|';
|
||||
public const SEARCH_HIGHLIGHT_OPEN = '||O_HIGHLIGHT';
|
||||
public const SEARCH_HIGHLIGHT_CLOSE = '||C_HIGHLIGHT';
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace Shaarli\Formatter;
|
||||
|
||||
use Shaarli\Config\ConfigManager;
|
||||
use Shaarli\Formatter\Parsedown\ShaarliParsedownExtra;
|
||||
|
||||
/**
|
||||
* Class BookmarkMarkdownExtraFormatter
|
||||
|
@ -18,7 +19,6 @@ class BookmarkMarkdownExtraFormatter extends BookmarkMarkdownFormatter
|
|||
public function __construct(ConfigManager $conf, bool $isLoggedIn)
|
||||
{
|
||||
parent::__construct($conf, $isLoggedIn);
|
||||
|
||||
$this->parsedown = new \ParsedownExtra();
|
||||
$this->parsedown = new ShaarliParsedownExtra();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace Shaarli\Formatter;
|
||||
|
||||
use Shaarli\Config\ConfigManager;
|
||||
use Shaarli\Formatter\Parsedown\ShaarliParsedown;
|
||||
|
||||
/**
|
||||
* Class BookmarkMarkdownFormatter
|
||||
|
@ -42,7 +43,7 @@ public function __construct(ConfigManager $conf, bool $isLoggedIn)
|
|||
{
|
||||
parent::__construct($conf, $isLoggedIn);
|
||||
|
||||
$this->parsedown = new \Parsedown();
|
||||
$this->parsedown = new ShaarliParsedown();
|
||||
$this->escape = $conf->get('security.markdown_escape', true);
|
||||
$this->allowedProtocols = $conf->get('security.allowed_protocols', []);
|
||||
}
|
||||
|
@ -128,6 +129,9 @@ function ($match) use ($allowedProtocols, $indexUrl) {
|
|||
protected function formatHashTags($description)
|
||||
{
|
||||
$indexUrl = ! empty($this->contextData['index_url']) ? $this->contextData['index_url'] : '';
|
||||
$tokens = '(?:' . BookmarkDefaultFormatter::SEARCH_HIGHLIGHT_OPEN . ')' .
|
||||
'(?:' . BookmarkDefaultFormatter::SEARCH_HIGHLIGHT_CLOSE . ')'
|
||||
;
|
||||
|
||||
/*
|
||||
* To support unicode: http://stackoverflow.com/a/35498078/1484919
|
||||
|
@ -136,8 +140,15 @@ protected function formatHashTags($description)
|
|||
* \p{L} - letter from any language
|
||||
* \p{Mn} - any non marking space (accents, umlauts, etc)
|
||||
*/
|
||||
$regex = '/(^|\s)#([\p{Pc}\p{N}\p{L}\p{Mn}]+)/mui';
|
||||
$replacement = '$1[#$2](' . $indexUrl . './add-tag/$2)';
|
||||
$regex = '/(^|\s)#([\p{Pc}\p{N}\p{L}\p{Mn}' . $tokens . ']+)/mui';
|
||||
$replacement = function (array $match) use ($indexUrl): string {
|
||||
$cleanMatch = str_replace(
|
||||
BookmarkDefaultFormatter::SEARCH_HIGHLIGHT_OPEN,
|
||||
'',
|
||||
str_replace(BookmarkDefaultFormatter::SEARCH_HIGHLIGHT_CLOSE, '', $match[2])
|
||||
);
|
||||
return $match[1] . '[#' . $match[2] . '](' . $indexUrl . './add-tag/' . $cleanMatch . ')';
|
||||
};
|
||||
|
||||
$descriptionLines = explode(PHP_EOL, $description);
|
||||
$descriptionOut = '';
|
||||
|
@ -156,7 +167,7 @@ protected function formatHashTags($description)
|
|||
}
|
||||
|
||||
if (!$codeBlockOn && !$codeLineOn) {
|
||||
$descriptionLine = preg_replace($regex, $replacement, $descriptionLine);
|
||||
$descriptionLine = preg_replace_callback($regex, $replacement, $descriptionLine);
|
||||
}
|
||||
|
||||
$descriptionOut .= $descriptionLine;
|
||||
|
|
15
application/formatter/Parsedown/ShaarliParsedown.php
Normal file
15
application/formatter/Parsedown/ShaarliParsedown.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shaarli\Formatter\Parsedown;
|
||||
|
||||
/**
|
||||
* Parsedown extension for Shaarli.
|
||||
*
|
||||
* Extension for both Parsedown and ParsedownExtra centralized in ShaarliParsedownTrait.
|
||||
*/
|
||||
class ShaarliParsedown extends \Parsedown
|
||||
{
|
||||
use ShaarliParsedownTrait;
|
||||
}
|
15
application/formatter/Parsedown/ShaarliParsedownExtra.php
Normal file
15
application/formatter/Parsedown/ShaarliParsedownExtra.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shaarli\Formatter\Parsedown;
|
||||
|
||||
/**
|
||||
* ParsedownExtra extension for Shaarli.
|
||||
*
|
||||
* Extension for both Parsedown and ParsedownExtra centralized in ShaarliParsedownTrait.
|
||||
*/
|
||||
class ShaarliParsedownExtra extends \ParsedownExtra
|
||||
{
|
||||
use ShaarliParsedownTrait;
|
||||
}
|
81
application/formatter/Parsedown/ShaarliParsedownTrait.php
Normal file
81
application/formatter/Parsedown/ShaarliParsedownTrait.php
Normal file
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shaarli\Formatter\Parsedown;
|
||||
|
||||
use Shaarli\Formatter\BookmarkDefaultFormatter as Formatter;
|
||||
|
||||
/**
|
||||
* Trait used for Parsedown and ParsedownExtra extension.
|
||||
*
|
||||
* Extended:
|
||||
* - Format links properly in search context
|
||||
*/
|
||||
trait ShaarliParsedownTrait
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function inlineLink($excerpt)
|
||||
{
|
||||
return $this->shaarliFormatLink(parent::inlineLink($excerpt), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function inlineUrl($excerpt)
|
||||
{
|
||||
return $this->shaarliFormatLink(parent::inlineUrl($excerpt), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Properly format markdown link:
|
||||
* - remove highlight tags from HREF attribute
|
||||
* - (optional) add highlight tags to link caption
|
||||
*
|
||||
* @param array|null $link Parsedown formatted link array.
|
||||
* It can be empty.
|
||||
* @param bool $fullWrap Add highlight tags the whole link caption
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
protected function shaarliFormatLink(?array $link, bool $fullWrap): ?array
|
||||
{
|
||||
// If open and clean search tokens are found in the link, process.
|
||||
if (
|
||||
is_array($link)
|
||||
&& strpos($link['element']['attributes']['href'] ?? '', Formatter::SEARCH_HIGHLIGHT_OPEN) !== false
|
||||
&& strpos($link['element']['attributes']['href'] ?? '', Formatter::SEARCH_HIGHLIGHT_CLOSE) !== false
|
||||
) {
|
||||
$link['element']['attributes']['href'] = $this->shaarliRemoveSearchTokens(
|
||||
$link['element']['attributes']['href']
|
||||
);
|
||||
|
||||
if ($fullWrap) {
|
||||
$link['element']['text'] = Formatter::SEARCH_HIGHLIGHT_OPEN .
|
||||
$link['element']['text'] .
|
||||
Formatter::SEARCH_HIGHLIGHT_CLOSE
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
return $link;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove open and close tags from provided string.
|
||||
*
|
||||
* @param string $entry input
|
||||
*
|
||||
* @return string Striped input
|
||||
*/
|
||||
protected function shaarliRemoveSearchTokens(string $entry): string
|
||||
{
|
||||
$entry = str_replace(Formatter::SEARCH_HIGHLIGHT_OPEN, '', $entry);
|
||||
$entry = str_replace(Formatter::SEARCH_HIGHLIGHT_CLOSE, '', $entry);
|
||||
|
||||
return $entry;
|
||||
}
|
||||
}
|
|
@ -57,9 +57,12 @@ public function save(Request $request, Response $response): Response
|
|||
}
|
||||
|
||||
// TODO: move this to bookmark service
|
||||
$count = 0;
|
||||
$bookmarks = $this->container->bookmarkService->search(['searchtags' => $fromTag], BookmarkFilter::$ALL, true);
|
||||
foreach ($bookmarks as $bookmark) {
|
||||
$searchResult = $this->container->bookmarkService->search(
|
||||
['searchtags' => $fromTag],
|
||||
BookmarkFilter::$ALL,
|
||||
true
|
||||
);
|
||||
foreach ($searchResult->getBookmarks() as $bookmark) {
|
||||
if (false === $isDelete) {
|
||||
$bookmark->renameTag($fromTag, $toTag);
|
||||
} else {
|
||||
|
@ -68,11 +71,11 @@ public function save(Request $request, Response $response): Response
|
|||
|
||||
$this->container->bookmarkService->set($bookmark, false);
|
||||
$this->container->history->updateLink($bookmark);
|
||||
$count++;
|
||||
}
|
||||
|
||||
$this->container->bookmarkService->save();
|
||||
|
||||
$count = $searchResult->getResultCount();
|
||||
if (true === $isDelete) {
|
||||
$alert = sprintf(
|
||||
t('The tag was removed from %d bookmark.', 'The tag was removed from %d bookmarks.', $count),
|
||||
|
|
|
@ -39,11 +39,16 @@ public function index(Request $request, Response $response): Response
|
|||
$currentVersion = $currentVersion === 'dev' ? $currentVersion : 'v' . $currentVersion;
|
||||
$phpEol = new \DateTimeImmutable(ApplicationUtils::getPhpEol(PHP_VERSION));
|
||||
|
||||
$permissions = array_merge(
|
||||
ApplicationUtils::checkResourcePermissions($this->container->conf),
|
||||
ApplicationUtils::checkDatastoreMutex()
|
||||
);
|
||||
|
||||
$this->assignView('php_version', PHP_VERSION);
|
||||
$this->assignView('php_eol', format_date($phpEol, false));
|
||||
$this->assignView('php_has_reached_eol', $phpEol < new \DateTimeImmutable());
|
||||
$this->assignView('php_extensions', ApplicationUtils::getPhpExtensionsRequirement());
|
||||
$this->assignView('permissions', ApplicationUtils::checkResourcePermissions($this->container->conf));
|
||||
$this->assignView('permissions', $permissions);
|
||||
$this->assignView('release_url', $releaseUrl);
|
||||
$this->assignView('latest_version', $latestVersion);
|
||||
$this->assignView('current_version', $currentVersion);
|
||||
|
|
|
@ -66,6 +66,10 @@ public function deleteBookmark(Request $request, Response $response): Response
|
|||
return $response->write('<script>self.close();</script>');
|
||||
}
|
||||
|
||||
if ($request->getParam('source') === 'batch') {
|
||||
return $response->withStatus(204);
|
||||
}
|
||||
|
||||
// Don't redirect to permalink after deletion.
|
||||
return $this->redirectFromReferer($request, $response, ['shaare/']);
|
||||
}
|
||||
|
|
|
@ -227,7 +227,7 @@ protected function buildLinkDataFromUrl(Request $request, string $url): array
|
|||
|
||||
protected function buildFormData(array $link, bool $isNew, Request $request): array
|
||||
{
|
||||
$link['tags'] = strlen($link['tags']) > 0
|
||||
$link['tags'] = $link['tags'] !== null && strlen($link['tags']) > 0
|
||||
? $link['tags'] . $this->container->conf->get('general.tags_separator', ' ')
|
||||
: $link['tags']
|
||||
;
|
||||
|
|
|
@ -22,7 +22,7 @@ class ThumbnailsController extends ShaarliAdminController
|
|||
public function index(Request $request, Response $response): Response
|
||||
{
|
||||
$ids = [];
|
||||
foreach ($this->container->bookmarkService->search() as $bookmark) {
|
||||
foreach ($this->container->bookmarkService->search()->getBookmarks() as $bookmark) {
|
||||
// A note or not HTTP(S)
|
||||
if ($bookmark->isNote() || !startsWith(strtolower($bookmark->getUrl()), 'http')) {
|
||||
continue;
|
||||
|
|
|
@ -33,10 +33,10 @@ public function index(Request $request, Response $response): Response
|
|||
|
||||
$formatter = $this->container->formatterFactory->getFormatter();
|
||||
$formatter->addContextData('base_path', $this->container->basePath);
|
||||
$formatter->addContextData('index_url', index_url($this->container->environment));
|
||||
|
||||
$searchTags = normalize_spaces($request->getParam('searchtags') ?? '');
|
||||
$searchTerm = escape(normalize_spaces($request->getParam('searchterm') ?? ''));
|
||||
;
|
||||
|
||||
// Filter bookmarks according search parameters.
|
||||
$visibility = $this->container->sessionManager->getSessionParameter('visibility');
|
||||
|
@ -44,39 +44,26 @@ public function index(Request $request, Response $response): Response
|
|||
'searchtags' => $searchTags,
|
||||
'searchterm' => $searchTerm,
|
||||
];
|
||||
$linksToDisplay = $this->container->bookmarkService->search(
|
||||
|
||||
// Select articles according to paging.
|
||||
$page = (int) ($request->getParam('page') ?? 1);
|
||||
$page = $page < 1 ? 1 : $page;
|
||||
$linksPerPage = $this->container->sessionManager->getSessionParameter('LINKS_PER_PAGE', 20) ?: 20;
|
||||
|
||||
$searchResult = $this->container->bookmarkService->search(
|
||||
$search,
|
||||
$visibility,
|
||||
false,
|
||||
!!$this->container->sessionManager->getSessionParameter('untaggedonly')
|
||||
!!$this->container->sessionManager->getSessionParameter('untaggedonly'),
|
||||
false,
|
||||
['offset' => $linksPerPage * ($page - 1), 'limit' => $linksPerPage]
|
||||
) ?? [];
|
||||
|
||||
// ---- Handle paging.
|
||||
$keys = [];
|
||||
foreach ($linksToDisplay as $key => $value) {
|
||||
$keys[] = $key;
|
||||
}
|
||||
|
||||
$linksPerPage = $this->container->sessionManager->getSessionParameter('LINKS_PER_PAGE', 20) ?: 20;
|
||||
|
||||
// Select articles according to paging.
|
||||
$pageCount = (int) ceil(count($keys) / $linksPerPage) ?: 1;
|
||||
$page = (int) $request->getParam('page') ?? 1;
|
||||
$page = $page < 1 ? 1 : $page;
|
||||
$page = $page > $pageCount ? $pageCount : $page;
|
||||
|
||||
// Start index.
|
||||
$i = ($page - 1) * $linksPerPage;
|
||||
$end = $i + $linksPerPage;
|
||||
|
||||
$linkDisp = [];
|
||||
$save = false;
|
||||
while ($i < $end && $i < count($keys)) {
|
||||
$save = $this->updateThumbnail($linksToDisplay[$keys[$i]], false) || $save;
|
||||
$link = $formatter->format($linksToDisplay[$keys[$i]]);
|
||||
|
||||
$linkDisp[$keys[$i]] = $link;
|
||||
$i++;
|
||||
$links = [];
|
||||
foreach ($searchResult->getBookmarks() as $key => $bookmark) {
|
||||
$save = $this->updateThumbnail($bookmark, false) || $save;
|
||||
$links[$key] = $formatter->format($bookmark);
|
||||
}
|
||||
|
||||
if ($save) {
|
||||
|
@ -86,15 +73,10 @@ public function index(Request $request, Response $response): Response
|
|||
// Compute paging navigation
|
||||
$searchtagsUrl = $searchTags === '' ? '' : '&searchtags=' . urlencode($searchTags);
|
||||
$searchtermUrl = $searchTerm === '' ? '' : '&searchterm=' . urlencode($searchTerm);
|
||||
$page = $searchResult->getPage();
|
||||
|
||||
$previous_page_url = '';
|
||||
if ($i !== count($keys)) {
|
||||
$previous_page_url = '?page=' . ($page + 1) . $searchtermUrl . $searchtagsUrl;
|
||||
}
|
||||
$next_page_url = '';
|
||||
if ($page > 1) {
|
||||
$next_page_url = '?page=' . ($page - 1) . $searchtermUrl . $searchtagsUrl;
|
||||
}
|
||||
$previousPageUrl = !$searchResult->isLastPage() ? '?page=' . ($page + 1) . $searchtermUrl . $searchtagsUrl : '';
|
||||
$nextPageUrl = !$searchResult->isFirstPage() ? '?page=' . ($page - 1) . $searchtermUrl . $searchtagsUrl : '';
|
||||
|
||||
$tagsSeparator = $this->container->conf->get('general.tags_separator', ' ');
|
||||
$searchTagsUrlEncoded = array_map('urlencode', tags_str2array($searchTags, $tagsSeparator));
|
||||
|
@ -104,16 +86,16 @@ public function index(Request $request, Response $response): Response
|
|||
$data = array_merge(
|
||||
$this->initializeTemplateVars(),
|
||||
[
|
||||
'previous_page_url' => $previous_page_url,
|
||||
'next_page_url' => $next_page_url,
|
||||
'previous_page_url' => $previousPageUrl,
|
||||
'next_page_url' => $nextPageUrl,
|
||||
'page_current' => $page,
|
||||
'page_max' => $pageCount,
|
||||
'result_count' => count($linksToDisplay),
|
||||
'page_max' => $searchResult->getLastPage(),
|
||||
'result_count' => $searchResult->getTotalCount(),
|
||||
'search_term' => escape($searchTerm),
|
||||
'search_tags' => escape($searchTags),
|
||||
'search_tags_url' => $searchTagsUrlEncoded,
|
||||
'visibility' => $visibility,
|
||||
'links' => $linkDisp,
|
||||
'links' => $links,
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -157,6 +139,7 @@ public function permalink(Request $request, Response $response, array $args): Re
|
|||
|
||||
$formatter = $this->container->formatterFactory->getFormatter();
|
||||
$formatter->addContextData('base_path', $this->container->basePath);
|
||||
$formatter->addContextData('index_url', index_url($this->container->environment));
|
||||
|
||||
$data = array_merge(
|
||||
$this->initializeTemplateVars(),
|
||||
|
|
|
@ -86,9 +86,11 @@ public function index(Request $request, Response $response): Response
|
|||
public function rss(Request $request, Response $response): Response
|
||||
{
|
||||
$response = $response->withHeader('Content-Type', 'application/rss+xml; charset=utf-8');
|
||||
$type = DailyPageHelper::extractRequestedType($request);
|
||||
$cacheDuration = DailyPageHelper::getCacheDatePeriodByType($type);
|
||||
|
||||
$pageUrl = page_url($this->container->environment);
|
||||
$cache = $this->container->pageCacheManager->getCachePage($pageUrl);
|
||||
$cache = $this->container->pageCacheManager->getCachePage($pageUrl, $cacheDuration);
|
||||
|
||||
$cached = $cache->cachedVersion();
|
||||
if (!empty($cached)) {
|
||||
|
@ -96,10 +98,9 @@ public function rss(Request $request, Response $response): Response
|
|||
}
|
||||
|
||||
$days = [];
|
||||
$type = DailyPageHelper::extractRequestedType($request);
|
||||
$format = DailyPageHelper::getFormatByType($type);
|
||||
$length = DailyPageHelper::getRssLengthByType($type);
|
||||
foreach ($this->container->bookmarkService->search() as $bookmark) {
|
||||
foreach ($this->container->bookmarkService->search()->getBookmarks() as $bookmark) {
|
||||
$day = $bookmark->getCreated()->format($format);
|
||||
|
||||
// Stop iterating after DAILY_RSS_NB_DAYS entries
|
||||
|
@ -131,7 +132,7 @@ public function rss(Request $request, Response $response): Response
|
|||
$dataPerDay[$day] = [
|
||||
'date' => $endDateTime,
|
||||
'date_rss' => $endDateTime->format(DateTime::RSS),
|
||||
'date_human' => DailyPageHelper::getDescriptionByType($type, $dayDateTime),
|
||||
'date_human' => DailyPageHelper::getDescriptionByType($type, $dayDateTime, false),
|
||||
'absolute_url' => $indexUrl . 'daily?' . $type . '=' . $day,
|
||||
'links' => [],
|
||||
];
|
||||
|
|
|
@ -56,11 +56,16 @@ public function index(Request $request, Response $response): Response
|
|||
|
||||
$phpEol = new \DateTimeImmutable(ApplicationUtils::getPhpEol(PHP_VERSION));
|
||||
|
||||
$permissions = array_merge(
|
||||
ApplicationUtils::checkResourcePermissions($this->container->conf),
|
||||
ApplicationUtils::checkDatastoreMutex()
|
||||
);
|
||||
|
||||
$this->assignView('php_version', PHP_VERSION);
|
||||
$this->assignView('php_eol', format_date($phpEol, false));
|
||||
$this->assignView('php_has_reached_eol', $phpEol < new \DateTimeImmutable());
|
||||
$this->assignView('php_extensions', ApplicationUtils::getPhpExtensionsRequirement());
|
||||
$this->assignView('permissions', ApplicationUtils::checkResourcePermissions($this->container->conf));
|
||||
$this->assignView('permissions', $permissions);
|
||||
|
||||
$this->assignView('pagetitle', t('Install Shaarli'));
|
||||
|
||||
|
|
|
@ -30,19 +30,19 @@ public function index(Request $request, Response $response): Response
|
|||
);
|
||||
|
||||
// Optionally filter the results:
|
||||
$links = $this->container->bookmarkService->search($request->getQueryParams());
|
||||
$linksToDisplay = [];
|
||||
$bookmarks = $this->container->bookmarkService->search($request->getQueryParams())->getBookmarks();
|
||||
$links = [];
|
||||
|
||||
// Get only bookmarks which have a thumbnail.
|
||||
// Note: we do not retrieve thumbnails here, the request is too heavy.
|
||||
$formatter = $this->container->formatterFactory->getFormatter('raw');
|
||||
foreach ($links as $key => $link) {
|
||||
if (!empty($link->getThumbnail())) {
|
||||
$linksToDisplay[] = $formatter->format($link);
|
||||
foreach ($bookmarks as $key => $bookmark) {
|
||||
if (!empty($bookmark->getThumbnail())) {
|
||||
$links[] = $formatter->format($bookmark);
|
||||
}
|
||||
}
|
||||
|
||||
$data = ['linksToDisplay' => $linksToDisplay];
|
||||
$data = ['linksToDisplay' => $links];
|
||||
$this->executePageHooks('render_picwall', $data, TemplatePage::PICTURE_WALL);
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
|
|
|
@ -56,6 +56,10 @@ protected function assignAllView(array $data): self
|
|||
|
||||
protected function render(string $template): string
|
||||
{
|
||||
// Legacy key that used to be injected by PluginManager
|
||||
$this->assignView('_PAGE_', $template);
|
||||
$this->assignView('template', $template);
|
||||
|
||||
$this->assignView('linkcount', $this->container->bookmarkService->count(BookmarkFilter::$ALL));
|
||||
$this->assignView('privateLinkcount', $this->container->bookmarkService->count(BookmarkFilter::$PRIVATE));
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
namespace Shaarli\Helper;
|
||||
|
||||
use Exception;
|
||||
use malkusch\lock\exception\LockAcquireException;
|
||||
use malkusch\lock\mutex\FlockMutex;
|
||||
use Shaarli\Config\ConfigManager;
|
||||
|
||||
/**
|
||||
|
@ -35,7 +37,7 @@ public static function getLatestGitVersionCode($url, $timeout = 2)
|
|||
{
|
||||
list($headers, $data) = get_http_response($url, $timeout);
|
||||
|
||||
if (strpos($headers[0], '200 OK') === false) {
|
||||
if (preg_match('#HTTP/[\d\.]+ 200(?: OK)?#', $headers[0]) !== 1) {
|
||||
error_log('Failed to retrieve ' . $url);
|
||||
return false;
|
||||
}
|
||||
|
@ -252,6 +254,20 @@ public static function checkResourcePermissions(ConfigManager $conf, bool $minim
|
|||
return $errors;
|
||||
}
|
||||
|
||||
public static function checkDatastoreMutex(): array
|
||||
{
|
||||
$mutex = new FlockMutex(fopen(SHAARLI_MUTEX_FILE, 'r'), 2);
|
||||
try {
|
||||
$mutex->synchronized(function () {
|
||||
return true;
|
||||
});
|
||||
} catch (LockAcquireException $e) {
|
||||
$errors[] = t('Lock can not be acquired on the datastore. You might encounter concurrent access issues.');
|
||||
}
|
||||
|
||||
return $errors ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a salted hash representing the current Shaarli version.
|
||||
*
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
|
||||
namespace Shaarli\Helper;
|
||||
|
||||
use DatePeriod;
|
||||
use DateTimeImmutable;
|
||||
use Exception;
|
||||
use Shaarli\Bookmark\Bookmark;
|
||||
use Slim\Http\Request;
|
||||
|
||||
|
@ -40,31 +43,31 @@ public static function extractRequestedType(Request $request): string
|
|||
* @param string|null $requestedDate Input string extracted from the request
|
||||
* @param Bookmark|null $latestBookmark Latest bookmark found in the datastore (by date)
|
||||
*
|
||||
* @return \DateTimeImmutable from input or latest bookmark.
|
||||
* @return DateTimeImmutable from input or latest bookmark.
|
||||
*
|
||||
* @throws \Exception Type not supported.
|
||||
* @throws Exception Type not supported.
|
||||
*/
|
||||
public static function extractRequestedDateTime(
|
||||
string $type,
|
||||
?string $requestedDate,
|
||||
Bookmark $latestBookmark = null
|
||||
): \DateTimeImmutable {
|
||||
): DateTimeImmutable {
|
||||
$format = static::getFormatByType($type);
|
||||
if (empty($requestedDate)) {
|
||||
return $latestBookmark instanceof Bookmark
|
||||
? new \DateTimeImmutable($latestBookmark->getCreated()->format(\DateTime::ATOM))
|
||||
: new \DateTimeImmutable()
|
||||
? new DateTimeImmutable($latestBookmark->getCreated()->format(\DateTime::ATOM))
|
||||
: new DateTimeImmutable()
|
||||
;
|
||||
}
|
||||
|
||||
// W is not supported by createFromFormat...
|
||||
if ($type === static::WEEK) {
|
||||
return (new \DateTimeImmutable())
|
||||
return (new DateTimeImmutable())
|
||||
->setISODate((int) substr($requestedDate, 0, 4), (int) substr($requestedDate, 4, 2))
|
||||
;
|
||||
}
|
||||
|
||||
return \DateTimeImmutable::createFromFormat($format, $requestedDate);
|
||||
return DateTimeImmutable::createFromFormat($format, $requestedDate);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -80,7 +83,7 @@ public static function extractRequestedDateTime(
|
|||
*
|
||||
* @see https://www.php.net/manual/en/datetime.format.php
|
||||
*
|
||||
* @throws \Exception Type not supported.
|
||||
* @throws Exception Type not supported.
|
||||
*/
|
||||
public static function getFormatByType(string $type): string
|
||||
{
|
||||
|
@ -92,7 +95,7 @@ public static function getFormatByType(string $type): string
|
|||
case static::DAY:
|
||||
return 'Ymd';
|
||||
default:
|
||||
throw new \Exception('Unsupported daily format type');
|
||||
throw new Exception('Unsupported daily format type');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,14 +105,14 @@ public static function getFormatByType(string $type): string
|
|||
* and we don't want to alter original datetime.
|
||||
*
|
||||
* @param string $type month/week/day
|
||||
* @param \DateTimeImmutable $requested DateTime extracted from request input
|
||||
* @param DateTimeImmutable $requested DateTime extracted from request input
|
||||
* (should come from extractRequestedDateTime)
|
||||
*
|
||||
* @return \DateTimeInterface First DateTime of the time period
|
||||
*
|
||||
* @throws \Exception Type not supported.
|
||||
* @throws Exception Type not supported.
|
||||
*/
|
||||
public static function getStartDateTimeByType(string $type, \DateTimeImmutable $requested): \DateTimeInterface
|
||||
public static function getStartDateTimeByType(string $type, DateTimeImmutable $requested): \DateTimeInterface
|
||||
{
|
||||
switch ($type) {
|
||||
case static::MONTH:
|
||||
|
@ -119,7 +122,7 @@ public static function getStartDateTimeByType(string $type, \DateTimeImmutable $
|
|||
case static::DAY:
|
||||
return $requested->modify('Today midnight');
|
||||
default:
|
||||
throw new \Exception('Unsupported daily format type');
|
||||
throw new Exception('Unsupported daily format type');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,14 +132,14 @@ public static function getStartDateTimeByType(string $type, \DateTimeImmutable $
|
|||
* and we don't want to alter original datetime.
|
||||
*
|
||||
* @param string $type month/week/day
|
||||
* @param \DateTimeImmutable $requested DateTime extracted from request input
|
||||
* @param DateTimeImmutable $requested DateTime extracted from request input
|
||||
* (should come from extractRequestedDateTime)
|
||||
*
|
||||
* @return \DateTimeInterface Last DateTime of the time period
|
||||
*
|
||||
* @throws \Exception Type not supported.
|
||||
* @throws Exception Type not supported.
|
||||
*/
|
||||
public static function getEndDateTimeByType(string $type, \DateTimeImmutable $requested): \DateTimeInterface
|
||||
public static function getEndDateTimeByType(string $type, DateTimeImmutable $requested): \DateTimeInterface
|
||||
{
|
||||
switch ($type) {
|
||||
case static::MONTH:
|
||||
|
@ -146,7 +149,7 @@ public static function getEndDateTimeByType(string $type, \DateTimeImmutable $re
|
|||
case static::DAY:
|
||||
return $requested->modify('Today 23:59:59');
|
||||
default:
|
||||
throw new \Exception('Unsupported daily format type');
|
||||
throw new Exception('Unsupported daily format type');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -154,16 +157,20 @@ public static function getEndDateTimeByType(string $type, \DateTimeImmutable $re
|
|||
* Get localized description of the time period depending on given datetime and type.
|
||||
* Example: for a month period, it returns `October, 2020`.
|
||||
*
|
||||
* @param string $type month/week/day
|
||||
* @param \DateTimeImmutable $requested DateTime extracted from request input
|
||||
* (should come from extractRequestedDateTime)
|
||||
* @param string $type month/week/day
|
||||
* @param \DateTimeImmutable $requested DateTime extracted from request input
|
||||
* (should come from extractRequestedDateTime)
|
||||
* @param bool $includeRelative Include relative date description (today, yesterday, etc.)
|
||||
*
|
||||
* @return string Localized time period description
|
||||
*
|
||||
* @throws \Exception Type not supported.
|
||||
* @throws Exception Type not supported.
|
||||
*/
|
||||
public static function getDescriptionByType(string $type, \DateTimeImmutable $requested): string
|
||||
{
|
||||
public static function getDescriptionByType(
|
||||
string $type,
|
||||
\DateTimeImmutable $requested,
|
||||
bool $includeRelative = true
|
||||
): string {
|
||||
switch ($type) {
|
||||
case static::MONTH:
|
||||
return $requested->format('F') . ', ' . $requested->format('Y');
|
||||
|
@ -172,14 +179,14 @@ public static function getDescriptionByType(string $type, \DateTimeImmutable $re
|
|||
return t('Week') . ' ' . $requested->format('W') . ' (' . format_date($requested, false) . ')';
|
||||
case static::DAY:
|
||||
$out = '';
|
||||
if ($requested->format('Ymd') === date('Ymd')) {
|
||||
if ($includeRelative && $requested->format('Ymd') === date('Ymd')) {
|
||||
$out = t('Today') . ' - ';
|
||||
} elseif ($requested->format('Ymd') === date('Ymd', strtotime('-1 days'))) {
|
||||
} elseif ($includeRelative && $requested->format('Ymd') === date('Ymd', strtotime('-1 days'))) {
|
||||
$out = t('Yesterday') . ' - ';
|
||||
}
|
||||
return $out . format_date($requested, false);
|
||||
default:
|
||||
throw new \Exception('Unsupported daily format type');
|
||||
throw new Exception('Unsupported daily format type');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -190,7 +197,7 @@ public static function getDescriptionByType(string $type, \DateTimeImmutable $re
|
|||
*
|
||||
* @return int number of elements
|
||||
*
|
||||
* @throws \Exception Type not supported.
|
||||
* @throws Exception Type not supported.
|
||||
*/
|
||||
public static function getRssLengthByType(string $type): int
|
||||
{
|
||||
|
@ -202,7 +209,28 @@ public static function getRssLengthByType(string $type): int
|
|||
case static::DAY:
|
||||
return 30; // ~1 month
|
||||
default:
|
||||
throw new \Exception('Unsupported daily format type');
|
||||
throw new Exception('Unsupported daily format type');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of items to display in the RSS feed depending on the given type.
|
||||
*
|
||||
* @param string $type month/week/day
|
||||
* @param ?DateTimeImmutable $requested Currently only used for UT
|
||||
*
|
||||
* @return DatePeriod number of elements
|
||||
*
|
||||
* @throws Exception Type not supported.
|
||||
*/
|
||||
public static function getCacheDatePeriodByType(string $type, DateTimeImmutable $requested = null): DatePeriod
|
||||
{
|
||||
$requested = $requested ?? new DateTimeImmutable();
|
||||
|
||||
return new DatePeriod(
|
||||
static::getStartDateTimeByType($type, $requested),
|
||||
new \DateInterval('P1D'),
|
||||
static::getEndDateTimeByType($type, $requested)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,10 +60,15 @@ public function retrieve(string $url): array
|
|||
$title = mb_convert_encoding($title, 'utf-8', $charset);
|
||||
}
|
||||
|
||||
return [
|
||||
return array_map([$this, 'cleanMetadata'], [
|
||||
'title' => $title,
|
||||
'description' => $description,
|
||||
'tags' => $tags,
|
||||
];
|
||||
]);
|
||||
}
|
||||
|
||||
protected function cleanMetadata($data): ?string
|
||||
{
|
||||
return !is_string($data) || empty(trim($data)) ? null : trim($data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ public function filterAndFormat(
|
|||
}
|
||||
|
||||
$bookmarkLinks = [];
|
||||
foreach ($this->bookmarkService->search([], $selection) as $bookmark) {
|
||||
foreach ($this->bookmarkService->search([], $selection)->getBookmarks() as $bookmark) {
|
||||
$link = $formatter->format($bookmark);
|
||||
$link['taglist'] = implode(',', $bookmark->getTags());
|
||||
if ($bookmark->isNote() && $prependNoteUrl) {
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
|
||||
namespace Shaarli\Plugin;
|
||||
|
||||
use Shaarli\Bookmark\Bookmark;
|
||||
use Shaarli\Config\ConfigManager;
|
||||
use Shaarli\Plugin\Exception\PluginFileNotFoundException;
|
||||
use Shaarli\Plugin\Exception\PluginInvalidRouteException;
|
||||
|
||||
/**
|
||||
* Class PluginManager
|
||||
|
@ -26,6 +28,14 @@ class PluginManager
|
|||
*/
|
||||
private $loadedPlugins = [];
|
||||
|
||||
/** @var array List of registered routes. Contains keys:
|
||||
* - `method`: HTTP method, GET/POST/PUT/PATCH/DELETE
|
||||
* - `route` (path): without prefix, e.g. `/up/{variable}`
|
||||
* It will be later prefixed by `/plugin/<plugin name>/`.
|
||||
* - `callable` string, function name or FQN class's method, e.g. `demo_plugin_custom_controller`.
|
||||
*/
|
||||
protected $registeredRoutes = [];
|
||||
|
||||
/**
|
||||
* @var ConfigManager Configuration Manager instance.
|
||||
*/
|
||||
|
@ -36,6 +46,9 @@ class PluginManager
|
|||
*/
|
||||
protected $errors;
|
||||
|
||||
/** @var callable[]|null Preloaded list of hook function for filterSearchEntry() */
|
||||
protected $filterSearchEntryHooks = null;
|
||||
|
||||
/**
|
||||
* Plugins subdirectory.
|
||||
*
|
||||
|
@ -86,6 +99,9 @@ public function load($authorizedPlugins)
|
|||
$this->loadPlugin($dirs[$index], $plugin);
|
||||
} catch (PluginFileNotFoundException $e) {
|
||||
error_log($e->getMessage());
|
||||
} catch (\Throwable $e) {
|
||||
$error = $plugin . t(' [plugin incompatibility]: ') . $e->getMessage();
|
||||
$this->errors = array_unique(array_merge($this->errors, [$error]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -166,6 +182,22 @@ private function loadPlugin($dir, $pluginName)
|
|||
}
|
||||
}
|
||||
|
||||
$registerRouteFunction = $pluginName . '_register_routes';
|
||||
$routes = null;
|
||||
if (function_exists($registerRouteFunction)) {
|
||||
$routes = call_user_func($registerRouteFunction);
|
||||
}
|
||||
|
||||
if ($routes !== null) {
|
||||
foreach ($routes as $route) {
|
||||
if (static::validateRouteRegistration($route)) {
|
||||
$this->registeredRoutes[$pluginName][] = $route;
|
||||
} else {
|
||||
throw new PluginInvalidRouteException($pluginName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->loadedPlugins[] = $pluginName;
|
||||
}
|
||||
|
||||
|
@ -237,6 +269,22 @@ public function getPluginsMeta()
|
|||
return $metaData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array List of registered custom routes by plugins.
|
||||
*/
|
||||
public function getRegisteredRoutes(): array
|
||||
{
|
||||
return $this->registeredRoutes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array List of registered filter_search_entry hooks
|
||||
*/
|
||||
public function getFilterSearchEntryHooks(): ?array
|
||||
{
|
||||
return $this->filterSearchEntryHooks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of encountered errors.
|
||||
*
|
||||
|
@ -246,4 +294,76 @@ public function getErrors()
|
|||
{
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply additional filter on every search result of BookmarkFilter calling plugins hooks.
|
||||
*
|
||||
* @param Bookmark $bookmark To check.
|
||||
* @param array $context Additional info about search context, depends on the search source.
|
||||
*
|
||||
* @return bool True if the result must be kept in search results, false otherwise.
|
||||
*/
|
||||
public function filterSearchEntry(Bookmark $bookmark, array $context): bool
|
||||
{
|
||||
if ($this->filterSearchEntryHooks === null) {
|
||||
$this->loadFilterSearchEntryHooks();
|
||||
}
|
||||
|
||||
if ($this->filterSearchEntryHooks === []) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($this->filterSearchEntryHooks as $filterSearchEntryHook) {
|
||||
if ($filterSearchEntryHook($bookmark, $context) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* filterSearchEntry() method will be called for every search result,
|
||||
* so for performances we preload existing functions to invoke them directly.
|
||||
*/
|
||||
protected function loadFilterSearchEntryHooks(): void
|
||||
{
|
||||
$this->filterSearchEntryHooks = [];
|
||||
|
||||
foreach ($this->loadedPlugins as $plugin) {
|
||||
$hookFunction = $this->buildHookName('filter_search_entry', $plugin);
|
||||
|
||||
if (function_exists($hookFunction)) {
|
||||
$this->filterSearchEntryHooks[] = $hookFunction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether provided input is valid to register a new route.
|
||||
* It must contain keys `method`, `route`, `callable` (all strings).
|
||||
*
|
||||
* @param string[] $input
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected static function validateRouteRegistration(array $input): bool
|
||||
{
|
||||
if (
|
||||
!array_key_exists('method', $input)
|
||||
|| !in_array(strtoupper($input['method']), ['GET', 'PUT', 'PATCH', 'POST', 'DELETE'])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!array_key_exists('route', $input) || !preg_match('#^[a-z\d/\.\-_]+$#', $input['route'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!array_key_exists('callable', $input)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
26
application/plugin/exception/PluginInvalidRouteException.php
Normal file
26
application/plugin/exception/PluginInvalidRouteException.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shaarli\Plugin\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Class PluginFileNotFoundException
|
||||
*
|
||||
* Raise when plugin files can't be found.
|
||||
*/
|
||||
class PluginInvalidRouteException extends Exception
|
||||
{
|
||||
/**
|
||||
* Construct exception with plugin name.
|
||||
* Generate message.
|
||||
*
|
||||
* @param string $pluginName name of the plugin not found
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->message = 'trying to register invalid route.';
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Shaarli\Render;
|
||||
|
||||
use DatePeriod;
|
||||
use Shaarli\Feed\CachedPage;
|
||||
|
||||
/**
|
||||
|
@ -49,12 +50,21 @@ public function invalidateCaches(): void
|
|||
$this->purgeCachedPages();
|
||||
}
|
||||
|
||||
public function getCachePage(string $pageUrl): CachedPage
|
||||
/**
|
||||
* Get CachedPage instance for provided URL.
|
||||
*
|
||||
* @param string $pageUrl
|
||||
* @param ?DatePeriod $validityPeriod Optionally specify a time limit on requested cache
|
||||
*
|
||||
* @return CachedPage
|
||||
*/
|
||||
public function getCachePage(string $pageUrl, DatePeriod $validityPeriod = null): CachedPage
|
||||
{
|
||||
return new CachedPage(
|
||||
$this->pageCacheDir,
|
||||
$pageUrl,
|
||||
false === $this->isLoggedIn
|
||||
false === $this->isLoggedIn,
|
||||
$validityPeriod
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -152,7 +152,7 @@ public function updateMethodMigrateExistingNotesUrl(): bool
|
|||
{
|
||||
$updated = false;
|
||||
|
||||
foreach ($this->bookmarkService->search() as $bookmark) {
|
||||
foreach ($this->bookmarkService->search()->getBookmarks() as $bookmark) {
|
||||
if (
|
||||
$bookmark->isNote()
|
||||
&& startsWith($bookmark->getUrl(), '?')
|
||||
|
|
|
@ -4,7 +4,11 @@ const sendBookmarkForm = (basePath, formElement) => {
|
|||
|
||||
const formData = new FormData();
|
||||
[...inputs].forEach((input) => {
|
||||
formData.append(input.getAttribute('name'), input.value);
|
||||
if (input.getAttribute('type') === 'checkbox') {
|
||||
formData.append(input.getAttribute('name'), input.checked);
|
||||
} else {
|
||||
formData.append(input.getAttribute('name'), input.value);
|
||||
}
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
@ -26,9 +30,9 @@ const sendBookmarkForm = (basePath, formElement) => {
|
|||
const sendBookmarkDelete = (buttonElement, formElement) => (
|
||||
new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', buttonElement.href);
|
||||
xhr.open('GET', `${buttonElement.href}&source=batch`);
|
||||
xhr.onload = () => {
|
||||
if (xhr.status !== 200) {
|
||||
if (xhr.status !== 204) {
|
||||
alert(`An error occurred. Return code: ${xhr.status}`);
|
||||
reject();
|
||||
} else {
|
||||
|
@ -100,7 +104,7 @@ const redirectIfEmptyBatch = (basePath, formElements, path) => {
|
|||
});
|
||||
|
||||
Promise.all(promises).then(() => {
|
||||
window.location.href = basePath || '/';
|
||||
window.location.href = `${basePath}/`;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
12
composer.lock
generated
12
composer.lock
generated
|
@ -8,16 +8,16 @@
|
|||
"packages": [
|
||||
{
|
||||
"name": "arthurhoaro/web-thumbnailer",
|
||||
"version": "v2.0.3",
|
||||
"version": "v2.0.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ArthurHoaro/web-thumbnailer.git",
|
||||
"reference": "39bfd4f3136d9e6096496b9720e877326cfe4775"
|
||||
"reference": "7780ddc0f44fccdce6cddb86d1db0354810290d0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ArthurHoaro/web-thumbnailer/zipball/39bfd4f3136d9e6096496b9720e877326cfe4775",
|
||||
"reference": "39bfd4f3136d9e6096496b9720e877326cfe4775",
|
||||
"url": "https://api.github.com/repos/ArthurHoaro/web-thumbnailer/zipball/7780ddc0f44fccdce6cddb86d1db0354810290d0",
|
||||
"reference": "7780ddc0f44fccdce6cddb86d1db0354810290d0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -53,9 +53,9 @@
|
|||
"description": "PHP library which will retrieve a thumbnail for any given URL",
|
||||
"support": {
|
||||
"issues": "https://github.com/ArthurHoaro/web-thumbnailer/issues",
|
||||
"source": "https://github.com/ArthurHoaro/web-thumbnailer/tree/v2.0.3"
|
||||
"source": "https://github.com/ArthurHoaro/web-thumbnailer/tree/v2.0.4"
|
||||
},
|
||||
"time": "2020-09-29T15:51:03+00:00"
|
||||
"time": "2021-02-22T10:43:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "erusev/parsedown",
|
||||
|
|
|
@ -194,7 +194,7 @@ $ docker logs -f <container-name-or-first-letters-of-id>
|
|||
# delete unused images to free up disk space
|
||||
$ docker system prune --images
|
||||
# delete unused volumes to free up disk space (CAUTION all data in unused volumes will be lost)
|
||||
$ docker system prunt --volumes
|
||||
$ docker system prune --volumes
|
||||
# delete unused containers
|
||||
$ docker system prune
|
||||
```
|
||||
|
|
|
@ -73,7 +73,7 @@ var_dump(getInfo($baseUrl, $secret));
|
|||
### Authentication
|
||||
|
||||
- All requests to Shaarli's API must include a **JWT token** to verify their authenticity.
|
||||
- This token must be included as an HTTP header called `Authentication: Bearer <jwt token>`.
|
||||
- This token must be included as an HTTP header called `Authorization: Bearer <jwt token>`.
|
||||
- JWT tokens are composed by three parts, separated by a dot `.` and encoded in base64:
|
||||
|
||||
```
|
||||
|
|
|
@ -199,6 +199,8 @@ sudo nano /etc/apache2/sites-available/shaarli.mydomain.org.conf
|
|||
Require all denied
|
||||
</FilesMatch>
|
||||
|
||||
DirectoryIndex index.php
|
||||
|
||||
<Files "index.php">
|
||||
Require all granted
|
||||
</Files>
|
||||
|
|
|
@ -27,7 +27,6 @@ You should have the following tree view:
|
|||
| |---| demo_plugin.php
|
||||
```
|
||||
|
||||
|
||||
### Plugin initialization
|
||||
|
||||
At the beginning of Shaarli execution, all enabled plugins are loaded. At this point, the plugin system looks for an `init()` function in the <plugin_name>.php to execute and run it if it exists. This function must be named this way, and takes the `ConfigManager` as parameter.
|
||||
|
@ -139,6 +138,31 @@ Each file contain two keys:
|
|||
|
||||
> Note: In PHP, `parse_ini_file()` seems to want strings to be between by quotes `"` in the ini file.
|
||||
|
||||
### Register plugin's routes
|
||||
|
||||
Shaarli lets you register custom Slim routes for your plugin.
|
||||
|
||||
To register a route, the plugin must include a function called `function <plugin_name>_register_routes(): array`.
|
||||
|
||||
This method must return an array of routes, each entry must contain the following keys:
|
||||
|
||||
- `method`: HTTP method, `GET/POST/PUT/PATCH/DELETE`
|
||||
- `route` (path): without prefix, e.g. `/up/{variable}`
|
||||
It will be later prefixed by `/plugin/<plugin name>/`.
|
||||
- `callable` string, function name or FQN class's method to execute, e.g. `demo_plugin_custom_controller`.
|
||||
|
||||
Callable functions or methods must have `Slim\Http\Request` and `Slim\Http\Response` parameters
|
||||
and return a `Slim\Http\Response`. We recommend creating a dedicated class and extend either
|
||||
`ShaarliVisitorController` or `ShaarliAdminController` to use helper functions they provide.
|
||||
|
||||
A dedicated plugin template is available for rendering content: `pluginscontent.html` using `content` placeholder.
|
||||
|
||||
> **Warning**: plugins are not able to use RainTPL template engine for their content due to technical restrictions.
|
||||
> RainTPL does not allow to register multiple template folders, so all HTML rendering must be done within plugin
|
||||
> custom controller.
|
||||
|
||||
Check out the `demo_plugin` for a live example: `GET <shaarli_url>/plugin/demo_plugin/custom`.
|
||||
|
||||
### Understanding relative paths
|
||||
|
||||
Because Shaarli is a self-hosted tool, an instance can either be installed at the root directory, or under a subfolder.
|
||||
|
@ -184,6 +208,7 @@ If it's still not working, please [open an issue](https://github.com/shaarli/Sha
|
|||
| [save_link](#save_link) | Allow to alter the link being saved in the datastore. |
|
||||
| [delete_link](#delete_link) | Allow to do an action before a link is deleted from the datastore. |
|
||||
| [save_plugin_parameters](#save_plugin_parameters) | Allow to manipulate plugin parameters before they're saved. |
|
||||
| [filter_search_entry](#filter_search_entry) | Add custom filters to Shaarli search engine |
|
||||
|
||||
|
||||
#### render_header
|
||||
|
@ -540,6 +565,23 @@ the array will contain an entry with `MYPLUGIN_PARAMETER` as a key.
|
|||
|
||||
Also [special data](#special-data).
|
||||
|
||||
#### filter_search_entry
|
||||
|
||||
Triggered for *every* bookmark when Shaarli's BookmarkService method `search()` is used.
|
||||
Any custom filter can be added to filter out bookmarks from search results.
|
||||
|
||||
The hook **must** return either:
|
||||
- `true` to keep bookmark entry in search result set
|
||||
- `false` to discard bookmark entry in result set
|
||||
|
||||
> Note: custom filters are called *before* default filters are applied.
|
||||
|
||||
##### Parameters
|
||||
|
||||
- `Shaarli\Bookmark\Bookmark` object: entry to evaluate
|
||||
- $context `array`: additional information provided depending on what search is currently used,
|
||||
the user request, etc.
|
||||
|
||||
## Guide for template designers
|
||||
|
||||
### Plugin administration
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,8 +1,8 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Shaarli\n"
|
||||
"POT-Creation-Date: 2020-11-09 14:39+0100\n"
|
||||
"PO-Revision-Date: 2020-11-09 14:42+0100\n"
|
||||
"POT-Creation-Date: 2020-11-24 13:13+0100\n"
|
||||
"PO-Revision-Date: 2020-11-24 13:14+0100\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Shaarli\n"
|
||||
"Language: fr_FR\n"
|
||||
|
@ -20,31 +20,31 @@ msgstr ""
|
|||
"X-Poedit-SearchPath-3: init.php\n"
|
||||
"X-Poedit-SearchPath-4: plugins\n"
|
||||
|
||||
#: application/History.php:180
|
||||
#: application/History.php:181
|
||||
msgid "History file isn't readable or writable"
|
||||
msgstr "Le fichier d'historique n'est pas accessible en lecture ou en écriture"
|
||||
|
||||
#: application/History.php:191
|
||||
#: application/History.php:192
|
||||
msgid "Could not parse history file"
|
||||
msgstr "Format incorrect pour le fichier d'historique"
|
||||
|
||||
#: application/Languages.php:181
|
||||
#: application/Languages.php:184
|
||||
msgid "Automatic"
|
||||
msgstr "Automatique"
|
||||
|
||||
#: application/Languages.php:182
|
||||
#: application/Languages.php:185
|
||||
msgid "German"
|
||||
msgstr "Allemand"
|
||||
|
||||
#: application/Languages.php:183
|
||||
#: application/Languages.php:186
|
||||
msgid "English"
|
||||
msgstr "Anglais"
|
||||
|
||||
#: application/Languages.php:184
|
||||
#: application/Languages.php:187
|
||||
msgid "French"
|
||||
msgstr "Français"
|
||||
|
||||
#: application/Languages.php:185
|
||||
#: application/Languages.php:188
|
||||
msgid "Japanese"
|
||||
msgstr "Japonais"
|
||||
|
||||
|
@ -56,46 +56,46 @@ msgstr ""
|
|||
"l'extension php-gd doit être chargée pour utiliser les miniatures. Les "
|
||||
"miniatures sont désormais désactivées. Rechargez la page."
|
||||
|
||||
#: application/Utils.php:402
|
||||
#: application/Utils.php:405
|
||||
msgid "Setting not set"
|
||||
msgstr "Paramètre non défini"
|
||||
|
||||
#: application/Utils.php:409
|
||||
#: application/Utils.php:412
|
||||
msgid "Unlimited"
|
||||
msgstr "Illimité"
|
||||
|
||||
#: application/Utils.php:412
|
||||
#: application/Utils.php:415
|
||||
msgid "B"
|
||||
msgstr "o"
|
||||
|
||||
#: application/Utils.php:412
|
||||
#: application/Utils.php:415
|
||||
msgid "kiB"
|
||||
msgstr "ko"
|
||||
|
||||
#: application/Utils.php:412
|
||||
#: application/Utils.php:415
|
||||
msgid "MiB"
|
||||
msgstr "Mo"
|
||||
|
||||
#: application/Utils.php:412
|
||||
#: application/Utils.php:415
|
||||
msgid "GiB"
|
||||
msgstr "Go"
|
||||
|
||||
#: application/bookmark/BookmarkFileService.php:183
|
||||
#: application/bookmark/BookmarkFileService.php:205
|
||||
#: application/bookmark/BookmarkFileService.php:227
|
||||
#: application/bookmark/BookmarkFileService.php:241
|
||||
#: application/bookmark/BookmarkFileService.php:185
|
||||
#: application/bookmark/BookmarkFileService.php:207
|
||||
#: application/bookmark/BookmarkFileService.php:229
|
||||
#: application/bookmark/BookmarkFileService.php:243
|
||||
msgid "You're not authorized to alter the datastore"
|
||||
msgstr "Vous n'êtes pas autorisé à modifier les données"
|
||||
|
||||
#: application/bookmark/BookmarkFileService.php:208
|
||||
#: application/bookmark/BookmarkFileService.php:210
|
||||
msgid "This bookmarks already exists"
|
||||
msgstr "Ce marque-page existe déjà"
|
||||
|
||||
#: application/bookmark/BookmarkInitializer.php:39
|
||||
#: application/bookmark/BookmarkInitializer.php:42
|
||||
msgid "(private bookmark with thumbnail demo)"
|
||||
msgstr "(marque page privé avec une miniature)"
|
||||
|
||||
#: application/bookmark/BookmarkInitializer.php:42
|
||||
#: application/bookmark/BookmarkInitializer.php:45
|
||||
msgid ""
|
||||
"Shaarli will automatically pick up the thumbnail for links to a variety of "
|
||||
"websites.\n"
|
||||
|
@ -118,11 +118,11 @@ msgstr ""
|
|||
"\n"
|
||||
"Maintenant, vous pouvez modifier ou supprimer les shaares créés par défaut.\n"
|
||||
|
||||
#: application/bookmark/BookmarkInitializer.php:55
|
||||
#: application/bookmark/BookmarkInitializer.php:58
|
||||
msgid "Note: Shaare descriptions"
|
||||
msgstr "Note : Description des Shaares"
|
||||
|
||||
#: application/bookmark/BookmarkInitializer.php:57
|
||||
#: application/bookmark/BookmarkInitializer.php:60
|
||||
msgid ""
|
||||
"Adding a shaare without entering a URL creates a text-only \"note\" post "
|
||||
"such as this one.\n"
|
||||
|
@ -186,7 +186,7 @@ msgstr ""
|
|||
"| Citron | Fruit | Jaune | 30 |\n"
|
||||
"| Carotte | Légume | Orange | 14 |\n"
|
||||
|
||||
#: application/bookmark/BookmarkInitializer.php:91
|
||||
#: application/bookmark/BookmarkInitializer.php:94
|
||||
#: application/legacy/LegacyLinkDB.php:246
|
||||
#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
|
||||
#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
|
||||
|
@ -198,7 +198,7 @@ msgstr ""
|
|||
"Le gestionnaire de marque-pages personnel, minimaliste, et sans base de "
|
||||
"données"
|
||||
|
||||
#: application/bookmark/BookmarkInitializer.php:94
|
||||
#: application/bookmark/BookmarkInitializer.php:97
|
||||
msgid ""
|
||||
"Welcome to Shaarli!\n"
|
||||
"\n"
|
||||
|
@ -247,11 +247,11 @@ msgstr ""
|
|||
"issues) si vous avez une suggestion ou si vous rencontrez un problème.\n"
|
||||
" \n"
|
||||
|
||||
#: application/bookmark/exception/BookmarkNotFoundException.php:13
|
||||
#: application/bookmark/exception/BookmarkNotFoundException.php:14
|
||||
msgid "The link you are trying to reach does not exist or has been deleted."
|
||||
msgstr "Le lien que vous essayez de consulter n'existe pas ou a été supprimé."
|
||||
|
||||
#: application/config/ConfigJson.php:52 application/config/ConfigPhp.php:129
|
||||
#: application/config/ConfigJson.php:52 application/config/ConfigPhp.php:131
|
||||
msgid ""
|
||||
"Shaarli could not create the config file. Please make sure Shaarli has the "
|
||||
"right to write in the folder is it installed in."
|
||||
|
@ -259,12 +259,12 @@ msgstr ""
|
|||
"Shaarli n'a pas pu créer le fichier de configuration. Merci de vérifier que "
|
||||
"Shaarli a les droits d'écriture dans le dossier dans lequel il est installé."
|
||||
|
||||
#: application/config/ConfigManager.php:136
|
||||
#: application/config/ConfigManager.php:163
|
||||
#: application/config/ConfigManager.php:137
|
||||
#: application/config/ConfigManager.php:164
|
||||
msgid "Invalid setting key parameter. String expected, got: "
|
||||
msgstr "Clé de paramétrage invalide. Chaîne de caractères obtenue, attendu : "
|
||||
|
||||
#: application/config/exception/MissingFieldConfigException.php:21
|
||||
#: application/config/exception/MissingFieldConfigException.php:20
|
||||
#, php-format
|
||||
msgid "Configuration value is required for %s"
|
||||
msgstr "Le paramètre %s est obligatoire"
|
||||
|
@ -274,48 +274,48 @@ msgid "An error occurred while trying to save plugins loading order."
|
|||
msgstr ""
|
||||
"Une erreur s'est produite lors de la sauvegarde de l'ordre des extensions."
|
||||
|
||||
#: application/config/exception/UnauthorizedConfigException.php:16
|
||||
#: application/config/exception/UnauthorizedConfigException.php:15
|
||||
msgid "You are not authorized to alter config."
|
||||
msgstr "Vous n'êtes pas autorisé à modifier la configuration."
|
||||
|
||||
#: application/exceptions/IOException.php:22
|
||||
#: application/exceptions/IOException.php:23
|
||||
msgid "Error accessing"
|
||||
msgstr "Une erreur s'est produite en accédant à"
|
||||
|
||||
#: application/feed/FeedBuilder.php:179
|
||||
#: application/feed/FeedBuilder.php:180
|
||||
msgid "Direct link"
|
||||
msgstr "Liens directs"
|
||||
|
||||
#: application/feed/FeedBuilder.php:181
|
||||
#: application/feed/FeedBuilder.php:182
|
||||
#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:103
|
||||
#: tmp/dailyrss.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26
|
||||
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:179
|
||||
msgid "Permalink"
|
||||
msgstr "Permalien"
|
||||
|
||||
#: application/front/controller/admin/ConfigureController.php:54
|
||||
#: application/front/controller/admin/ConfigureController.php:56
|
||||
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24
|
||||
msgid "Configure"
|
||||
msgstr "Configurer"
|
||||
|
||||
#: application/front/controller/admin/ConfigureController.php:102
|
||||
#: application/legacy/LegacyUpdater.php:537
|
||||
#: application/front/controller/admin/ConfigureController.php:106
|
||||
#: application/legacy/LegacyUpdater.php:539
|
||||
msgid "You have enabled or changed thumbnails mode."
|
||||
msgstr "Vous avez activé ou changé le mode de miniatures."
|
||||
|
||||
#: application/front/controller/admin/ConfigureController.php:103
|
||||
#: application/front/controller/admin/ServerController.php:75
|
||||
#: application/legacy/LegacyUpdater.php:538
|
||||
#: application/front/controller/admin/ConfigureController.php:108
|
||||
#: application/front/controller/admin/ServerController.php:76
|
||||
#: application/legacy/LegacyUpdater.php:540
|
||||
msgid "Please synchronize them."
|
||||
msgstr "Merci de les synchroniser."
|
||||
|
||||
#: application/front/controller/admin/ConfigureController.php:113
|
||||
#: application/front/controller/visitor/InstallController.php:146
|
||||
#: application/front/controller/admin/ConfigureController.php:119
|
||||
#: application/front/controller/visitor/InstallController.php:149
|
||||
msgid "Error while writing config file after configuration update."
|
||||
msgstr ""
|
||||
"Une erreur s'est produite lors de la sauvegarde du fichier de configuration."
|
||||
|
||||
#: application/front/controller/admin/ConfigureController.php:122
|
||||
#: application/front/controller/admin/ConfigureController.php:128
|
||||
msgid "Configuration was saved."
|
||||
msgstr "La configuration a été sauvegardée."
|
||||
|
||||
|
@ -433,7 +433,7 @@ msgstr "Administration serveur"
|
|||
msgid "Thumbnails cache has been cleared."
|
||||
msgstr "Le cache des miniatures a été vidé."
|
||||
|
||||
#: application/front/controller/admin/ServerController.php:83
|
||||
#: application/front/controller/admin/ServerController.php:85
|
||||
msgid "Shaarli's cache folder has been cleared!"
|
||||
msgstr "Le dossier de cache de Shaarli a été vidé !"
|
||||
|
||||
|
@ -459,18 +459,18 @@ msgstr "Le lien avec l'identifiant %s n'a pas pu être trouvé."
|
|||
msgid "Invalid visibility provided."
|
||||
msgstr "Visibilité du lien non valide."
|
||||
|
||||
#: application/front/controller/admin/ShaarePublishController.php:171
|
||||
#: application/front/controller/admin/ShaarePublishController.php:173
|
||||
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:171
|
||||
msgid "Edit"
|
||||
msgstr "Modifier"
|
||||
|
||||
#: application/front/controller/admin/ShaarePublishController.php:174
|
||||
#: application/front/controller/admin/ShaarePublishController.php:176
|
||||
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
|
||||
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:28
|
||||
msgid "Shaare"
|
||||
msgstr "Shaare"
|
||||
|
||||
#: application/front/controller/admin/ShaarePublishController.php:205
|
||||
#: application/front/controller/admin/ShaarePublishController.php:208
|
||||
msgid "Note: "
|
||||
msgstr "Note : "
|
||||
|
||||
|
@ -485,7 +485,7 @@ msgstr "Mise à jour des miniatures"
|
|||
msgid "Tools"
|
||||
msgstr "Outils"
|
||||
|
||||
#: application/front/controller/visitor/BookmarkListController.php:120
|
||||
#: application/front/controller/visitor/BookmarkListController.php:121
|
||||
msgid "Search: "
|
||||
msgstr "Recherche : "
|
||||
|
||||
|
@ -535,12 +535,12 @@ msgstr "Une erreur inattendue s'est produite."
|
|||
msgid "Requested page could not be found."
|
||||
msgstr "La page demandée n'a pas pu être trouvée."
|
||||
|
||||
#: application/front/controller/visitor/InstallController.php:64
|
||||
#: application/front/controller/visitor/InstallController.php:65
|
||||
#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22
|
||||
msgid "Install Shaarli"
|
||||
msgstr "Installation de Shaarli"
|
||||
|
||||
#: application/front/controller/visitor/InstallController.php:83
|
||||
#: application/front/controller/visitor/InstallController.php:85
|
||||
#, php-format
|
||||
msgid ""
|
||||
"<pre>Sessions do not seem to work correctly on your server.<br>Make sure the "
|
||||
|
@ -559,14 +559,14 @@ msgstr ""
|
|||
"des cookies. Nous vous recommandons d'accéder à votre serveur depuis son "
|
||||
"adresse IP ou un <em>Fully Qualified Domain Name</em>.<br>"
|
||||
|
||||
#: application/front/controller/visitor/InstallController.php:154
|
||||
#: application/front/controller/visitor/InstallController.php:157
|
||||
msgid ""
|
||||
"Shaarli is now configured. Please login and start shaaring your bookmarks!"
|
||||
msgstr ""
|
||||
"Shaarli est maintenant configuré. Vous pouvez vous connecter et commencez à "
|
||||
"shaare vos liens !"
|
||||
|
||||
#: application/front/controller/visitor/InstallController.php:168
|
||||
#: application/front/controller/visitor/InstallController.php:171
|
||||
msgid "Insufficient permissions:"
|
||||
msgstr "Permissions insuffisantes :"
|
||||
|
||||
|
@ -580,7 +580,7 @@ msgstr "Permissions insuffisantes :"
|
|||
msgid "Login"
|
||||
msgstr "Connexion"
|
||||
|
||||
#: application/front/controller/visitor/LoginController.php:77
|
||||
#: application/front/controller/visitor/LoginController.php:78
|
||||
msgid "Wrong login/password."
|
||||
msgstr "Nom d'utilisateur ou mot de passe incorrect(s)."
|
||||
|
||||
|
@ -620,7 +620,7 @@ msgstr ""
|
|||
msgid "Wrong token."
|
||||
msgstr "Jeton invalide."
|
||||
|
||||
#: application/helper/ApplicationUtils.php:162
|
||||
#: application/helper/ApplicationUtils.php:165
|
||||
#, php-format
|
||||
msgid ""
|
||||
"Your PHP version is obsolete! Shaarli requires at least PHP %s, and thus "
|
||||
|
@ -631,52 +631,60 @@ msgstr ""
|
|||
"peut donc pas fonctionner. Votre version de PHP a des failles de sécurités "
|
||||
"connues et devrait être mise à jour au plus tôt."
|
||||
|
||||
#: application/helper/ApplicationUtils.php:195
|
||||
#: application/helper/ApplicationUtils.php:215
|
||||
#: application/helper/ApplicationUtils.php:200
|
||||
#: application/helper/ApplicationUtils.php:220
|
||||
msgid "directory is not readable"
|
||||
msgstr "le répertoire n'est pas accessible en lecture"
|
||||
|
||||
#: application/helper/ApplicationUtils.php:218
|
||||
#: application/helper/ApplicationUtils.php:223
|
||||
msgid "directory is not writable"
|
||||
msgstr "le répertoire n'est pas accessible en écriture"
|
||||
|
||||
#: application/helper/ApplicationUtils.php:240
|
||||
#: application/helper/ApplicationUtils.php:247
|
||||
msgid "file is not readable"
|
||||
msgstr "le fichier n'est pas accessible en lecture"
|
||||
|
||||
#: application/helper/ApplicationUtils.php:243
|
||||
#: application/helper/ApplicationUtils.php:250
|
||||
msgid "file is not writable"
|
||||
msgstr "le fichier n'est pas accessible en écriture"
|
||||
|
||||
#: application/helper/ApplicationUtils.php:277
|
||||
#: application/helper/ApplicationUtils.php:260
|
||||
msgid ""
|
||||
"Lock can not be acquired on the datastore. You might encounter concurrent "
|
||||
"access issues."
|
||||
msgstr ""
|
||||
"Le fichier datastore ne peut pas être verrouillé. Vous pourriez rencontrer "
|
||||
"des problèmes d'accès concurrents."
|
||||
|
||||
#: application/helper/ApplicationUtils.php:293
|
||||
msgid "Configuration parsing"
|
||||
msgstr "Chargement de la configuration"
|
||||
|
||||
#: application/helper/ApplicationUtils.php:278
|
||||
#: application/helper/ApplicationUtils.php:294
|
||||
msgid "Slim Framework (routing, etc.)"
|
||||
msgstr "Slim Framwork (routage, etc.)"
|
||||
|
||||
#: application/helper/ApplicationUtils.php:279
|
||||
#: application/helper/ApplicationUtils.php:295
|
||||
msgid "Multibyte (Unicode) string support"
|
||||
msgstr "Support des chaînes de caractère multibytes (Unicode)"
|
||||
|
||||
#: application/helper/ApplicationUtils.php:280
|
||||
#: application/helper/ApplicationUtils.php:296
|
||||
msgid "Required to use thumbnails"
|
||||
msgstr "Obligatoire pour utiliser les miniatures"
|
||||
|
||||
#: application/helper/ApplicationUtils.php:281
|
||||
#: application/helper/ApplicationUtils.php:297
|
||||
msgid "Localized text sorting (e.g. e->è->f)"
|
||||
msgstr "Tri des textes traduits (ex : e->è->f)"
|
||||
|
||||
#: application/helper/ApplicationUtils.php:282
|
||||
#: application/helper/ApplicationUtils.php:298
|
||||
msgid "Better retrieval of bookmark metadata and thumbnail"
|
||||
msgstr "Meilleure récupération des meta-données des marque-pages et minatures"
|
||||
|
||||
#: application/helper/ApplicationUtils.php:283
|
||||
#: application/helper/ApplicationUtils.php:299
|
||||
msgid "Use the translation system in gettext mode"
|
||||
msgstr "Utiliser le système de traduction en mode gettext"
|
||||
|
||||
#: application/helper/ApplicationUtils.php:284
|
||||
#: application/helper/ApplicationUtils.php:300
|
||||
msgid "Login using LDAP server"
|
||||
msgstr "Authentification via un serveur LDAP"
|
||||
|
||||
|
@ -750,7 +758,7 @@ msgstr ""
|
|||
msgid "Couldn't retrieve updater class methods."
|
||||
msgstr "Impossible de récupérer les méthodes de la classe Updater."
|
||||
|
||||
#: application/legacy/LegacyUpdater.php:538
|
||||
#: application/legacy/LegacyUpdater.php:540
|
||||
msgid "<a href=\"./admin/thumbnails\">"
|
||||
msgstr "<a href=\"./admin/thumbnails\">"
|
||||
|
||||
|
@ -776,11 +784,11 @@ msgstr ""
|
|||
"a été importé avec succès en %d secondes : %d liens importés, %d liens "
|
||||
"écrasés, %d liens ignorés."
|
||||
|
||||
#: application/plugin/PluginManager.php:124
|
||||
#: application/plugin/PluginManager.php:125
|
||||
msgid " [plugin incompatibility]: "
|
||||
msgstr " [incompatibilité de l'extension] : "
|
||||
|
||||
#: application/plugin/exception/PluginFileNotFoundException.php:21
|
||||
#: application/plugin/exception/PluginFileNotFoundException.php:22
|
||||
#, php-format
|
||||
msgid "Plugin \"%s\" files not found."
|
||||
msgstr "Les fichiers de l'extension \"%s\" sont introuvables."
|
||||
|
@ -794,7 +802,7 @@ msgstr "Impossible de purger %s : le répertoire n'existe pas"
|
|||
msgid "An error occurred while running the update "
|
||||
msgstr "Une erreur s'est produite lors de l'exécution de la mise à jour "
|
||||
|
||||
#: index.php:80
|
||||
#: index.php:81
|
||||
msgid "Shared bookmarks on "
|
||||
msgstr "Liens partagés sur "
|
||||
|
||||
|
@ -811,11 +819,11 @@ msgstr "Shaare"
|
|||
msgid "Adds the addlink input on the linklist page."
|
||||
msgstr "Ajoute le formulaire d'ajout de liens sur la page principale."
|
||||
|
||||
#: plugins/archiveorg/archiveorg.php:28
|
||||
#: plugins/archiveorg/archiveorg.php:29
|
||||
msgid "View on archive.org"
|
||||
msgstr "Voir sur archive.org"
|
||||
|
||||
#: plugins/archiveorg/archiveorg.php:41
|
||||
#: plugins/archiveorg/archiveorg.php:42
|
||||
msgid "For each link, add an Archive.org icon."
|
||||
msgstr "Pour chaque lien, ajoute une icône pour Archive.org."
|
||||
|
||||
|
@ -845,7 +853,7 @@ msgstr "Couleur de fond (gris léger)"
|
|||
msgid "Dark main color (e.g. visited links)"
|
||||
msgstr "Couleur principale sombre (ex : les liens visités)"
|
||||
|
||||
#: plugins/demo_plugin/demo_plugin.php:477
|
||||
#: plugins/demo_plugin/demo_plugin.php:478
|
||||
msgid ""
|
||||
"A demo plugin covering all use cases for template designers and plugin "
|
||||
"developers."
|
||||
|
@ -853,11 +861,11 @@ msgstr ""
|
|||
"Une extension de démonstration couvrant tous les cas d'utilisation pour les "
|
||||
"designers de thèmes et les développeurs d'extensions."
|
||||
|
||||
#: plugins/demo_plugin/demo_plugin.php:478
|
||||
#: plugins/demo_plugin/demo_plugin.php:479
|
||||
msgid "This is a parameter dedicated to the demo plugin. It'll be suffixed."
|
||||
msgstr "Ceci est un paramètre dédié au plugin de démo. Il sera suffixé."
|
||||
|
||||
#: plugins/demo_plugin/demo_plugin.php:479
|
||||
#: plugins/demo_plugin/demo_plugin.php:480
|
||||
msgid "Other demo parameter"
|
||||
msgstr "Un autre paramètre de démo"
|
||||
|
||||
|
@ -879,7 +887,7 @@ msgstr ""
|
|||
msgid "Isso server URL (without 'http://')"
|
||||
msgstr "URL du serveur Isso (sans 'http://')"
|
||||
|
||||
#: plugins/piwik/piwik.php:23
|
||||
#: plugins/piwik/piwik.php:24
|
||||
msgid ""
|
||||
"Piwik plugin error: Please define PIWIK_URL and PIWIK_SITEID in the plugin "
|
||||
"administration page."
|
||||
|
@ -887,27 +895,27 @@ msgstr ""
|
|||
"Erreur de l'extension Piwik : Merci de définir les paramètres PIWIK_URL et "
|
||||
"PIWIK_SITEID dans la page d'administration des extensions."
|
||||
|
||||
#: plugins/piwik/piwik.php:72
|
||||
#: plugins/piwik/piwik.php:73
|
||||
msgid "A plugin that adds Piwik tracking code to Shaarli pages."
|
||||
msgstr "Ajoute le code de traçage de Piwik sur les pages de Shaarli."
|
||||
|
||||
#: plugins/piwik/piwik.php:73
|
||||
#: plugins/piwik/piwik.php:74
|
||||
msgid "Piwik URL"
|
||||
msgstr "URL de Piwik"
|
||||
|
||||
#: plugins/piwik/piwik.php:74
|
||||
#: plugins/piwik/piwik.php:75
|
||||
msgid "Piwik site ID"
|
||||
msgstr "Site ID de Piwik"
|
||||
|
||||
#: plugins/playvideos/playvideos.php:25
|
||||
#: plugins/playvideos/playvideos.php:26
|
||||
msgid "Video player"
|
||||
msgstr "Lecteur vidéo"
|
||||
|
||||
#: plugins/playvideos/playvideos.php:28
|
||||
#: plugins/playvideos/playvideos.php:29
|
||||
msgid "Play Videos"
|
||||
msgstr "Jouer les vidéos"
|
||||
|
||||
#: plugins/playvideos/playvideos.php:59
|
||||
#: plugins/playvideos/playvideos.php:60
|
||||
msgid "Add a button in the toolbar allowing to watch all videos."
|
||||
msgstr ""
|
||||
"Ajoute un bouton dans la barre de menu pour regarder toutes les vidéos."
|
||||
|
@ -935,11 +943,11 @@ msgstr "Mauvaise réponse du hub %s"
|
|||
msgid "Enable PubSubHubbub feed publishing."
|
||||
msgstr "Active la publication de flux vers PubSubHubbub."
|
||||
|
||||
#: plugins/qrcode/qrcode.php:73 plugins/wallabag/wallabag.php:71
|
||||
#: plugins/qrcode/qrcode.php:74 plugins/wallabag/wallabag.php:72
|
||||
msgid "For each link, add a QRCode icon."
|
||||
msgstr "Pour chaque lien, ajouter une icône de QRCode."
|
||||
|
||||
#: plugins/wallabag/wallabag.php:21
|
||||
#: plugins/wallabag/wallabag.php:22
|
||||
msgid ""
|
||||
"Wallabag plugin error: Please define the \"WALLABAG_URL\" setting in the "
|
||||
"plugin administration page."
|
||||
|
@ -947,15 +955,15 @@ msgstr ""
|
|||
"Erreur de l'extension Wallabag : Merci de définir le paramètre « "
|
||||
"WALLABAG_URL » dans la page d'administration des extensions."
|
||||
|
||||
#: plugins/wallabag/wallabag.php:48
|
||||
#: plugins/wallabag/wallabag.php:49
|
||||
msgid "Save to wallabag"
|
||||
msgstr "Sauvegarder dans Wallabag"
|
||||
|
||||
#: plugins/wallabag/wallabag.php:72
|
||||
#: plugins/wallabag/wallabag.php:73
|
||||
msgid "Wallabag API URL"
|
||||
msgstr "URL de l'API Wallabag"
|
||||
|
||||
#: plugins/wallabag/wallabag.php:73
|
||||
#: plugins/wallabag/wallabag.php:74
|
||||
msgid "Wallabag API version (1 or 2)"
|
||||
msgstr "Version de l'API Wallabag (1 ou 2)"
|
||||
|
||||
|
|
|
@ -3,14 +3,14 @@ msgstr ""
|
|||
"Project-Id-Version: Shaarli\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-10-19 10:19+0900\n"
|
||||
"PO-Revision-Date: 2020-10-19 10:25+0900\n"
|
||||
"PO-Revision-Date: 2021-01-04 18:54+0900\n"
|
||||
"Last-Translator: yude <yudesleepy@gmail.com>\n"
|
||||
"Language-Team: Shaarli\n"
|
||||
"Language: ja\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 2.2.3\n"
|
||||
"X-Generator: Poedit 2.4.2\n"
|
||||
"X-Poedit-Basepath: ../../../..\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
@ -79,8 +79,8 @@ msgid ""
|
|||
"php-gd extension must be loaded to use thumbnails. Thumbnails are now "
|
||||
"disabled. Please reload the page."
|
||||
msgstr ""
|
||||
"サムネイルを使用するには、php-gd エクステンションが読み込まれている必要があり"
|
||||
"ます。サムネイルは無効化されました。ページを再読込してください。"
|
||||
"サムネイルを使用するには、php-gd 拡張機能が読み込まれている必要があります。サ"
|
||||
"ムネイルは無効化されました。ページを再読込してください。"
|
||||
|
||||
#: application/Utils.php:383 tests/UtilsTest.php:343
|
||||
msgid "Setting not set"
|
||||
|
@ -118,7 +118,7 @@ msgstr "設定を変更する権限がありません"
|
|||
|
||||
#: application/bookmark/BookmarkFileService.php:205
|
||||
msgid "This bookmarks already exists"
|
||||
msgstr "このブックマークは既に存在します。"
|
||||
msgstr "このブックマークは既に存在します"
|
||||
|
||||
#: application/bookmark/BookmarkInitializer.php:39
|
||||
msgid "(private bookmark with thumbnail demo)"
|
||||
|
@ -594,8 +594,6 @@ msgstr ""
|
|||
"す。"
|
||||
|
||||
#: application/legacy/LegacyUpdater.php:104
|
||||
#, fuzzy
|
||||
#| msgid "Couldn't retrieve Updater class methods."
|
||||
msgid "Couldn't retrieve updater class methods."
|
||||
msgstr "アップデーターのクラスメゾットを受信できませんでした。"
|
||||
|
||||
|
@ -617,10 +615,7 @@ msgid "has an unknown file format. Nothing was imported."
|
|||
msgstr "は不明なファイル形式です。インポートは中止されました。"
|
||||
|
||||
#: application/netscape/NetscapeBookmarkUtils.php:221
|
||||
#, fuzzy, php-format
|
||||
#| msgid ""
|
||||
#| "was successfully processed in %d seconds: %d links imported, %d links "
|
||||
#| "overwritten, %d links skipped."
|
||||
#, php-format
|
||||
msgid ""
|
||||
"was successfully processed in %d seconds: %d bookmarks imported, %d "
|
||||
"bookmarks overwritten, %d bookmarks skipped."
|
||||
|
@ -630,7 +625,7 @@ msgstr ""
|
|||
|
||||
#: application/plugin/PluginManager.php:124
|
||||
msgid " [plugin incompatibility]: "
|
||||
msgstr "[非対応のプラグイン]: "
|
||||
msgstr " [非対応のプラグイン]: "
|
||||
|
||||
#: application/plugin/exception/PluginFileNotFoundException.php:21
|
||||
#, php-format
|
||||
|
|
0
inc/languages/ru/LC_MESSAGES/shaarli.po
Executable file → Normal file
0
inc/languages/ru/LC_MESSAGES/shaarli.po
Executable file → Normal file
22
index.php
22
index.php
|
@ -31,6 +31,7 @@
|
|||
use Shaarli\Config\ConfigManager;
|
||||
use Shaarli\Container\ContainerBuilder;
|
||||
use Shaarli\Languages;
|
||||
use Shaarli\Plugin\PluginManager;
|
||||
use Shaarli\Security\BanManager;
|
||||
use Shaarli\Security\CookieManager;
|
||||
use Shaarli\Security\LoginManager;
|
||||
|
@ -87,7 +88,17 @@
|
|||
|
||||
$loginManager->checkLoginState(client_ip_id($_SERVER));
|
||||
|
||||
$containerBuilder = new ContainerBuilder($conf, $sessionManager, $cookieManager, $loginManager, $logger);
|
||||
$pluginManager = new PluginManager($conf);
|
||||
$pluginManager->load($conf->get('general.enabled_plugins', []));
|
||||
|
||||
$containerBuilder = new ContainerBuilder(
|
||||
$conf,
|
||||
$sessionManager,
|
||||
$cookieManager,
|
||||
$loginManager,
|
||||
$pluginManager,
|
||||
$logger
|
||||
);
|
||||
$container = $containerBuilder->build();
|
||||
$app = new App($container);
|
||||
|
||||
|
@ -154,6 +165,15 @@
|
|||
$this->get('/visibility/{visibility}', '\Shaarli\Front\Controller\Admin\SessionFilterController:visibility');
|
||||
})->add('\Shaarli\Front\ShaarliAdminMiddleware');
|
||||
|
||||
$app->group('/plugin', function () use ($pluginManager) {
|
||||
foreach ($pluginManager->getRegisteredRoutes() as $pluginName => $routes) {
|
||||
$this->group('/' . $pluginName, function () use ($routes) {
|
||||
foreach ($routes as $route) {
|
||||
$this->{strtolower($route['method'])}('/' . ltrim($route['route'], '/'), $route['callable']);
|
||||
}
|
||||
});
|
||||
}
|
||||
})->add('\Shaarli\Front\ShaarliMiddleware');
|
||||
|
||||
// REST API routes
|
||||
$app->group('/api/v1', function () {
|
||||
|
|
|
@ -18,5 +18,6 @@
|
|||
<rule ref="PSR1.Files.SideEffects.FoundWithSymbols">
|
||||
<!-- index.php bootstraps everything, so yes mixed symbols with side effects -->
|
||||
<exclude-pattern>index.php</exclude-pattern>
|
||||
<exclude-pattern>plugins/*</exclude-pattern>
|
||||
</rule>
|
||||
</ruleset>
|
||||
|
|
|
@ -46,6 +46,20 @@ function default_colors_init($conf)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When plugin parameters are saved, we regenerate the custom CSS file with provided settings.
|
||||
*
|
||||
* @param array $data $_POST array
|
||||
*
|
||||
* @return array Updated $_POST array
|
||||
*/
|
||||
function hook_default_colors_save_plugin_parameters($data)
|
||||
{
|
||||
default_colors_generate_css_file($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* When linklist is displayed, include default_colors CSS file.
|
||||
*
|
||||
|
|
24
plugins/demo_plugin/DemoPluginController.php
Normal file
24
plugins/demo_plugin/DemoPluginController.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shaarli\DemoPlugin;
|
||||
|
||||
use Shaarli\Front\Controller\Admin\ShaarliAdminController;
|
||||
use Slim\Http\Request;
|
||||
use Slim\Http\Response;
|
||||
|
||||
class DemoPluginController extends ShaarliAdminController
|
||||
{
|
||||
public function index(Request $request, Response $response): Response
|
||||
{
|
||||
$this->assignView(
|
||||
'content',
|
||||
'<div class="center">' .
|
||||
'This is a demo page. I have access to Shaarli container, so I\'m free to do whatever I want here.' .
|
||||
'</div>'
|
||||
);
|
||||
|
||||
return $response->write($this->render('pluginscontent'));
|
||||
}
|
||||
}
|
|
@ -7,6 +7,8 @@
|
|||
* Can be used by plugin developers to make their own plugin.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/DemoPluginController.php';
|
||||
|
||||
/*
|
||||
* RENDER HEADER, INCLUDES, FOOTER
|
||||
*
|
||||
|
@ -15,6 +17,7 @@
|
|||
* and check user status with _LOGGEDIN_.
|
||||
*/
|
||||
|
||||
use Shaarli\Bookmark\Bookmark;
|
||||
use Shaarli\Config\ConfigManager;
|
||||
use Shaarli\Plugin\PluginManager;
|
||||
use Shaarli\Render\TemplatePage;
|
||||
|
@ -60,6 +63,17 @@ function demo_plugin_init($conf)
|
|||
return $errors;
|
||||
}
|
||||
|
||||
function demo_plugin_register_routes(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'method' => 'GET',
|
||||
'route' => '/custom',
|
||||
'callable' => 'Shaarli\DemoPlugin\DemoPluginController:index',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook render_header.
|
||||
* Executed on every page render.
|
||||
|
@ -250,6 +264,17 @@ function hook_demo_plugin_render_linklist($data)
|
|||
}
|
||||
$data['action_plugin'][] = $action;
|
||||
|
||||
// Action to trigger custom filter hiding bookmarks not containing 'e' letter in their description
|
||||
$action = [
|
||||
'attr' => [
|
||||
'href' => '?e',
|
||||
'title' => 'Hide bookmarks without "e" in their description.',
|
||||
],
|
||||
'html' => 'e',
|
||||
'on' => isset($_GET['e'])
|
||||
];
|
||||
$data['action_plugin'][] = $action;
|
||||
|
||||
// link_plugin (for each link)
|
||||
foreach ($data['links'] as &$value) {
|
||||
$value['link_plugin'][] = ' DEMO \o/';
|
||||
|
@ -304,7 +329,11 @@ function hook_demo_plugin_render_editlink($data)
|
|||
function hook_demo_plugin_render_tools($data)
|
||||
{
|
||||
// field_plugin
|
||||
$data['tools_plugin'][] = 'tools_plugin';
|
||||
$data['tools_plugin'][] = '<div class="tools-item">
|
||||
<a href="' . $data['_BASE_PATH_'] . '/plugin/demo_plugin/custom">
|
||||
<span class="pure-button pure-u-lg-2-3 pure-u-3-4">Demo Plugin Custom Route</span>
|
||||
</a>
|
||||
</div>';
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
@ -469,6 +498,27 @@ function hook_demo_plugin_save_plugin_parameters($data)
|
|||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* This hook is called when a search is performed, on every search entry.
|
||||
* It allows to add custom filters, and filter out additional link.
|
||||
*
|
||||
* For exemple here, we hide all bookmarks not containing the letter 'e' in their description.
|
||||
*
|
||||
* @param Bookmark $bookmark Search entry. Note that this is a Bookmark object, and not a link array.
|
||||
* It should NOT be altered.
|
||||
* @param array $context Additional info on the search performed.
|
||||
*
|
||||
* @return bool True if the bookmark should be kept in the search result, false to discard it.
|
||||
*/
|
||||
function hook_demo_plugin_filter_search_entry(Bookmark $bookmark, array $context): bool
|
||||
{
|
||||
if (isset($_GET['e'])) {
|
||||
return strpos($bookmark->getDescription(), 'e') !== false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is never called, but contains translation calls for GNU gettext extraction.
|
||||
*/
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Shaarli\Plugin;
|
||||
|
||||
use Shaarli\Bookmark\Bookmark;
|
||||
use Shaarli\Config\ConfigManager;
|
||||
|
||||
/**
|
||||
|
@ -120,4 +121,58 @@ public function testGetPluginsMeta(): void
|
|||
$this->assertEquals('test plugin', $meta[self::$pluginName]['description']);
|
||||
$this->assertEquals($expectedParameters, $meta[self::$pluginName]['parameters']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test plugin custom routes - note that there is no check on callable functions
|
||||
*/
|
||||
public function testRegisteredRoutes(): void
|
||||
{
|
||||
PluginManager::$PLUGINS_PATH = self::$pluginPath;
|
||||
$this->pluginManager->load([self::$pluginName]);
|
||||
|
||||
$expectedParameters = [
|
||||
[
|
||||
'method' => 'GET',
|
||||
'route' => '/test',
|
||||
'callable' => 'getFunction',
|
||||
],
|
||||
[
|
||||
'method' => 'POST',
|
||||
'route' => '/custom',
|
||||
'callable' => 'postFunction',
|
||||
],
|
||||
];
|
||||
$meta = $this->pluginManager->getRegisteredRoutes();
|
||||
static::assertSame($expectedParameters, $meta[self::$pluginName]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test plugin custom routes with invalid route
|
||||
*/
|
||||
public function testRegisteredRoutesInvalid(): void
|
||||
{
|
||||
$plugin = 'test_route_invalid';
|
||||
$this->pluginManager->load([$plugin]);
|
||||
|
||||
$meta = $this->pluginManager->getRegisteredRoutes();
|
||||
static::assertSame([], $meta);
|
||||
|
||||
$errors = $this->pluginManager->getErrors();
|
||||
static::assertSame(['test_route_invalid [plugin incompatibility]: trying to register invalid route.'], $errors);
|
||||
}
|
||||
|
||||
public function testSearchFilterPlugin(): void
|
||||
{
|
||||
PluginManager::$PLUGINS_PATH = self::$pluginPath;
|
||||
$this->pluginManager->load([self::$pluginName]);
|
||||
|
||||
static::assertNull($this->pluginManager->getFilterSearchEntryHooks());
|
||||
|
||||
static::assertTrue($this->pluginManager->filterSearchEntry(new Bookmark(), ['_result' => true]));
|
||||
|
||||
static::assertCount(1, $this->pluginManager->getFilterSearchEntryHooks());
|
||||
static::assertSame('hook_test_filter_search_entry', $this->pluginManager->getFilterSearchEntryHooks()[0]);
|
||||
|
||||
static::assertFalse($this->pluginManager->filterSearchEntry(new Bookmark(), ['_result' => false]));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
use Shaarli\Config\ConfigManager;
|
||||
use Shaarli\History;
|
||||
use Shaarli\Plugin\PluginManager;
|
||||
use Slim\Container;
|
||||
use Slim\Http\Environment;
|
||||
use Slim\Http\Request;
|
||||
|
@ -56,6 +57,7 @@ protected function setUp(): void
|
|||
$this->container = new Container();
|
||||
$this->container['conf'] = $this->conf;
|
||||
$this->container['history'] = $history;
|
||||
$this->container['pluginManager'] = new PluginManager($this->conf);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
use Shaarli\Bookmark\BookmarkFileService;
|
||||
use Shaarli\Config\ConfigManager;
|
||||
use Shaarli\History;
|
||||
use Shaarli\Plugin\PluginManager;
|
||||
use Shaarli\TestCase;
|
||||
use Slim\Container;
|
||||
use Slim\Http\Environment;
|
||||
|
@ -55,12 +56,18 @@ protected function setUp(): void
|
|||
$this->conf->set('resource.datastore', self::$testDatastore);
|
||||
$this->refDB = new \ReferenceLinkDB();
|
||||
$this->refDB->write(self::$testDatastore);
|
||||
|
||||
$this->pluginManager = new PluginManager($this->conf);
|
||||
$history = new History('sandbox/history.php');
|
||||
|
||||
$this->container = new Container();
|
||||
$this->container['conf'] = $this->conf;
|
||||
$this->container['db'] = new BookmarkFileService($this->conf, $history, $mutex, true);
|
||||
$this->container['db'] = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$this->pluginManager,
|
||||
$history,
|
||||
$mutex,
|
||||
true
|
||||
);
|
||||
$this->container['history'] = null;
|
||||
|
||||
$this->controller = new Info($this->container);
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
use Shaarli\Bookmark\BookmarkFileService;
|
||||
use Shaarli\Config\ConfigManager;
|
||||
use Shaarli\History;
|
||||
use Shaarli\Plugin\PluginManager;
|
||||
use Slim\Container;
|
||||
use Slim\Http\Environment;
|
||||
use Slim\Http\Request;
|
||||
|
@ -57,6 +58,9 @@ class DeleteLinkTest extends \Shaarli\TestCase
|
|||
/** @var NoMutex */
|
||||
protected $mutex;
|
||||
|
||||
/** @var PluginManager */
|
||||
protected $pluginManager;
|
||||
|
||||
/**
|
||||
* Before each test, instantiate a new Api with its config, plugins and bookmarks.
|
||||
*/
|
||||
|
@ -70,7 +74,14 @@ protected function setUp(): void
|
|||
$refHistory = new \ReferenceHistory();
|
||||
$refHistory->write(self::$testHistory);
|
||||
$this->history = new History(self::$testHistory);
|
||||
$this->bookmarkService = new BookmarkFileService($this->conf, $this->history, $this->mutex, true);
|
||||
$this->pluginManager = new PluginManager($this->conf);
|
||||
$this->bookmarkService = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$this->pluginManager,
|
||||
$this->history,
|
||||
$this->mutex,
|
||||
true
|
||||
);
|
||||
|
||||
$this->container = new Container();
|
||||
$this->container['conf'] = $this->conf;
|
||||
|
@ -105,7 +116,13 @@ public function testDeleteLinkValid()
|
|||
$this->assertEquals(204, $response->getStatusCode());
|
||||
$this->assertEmpty((string) $response->getBody());
|
||||
|
||||
$this->bookmarkService = new BookmarkFileService($this->conf, $this->history, $this->mutex, true);
|
||||
$this->bookmarkService = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$this->pluginManager,
|
||||
$this->history,
|
||||
$this->mutex,
|
||||
true
|
||||
);
|
||||
$this->assertFalse($this->bookmarkService->exists($id));
|
||||
|
||||
$historyEntry = $this->history->getHistory()[0];
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
use Shaarli\Bookmark\BookmarkFileService;
|
||||
use Shaarli\Config\ConfigManager;
|
||||
use Shaarli\History;
|
||||
use Shaarli\Plugin\PluginManager;
|
||||
use Slim\Container;
|
||||
use Slim\Http\Environment;
|
||||
use Slim\Http\Request;
|
||||
|
@ -67,7 +68,14 @@ protected function setUp(): void
|
|||
|
||||
$this->container = new Container();
|
||||
$this->container['conf'] = $this->conf;
|
||||
$this->container['db'] = new BookmarkFileService($this->conf, $history, $mutex, true);
|
||||
$pluginManager = new PluginManager($this->conf);
|
||||
$this->container['db'] = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$pluginManager,
|
||||
$history,
|
||||
$mutex,
|
||||
true
|
||||
);
|
||||
$this->container['history'] = null;
|
||||
|
||||
$this->controller = new Links($this->container);
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
use Shaarli\Bookmark\LinkDB;
|
||||
use Shaarli\Config\ConfigManager;
|
||||
use Shaarli\History;
|
||||
use Shaarli\Plugin\PluginManager;
|
||||
use Slim\Container;
|
||||
use Slim\Http\Environment;
|
||||
use Slim\Http\Request;
|
||||
|
@ -67,7 +68,14 @@ protected function setUp(): void
|
|||
|
||||
$this->container = new Container();
|
||||
$this->container['conf'] = $this->conf;
|
||||
$this->container['db'] = new BookmarkFileService($this->conf, $history, $mutex, true);
|
||||
$pluginManager = new PluginManager($this->conf);
|
||||
$this->container['db'] = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$pluginManager,
|
||||
$history,
|
||||
$mutex,
|
||||
true
|
||||
);
|
||||
$this->container['history'] = null;
|
||||
|
||||
$this->controller = new Links($this->container);
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
use Shaarli\Bookmark\BookmarkFileService;
|
||||
use Shaarli\Config\ConfigManager;
|
||||
use Shaarli\History;
|
||||
use Shaarli\Plugin\PluginManager;
|
||||
use Shaarli\TestCase;
|
||||
use Slim\Container;
|
||||
use Slim\Http\Environment;
|
||||
|
@ -81,8 +82,14 @@ protected function setUp(): void
|
|||
$refHistory = new \ReferenceHistory();
|
||||
$refHistory->write(self::$testHistory);
|
||||
$this->history = new History(self::$testHistory);
|
||||
$this->bookmarkService = new BookmarkFileService($this->conf, $this->history, $mutex, true);
|
||||
|
||||
$pluginManager = new PluginManager($this->conf);
|
||||
$this->bookmarkService = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$pluginManager,
|
||||
$this->history,
|
||||
$mutex,
|
||||
true
|
||||
);
|
||||
$this->container = new Container();
|
||||
$this->container['conf'] = $this->conf;
|
||||
$this->container['db'] = $this->bookmarkService;
|
||||
|
@ -229,4 +236,52 @@ public function testPostLinkDuplicate()
|
|||
\DateTime::createFromFormat(\DateTime::ATOM, $data['updated'])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test link creation with a tag string provided
|
||||
*/
|
||||
public function testPostLinkWithTagString(): void
|
||||
{
|
||||
$link = [
|
||||
'tags' => 'one two',
|
||||
];
|
||||
$env = Environment::mock([
|
||||
'REQUEST_METHOD' => 'POST',
|
||||
'CONTENT_TYPE' => 'application/json'
|
||||
]);
|
||||
|
||||
$request = Request::createFromEnvironment($env);
|
||||
$request = $request->withParsedBody($link);
|
||||
$response = $this->controller->postLink($request, new Response());
|
||||
|
||||
$this->assertEquals(201, $response->getStatusCode());
|
||||
$this->assertEquals('/api/v1/bookmarks/1', $response->getHeader('Location')[0]);
|
||||
$data = json_decode((string) $response->getBody(), true);
|
||||
$this->assertEquals(self::NB_FIELDS_LINK, count($data));
|
||||
$this->assertEquals(['one', 'two'], $data['tags']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test link creation with a tag string provided
|
||||
*/
|
||||
public function testPostLinkWithTagString2(): void
|
||||
{
|
||||
$link = [
|
||||
'tags' => ['one two'],
|
||||
];
|
||||
$env = Environment::mock([
|
||||
'REQUEST_METHOD' => 'POST',
|
||||
'CONTENT_TYPE' => 'application/json'
|
||||
]);
|
||||
|
||||
$request = Request::createFromEnvironment($env);
|
||||
$request = $request->withParsedBody($link);
|
||||
$response = $this->controller->postLink($request, new Response());
|
||||
|
||||
$this->assertEquals(201, $response->getStatusCode());
|
||||
$this->assertEquals('/api/v1/bookmarks/1', $response->getHeader('Location')[0]);
|
||||
$data = json_decode((string) $response->getBody(), true);
|
||||
$this->assertEquals(self::NB_FIELDS_LINK, count($data));
|
||||
$this->assertEquals(['one', 'two'], $data['tags']);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
use Shaarli\Bookmark\BookmarkFileService;
|
||||
use Shaarli\Config\ConfigManager;
|
||||
use Shaarli\History;
|
||||
use Shaarli\Plugin\PluginManager;
|
||||
use Slim\Container;
|
||||
use Slim\Http\Environment;
|
||||
use Slim\Http\Request;
|
||||
|
@ -73,8 +74,14 @@ protected function setUp(): void
|
|||
$refHistory = new \ReferenceHistory();
|
||||
$refHistory->write(self::$testHistory);
|
||||
$this->history = new History(self::$testHistory);
|
||||
$this->bookmarkService = new BookmarkFileService($this->conf, $this->history, $mutex, true);
|
||||
|
||||
$pluginManager = new PluginManager($this->conf);
|
||||
$this->bookmarkService = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$pluginManager,
|
||||
$this->history,
|
||||
$mutex,
|
||||
true
|
||||
);
|
||||
$this->container = new Container();
|
||||
$this->container['conf'] = $this->conf;
|
||||
$this->container['db'] = $this->bookmarkService;
|
||||
|
@ -233,4 +240,52 @@ public function testGetLink404()
|
|||
|
||||
$this->controller->putLink($request, new Response(), ['id' => -1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test link creation with a tag string provided
|
||||
*/
|
||||
public function testPutLinkWithTagString(): void
|
||||
{
|
||||
$link = [
|
||||
'tags' => 'one two',
|
||||
];
|
||||
$id = '41';
|
||||
$env = Environment::mock([
|
||||
'REQUEST_METHOD' => 'PUT',
|
||||
'CONTENT_TYPE' => 'application/json'
|
||||
]);
|
||||
|
||||
$request = Request::createFromEnvironment($env);
|
||||
$request = $request->withParsedBody($link);
|
||||
$response = $this->controller->putLink($request, new Response(), ['id' => $id]);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$data = json_decode((string) $response->getBody(), true);
|
||||
$this->assertEquals(self::NB_FIELDS_LINK, count($data));
|
||||
$this->assertEquals(['one', 'two'], $data['tags']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test link creation with a tag string provided
|
||||
*/
|
||||
public function testPutLinkWithTagString2(): void
|
||||
{
|
||||
$link = [
|
||||
'tags' => ['one two'],
|
||||
];
|
||||
$id = '41';
|
||||
$env = Environment::mock([
|
||||
'REQUEST_METHOD' => 'PUT',
|
||||
'CONTENT_TYPE' => 'application/json'
|
||||
]);
|
||||
|
||||
$request = Request::createFromEnvironment($env);
|
||||
$request = $request->withParsedBody($link);
|
||||
$response = $this->controller->putLink($request, new Response(), ['id' => $id]);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$data = json_decode((string) $response->getBody(), true);
|
||||
$this->assertEquals(self::NB_FIELDS_LINK, count($data));
|
||||
$this->assertEquals(['one', 'two'], $data['tags']);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
use Shaarli\Bookmark\LinkDB;
|
||||
use Shaarli\Config\ConfigManager;
|
||||
use Shaarli\History;
|
||||
use Shaarli\Plugin\PluginManager;
|
||||
use Slim\Container;
|
||||
use Slim\Http\Environment;
|
||||
use Slim\Http\Request;
|
||||
|
@ -55,6 +56,9 @@ class DeleteTagTest extends \Shaarli\TestCase
|
|||
*/
|
||||
protected $controller;
|
||||
|
||||
/** @var PluginManager */
|
||||
protected $pluginManager;
|
||||
|
||||
/** @var NoMutex */
|
||||
protected $mutex;
|
||||
|
||||
|
@ -71,7 +75,14 @@ protected function setUp(): void
|
|||
$refHistory = new \ReferenceHistory();
|
||||
$refHistory->write(self::$testHistory);
|
||||
$this->history = new History(self::$testHistory);
|
||||
$this->bookmarkService = new BookmarkFileService($this->conf, $this->history, $this->mutex, true);
|
||||
$this->pluginManager = new PluginManager($this->conf);
|
||||
$this->bookmarkService = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$this->pluginManager,
|
||||
$this->history,
|
||||
$this->mutex,
|
||||
true
|
||||
);
|
||||
|
||||
$this->container = new Container();
|
||||
$this->container['conf'] = $this->conf;
|
||||
|
@ -107,7 +118,13 @@ public function testDeleteTagValid()
|
|||
$this->assertEquals(204, $response->getStatusCode());
|
||||
$this->assertEmpty((string) $response->getBody());
|
||||
|
||||
$this->bookmarkService = new BookmarkFileService($this->conf, $this->history, $this->mutex, true);
|
||||
$this->bookmarkService = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$this->pluginManager,
|
||||
$this->history,
|
||||
$this->mutex,
|
||||
true
|
||||
);
|
||||
$tags = $this->bookmarkService->bookmarksCountPerTag();
|
||||
$this->assertFalse(isset($tags[$tagName]));
|
||||
|
||||
|
@ -141,7 +158,13 @@ public function testDeleteTagCaseSensitivity()
|
|||
$this->assertEquals(204, $response->getStatusCode());
|
||||
$this->assertEmpty((string) $response->getBody());
|
||||
|
||||
$this->bookmarkService = new BookmarkFileService($this->conf, $this->history, $this->mutex, true);
|
||||
$this->bookmarkService = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$this->pluginManager,
|
||||
$this->history,
|
||||
$this->mutex,
|
||||
true
|
||||
);
|
||||
$tags = $this->bookmarkService->bookmarksCountPerTag();
|
||||
$this->assertFalse(isset($tags[$tagName]));
|
||||
$this->assertTrue($tags[strtolower($tagName)] > 0);
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
use Shaarli\Bookmark\LinkDB;
|
||||
use Shaarli\Config\ConfigManager;
|
||||
use Shaarli\History;
|
||||
use Shaarli\Plugin\PluginManager;
|
||||
use Slim\Container;
|
||||
use Slim\Http\Environment;
|
||||
use Slim\Http\Request;
|
||||
|
@ -46,6 +47,9 @@ class GetTagNameTest extends \Shaarli\TestCase
|
|||
*/
|
||||
protected $controller;
|
||||
|
||||
/** @var PluginManager */
|
||||
protected $pluginManager;
|
||||
|
||||
/**
|
||||
* Number of JSON fields per link.
|
||||
*/
|
||||
|
@ -65,7 +69,14 @@ protected function setUp(): void
|
|||
|
||||
$this->container = new Container();
|
||||
$this->container['conf'] = $this->conf;
|
||||
$this->container['db'] = new BookmarkFileService($this->conf, $history, $mutex, true);
|
||||
$this->pluginManager = new PluginManager($this->conf);
|
||||
$this->container['db'] = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$this->pluginManager,
|
||||
$history,
|
||||
$mutex,
|
||||
true
|
||||
);
|
||||
$this->container['history'] = null;
|
||||
|
||||
$this->controller = new Tags($this->container);
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
use Shaarli\Bookmark\LinkDB;
|
||||
use Shaarli\Config\ConfigManager;
|
||||
use Shaarli\History;
|
||||
use Shaarli\Plugin\PluginManager;
|
||||
use Slim\Container;
|
||||
use Slim\Http\Environment;
|
||||
use Slim\Http\Request;
|
||||
|
@ -50,6 +51,9 @@ class GetTagsTest extends \Shaarli\TestCase
|
|||
*/
|
||||
protected $controller;
|
||||
|
||||
/** @var PluginManager */
|
||||
protected $pluginManager;
|
||||
|
||||
/**
|
||||
* Number of JSON field per link.
|
||||
*/
|
||||
|
@ -66,9 +70,14 @@ protected function setUp(): void
|
|||
$this->refDB = new \ReferenceLinkDB();
|
||||
$this->refDB->write(self::$testDatastore);
|
||||
$history = new History('sandbox/history.php');
|
||||
|
||||
$this->bookmarkService = new BookmarkFileService($this->conf, $history, $mutex, true);
|
||||
|
||||
$this->pluginManager = new PluginManager($this->conf);
|
||||
$this->bookmarkService = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$this->pluginManager,
|
||||
$history,
|
||||
$mutex,
|
||||
true
|
||||
);
|
||||
$this->container = new Container();
|
||||
$this->container['conf'] = $this->conf;
|
||||
$this->container['db'] = $this->bookmarkService;
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
use Shaarli\Bookmark\LinkDB;
|
||||
use Shaarli\Config\ConfigManager;
|
||||
use Shaarli\History;
|
||||
use Shaarli\Plugin\PluginManager;
|
||||
use Slim\Container;
|
||||
use Slim\Http\Environment;
|
||||
use Slim\Http\Request;
|
||||
|
@ -55,6 +56,9 @@ class PutTagTest extends \Shaarli\TestCase
|
|||
*/
|
||||
protected $controller;
|
||||
|
||||
/** @var PluginManager */
|
||||
protected $pluginManager;
|
||||
|
||||
/**
|
||||
* Number of JSON field per link.
|
||||
*/
|
||||
|
@ -73,7 +77,14 @@ protected function setUp(): void
|
|||
$refHistory = new \ReferenceHistory();
|
||||
$refHistory->write(self::$testHistory);
|
||||
$this->history = new History(self::$testHistory);
|
||||
$this->bookmarkService = new BookmarkFileService($this->conf, $this->history, $mutex, true);
|
||||
$this->pluginManager = new PluginManager($this->conf);
|
||||
$this->bookmarkService = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$this->pluginManager,
|
||||
$this->history,
|
||||
$mutex,
|
||||
true
|
||||
);
|
||||
|
||||
$this->container = new Container();
|
||||
$this->container['conf'] = $this->conf;
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
use Shaarli\Config\ConfigManager;
|
||||
use Shaarli\Formatter\BookmarkMarkdownFormatter;
|
||||
use Shaarli\History;
|
||||
use Shaarli\Plugin\PluginManager;
|
||||
use Shaarli\TestCase;
|
||||
|
||||
/**
|
||||
|
@ -56,6 +57,9 @@ class BookmarkFileServiceTest extends TestCase
|
|||
/** @var NoMutex */
|
||||
protected $mutex;
|
||||
|
||||
/** @var PluginManager */
|
||||
protected $pluginManager;
|
||||
|
||||
/**
|
||||
* Instantiates public and private LinkDBs with test data
|
||||
*
|
||||
|
@ -93,8 +97,21 @@ protected function setUp(): void
|
|||
$this->refDB = new \ReferenceLinkDB();
|
||||
$this->refDB->write(self::$testDatastore);
|
||||
$this->history = new History('sandbox/history.php');
|
||||
$this->publicLinkDB = new BookmarkFileService($this->conf, $this->history, $this->mutex, false);
|
||||
$this->privateLinkDB = new BookmarkFileService($this->conf, $this->history, $this->mutex, true);
|
||||
$this->pluginManager = new PluginManager($this->conf);
|
||||
$this->publicLinkDB = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$this->pluginManager,
|
||||
$this->history,
|
||||
$this->mutex,
|
||||
false
|
||||
);
|
||||
$this->privateLinkDB = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$this->pluginManager,
|
||||
$this->history,
|
||||
$this->mutex,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -111,7 +128,13 @@ public function testDatabaseMigration()
|
|||
$db = self::getMethod('migrate');
|
||||
$db->invokeArgs($this->privateLinkDB, []);
|
||||
|
||||
$db = new \FakeBookmarkService($this->conf, $this->history, $this->mutex, true);
|
||||
$db = new \FakeBookmarkService(
|
||||
$this->conf,
|
||||
$this->pluginManager,
|
||||
$this->history,
|
||||
$this->mutex,
|
||||
true
|
||||
);
|
||||
$this->assertInstanceOf(BookmarkArray::class, $db->getBookmarks());
|
||||
$this->assertEquals($this->refDB->countLinks(), $db->count());
|
||||
}
|
||||
|
@ -180,7 +203,13 @@ public function testAddFull()
|
|||
$this->assertEquals($updated, $bookmark->getUpdated());
|
||||
|
||||
// reload from file
|
||||
$this->privateLinkDB = new BookmarkFileService($this->conf, $this->history, $this->mutex, true);
|
||||
$this->privateLinkDB = new \FakeBookmarkService(
|
||||
$this->conf,
|
||||
$this->pluginManager,
|
||||
$this->history,
|
||||
$this->mutex,
|
||||
true
|
||||
);
|
||||
|
||||
$bookmark = $this->privateLinkDB->get(43);
|
||||
$this->assertEquals(43, $bookmark->getId());
|
||||
|
@ -218,7 +247,13 @@ public function testAddMinimal()
|
|||
$this->assertNull($bookmark->getUpdated());
|
||||
|
||||
// reload from file
|
||||
$this->privateLinkDB = new BookmarkFileService($this->conf, $this->history, $this->mutex, true);
|
||||
$this->privateLinkDB = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$this->pluginManager,
|
||||
$this->history,
|
||||
$this->mutex,
|
||||
true
|
||||
);
|
||||
|
||||
$bookmark = $this->privateLinkDB->get(43);
|
||||
$this->assertEquals(43, $bookmark->getId());
|
||||
|
@ -248,7 +283,13 @@ public function testAddMinimalNoWrite()
|
|||
$this->assertEquals(43, $bookmark->getId());
|
||||
|
||||
// reload from file
|
||||
$this->privateLinkDB = new BookmarkFileService($this->conf, $this->history, $this->mutex, true);
|
||||
$this->privateLinkDB = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$this->pluginManager,
|
||||
$this->history,
|
||||
$this->mutex,
|
||||
true
|
||||
);
|
||||
|
||||
$this->privateLinkDB->get(43);
|
||||
}
|
||||
|
@ -309,7 +350,13 @@ public function testSetFull()
|
|||
$this->assertTrue(new \DateTime('5 seconds ago') < $bookmark->getUpdated());
|
||||
|
||||
// reload from file
|
||||
$this->privateLinkDB = new BookmarkFileService($this->conf, $this->history, $this->mutex, true);
|
||||
$this->privateLinkDB = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$this->pluginManager,
|
||||
$this->history,
|
||||
$this->mutex,
|
||||
true
|
||||
);
|
||||
|
||||
$bookmark = $this->privateLinkDB->get(42);
|
||||
$this->assertEquals(42, $bookmark->getId());
|
||||
|
@ -350,7 +397,13 @@ public function testSetMinimal()
|
|||
$this->assertTrue(new \DateTime('5 seconds ago') < $bookmark->getUpdated());
|
||||
|
||||
// reload from file
|
||||
$this->privateLinkDB = new BookmarkFileService($this->conf, $this->history, $this->mutex, true);
|
||||
$this->privateLinkDB = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$this->pluginManager,
|
||||
$this->history,
|
||||
$this->mutex,
|
||||
true
|
||||
);
|
||||
|
||||
$bookmark = $this->privateLinkDB->get(42);
|
||||
$this->assertEquals(42, $bookmark->getId());
|
||||
|
@ -383,7 +436,13 @@ public function testSetMinimalNoWrite()
|
|||
$this->assertEquals($title, $bookmark->getTitle());
|
||||
|
||||
// reload from file
|
||||
$this->privateLinkDB = new BookmarkFileService($this->conf, $this->history, $this->mutex, true);
|
||||
$this->privateLinkDB = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$this->pluginManager,
|
||||
$this->history,
|
||||
$this->mutex,
|
||||
true
|
||||
);
|
||||
|
||||
$bookmark = $this->privateLinkDB->get(42);
|
||||
$this->assertEquals(42, $bookmark->getId());
|
||||
|
@ -436,7 +495,13 @@ public function testAddOrSetNew()
|
|||
$this->assertEquals(43, $bookmark->getId());
|
||||
|
||||
// reload from file
|
||||
$this->privateLinkDB = new BookmarkFileService($this->conf, $this->history, $this->mutex, true);
|
||||
$this->privateLinkDB = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$this->pluginManager,
|
||||
$this->history,
|
||||
$this->mutex,
|
||||
true
|
||||
);
|
||||
|
||||
$bookmark = $this->privateLinkDB->get(43);
|
||||
$this->assertEquals(43, $bookmark->getId());
|
||||
|
@ -456,7 +521,13 @@ public function testAddOrSetExisting()
|
|||
$this->assertEquals($title, $bookmark->getTitle());
|
||||
|
||||
// reload from file
|
||||
$this->privateLinkDB = new BookmarkFileService($this->conf, $this->history, $this->mutex, true);
|
||||
$this->privateLinkDB = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$this->pluginManager,
|
||||
$this->history,
|
||||
$this->mutex,
|
||||
true
|
||||
);
|
||||
|
||||
$bookmark = $this->privateLinkDB->get(42);
|
||||
$this->assertEquals(42, $bookmark->getId());
|
||||
|
@ -488,7 +559,13 @@ public function testAddOrSetMinimalNoWrite()
|
|||
$this->assertEquals($title, $bookmark->getTitle());
|
||||
|
||||
// reload from file
|
||||
$this->privateLinkDB = new BookmarkFileService($this->conf, $this->history, $this->mutex, true);
|
||||
$this->privateLinkDB = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$this->pluginManager,
|
||||
$this->history,
|
||||
$this->mutex,
|
||||
true
|
||||
);
|
||||
|
||||
$bookmark = $this->privateLinkDB->get(42);
|
||||
$this->assertEquals(42, $bookmark->getId());
|
||||
|
@ -514,7 +591,13 @@ public function testRemoveExisting()
|
|||
$this->assertInstanceOf(BookmarkNotFoundException::class, $exception);
|
||||
|
||||
// reload from file
|
||||
$this->privateLinkDB = new BookmarkFileService($this->conf, $this->history, $this->mutex, true);
|
||||
$this->privateLinkDB = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$this->pluginManager,
|
||||
$this->history,
|
||||
$this->mutex,
|
||||
true
|
||||
);
|
||||
|
||||
$this->privateLinkDB->get(42);
|
||||
}
|
||||
|
@ -607,7 +690,7 @@ public function testConstructDatastoreNotWriteable()
|
|||
|
||||
$conf = new ConfigManager('tests/utils/config/configJson');
|
||||
$conf->set('resource.datastore', 'null/store.db');
|
||||
new BookmarkFileService($conf, $this->history, $this->mutex, true);
|
||||
new BookmarkFileService($conf, $this->pluginManager, $this->history, $this->mutex, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -617,7 +700,7 @@ public function testCheckDBNewLoggedIn()
|
|||
{
|
||||
unlink(self::$testDatastore);
|
||||
$this->assertFileNotExists(self::$testDatastore);
|
||||
new BookmarkFileService($this->conf, $this->history, $this->mutex, true);
|
||||
new BookmarkFileService($this->conf, $this->pluginManager, $this->history, $this->mutex, true);
|
||||
$this->assertFileExists(self::$testDatastore);
|
||||
|
||||
// ensure the correct data has been written
|
||||
|
@ -631,7 +714,7 @@ public function testCheckDBNewLoggedOut()
|
|||
{
|
||||
unlink(self::$testDatastore);
|
||||
$this->assertFileNotExists(self::$testDatastore);
|
||||
$db = new \FakeBookmarkService($this->conf, $this->history, $this->mutex, false);
|
||||
$db = new \FakeBookmarkService($this->conf, $this->pluginManager, $this->history, $this->mutex, false);
|
||||
$this->assertFileNotExists(self::$testDatastore);
|
||||
$this->assertInstanceOf(BookmarkArray::class, $db->getBookmarks());
|
||||
$this->assertCount(0, $db->getBookmarks());
|
||||
|
@ -664,13 +747,13 @@ public function testReadPrivateDB()
|
|||
*/
|
||||
public function testSave()
|
||||
{
|
||||
$testDB = new BookmarkFileService($this->conf, $this->history, $this->mutex, true);
|
||||
$testDB = new BookmarkFileService($this->conf, $this->pluginManager, $this->history, $this->mutex, true);
|
||||
$dbSize = $testDB->count();
|
||||
|
||||
$bookmark = new Bookmark();
|
||||
$testDB->add($bookmark);
|
||||
|
||||
$testDB = new BookmarkFileService($this->conf, $this->history, $this->mutex, true);
|
||||
$testDB = new BookmarkFileService($this->conf, $this->pluginManager, $this->history, $this->mutex, true);
|
||||
$this->assertEquals($dbSize + 1, $testDB->count());
|
||||
}
|
||||
|
||||
|
@ -680,7 +763,7 @@ public function testSave()
|
|||
public function testCountHiddenPublic()
|
||||
{
|
||||
$this->conf->set('privacy.hide_public_links', true);
|
||||
$linkDB = new BookmarkFileService($this->conf, $this->history, $this->mutex, false);
|
||||
$linkDB = new BookmarkFileService($this->conf, $this->pluginManager, $this->history, $this->mutex, false);
|
||||
|
||||
$this->assertEquals(0, $linkDB->count());
|
||||
}
|
||||
|
@ -807,7 +890,7 @@ public function testFilterString()
|
|||
$request = ['searchtags' => $tags];
|
||||
$this->assertEquals(
|
||||
2,
|
||||
count($this->privateLinkDB->search($request, null, true))
|
||||
count($this->privateLinkDB->search($request, null, true)->getBookmarks())
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -820,7 +903,7 @@ public function testFilterArray()
|
|||
$request = ['searchtags' => $tags];
|
||||
$this->assertEquals(
|
||||
2,
|
||||
count($this->privateLinkDB->search($request, null, true))
|
||||
count($this->privateLinkDB->search($request, null, true)->getBookmarks())
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -834,12 +917,12 @@ public function testHiddenTags()
|
|||
$request = ['searchtags' => $tags];
|
||||
$this->assertEquals(
|
||||
1,
|
||||
count($this->privateLinkDB->search($request, 'all', true))
|
||||
count($this->privateLinkDB->search($request, 'all', true)->getBookmarks())
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
0,
|
||||
count($this->publicLinkDB->search($request, 'public', true))
|
||||
count($this->publicLinkDB->search($request, 'public', true)->getBookmarks())
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -906,7 +989,13 @@ public function testFilterHashWithPrivateKey()
|
|||
$bookmark->addAdditionalContentEntry('private_key', $privateKey);
|
||||
$this->privateLinkDB->save();
|
||||
|
||||
$this->privateLinkDB = new BookmarkFileService($this->conf, $this->history, $this->mutex, false);
|
||||
$this->privateLinkDB = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$this->pluginManager,
|
||||
$this->history,
|
||||
$this->mutex,
|
||||
false
|
||||
);
|
||||
$bookmark = $this->privateLinkDB->findByHash($hash, $privateKey);
|
||||
|
||||
static::assertSame(6, $bookmark->getId());
|
||||
|
@ -1152,7 +1241,13 @@ public function testGetLatestWithSticky(): void
|
|||
public function testGetLatestEmptyDatastore(): void
|
||||
{
|
||||
unlink($this->conf->get('resource.datastore'));
|
||||
$this->publicLinkDB = new BookmarkFileService($this->conf, $this->history, $this->mutex, false);
|
||||
$this->publicLinkDB = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$this->pluginManager,
|
||||
$this->history,
|
||||
$this->mutex,
|
||||
false
|
||||
);
|
||||
|
||||
$bookmark = $this->publicLinkDB->getLatest();
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
use ReferenceLinkDB;
|
||||
use Shaarli\Config\ConfigManager;
|
||||
use Shaarli\History;
|
||||
use Shaarli\Plugin\PluginManager;
|
||||
use Shaarli\TestCase;
|
||||
|
||||
/**
|
||||
|
@ -32,19 +33,24 @@ class BookmarkFilterTest extends TestCase
|
|||
*/
|
||||
protected static $bookmarkService;
|
||||
|
||||
/** @var PluginManager */
|
||||
protected static $pluginManager;
|
||||
|
||||
/**
|
||||
* Instantiate linkFilter with ReferenceLinkDB data.
|
||||
*/
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
|
||||
$mutex = new NoMutex();
|
||||
$conf = new ConfigManager('tests/utils/config/configJson');
|
||||
$conf->set('resource.datastore', self::$testDatastore);
|
||||
static::$pluginManager = new PluginManager($conf);
|
||||
self::$refDB = new \ReferenceLinkDB();
|
||||
self::$refDB->write(self::$testDatastore);
|
||||
$history = new History('sandbox/history.php');
|
||||
self::$bookmarkService = new \FakeBookmarkService($conf, $history, $mutex, true);
|
||||
self::$linkFilter = new BookmarkFilter(self::$bookmarkService->getBookmarks(), $conf);
|
||||
self::$bookmarkService = new \FakeBookmarkService($conf, static::$pluginManager, $history, $mutex, true);
|
||||
self::$linkFilter = new BookmarkFilter(self::$bookmarkService->getBookmarks(), $conf, static::$pluginManager);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -178,61 +184,6 @@ public function testFilterUnknownTag()
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return bookmarks for a given day
|
||||
*/
|
||||
public function testFilterDay()
|
||||
{
|
||||
$this->assertEquals(
|
||||
4,
|
||||
count(self::$linkFilter->filter(BookmarkFilter::$FILTER_DAY, '20121206'))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return bookmarks for a given day
|
||||
*/
|
||||
public function testFilterDayRestrictedVisibility(): void
|
||||
{
|
||||
$this->assertEquals(
|
||||
3,
|
||||
count(self::$linkFilter->filter(BookmarkFilter::$FILTER_DAY, '20121206', false, BookmarkFilter::$PUBLIC))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 404 - day not found
|
||||
*/
|
||||
public function testFilterUnknownDay()
|
||||
{
|
||||
$this->assertEquals(
|
||||
0,
|
||||
count(self::$linkFilter->filter(BookmarkFilter::$FILTER_DAY, '19700101'))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use an invalid date format
|
||||
*/
|
||||
public function testFilterInvalidDayWithChars()
|
||||
{
|
||||
$this->expectException(\Exception::class);
|
||||
$this->expectExceptionMessageRegExp('/Invalid date format/');
|
||||
|
||||
self::$linkFilter->filter(BookmarkFilter::$FILTER_DAY, 'Rainy day, dream away');
|
||||
}
|
||||
|
||||
/**
|
||||
* Use an invalid date format
|
||||
*/
|
||||
public function testFilterInvalidDayDigits()
|
||||
{
|
||||
$this->expectException(\Exception::class);
|
||||
$this->expectExceptionMessageRegExp('/Invalid date format/');
|
||||
|
||||
self::$linkFilter->filter(BookmarkFilter::$FILTER_DAY, '20');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a link entry with its hash
|
||||
*/
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
use malkusch\lock\mutex\NoMutex;
|
||||
use Shaarli\Config\ConfigManager;
|
||||
use Shaarli\History;
|
||||
use Shaarli\Plugin\PluginManager;
|
||||
use Shaarli\TestCase;
|
||||
|
||||
/**
|
||||
|
@ -38,6 +39,9 @@ class BookmarkInitializerTest extends TestCase
|
|||
/** @var NoMutex */
|
||||
protected $mutex;
|
||||
|
||||
/** @var PluginManager */
|
||||
protected $pluginManager;
|
||||
|
||||
/**
|
||||
* Initialize an empty BookmarkFileService
|
||||
*/
|
||||
|
@ -51,8 +55,15 @@ public function setUp(): void
|
|||
copy('tests/utils/config/configJson.json.php', self::$testConf .'.json.php');
|
||||
$this->conf = new ConfigManager(self::$testConf);
|
||||
$this->conf->set('resource.datastore', self::$testDatastore);
|
||||
$this->pluginManager = new PluginManager($this->conf);
|
||||
$this->history = new History('sandbox/history.php');
|
||||
$this->bookmarkService = new BookmarkFileService($this->conf, $this->history, $this->mutex, true);
|
||||
$this->bookmarkService = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$this->pluginManager,
|
||||
$this->history,
|
||||
$this->mutex,
|
||||
true
|
||||
);
|
||||
|
||||
$this->initializer = new BookmarkInitializer($this->bookmarkService);
|
||||
}
|
||||
|
@ -64,7 +75,13 @@ public function testInitializeNotEmptyDataStore(): void
|
|||
{
|
||||
$refDB = new \ReferenceLinkDB();
|
||||
$refDB->write(self::$testDatastore);
|
||||
$this->bookmarkService = new BookmarkFileService($this->conf, $this->history, $this->mutex, true);
|
||||
$this->bookmarkService = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$this->pluginManager,
|
||||
$this->history,
|
||||
$this->mutex,
|
||||
true
|
||||
);
|
||||
$this->initializer = new BookmarkInitializer($this->bookmarkService);
|
||||
|
||||
$this->initializer->initialize();
|
||||
|
@ -95,7 +112,13 @@ public function testInitializeNotEmptyDataStore(): void
|
|||
$this->bookmarkService->save();
|
||||
|
||||
// Reload from file
|
||||
$this->bookmarkService = new BookmarkFileService($this->conf, $this->history, $this->mutex, true);
|
||||
$this->bookmarkService = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$this->pluginManager,
|
||||
$this->history,
|
||||
$this->mutex,
|
||||
true
|
||||
);
|
||||
$this->assertEquals($refDB->countLinks() + 3, $this->bookmarkService->count());
|
||||
|
||||
$bookmark = $this->bookmarkService->get(43);
|
||||
|
@ -126,7 +149,13 @@ public function testInitializeNotEmptyDataStore(): void
|
|||
public function testInitializeNonExistentDataStore(): void
|
||||
{
|
||||
$this->conf->set('resource.datastore', static::$testDatastore . '_empty');
|
||||
$this->bookmarkService = new BookmarkFileService($this->conf, $this->history, $this->mutex, true);
|
||||
$this->bookmarkService = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$this->pluginManager,
|
||||
$this->history,
|
||||
$this->mutex,
|
||||
true
|
||||
);
|
||||
|
||||
$this->initializer->initialize();
|
||||
|
||||
|
|
|
@ -245,6 +245,16 @@ public function testHtmlExtractNonExistentOgTag()
|
|||
$this->assertFalse(html_extract_tag('description', $html));
|
||||
}
|
||||
|
||||
public function testHtmlExtractDescriptionFromGoogleRealCase(): void
|
||||
{
|
||||
$html = 'id="gsr"><meta content="Fêtes de fin d\'année" property="twitter:title"><meta '.
|
||||
'content="Bonnes fêtes de fin d\'année ! #GoogleDoodle" property="twitter:description">'.
|
||||
'<meta content="Bonnes fêtes de fin d\'année ! #GoogleDoodle" property="og:description">'.
|
||||
'<meta content="summary_large_image" property="twitter:card"><meta co'
|
||||
;
|
||||
$this->assertSame('Bonnes fêtes de fin d\'année ! #GoogleDoodle', html_extract_tag('description', $html));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the header callback with valid value
|
||||
*/
|
||||
|
|
125
tests/bookmark/SearchResultTest.php
Normal file
125
tests/bookmark/SearchResultTest.php
Normal file
|
@ -0,0 +1,125 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shaarli\Bookmark;
|
||||
|
||||
use Shaarli\TestCase;
|
||||
|
||||
/**
|
||||
* Test SearchResult class.
|
||||
*/
|
||||
class SearchResultTest extends TestCase
|
||||
{
|
||||
/** Create a SearchResult without any pagination parameter. */
|
||||
public function testResultNoParameters(): void
|
||||
{
|
||||
$searchResult = SearchResult::getSearchResult($data = ['a', 'b', 'c', 'd', 'e', 'f']);
|
||||
|
||||
static::assertSame($data, $searchResult->getBookmarks());
|
||||
static::assertSame(6, $searchResult->getResultCount());
|
||||
static::assertSame(6, $searchResult->getTotalCount());
|
||||
static::assertSame(null, $searchResult->getLimit());
|
||||
static::assertSame(0, $searchResult->getOffset());
|
||||
static::assertSame(1, $searchResult->getPage());
|
||||
static::assertSame(1, $searchResult->getLastPage());
|
||||
static::assertTrue($searchResult->isFirstPage());
|
||||
static::assertTrue($searchResult->isLastPage());
|
||||
}
|
||||
|
||||
/** Create a SearchResult with only an offset parameter */
|
||||
public function testResultWithOffset(): void
|
||||
{
|
||||
$searchResult = SearchResult::getSearchResult(['a', 'b', 'c', 'd', 'e', 'f'], 2);
|
||||
|
||||
static::assertSame([2 => 'c', 3 => 'd', 4 => 'e', 5 => 'f'], $searchResult->getBookmarks());
|
||||
static::assertSame(4, $searchResult->getResultCount());
|
||||
static::assertSame(6, $searchResult->getTotalCount());
|
||||
static::assertSame(null, $searchResult->getLimit());
|
||||
static::assertSame(2, $searchResult->getOffset());
|
||||
static::assertSame(2, $searchResult->getPage());
|
||||
static::assertSame(2, $searchResult->getLastPage());
|
||||
static::assertFalse($searchResult->isFirstPage());
|
||||
static::assertTrue($searchResult->isLastPage());
|
||||
}
|
||||
|
||||
/** Create a SearchResult with only a limit parameter */
|
||||
public function testResultWithLimit(): void
|
||||
{
|
||||
$searchResult = SearchResult::getSearchResult(['a', 'b', 'c', 'd', 'e', 'f'], 0, 2);
|
||||
|
||||
static::assertSame([0 => 'a', 1 => 'b'], $searchResult->getBookmarks());
|
||||
static::assertSame(2, $searchResult->getResultCount());
|
||||
static::assertSame(6, $searchResult->getTotalCount());
|
||||
static::assertSame(2, $searchResult->getLimit());
|
||||
static::assertSame(0, $searchResult->getOffset());
|
||||
static::assertSame(1, $searchResult->getPage());
|
||||
static::assertSame(3, $searchResult->getLastPage());
|
||||
static::assertTrue($searchResult->isFirstPage());
|
||||
static::assertFalse($searchResult->isLastPage());
|
||||
}
|
||||
|
||||
/** Create a SearchResult with offset and limit parameters */
|
||||
public function testResultWithLimitAndOffset(): void
|
||||
{
|
||||
$searchResult = SearchResult::getSearchResult(['a', 'b', 'c', 'd', 'e', 'f'], 2, 2);
|
||||
|
||||
static::assertSame([2 => 'c', 3 => 'd'], $searchResult->getBookmarks());
|
||||
static::assertSame(2, $searchResult->getResultCount());
|
||||
static::assertSame(6, $searchResult->getTotalCount());
|
||||
static::assertSame(2, $searchResult->getLimit());
|
||||
static::assertSame(2, $searchResult->getOffset());
|
||||
static::assertSame(2, $searchResult->getPage());
|
||||
static::assertSame(3, $searchResult->getLastPage());
|
||||
static::assertFalse($searchResult->isFirstPage());
|
||||
static::assertFalse($searchResult->isLastPage());
|
||||
}
|
||||
|
||||
/** Create a SearchResult with offset and limit parameters displaying the last page */
|
||||
public function testResultWithLimitAndOffsetLastPage(): void
|
||||
{
|
||||
$searchResult = SearchResult::getSearchResult(['a', 'b', 'c', 'd', 'e', 'f'], 4, 2);
|
||||
|
||||
static::assertSame([4 => 'e', 5 => 'f'], $searchResult->getBookmarks());
|
||||
static::assertSame(2, $searchResult->getResultCount());
|
||||
static::assertSame(6, $searchResult->getTotalCount());
|
||||
static::assertSame(2, $searchResult->getLimit());
|
||||
static::assertSame(4, $searchResult->getOffset());
|
||||
static::assertSame(3, $searchResult->getPage());
|
||||
static::assertSame(3, $searchResult->getLastPage());
|
||||
static::assertFalse($searchResult->isFirstPage());
|
||||
static::assertTrue($searchResult->isLastPage());
|
||||
}
|
||||
|
||||
/** Create a SearchResult with offset and limit parameters out of bound (display the last page) */
|
||||
public function testResultWithLimitAndOffsetOutOfBounds(): void
|
||||
{
|
||||
$searchResult = SearchResult::getSearchResult(['a', 'b', 'c', 'd', 'e', 'f'], 12, 2);
|
||||
|
||||
static::assertSame([4 => 'e', 5 => 'f'], $searchResult->getBookmarks());
|
||||
static::assertSame(2, $searchResult->getResultCount());
|
||||
static::assertSame(6, $searchResult->getTotalCount());
|
||||
static::assertSame(2, $searchResult->getLimit());
|
||||
static::assertSame(-2, $searchResult->getOffset());
|
||||
static::assertSame(3, $searchResult->getPage());
|
||||
static::assertSame(3, $searchResult->getLastPage());
|
||||
static::assertFalse($searchResult->isFirstPage());
|
||||
static::assertTrue($searchResult->isLastPage());
|
||||
}
|
||||
|
||||
/** Create a SearchResult with offset and limit parameters out of bound (no result) */
|
||||
public function testResultWithLimitAndOffsetOutOfBoundsNoResult(): void
|
||||
{
|
||||
$searchResult = SearchResult::getSearchResult(['a', 'b', 'c', 'd', 'e', 'f'], 12, 2, true);
|
||||
|
||||
static::assertSame([], $searchResult->getBookmarks());
|
||||
static::assertSame(0, $searchResult->getResultCount());
|
||||
static::assertSame(6, $searchResult->getTotalCount());
|
||||
static::assertSame(2, $searchResult->getLimit());
|
||||
static::assertSame(12, $searchResult->getOffset());
|
||||
static::assertSame(7, $searchResult->getPage());
|
||||
static::assertSame(3, $searchResult->getLastPage());
|
||||
static::assertFalse($searchResult->isFirstPage());
|
||||
static::assertFalse($searchResult->isLastPage());
|
||||
}
|
||||
}
|
|
@ -43,11 +43,15 @@ class ContainerBuilderTest extends TestCase
|
|||
/** @var CookieManager */
|
||||
protected $cookieManager;
|
||||
|
||||
/** @var PluginManager */
|
||||
protected $pluginManager;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->conf = new ConfigManager('tests/utils/config/configJson');
|
||||
$this->sessionManager = $this->createMock(SessionManager::class);
|
||||
$this->cookieManager = $this->createMock(CookieManager::class);
|
||||
$this->pluginManager = $this->createMock(PluginManager::class);
|
||||
|
||||
$this->loginManager = $this->createMock(LoginManager::class);
|
||||
$this->loginManager->method('isLoggedIn')->willReturn(true);
|
||||
|
@ -57,6 +61,7 @@ public function setUp(): void
|
|||
$this->sessionManager,
|
||||
$this->cookieManager,
|
||||
$this->loginManager,
|
||||
$this->pluginManager,
|
||||
$this->createMock(LoggerInterface::class)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -40,10 +40,10 @@ protected function setUp(): void
|
|||
*/
|
||||
public function testConstruct()
|
||||
{
|
||||
new CachedPage(self::$testCacheDir, '', true);
|
||||
new CachedPage(self::$testCacheDir, '', false);
|
||||
new CachedPage(self::$testCacheDir, 'http://shaar.li/feed/rss', true);
|
||||
new CachedPage(self::$testCacheDir, 'http://shaar.li/feed/atom', false);
|
||||
new CachedPage(self::$testCacheDir, '', true, null);
|
||||
new CachedPage(self::$testCacheDir, '', false, null);
|
||||
new CachedPage(self::$testCacheDir, 'http://shaar.li/feed/rss', true, null);
|
||||
new CachedPage(self::$testCacheDir, 'http://shaar.li/feed/atom', false, null);
|
||||
$this->addToAssertionCount(1);
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,7 @@ public function testConstruct()
|
|||
*/
|
||||
public function testCache()
|
||||
{
|
||||
$page = new CachedPage(self::$testCacheDir, self::$url, true);
|
||||
$page = new CachedPage(self::$testCacheDir, self::$url, true, null);
|
||||
|
||||
$this->assertFileNotExists(self::$filename);
|
||||
$page->cache('<p>Some content</p>');
|
||||
|
@ -68,7 +68,7 @@ public function testCache()
|
|||
*/
|
||||
public function testShouldNotCache()
|
||||
{
|
||||
$page = new CachedPage(self::$testCacheDir, self::$url, false);
|
||||
$page = new CachedPage(self::$testCacheDir, self::$url, false, null);
|
||||
|
||||
$this->assertFileNotExists(self::$filename);
|
||||
$page->cache('<p>Some content</p>');
|
||||
|
@ -80,7 +80,7 @@ public function testShouldNotCache()
|
|||
*/
|
||||
public function testCachedVersion()
|
||||
{
|
||||
$page = new CachedPage(self::$testCacheDir, self::$url, true);
|
||||
$page = new CachedPage(self::$testCacheDir, self::$url, true, null);
|
||||
|
||||
$this->assertFileNotExists(self::$filename);
|
||||
$page->cache('<p>Some content</p>');
|
||||
|
@ -96,7 +96,7 @@ public function testCachedVersion()
|
|||
*/
|
||||
public function testCachedVersionNoFile()
|
||||
{
|
||||
$page = new CachedPage(self::$testCacheDir, self::$url, true);
|
||||
$page = new CachedPage(self::$testCacheDir, self::$url, true, null);
|
||||
|
||||
$this->assertFileNotExists(self::$filename);
|
||||
$this->assertEquals(
|
||||
|
@ -110,7 +110,7 @@ public function testCachedVersionNoFile()
|
|||
*/
|
||||
public function testNoCachedVersion()
|
||||
{
|
||||
$page = new CachedPage(self::$testCacheDir, self::$url, false);
|
||||
$page = new CachedPage(self::$testCacheDir, self::$url, false, null);
|
||||
|
||||
$this->assertFileNotExists(self::$filename);
|
||||
$this->assertEquals(
|
||||
|
@ -118,4 +118,43 @@ public function testNoCachedVersion()
|
|||
$page->cachedVersion()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a page's cached content within date period
|
||||
*/
|
||||
public function testCachedVersionInDatePeriod()
|
||||
{
|
||||
$period = new \DatePeriod(
|
||||
new \DateTime('yesterday'),
|
||||
new \DateInterval('P1D'),
|
||||
new \DateTime('tomorrow')
|
||||
);
|
||||
$page = new CachedPage(self::$testCacheDir, self::$url, true, $period);
|
||||
|
||||
$this->assertFileNotExists(self::$filename);
|
||||
$page->cache('<p>Some content</p>');
|
||||
$this->assertFileExists(self::$filename);
|
||||
$this->assertEquals(
|
||||
'<p>Some content</p>',
|
||||
$page->cachedVersion()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a page's cached content outside of date period
|
||||
*/
|
||||
public function testCachedVersionNotInDatePeriod()
|
||||
{
|
||||
$period = new \DatePeriod(
|
||||
new \DateTime('yesterday noon'),
|
||||
new \DateInterval('P1D'),
|
||||
new \DateTime('yesterday midnight')
|
||||
);
|
||||
$page = new CachedPage(self::$testCacheDir, self::$url, true, $period);
|
||||
|
||||
$this->assertFileNotExists(self::$filename);
|
||||
$page->cache('<p>Some content</p>');
|
||||
$this->assertFileExists(self::$filename);
|
||||
$this->assertNull($page->cachedVersion());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
use Shaarli\Config\ConfigManager;
|
||||
use Shaarli\Formatter\FormatterFactory;
|
||||
use Shaarli\History;
|
||||
use Shaarli\Plugin\PluginManager;
|
||||
use Shaarli\TestCase;
|
||||
|
||||
/**
|
||||
|
@ -55,8 +56,15 @@ public static function setUpBeforeClass(): void
|
|||
$refLinkDB->write(self::$testDatastore);
|
||||
$history = new History('sandbox/history.php');
|
||||
$factory = new FormatterFactory($conf, true);
|
||||
$pluginManager = new PluginManager($conf);
|
||||
self::$formatter = $factory->getFormatter();
|
||||
self::$bookmarkService = new BookmarkFileService($conf, $history, $mutex, true);
|
||||
self::$bookmarkService = new BookmarkFileService(
|
||||
$conf,
|
||||
$pluginManager,
|
||||
$history,
|
||||
$mutex,
|
||||
true
|
||||
);
|
||||
|
||||
self::$serverInfo = array(
|
||||
'HTTPS' => 'Off',
|
||||
|
|
|
@ -211,13 +211,17 @@ public function testFormatDescriptionWithSearchHighlight(): void
|
|||
$this->formatter = new BookmarkDefaultFormatter($this->conf, false);
|
||||
|
||||
$bookmark = new Bookmark();
|
||||
$bookmark->setDescription('This guide extends and expands on PSR-1, the basic coding standard.');
|
||||
$bookmark->setDescription(
|
||||
'This guide extends and expands on PSR-1, the basic coding standard.' . PHP_EOL .
|
||||
'https://www.php-fig.org/psr/psr-1/'
|
||||
);
|
||||
$bookmark->addAdditionalContentEntry(
|
||||
'search_highlight',
|
||||
['description' => [
|
||||
['start' => 0, 'end' => 10], // "This guide"
|
||||
['start' => 45, 'end' => 50], // basic
|
||||
['start' => 58, 'end' => 67], // standard.
|
||||
['start' => 84, 'end' => 87], // fig
|
||||
]]
|
||||
);
|
||||
|
||||
|
@ -226,7 +230,10 @@ public function testFormatDescriptionWithSearchHighlight(): void
|
|||
$this->assertSame(
|
||||
'<span class="search-highlight">This guide</span> extends and expands on PSR-1, the ' .
|
||||
'<span class="search-highlight">basic</span> coding ' .
|
||||
'<span class="search-highlight">standard.</span>',
|
||||
'<span class="search-highlight">standard.</span><br />' . PHP_EOL .
|
||||
'<a href="https://www.php-fig.org/psr/psr-1/">' .
|
||||
'https://www.php-<span class="search-highlight">fig</span>.org/psr/psr-1/' .
|
||||
'</a>',
|
||||
$link['description']
|
||||
);
|
||||
}
|
||||
|
|
|
@ -132,6 +132,49 @@ public function testFormatDescription()
|
|||
$this->assertEquals($description, $link['description']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure that the description is properly formatted by the default formatter.
|
||||
*/
|
||||
public function testFormatDescriptionWithSearchHighlight()
|
||||
{
|
||||
$description = 'This a <strong>description</strong>'. PHP_EOL;
|
||||
$description .= 'text https://sub.domain.tld?query=here&for=real#hash more text'. PHP_EOL;
|
||||
$description .= 'Also, there is an #hashtag added'. PHP_EOL;
|
||||
$description .= ' A N D KEEP SPACES ! '. PHP_EOL;
|
||||
$description .= 'And [yet another link](https://other.domain.tld)'. PHP_EOL;
|
||||
|
||||
$bookmark = new Bookmark();
|
||||
$bookmark->setDescription($description);
|
||||
$bookmark->addAdditionalContentEntry(
|
||||
'search_highlight',
|
||||
['description' => [
|
||||
['start' => 18, 'end' => 26], // cription
|
||||
['start' => 49, 'end' => 52], // sub
|
||||
['start' => 84, 'end' => 88], // hash
|
||||
['start' => 118, 'end' => 123], // hasht
|
||||
['start' => 203, 'end' => 215], // other.domain
|
||||
]]
|
||||
);
|
||||
|
||||
$link = $this->formatter->format($bookmark);
|
||||
|
||||
$description = '<div class="markdown"><p>';
|
||||
$description .= 'This a <strong>des<span class="search-highlight">cription</span></strong><br />' .
|
||||
PHP_EOL;
|
||||
$url = 'https://sub.domain.tld?query=here&for=real#hash';
|
||||
$highlighted = 'https://<span class="search-highlight">sub</span>.domain.tld';
|
||||
$highlighted .= '?query=here&for=real#<span class="search-highlight">hash</span>';
|
||||
$description .= 'text <a href="'. $url .'">'. $highlighted .'</a> more text<br />'. PHP_EOL;
|
||||
$description .= 'Also, there is an <a href="./add-tag/hashtag">#<span class="search-highlight">hasht</span>' .
|
||||
'ag</a> added<br />'. PHP_EOL;
|
||||
$description .= 'A N D KEEP SPACES !<br />' . PHP_EOL;
|
||||
$description .= 'And <a href="https://other.domain.tld">' .
|
||||
'<span class="search-highlight">yet another link</span></a>';
|
||||
$description .= '</p></div>';
|
||||
|
||||
$this->assertEquals($description, $link['description']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test formatting URL with an index_url set
|
||||
* It should prepend relative links.
|
||||
|
|
|
@ -62,7 +62,7 @@ public function testIndex(): void
|
|||
static::assertSame('privacy.hide_public_links', $assignedVariables['hide_public_links']);
|
||||
static::assertSame('api.enabled', $assignedVariables['api_enabled']);
|
||||
static::assertSame('api.secret', $assignedVariables['api_secret']);
|
||||
static::assertCount(5, $assignedVariables['languages']);
|
||||
static::assertCount(6, $assignedVariables['languages']);
|
||||
static::assertArrayHasKey('gd_enabled', $assignedVariables);
|
||||
static::assertSame('thumbnails.mode', $assignedVariables['thumbnails_mode']);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
use Shaarli\Bookmark\Bookmark;
|
||||
use Shaarli\Bookmark\BookmarkFilter;
|
||||
use Shaarli\Bookmark\SearchResult;
|
||||
use Shaarli\Config\ConfigManager;
|
||||
use Shaarli\Front\Exception\WrongTokenException;
|
||||
use Shaarli\Security\SessionManager;
|
||||
|
@ -100,11 +101,11 @@ public function testSaveRenameTagValid(): void
|
|||
->expects(static::once())
|
||||
->method('search')
|
||||
->with(['searchtags' => 'old-tag'], BookmarkFilter::$ALL, true)
|
||||
->willReturnCallback(function () use ($bookmark1, $bookmark2): array {
|
||||
->willReturnCallback(function () use ($bookmark1, $bookmark2): SearchResult {
|
||||
$bookmark1->expects(static::once())->method('renameTag')->with('old-tag', 'new-tag');
|
||||
$bookmark2->expects(static::once())->method('renameTag')->with('old-tag', 'new-tag');
|
||||
|
||||
return [$bookmark1, $bookmark2];
|
||||
return SearchResult::getSearchResult([$bookmark1, $bookmark2]);
|
||||
})
|
||||
;
|
||||
$this->container->bookmarkService
|
||||
|
@ -153,11 +154,11 @@ public function testSaveDeleteTagValid(): void
|
|||
->expects(static::once())
|
||||
->method('search')
|
||||
->with(['searchtags' => 'old-tag'], BookmarkFilter::$ALL, true)
|
||||
->willReturnCallback(function () use ($bookmark1, $bookmark2): array {
|
||||
->willReturnCallback(function () use ($bookmark1, $bookmark2): SearchResult {
|
||||
$bookmark1->expects(static::once())->method('deleteTag')->with('old-tag');
|
||||
$bookmark2->expects(static::once())->method('deleteTag')->with('old-tag');
|
||||
|
||||
return [$bookmark1, $bookmark2];
|
||||
return SearchResult::getSearchResult([$bookmark1, $bookmark2]);
|
||||
})
|
||||
;
|
||||
$this->container->bookmarkService
|
||||
|
|
|
@ -363,6 +363,7 @@ public function testDeleteBookmarkFromBookmarklet(): void
|
|||
$this->container->bookmarkService->method('get')->with('123')->willReturn(
|
||||
(new Bookmark())->setId(123)->setUrl('http://domain.tld')->setTitle('Title 123')
|
||||
);
|
||||
$this->container->bookmarkService->expects(static::once())->method('remove');
|
||||
|
||||
$this->container->formatterFactory = $this->createMock(FormatterFactory::class);
|
||||
$this->container->formatterFactory
|
||||
|
@ -379,6 +380,48 @@ public function testDeleteBookmarkFromBookmarklet(): void
|
|||
$result = $this->controller->deleteBookmark($request, $response);
|
||||
|
||||
static::assertSame(200, $result->getStatusCode());
|
||||
static::assertSame('<script>self.close();</script>', (string) $result->getBody('location'));
|
||||
static::assertSame('<script>self.close();</script>', (string) $result->getBody());
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete bookmark - from batch view
|
||||
*/
|
||||
public function testDeleteBookmarkFromBatch(): void
|
||||
{
|
||||
$parameters = [
|
||||
'id' => '123',
|
||||
'source' => 'batch',
|
||||
];
|
||||
|
||||
$request = $this->createMock(Request::class);
|
||||
$request
|
||||
->method('getParam')
|
||||
->willReturnCallback(function (string $key) use ($parameters): ?string {
|
||||
return $parameters[$key] ?? null;
|
||||
})
|
||||
;
|
||||
$response = new Response();
|
||||
|
||||
$this->container->bookmarkService->method('get')->with('123')->willReturn(
|
||||
(new Bookmark())->setId(123)->setUrl('http://domain.tld')->setTitle('Title 123')
|
||||
);
|
||||
$this->container->bookmarkService->expects(static::once())->method('remove');
|
||||
|
||||
$this->container->formatterFactory = $this->createMock(FormatterFactory::class);
|
||||
$this->container->formatterFactory
|
||||
->expects(static::once())
|
||||
->method('getFormatter')
|
||||
->willReturnCallback(function (): BookmarkFormatter {
|
||||
$formatter = $this->createMock(BookmarkFormatter::class);
|
||||
$formatter->method('format')->willReturn(['formatted']);
|
||||
|
||||
return $formatter;
|
||||
})
|
||||
;
|
||||
|
||||
$result = $this->controller->deleteBookmark($request, $response);
|
||||
|
||||
static::assertSame(204, $result->getStatusCode());
|
||||
static::assertEmpty((string) $result->getBody());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
use Shaarli\Bookmark\Bookmark;
|
||||
use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
|
||||
use Shaarli\Bookmark\SearchResult;
|
||||
use Shaarli\TestCase;
|
||||
use Shaarli\Thumbnailer;
|
||||
use Slim\Http\Request;
|
||||
|
@ -40,12 +41,12 @@ public function testIndex(): void
|
|||
$this->container->bookmarkService
|
||||
->expects(static::once())
|
||||
->method('search')
|
||||
->willReturn([
|
||||
->willReturn(SearchResult::getSearchResult([
|
||||
(new Bookmark())->setId(1)->setUrl('http://url1.tld')->setTitle('Title 1'),
|
||||
(new Bookmark())->setId(2)->setUrl('?abcdef')->setTitle('Note 1'),
|
||||
(new Bookmark())->setId(3)->setUrl('http://url2.tld')->setTitle('Title 2'),
|
||||
(new Bookmark())->setId(4)->setUrl('ftp://domain.tld', ['ftp'])->setTitle('FTP'),
|
||||
])
|
||||
]))
|
||||
;
|
||||
|
||||
$result = $this->controller->index($request, $response);
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
use Shaarli\Bookmark\Bookmark;
|
||||
use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
|
||||
use Shaarli\Bookmark\SearchResult;
|
||||
use Shaarli\Config\ConfigManager;
|
||||
use Shaarli\Security\LoginManager;
|
||||
use Shaarli\TestCase;
|
||||
|
@ -45,13 +46,15 @@ public function testIndexDefaultFirstPage(): void
|
|||
['searchtags' => '', 'searchterm' => ''],
|
||||
null,
|
||||
false,
|
||||
false
|
||||
false,
|
||||
false,
|
||||
['offset' => 0, 'limit' => 2]
|
||||
)
|
||||
->willReturn([
|
||||
->willReturn(SearchResult::getSearchResult([
|
||||
(new Bookmark())->setId(1)->setUrl('http://url1.tld')->setTitle('Title 1'),
|
||||
(new Bookmark())->setId(2)->setUrl('http://url2.tld')->setTitle('Title 2'),
|
||||
(new Bookmark())->setId(3)->setUrl('http://url3.tld')->setTitle('Title 3'),
|
||||
]
|
||||
], 0, 2)
|
||||
);
|
||||
|
||||
$this->container->sessionManager
|
||||
|
@ -119,13 +122,15 @@ public function testIndexDefaultSecondPage(): void
|
|||
['searchtags' => '', 'searchterm' => ''],
|
||||
null,
|
||||
false,
|
||||
false
|
||||
false,
|
||||
false,
|
||||
['offset' => 2, 'limit' => 2]
|
||||
)
|
||||
->willReturn([
|
||||
->willReturn(SearchResult::getSearchResult([
|
||||
(new Bookmark())->setId(1)->setUrl('http://url1.tld')->setTitle('Title 1'),
|
||||
(new Bookmark())->setId(2)->setUrl('http://url2.tld')->setTitle('Title 2'),
|
||||
(new Bookmark())->setId(3)->setUrl('http://url3.tld')->setTitle('Title 3'),
|
||||
])
|
||||
], 2, 2))
|
||||
;
|
||||
|
||||
$this->container->sessionManager
|
||||
|
@ -207,13 +212,15 @@ public function testIndexDefaultWithFilters(): void
|
|||
['searchtags' => 'abc@def', 'searchterm' => 'ghi jkl'],
|
||||
'private',
|
||||
false,
|
||||
true
|
||||
true,
|
||||
false,
|
||||
['offset' => 0, 'limit' => 2]
|
||||
)
|
||||
->willReturn([
|
||||
->willReturn(SearchResult::getSearchResult([
|
||||
(new Bookmark())->setId(1)->setUrl('http://url1.tld')->setTitle('Title 1'),
|
||||
(new Bookmark())->setId(2)->setUrl('http://url2.tld')->setTitle('Title 2'),
|
||||
(new Bookmark())->setId(3)->setUrl('http://url3.tld')->setTitle('Title 3'),
|
||||
])
|
||||
], 0, 2))
|
||||
;
|
||||
|
||||
$result = $this->controller->index($request, $response);
|
||||
|
@ -358,13 +365,13 @@ public function testThumbnailUpdateFromLinkList(): void
|
|||
$this->container->bookmarkService
|
||||
->expects(static::once())
|
||||
->method('search')
|
||||
->willReturn([
|
||||
->willReturn(SearchResult::getSearchResult([
|
||||
(new Bookmark())->setId(1)->setUrl('https://url1.tld')->setTitle('Title 1')->setThumbnail(false),
|
||||
$b1 = (new Bookmark())->setId(2)->setUrl('https://url2.tld')->setTitle('Title 2'),
|
||||
(new Bookmark())->setId(3)->setUrl('https://url3.tld')->setTitle('Title 3')->setThumbnail(false),
|
||||
$b2 = (new Bookmark())->setId(2)->setUrl('https://url4.tld')->setTitle('Title 4'),
|
||||
(new Bookmark())->setId(2)->setUrl('ftp://url5.tld', ['ftp'])->setTitle('Title 5'),
|
||||
])
|
||||
]))
|
||||
;
|
||||
$this->container->bookmarkService
|
||||
->expects(static::exactly(2))
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
namespace Shaarli\Front\Controller\Visitor;
|
||||
|
||||
use Shaarli\Bookmark\Bookmark;
|
||||
use Shaarli\Bookmark\SearchResult;
|
||||
use Shaarli\Feed\CachedPage;
|
||||
use Shaarli\TestCase;
|
||||
use Slim\Http\Request;
|
||||
|
@ -347,13 +348,15 @@ public function testValidRssControllerInvokeDefault(): void
|
|||
$request = $this->createMock(Request::class);
|
||||
$response = new Response();
|
||||
|
||||
$this->container->bookmarkService->expects(static::once())->method('search')->willReturn([
|
||||
(new Bookmark())->setId(1)->setCreated($dates[0])->setUrl('http://domain.tld/1'),
|
||||
(new Bookmark())->setId(2)->setCreated($dates[1])->setUrl('http://domain.tld/2'),
|
||||
(new Bookmark())->setId(3)->setCreated($dates[1])->setUrl('http://domain.tld/3'),
|
||||
(new Bookmark())->setId(4)->setCreated($dates[2])->setUrl('http://domain.tld/4'),
|
||||
(new Bookmark())->setId(5)->setCreated($dates[3])->setUrl('http://domain.tld/5'),
|
||||
]);
|
||||
$this->container->bookmarkService->expects(static::once())->method('search')->willReturn(
|
||||
SearchResult::getSearchResult([
|
||||
(new Bookmark())->setId(1)->setCreated($dates[0])->setUrl('http://domain.tld/1'),
|
||||
(new Bookmark())->setId(2)->setCreated($dates[1])->setUrl('http://domain.tld/2'),
|
||||
(new Bookmark())->setId(3)->setCreated($dates[1])->setUrl('http://domain.tld/3'),
|
||||
(new Bookmark())->setId(4)->setCreated($dates[2])->setUrl('http://domain.tld/4'),
|
||||
(new Bookmark())->setId(5)->setCreated($dates[3])->setUrl('http://domain.tld/5'),
|
||||
])
|
||||
);
|
||||
|
||||
$this->container->pageCacheManager
|
||||
->expects(static::once())
|
||||
|
@ -454,7 +457,9 @@ public function testValidRssControllerInvokeNoBookmark(): void
|
|||
$request = $this->createMock(Request::class);
|
||||
$response = new Response();
|
||||
|
||||
$this->container->bookmarkService->expects(static::once())->method('search')->willReturn([]);
|
||||
$this->container->bookmarkService
|
||||
->expects(static::once())->method('search')
|
||||
->willReturn(SearchResult::getSearchResult([]));
|
||||
|
||||
// Save RainTPL assigned variables
|
||||
$assignedVariables = [];
|
||||
|
@ -613,11 +618,13 @@ public function testSimpleRssWeekly(): void
|
|||
});
|
||||
$response = new Response();
|
||||
|
||||
$this->container->bookmarkService->expects(static::once())->method('search')->willReturn([
|
||||
(new Bookmark())->setId(1)->setCreated($dates[0])->setUrl('http://domain.tld/1'),
|
||||
(new Bookmark())->setId(2)->setCreated($dates[1])->setUrl('http://domain.tld/2'),
|
||||
(new Bookmark())->setId(3)->setCreated($dates[1])->setUrl('http://domain.tld/3'),
|
||||
]);
|
||||
$this->container->bookmarkService->expects(static::once())->method('search')->willReturn(
|
||||
SearchResult::getSearchResult([
|
||||
(new Bookmark())->setId(1)->setCreated($dates[0])->setUrl('http://domain.tld/1'),
|
||||
(new Bookmark())->setId(2)->setCreated($dates[1])->setUrl('http://domain.tld/2'),
|
||||
(new Bookmark())->setId(3)->setCreated($dates[1])->setUrl('http://domain.tld/3'),
|
||||
])
|
||||
);
|
||||
|
||||
// Save RainTPL assigned variables
|
||||
$assignedVariables = [];
|
||||
|
@ -674,11 +681,13 @@ public function testSimpleRssMonthly(): void
|
|||
});
|
||||
$response = new Response();
|
||||
|
||||
$this->container->bookmarkService->expects(static::once())->method('search')->willReturn([
|
||||
(new Bookmark())->setId(1)->setCreated($dates[0])->setUrl('http://domain.tld/1'),
|
||||
(new Bookmark())->setId(2)->setCreated($dates[1])->setUrl('http://domain.tld/2'),
|
||||
(new Bookmark())->setId(3)->setCreated($dates[1])->setUrl('http://domain.tld/3'),
|
||||
]);
|
||||
$this->container->bookmarkService->expects(static::once())->method('search')->willReturn(
|
||||
SearchResult::getSearchResult([
|
||||
(new Bookmark())->setId(1)->setCreated($dates[0])->setUrl('http://domain.tld/1'),
|
||||
(new Bookmark())->setId(2)->setCreated($dates[1])->setUrl('http://domain.tld/2'),
|
||||
(new Bookmark())->setId(3)->setCreated($dates[1])->setUrl('http://domain.tld/3'),
|
||||
])
|
||||
);
|
||||
|
||||
// Save RainTPL assigned variables
|
||||
$assignedVariables = [];
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
namespace Shaarli\Front\Controller\Visitor;
|
||||
|
||||
use Shaarli\Bookmark\Bookmark;
|
||||
use Shaarli\Bookmark\SearchResult;
|
||||
use Shaarli\Config\ConfigManager;
|
||||
use Shaarli\Front\Exception\ThumbnailsDisabledException;
|
||||
use Shaarli\TestCase;
|
||||
|
@ -50,17 +51,17 @@ public function testValidControllerInvokeDefault(): void
|
|||
$this->container->bookmarkService
|
||||
->expects(static::once())
|
||||
->method('search')
|
||||
->willReturnCallback(function (array $parameters, ?string $visibility): array {
|
||||
->willReturnCallback(function (array $parameters, ?string $visibility): SearchResult {
|
||||
// Visibility is set through the container, not the call
|
||||
static::assertNull($visibility);
|
||||
|
||||
// No query parameters
|
||||
if (count($parameters) === 0) {
|
||||
return [
|
||||
return SearchResult::getSearchResult([
|
||||
(new Bookmark())->setId(1)->setUrl('http://url.tld')->setThumbnail('thumb1'),
|
||||
(new Bookmark())->setId(2)->setUrl('http://url2.tld'),
|
||||
(new Bookmark())->setId(3)->setUrl('http://url3.tld')->setThumbnail('thumb2'),
|
||||
];
|
||||
]);
|
||||
}
|
||||
})
|
||||
;
|
||||
|
|
|
@ -93,6 +93,9 @@ public function testRender(): void
|
|||
|
||||
static::assertSame('templateName', $render);
|
||||
|
||||
static::assertSame('templateName', $this->assignedValues['_PAGE_']);
|
||||
static::assertSame('templateName', $this->assignedValues['template']);
|
||||
|
||||
static::assertSame(10, $this->assignedValues['linkcount']);
|
||||
static::assertSame(5, $this->assignedValues['privateLinkcount']);
|
||||
static::assertSame(['error'], $this->assignedValues['plugin_errors']);
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
namespace Shaarli\Helper;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use Shaarli\Bookmark\Bookmark;
|
||||
use Shaarli\TestCase;
|
||||
use Slim\Http\Request;
|
||||
|
@ -32,7 +34,7 @@ public function testExtractRequestedDateTime(
|
|||
string $type,
|
||||
string $input,
|
||||
?Bookmark $bookmark,
|
||||
\DateTimeInterface $expectedDateTime,
|
||||
DateTimeInterface $expectedDateTime,
|
||||
string $compareFormat = 'Ymd'
|
||||
): void {
|
||||
$dateTime = DailyPageHelper::extractRequestedDateTime($type, $input, $bookmark);
|
||||
|
@ -71,8 +73,8 @@ public function testGetFormatByTypeExceptionUnknownType(): void
|
|||
*/
|
||||
public function testGetStartDatesByType(
|
||||
string $type,
|
||||
\DateTimeImmutable $dateTime,
|
||||
\DateTimeInterface $expectedDateTime
|
||||
DateTimeImmutable $dateTime,
|
||||
DateTimeInterface $expectedDateTime
|
||||
): void {
|
||||
$startDateTime = DailyPageHelper::getStartDateTimeByType($type, $dateTime);
|
||||
|
||||
|
@ -84,7 +86,7 @@ public function testGetStartDatesByTypeExceptionUnknownType(): void
|
|||
$this->expectException(\Exception::class);
|
||||
$this->expectExceptionMessage('Unsupported daily format type');
|
||||
|
||||
DailyPageHelper::getStartDateTimeByType('nope', new \DateTimeImmutable());
|
||||
DailyPageHelper::getStartDateTimeByType('nope', new DateTimeImmutable());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -92,8 +94,8 @@ public function testGetStartDatesByTypeExceptionUnknownType(): void
|
|||
*/
|
||||
public function testGetEndDatesByType(
|
||||
string $type,
|
||||
\DateTimeImmutable $dateTime,
|
||||
\DateTimeInterface $expectedDateTime
|
||||
DateTimeImmutable $dateTime,
|
||||
DateTimeInterface $expectedDateTime
|
||||
): void {
|
||||
$endDateTime = DailyPageHelper::getEndDateTimeByType($type, $dateTime);
|
||||
|
||||
|
@ -105,7 +107,7 @@ public function testGetEndDatesByTypeExceptionUnknownType(): void
|
|||
$this->expectException(\Exception::class);
|
||||
$this->expectExceptionMessage('Unsupported daily format type');
|
||||
|
||||
DailyPageHelper::getEndDateTimeByType('nope', new \DateTimeImmutable());
|
||||
DailyPageHelper::getEndDateTimeByType('nope', new DateTimeImmutable());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -113,7 +115,7 @@ public function testGetEndDatesByTypeExceptionUnknownType(): void
|
|||
*/
|
||||
public function testGeDescriptionsByType(
|
||||
string $type,
|
||||
\DateTimeImmutable $dateTime,
|
||||
DateTimeImmutable $dateTime,
|
||||
string $expectedDescription
|
||||
): void {
|
||||
$description = DailyPageHelper::getDescriptionByType($type, $dateTime);
|
||||
|
@ -121,12 +123,25 @@ public function testGeDescriptionsByType(
|
|||
static::assertEquals($expectedDescription, $description);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getDescriptionsByTypeNotIncludeRelative
|
||||
*/
|
||||
public function testGeDescriptionsByTypeNotIncludeRelative(
|
||||
string $type,
|
||||
\DateTimeImmutable $dateTime,
|
||||
string $expectedDescription
|
||||
): void {
|
||||
$description = DailyPageHelper::getDescriptionByType($type, $dateTime, false);
|
||||
|
||||
static::assertEquals($expectedDescription, $description);
|
||||
}
|
||||
|
||||
public function getDescriptionByTypeExceptionUnknownType(): void
|
||||
{
|
||||
$this->expectException(\Exception::class);
|
||||
$this->expectExceptionMessage('Unsupported daily format type');
|
||||
|
||||
DailyPageHelper::getDescriptionByType('nope', new \DateTimeImmutable());
|
||||
DailyPageHelper::getDescriptionByType('nope', new DateTimeImmutable());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -146,6 +161,29 @@ public function testGeRssLengthsByTypeExceptionUnknownType(): void
|
|||
DailyPageHelper::getRssLengthByType('nope');
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getCacheDatePeriodByType
|
||||
*/
|
||||
public function testGetCacheDatePeriodByType(
|
||||
string $type,
|
||||
DateTimeImmutable $requested,
|
||||
DateTimeInterface $start,
|
||||
DateTimeInterface $end
|
||||
): void {
|
||||
$period = DailyPageHelper::getCacheDatePeriodByType($type, $requested);
|
||||
|
||||
static::assertEquals($start, $period->getStartDate());
|
||||
static::assertEquals($end, $period->getEndDate());
|
||||
}
|
||||
|
||||
public function testGetCacheDatePeriodByTypeExceptionUnknownType(): void
|
||||
{
|
||||
$this->expectException(\Exception::class);
|
||||
$this->expectExceptionMessage('Unsupported daily format type');
|
||||
|
||||
DailyPageHelper::getCacheDatePeriodByType('nope');
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testExtractRequestedType() test method.
|
||||
*/
|
||||
|
@ -216,9 +254,9 @@ public function getFormatsByType(): array
|
|||
public function getStartDatesByType(): array
|
||||
{
|
||||
return [
|
||||
[DailyPageHelper::DAY, new \DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-09 00:00:00')],
|
||||
[DailyPageHelper::WEEK, new \DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-05 00:00:00')],
|
||||
[DailyPageHelper::MONTH, new \DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-01 00:00:00')],
|
||||
[DailyPageHelper::DAY, new DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-09 00:00:00')],
|
||||
[DailyPageHelper::WEEK, new DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-05 00:00:00')],
|
||||
[DailyPageHelper::MONTH, new DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-01 00:00:00')],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -228,9 +266,9 @@ public function getStartDatesByType(): array
|
|||
public function getEndDatesByType(): array
|
||||
{
|
||||
return [
|
||||
[DailyPageHelper::DAY, new \DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-09 23:59:59')],
|
||||
[DailyPageHelper::WEEK, new \DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-11 23:59:59')],
|
||||
[DailyPageHelper::MONTH, new \DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-31 23:59:59')],
|
||||
[DailyPageHelper::DAY, new DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-09 23:59:59')],
|
||||
[DailyPageHelper::WEEK, new DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-11 23:59:59')],
|
||||
[DailyPageHelper::MONTH, new DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-31 23:59:59')],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -240,8 +278,22 @@ public function getEndDatesByType(): array
|
|||
public function getDescriptionsByType(): array
|
||||
{
|
||||
return [
|
||||
[DailyPageHelper::DAY, $date = new \DateTimeImmutable(), 'Today - ' . $date->format('F j, Y')],
|
||||
[DailyPageHelper::DAY, $date = new \DateTimeImmutable('-1 day'), 'Yesterday - ' . $date->format('F j, Y')],
|
||||
[DailyPageHelper::DAY, $date = new DateTimeImmutable(), 'Today - ' . $date->format('F j, Y')],
|
||||
[DailyPageHelper::DAY, $date = new DateTimeImmutable('-1 day'), 'Yesterday - ' . $date->format('F j, Y')],
|
||||
[DailyPageHelper::DAY, new DateTimeImmutable('2020-10-09 04:05:06'), 'October 9, 2020'],
|
||||
[DailyPageHelper::WEEK, new DateTimeImmutable('2020-10-09 04:05:06'), 'Week 41 (October 5, 2020)'],
|
||||
[DailyPageHelper::MONTH, new DateTimeImmutable('2020-10-09 04:05:06'), 'October, 2020'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testGeDescriptionsByTypeNotIncludeRelative() test method.
|
||||
*/
|
||||
public function getDescriptionsByTypeNotIncludeRelative(): array
|
||||
{
|
||||
return [
|
||||
[DailyPageHelper::DAY, $date = new \DateTimeImmutable(), $date->format('F j, Y')],
|
||||
[DailyPageHelper::DAY, $date = new \DateTimeImmutable('-1 day'), $date->format('F j, Y')],
|
||||
[DailyPageHelper::DAY, new \DateTimeImmutable('2020-10-09 04:05:06'), 'October 9, 2020'],
|
||||
[DailyPageHelper::WEEK, new \DateTimeImmutable('2020-10-09 04:05:06'), 'Week 41 (October 5, 2020)'],
|
||||
[DailyPageHelper::MONTH, new \DateTimeImmutable('2020-10-09 04:05:06'), 'October, 2020'],
|
||||
|
@ -249,7 +301,7 @@ public function getDescriptionsByType(): array
|
|||
}
|
||||
|
||||
/**
|
||||
* Data provider for testGetDescriptionsByType() test method.
|
||||
* Data provider for testGetRssLengthsByType() test method.
|
||||
*/
|
||||
public function getRssLengthsByType(): array
|
||||
{
|
||||
|
@ -259,4 +311,31 @@ public function getRssLengthsByType(): array
|
|||
[DailyPageHelper::MONTH],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testGetCacheDatePeriodByType() test method.
|
||||
*/
|
||||
public function getCacheDatePeriodByType(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
DailyPageHelper::DAY,
|
||||
new DateTimeImmutable('2020-10-09 04:05:06'),
|
||||
new \DateTime('2020-10-09 00:00:00'),
|
||||
new \DateTime('2020-10-09 23:59:59'),
|
||||
],
|
||||
[
|
||||
DailyPageHelper::WEEK,
|
||||
new DateTimeImmutable('2020-10-09 04:05:06'),
|
||||
new \DateTime('2020-10-05 00:00:00'),
|
||||
new \DateTime('2020-10-11 23:59:59'),
|
||||
],
|
||||
[
|
||||
DailyPageHelper::MONTH,
|
||||
new DateTimeImmutable('2020-10-09 04:05:06'),
|
||||
new \DateTime('2020-10-01 00:00:00'),
|
||||
new \DateTime('2020-10-31 23:59:59'),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ public function testFullRetrieval(): void
|
|||
$remoteCharset = 'utf-8';
|
||||
|
||||
$expectedResult = [
|
||||
'title' => $remoteTitle,
|
||||
'title' => trim($remoteTitle),
|
||||
'description' => $remoteDesc,
|
||||
'tags' => $remoteTags,
|
||||
];
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
use Shaarli\Formatter\BookmarkFormatter;
|
||||
use Shaarli\Formatter\FormatterFactory;
|
||||
use Shaarli\History;
|
||||
use Shaarli\Plugin\PluginManager;
|
||||
use Shaarli\TestCase;
|
||||
|
||||
require_once 'tests/utils/ReferenceLinkDB.php';
|
||||
|
@ -47,6 +48,9 @@ class BookmarkExportTest extends TestCase
|
|||
*/
|
||||
protected static $history;
|
||||
|
||||
/** @var PluginManager */
|
||||
protected static $pluginManager;
|
||||
|
||||
/**
|
||||
* @var NetscapeBookmarkUtils
|
||||
*/
|
||||
|
@ -63,7 +67,14 @@ public static function setUpBeforeClass(): void
|
|||
static::$refDb = new \ReferenceLinkDB();
|
||||
static::$refDb->write(static::$testDatastore);
|
||||
static::$history = new History('sandbox/history.php');
|
||||
static::$bookmarkService = new BookmarkFileService(static::$conf, static::$history, $mutex, true);
|
||||
static::$pluginManager = new PluginManager(static::$conf);
|
||||
static::$bookmarkService = new BookmarkFileService(
|
||||
static::$conf,
|
||||
static::$pluginManager,
|
||||
static::$history,
|
||||
$mutex,
|
||||
true
|
||||
);
|
||||
$factory = new FormatterFactory(static::$conf, true);
|
||||
static::$formatter = $factory->getFormatter('raw');
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
use Shaarli\Bookmark\BookmarkFilter;
|
||||
use Shaarli\Config\ConfigManager;
|
||||
use Shaarli\History;
|
||||
use Shaarli\Plugin\PluginManager;
|
||||
use Shaarli\TestCase;
|
||||
use Slim\Http\UploadedFile;
|
||||
|
||||
|
@ -71,6 +72,9 @@ class BookmarkImportTest extends TestCase
|
|||
*/
|
||||
protected $netscapeBookmarkUtils;
|
||||
|
||||
/** @var PluginManager */
|
||||
protected $pluginManager;
|
||||
|
||||
/**
|
||||
* @var string Save the current timezone.
|
||||
*/
|
||||
|
@ -99,7 +103,14 @@ protected function setUp(): void
|
|||
$this->conf->set('resource.page_cache', $this->pagecache);
|
||||
$this->conf->set('resource.datastore', self::$testDatastore);
|
||||
$this->history = new History(self::$historyFilePath);
|
||||
$this->bookmarkService = new BookmarkFileService($this->conf, $this->history, $mutex, true);
|
||||
$this->pluginManager = new PluginManager($this->conf);
|
||||
$this->bookmarkService = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$this->pluginManager,
|
||||
$this->history,
|
||||
$mutex,
|
||||
true
|
||||
);
|
||||
$this->netscapeBookmarkUtils = new NetscapeBookmarkUtils($this->bookmarkService, $this->conf, $this->history);
|
||||
}
|
||||
|
||||
|
|
|
@ -193,4 +193,27 @@ public function testFormatCssRuleInvalid()
|
|||
$result = default_colors_format_css_rule($data, '');
|
||||
$this->assertEmpty($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure that a new CSS file is generated when save_plugin_parameters hook is triggered.
|
||||
*/
|
||||
public function testHookSavePluginParameters(): void
|
||||
{
|
||||
$params = [
|
||||
'other1' => true,
|
||||
'DEFAULT_COLORS_BACKGROUND' => 'pink',
|
||||
'other2' => ['yep'],
|
||||
'DEFAULT_COLORS_DARK_MAIN' => '',
|
||||
];
|
||||
|
||||
hook_default_colors_save_plugin_parameters($params);
|
||||
$this->assertFileExists($file = 'sandbox/default_colors/default_colors.css');
|
||||
$content = file_get_contents($file);
|
||||
$expected = ':root {
|
||||
--background-color: pink;
|
||||
|
||||
}
|
||||
';
|
||||
$this->assertEquals($expected, $content);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
<?php
|
||||
|
||||
use Shaarli\Bookmark\Bookmark;
|
||||
|
||||
/**
|
||||
* Hook for test.
|
||||
*
|
||||
|
@ -27,3 +29,24 @@ function hook_test_error()
|
|||
{
|
||||
new Unknown();
|
||||
}
|
||||
|
||||
function test_register_routes(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'method' => 'GET',
|
||||
'route' => '/test',
|
||||
'callable' => 'getFunction',
|
||||
],
|
||||
[
|
||||
'method' => 'POST',
|
||||
'route' => '/custom',
|
||||
'callable' => 'postFunction',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
function hook_test_filter_search_entry(Bookmark $bookmark, array $context): bool
|
||||
{
|
||||
return $context['_result'];
|
||||
}
|
||||
|
|
12
tests/plugins/test_route_invalid/test_route_invalid.php
Normal file
12
tests/plugins/test_route_invalid/test_route_invalid.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
function test_route_invalid_register_routes(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'method' => 'GET',
|
||||
'route' => 'not a route',
|
||||
'callable' => 'getFunction',
|
||||
],
|
||||
];
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
use Shaarli\Bookmark\BookmarkServiceInterface;
|
||||
use Shaarli\Config\ConfigManager;
|
||||
use Shaarli\History;
|
||||
use Shaarli\Plugin\PluginManager;
|
||||
use Shaarli\TestCase;
|
||||
|
||||
|
||||
|
@ -51,7 +52,13 @@ protected function setUp(): void
|
|||
|
||||
copy('tests/utils/config/configJson.json.php', self::$configFile .'.json.php');
|
||||
$this->conf = new ConfigManager(self::$configFile);
|
||||
$this->bookmarkService = new BookmarkFileService($this->conf, $this->createMock(History::class), $mutex, true);
|
||||
$this->bookmarkService = new BookmarkFileService(
|
||||
$this->conf,
|
||||
$this->createMock(PluginManager::class),
|
||||
$this->createMock(History::class),
|
||||
$mutex,
|
||||
true
|
||||
);
|
||||
$this->updater = new Updater([], $this->bookmarkService, $this->conf, true);
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
</div>
|
||||
|
||||
{loop="$links"}
|
||||
{$batchId=$key}
|
||||
{include="editlink"}
|
||||
{/loop}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
{$batchId=isset($batchId) ? $batchId : ''}
|
||||
{if="empty($batch_mode)"}
|
||||
<!DOCTYPE html>
|
||||
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
|
||||
|
@ -10,7 +11,7 @@
|
|||
{ignore}Lil hack: when included in a loop in batch mode, `$value` is assigned by RainTPL with template vars.{/ignore}
|
||||
{function="extract($value) ? '' : ''"}
|
||||
{/if}
|
||||
<div id="editlinkform" class="edit-link-container" class="pure-g">
|
||||
<div id="editlinkform{$batchId}" class="edit-link-container" class="pure-g">
|
||||
<div class="pure-u-lg-1-5 pure-u-1-24"></div>
|
||||
<form method="post"
|
||||
name="linkform"
|
||||
|
@ -27,16 +28,16 @@ <h2 class="window-title">
|
|||
{/if}
|
||||
{if="!$link_is_new"}<div class="created-date">{'Created:'|t} {$link.created|format_date}</div>{/if}
|
||||
<div>
|
||||
<label for="lf_url">{'URL'|t}</label>
|
||||
<label for="lf_url{$batchId}">{'URL'|t}</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="text" name="lf_url" id="lf_url" value="{$link.url}" class="lf_input">
|
||||
<input type="text" name="lf_url" id="lf_url{$batchId}" value="{$link.url}" class="lf_input">
|
||||
</div>
|
||||
<div>
|
||||
<label for="lf_title">{'Title'|t}</label>
|
||||
<label for="lf_title{$batchId}">{'Title'|t}</label>
|
||||
</div>
|
||||
<div class="{$asyncLoadClass}">
|
||||
<input type="text" name="lf_title" id="lf_title" value="{$link.title}"
|
||||
<input type="text" name="lf_title" id="lf_title{$batchId}" value="{$link.title}"
|
||||
class="lf_input {if="!$async_metadata"}autofocus{/if}"
|
||||
>
|
||||
<div class="icon-container">
|
||||
|
@ -44,19 +45,19 @@ <h2 class="window-title">
|
|||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="lf_description">{'Description'|t}</label>
|
||||
<label for="lf_description{$batchId}">{'Description'|t}</label>
|
||||
</div>
|
||||
<div class="{if="$retrieve_description"}{$asyncLoadClass}{/if}">
|
||||
<textarea name="lf_description" id="lf_description" class="autofocus">{$link.description}</textarea>
|
||||
<textarea name="lf_description" id="lf_description{$batchId}" class="autofocus">{$link.description}</textarea>
|
||||
<div class="icon-container">
|
||||
<i class="loader"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="lf_tags">{'Tags'|t}</label>
|
||||
<label for="lf_tags{$batchId}">{'Tags'|t}</label>
|
||||
</div>
|
||||
<div class="{if="$retrieve_description"}{$asyncLoadClass}{/if}">
|
||||
<input type="text" name="lf_tags" id="lf_tags" value="{$link.tags}" class="lf_input autofocus"
|
||||
<input type="text" name="lf_tags" id="lf_tags{$batchId}" value="{$link.tags}" class="lf_input autofocus"
|
||||
data-list="{loop="$tags"}{$key}, {/loop}" data-multiple data-autofirst autocomplete="off" >
|
||||
<div class="icon-container">
|
||||
<i class="loader"></i>
|
||||
|
@ -64,11 +65,11 @@ <h2 class="window-title">
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<input type="checkbox" name="lf_private" id="lf_private"
|
||||
<input type="checkbox" name="lf_private" id="lf_private{$batchId}"
|
||||
{if="$link.private === true"}
|
||||
checked="checked"
|
||||
{/if}>
|
||||
<label for="lf_private">{'Private'|t}</label>
|
||||
<label for="lf_private{$batchId}">{'Private'|t}</label>
|
||||
</div>
|
||||
|
||||
{if="$formatter==='markdown'"}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
{/if}
|
||||
<link rel="search" type="application/opensearchdescription+xml" href="{$base_path}/open-search#"
|
||||
title="Shaarli search - {$shaarlititle}" />
|
||||
{if="! empty($links) && count($links) === 1"}
|
||||
{if="$template === 'linklist' && ! empty($links) && count($links) === 1"}
|
||||
{$link=reset($links)}
|
||||
<meta property="og:title" content="{$link.title}" />
|
||||
<meta property="og:type" content="article" />
|
||||
|
|
13
tpl/default/pluginscontent.html
Normal file
13
tpl/default/pluginscontent.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
|
||||
<head>
|
||||
{include="includes"}
|
||||
</head>
|
||||
<body>
|
||||
{include="page.header"}
|
||||
|
||||
{$content}
|
||||
|
||||
{include="page.footer"}
|
||||
</body>
|
||||
</html>
|
|
@ -56,11 +56,11 @@ <h3 class="window-subtitle">{'General'|t}</h3>
|
|||
|
||||
{include="server.requirements"}
|
||||
|
||||
<h3 class="window-subtitle">Version</h3>
|
||||
<h3 class="window-subtitle">{'Version'|t}</h3>
|
||||
|
||||
<div class="pure-g server-row">
|
||||
<div class="pure-u-lg-1-2 pure-u-1 server-label">
|
||||
<p>Current version</p>
|
||||
<p>{'Current version'|t}</p>
|
||||
</div>
|
||||
<div class="pure-u-lg-1-2 pure-u-1">
|
||||
<p>{$current_version}</p>
|
||||
|
@ -69,7 +69,7 @@ <h3 class="window-subtitle">Version</h3>
|
|||
|
||||
<div class="pure-g server-row">
|
||||
<div class="pure-u-lg-1-2 pure-u-1 server-label">
|
||||
<p>Latest release</p>
|
||||
<p>{'Latest release'|t}</p>
|
||||
</div>
|
||||
<div class="pure-u-lg-1-2 pure-u-1">
|
||||
<p>
|
||||
|
@ -80,11 +80,11 @@ <h3 class="window-subtitle">Version</h3>
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="window-subtitle">Thumbnails</h3>
|
||||
<h3 class="window-subtitle">{'Thumbnails'|t}</h3>
|
||||
|
||||
<div class="pure-g server-row">
|
||||
<div class="pure-u-lg-1-2 pure-u-1 server-label">
|
||||
<p>Thumbnails status</p>
|
||||
<p>{'Thumbnails status'|t}</p>
|
||||
</div>
|
||||
<div class="pure-u-lg-1-2 pure-u-1">
|
||||
<p>
|
||||
|
@ -107,17 +107,17 @@ <h3 class="window-subtitle">Thumbnails</h3>
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
<h3 class="window-subtitle">Cache</h3>
|
||||
<h3 class="window-subtitle">{'Cache'|t}</h3>
|
||||
|
||||
<div class="center tools-item">
|
||||
<a href="{$base_path}/admin/clear-cache?type=main">
|
||||
<span class="pure-button pure-u-lg-2-3 pure-u-3-4">Clear main cache</span>
|
||||
<span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Clear main cache'|t}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="center tools-item">
|
||||
<a href="{$base_path}/admin/clear-cache?type=thumbnails">
|
||||
<span class="pure-button pure-u-lg-2-3 pure-u-3-4">Clear thumbnails cache</span>
|
||||
<span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Clear thumbnails cache'|t}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
{if="is_file('data/user.css')"}<link type="text/css" rel="stylesheet" href="{$base_path}/data/user.css#" />{/if}
|
||||
<link rel="search" type="application/opensearchdescription+xml" href="{$base_path}/open-search#"
|
||||
title="Shaarli search - {$shaarlititle|htmlspecialchars}" />
|
||||
{if="! empty($links) && count($links) === 1"}
|
||||
{if="$template === 'linklist' && ! empty($links) && count($links) === 1"}
|
||||
{$link=reset($links)}
|
||||
<meta property="og:title" content="{$link.title}" />
|
||||
<meta property="og:type" content="article" />
|
||||
|
|
38
yarn.lock
38
yarn.lock
|
@ -1369,10 +1369,10 @@ bluebird@^3.5.5:
|
|||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
|
||||
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
|
||||
|
||||
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.4.0:
|
||||
version "4.11.9"
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828"
|
||||
integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==
|
||||
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9:
|
||||
version "4.12.0"
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"
|
||||
integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==
|
||||
|
||||
bn.js@^5.1.1:
|
||||
version "5.1.3"
|
||||
|
@ -1410,7 +1410,7 @@ braces@^3.0.1, braces@~3.0.2:
|
|||
dependencies:
|
||||
fill-range "^7.0.1"
|
||||
|
||||
brorand@^1.0.1:
|
||||
brorand@^1.0.1, brorand@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
|
||||
integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=
|
||||
|
@ -2130,17 +2130,17 @@ electron-to-chromium@^1.3.570:
|
|||
integrity sha512-Y6OCoVQgFQBP5py6A/06+yWxUZHDlNr/gNDGatjH8AZqXl8X0tE4LfjLJsXGz/JmWJz8a6K7bR1k+QzZ+k//fg==
|
||||
|
||||
elliptic@^6.5.3:
|
||||
version "6.5.3"
|
||||
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6"
|
||||
integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==
|
||||
version "6.5.4"
|
||||
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
|
||||
integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==
|
||||
dependencies:
|
||||
bn.js "^4.4.0"
|
||||
brorand "^1.0.1"
|
||||
bn.js "^4.11.9"
|
||||
brorand "^1.1.0"
|
||||
hash.js "^1.0.0"
|
||||
hmac-drbg "^1.0.0"
|
||||
inherits "^2.0.1"
|
||||
minimalistic-assert "^1.0.0"
|
||||
minimalistic-crypto-utils "^1.0.0"
|
||||
hmac-drbg "^1.0.1"
|
||||
inherits "^2.0.4"
|
||||
minimalistic-assert "^1.0.1"
|
||||
minimalistic-crypto-utils "^1.0.1"
|
||||
|
||||
emoji-regex@^7.0.1:
|
||||
version "7.0.3"
|
||||
|
@ -2917,7 +2917,7 @@ he@^1.2.0:
|
|||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||
|
||||
hmac-drbg@^1.0.0:
|
||||
hmac-drbg@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
|
||||
integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=
|
||||
|
@ -3052,9 +3052,9 @@ inherits@2.0.3:
|
|||
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
|
||||
|
||||
ini@^1.3.4, ini@^1.3.5:
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
|
||||
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
|
||||
version "1.3.7"
|
||||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84"
|
||||
integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==
|
||||
|
||||
interpret@^1.4.0:
|
||||
version "1.4.0"
|
||||
|
@ -3714,7 +3714,7 @@ minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
|
||||
integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
|
||||
|
||||
minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
|
||||
minimalistic-crypto-utils@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
|
||||
integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
|
||||
|
|
Loading…
Reference in a new issue