Merge pull request #1727 from ArthurHoaro/feature/readit
New Core Plugin: ReadItLater
This commit is contained in:
commit
3665594d36
21 changed files with 709 additions and 16 deletions
|
@ -85,6 +85,7 @@ public function fromArray(array $data, string $tagsSeparator = ' '): Bookmark
|
||||||
$this->updated = $data['updated'];
|
$this->updated = $data['updated'];
|
||||||
}
|
}
|
||||||
$this->private = ($data['private'] ?? false) ? true : false;
|
$this->private = ($data['private'] ?? false) ? true : false;
|
||||||
|
$this->additionalContent = $data['additional_content'] ?? [];
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
@ -483,7 +484,7 @@ public function getAdditionalContent(): array
|
||||||
*
|
*
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function addAdditionalContentEntry(string $key, $value): self
|
public function setAdditionalContentEntry(string $key, $value): self
|
||||||
{
|
{
|
||||||
$this->additionalContent[$key] = $value;
|
$this->additionalContent[$key] = $value;
|
||||||
|
|
||||||
|
|
|
@ -290,7 +290,7 @@ private function filterFulltext(string $searchterms, string $visibility = 'all')
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($found !== false) {
|
if ($found !== false) {
|
||||||
$bookmark->addAdditionalContentEntry(
|
$bookmark->setAdditionalContentEntry(
|
||||||
'search_highlight',
|
'search_highlight',
|
||||||
$this->postProcessFoundPositions($lengths, $foundPositions)
|
$this->postProcessFoundPositions($lengths, $foundPositions)
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
namespace Shaarli\Formatter;
|
namespace Shaarli\Formatter;
|
||||||
|
|
||||||
|
use Shaarli\Bookmark\Bookmark;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class BookmarkDefaultFormatter
|
* Class BookmarkDefaultFormatter
|
||||||
*
|
*
|
||||||
|
@ -145,6 +147,18 @@ protected function formatThumbnail($bookmark)
|
||||||
return escape($bookmark->getThumbnail());
|
return escape($bookmark->getThumbnail());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
protected function formatAdditionalContent(Bookmark $bookmark): array
|
||||||
|
{
|
||||||
|
$additionalContent = parent::formatAdditionalContent($bookmark);
|
||||||
|
|
||||||
|
unset($additionalContent['search_highlight']);
|
||||||
|
|
||||||
|
return $additionalContent;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Insert search highlight token in provided field content based on a list of search result positions
|
* Insert search highlight token in provided field content based on a list of search result positions
|
||||||
*
|
*
|
||||||
|
|
|
@ -95,6 +95,7 @@ public function format($bookmark)
|
||||||
$out['updated'] = $this->formatUpdated($bookmark);
|
$out['updated'] = $this->formatUpdated($bookmark);
|
||||||
$out['timestamp'] = $this->formatCreatedTimestamp($bookmark);
|
$out['timestamp'] = $this->formatCreatedTimestamp($bookmark);
|
||||||
$out['updated_timestamp'] = $this->formatUpdatedTimestamp($bookmark);
|
$out['updated_timestamp'] = $this->formatUpdatedTimestamp($bookmark);
|
||||||
|
$out['additional_content'] = $this->formatAdditionalContent($bookmark);
|
||||||
|
|
||||||
return $out;
|
return $out;
|
||||||
}
|
}
|
||||||
|
@ -349,6 +350,18 @@ protected function formatUpdatedTimestamp(Bookmark $bookmark)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format bookmark's additional content
|
||||||
|
*
|
||||||
|
* @param Bookmark $bookmark instance
|
||||||
|
*
|
||||||
|
* @return mixed[]
|
||||||
|
*/
|
||||||
|
protected function formatAdditionalContent(Bookmark $bookmark): array
|
||||||
|
{
|
||||||
|
return $bookmark->getAdditionalContent();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format tag list, e.g. remove private tags if the user is not logged in.
|
* Format tag list, e.g. remove private tags if the user is not logged in.
|
||||||
* TODO: this method is called multiple time to format tags, the result should be cached.
|
* TODO: this method is called multiple time to format tags, the result should be cached.
|
||||||
|
|
|
@ -194,7 +194,7 @@ public function sharePrivate(Request $request, Response $response, array $args):
|
||||||
|
|
||||||
if (empty($bookmark->getAdditionalContentEntry('private_key'))) {
|
if (empty($bookmark->getAdditionalContentEntry('private_key'))) {
|
||||||
$privateKey = bin2hex(random_bytes(16));
|
$privateKey = bin2hex(random_bytes(16));
|
||||||
$bookmark->addAdditionalContentEntry('private_key', $privateKey);
|
$bookmark->setAdditionalContentEntry('private_key', $privateKey);
|
||||||
$this->container->bookmarkService->set($bookmark);
|
$this->container->bookmarkService->set($bookmark);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -343,6 +343,8 @@ protected function loadFilterSearchEntryHooks(): void
|
||||||
* Checks whether provided input is valid to register a new route.
|
* Checks whether provided input is valid to register a new route.
|
||||||
* It must contain keys `method`, `route`, `callable` (all strings).
|
* It must contain keys `method`, `route`, `callable` (all strings).
|
||||||
*
|
*
|
||||||
|
* We do not check the format because Slim routes support regexes.
|
||||||
|
*
|
||||||
* @param string[] $input
|
* @param string[] $input
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
|
@ -356,10 +358,6 @@ protected static function validateRouteRegistration(array $input): bool
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!array_key_exists('route', $input) || !preg_match('#^[a-z\d/\.\-_]+$#', $input['route'])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!array_key_exists('callable', $input)) {
|
if (!array_key_exists('callable', $input)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,7 @@
|
||||||
"Shaarli\\Plugin\\": "application/plugin",
|
"Shaarli\\Plugin\\": "application/plugin",
|
||||||
"Shaarli\\Plugin\\Exception\\": "application/plugin/exception",
|
"Shaarli\\Plugin\\Exception\\": "application/plugin/exception",
|
||||||
"Shaarli\\Plugin\\Wallabag\\": "plugins/wallabag",
|
"Shaarli\\Plugin\\Wallabag\\": "plugins/wallabag",
|
||||||
|
"Shaarli\\Plugin\\ReadItLater\\": "plugins/readitlater",
|
||||||
"Shaarli\\Render\\": "application/render",
|
"Shaarli\\Render\\": "application/render",
|
||||||
"Shaarli\\Security\\": "application/security",
|
"Shaarli\\Security\\": "application/security",
|
||||||
"Shaarli\\Updater\\": "application/updater",
|
"Shaarli\\Updater\\": "application/updater",
|
||||||
|
|
|
@ -53,6 +53,7 @@ Usage of each plugin is documented in it's README file:
|
||||||
* [`playvideos`](https://github.com/shaarli/Shaarli/blob/master/plugins/playvideos/README.md): Add a button in the toolbar allowing to watch all videos.
|
* [`playvideos`](https://github.com/shaarli/Shaarli/blob/master/plugins/playvideos/README.md): Add a button in the toolbar allowing to watch all videos.
|
||||||
* `pubsubhubbub`: Enable PubSubHubbub feed publishing
|
* `pubsubhubbub`: Enable PubSubHubbub feed publishing
|
||||||
* `qrcode`: For each Shaare, add a QRCode icon.
|
* `qrcode`: For each Shaare, add a QRCode icon.
|
||||||
|
* `readitlater`: Mark bookmarks to read them later, with bookmark list highlight and filter.
|
||||||
* [`wallabag`](https://github.com/shaarli/Shaarli/blob/master/plugins/wallabag/README.md): For each Shaare, add a Wallabag icon to save it in your instance.
|
* [`wallabag`](https://github.com/shaarli/Shaarli/blob/master/plugins/wallabag/README.md): For each Shaare, add a Wallabag icon to save it in your instance.
|
||||||
|
|
||||||
|
|
||||||
|
|
46
plugins/readitlater/ReadItLaterController.php
Normal file
46
plugins/readitlater/ReadItLaterController.php
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shaarli\Plugin\ReadItLater;
|
||||||
|
|
||||||
|
use Shaarli\Front\Controller\Admin\ShaarliAdminController;
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
class ReadItLaterController extends ShaarliAdminController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* GET /plugin/readitlater/bookmarks
|
||||||
|
*/
|
||||||
|
public function toggleFilterBookmarkList(Request $request, Response $response): Response
|
||||||
|
{
|
||||||
|
$this->container->sessionManager->setSessionParameter(
|
||||||
|
'readitlater-only',
|
||||||
|
!$this->container->sessionManager->getSessionParameter('readitlater-only', false)
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->redirectFromReferer($request, $response, ['readitlater']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /plugin/readitlater/toggle/:id
|
||||||
|
*/
|
||||||
|
public function toggleBookmark(Request $request, Response $response, array $args): Response
|
||||||
|
{
|
||||||
|
if (!array_key_exists('id', $args) || !$this->container->bookmarkService->exists((int) $args['id'])) {
|
||||||
|
$this->saveErrorMessage('Invalid ID provided.');
|
||||||
|
|
||||||
|
return $this->redirectFromReferer($request, $response, ['readitlater']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$bookmark = $this->container->bookmarkService->get((int) $args['id']);
|
||||||
|
$bookmark->setAdditionalContentEntry(
|
||||||
|
'readitlater',
|
||||||
|
!$bookmark->getAdditionalContentEntry('readitlater', false)
|
||||||
|
);
|
||||||
|
$this->container->bookmarkService->save();
|
||||||
|
|
||||||
|
return $this->redirectFromReferer($request, $response, ['readitlater']);
|
||||||
|
}
|
||||||
|
}
|
21
plugins/readitlater/readitlater.default.css
Normal file
21
plugins/readitlater/readitlater.default.css
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
.linklist-item.readitlater-unread::after {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 51;
|
||||||
|
background: red;
|
||||||
|
width: 2px;
|
||||||
|
height: 100%;
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
.readitlater-unread .label-unread {
|
||||||
|
color: red;
|
||||||
|
border: 1px solid red;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.readitlater-unread .readitlater-toggle .fa-eye-slash {
|
||||||
|
color: red;
|
||||||
|
}
|
14
plugins/readitlater/readitlater.default.js
Normal file
14
plugins/readitlater/readitlater.default.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
(() => {
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const unreadLinks = document.querySelectorAll('.readitlater-unread');
|
||||||
|
if (unreadLinks) {
|
||||||
|
const unreadLabel = document.createElement('span');
|
||||||
|
unreadLabel.className = 'label label-unread';
|
||||||
|
unreadLabel.innerHTML = ' To Read';
|
||||||
|
[...unreadLinks].forEach((element) => {
|
||||||
|
const button = unreadLabel.cloneNode(true);
|
||||||
|
element.querySelector('.linklist-item-editbuttons').prepend(button);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
3
plugins/readitlater/readitlater.meta
Normal file
3
plugins/readitlater/readitlater.meta
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
description="Mark bookmarks to read them later, with bookmark list highlight and filter."
|
||||||
|
parameters="READITLATER_DEFAULT_CHECK"
|
||||||
|
parameter.READITLATER_DEFAULT_CHECK="By default, mark new bookmarks to read them later (with <code>1</code>) or not (with <code>0</code>)"
|
158
plugins/readitlater/readitlater.php
Normal file
158
plugins/readitlater/readitlater.php
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Shaarli\Bookmark\Bookmark;
|
||||||
|
use Shaarli\Config\ConfigManager;
|
||||||
|
use Shaarli\Plugin\PluginManager;
|
||||||
|
|
||||||
|
function readitlater_register_routes(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'method' => 'GET',
|
||||||
|
'route' => '/toggle-filter',
|
||||||
|
'callable' => 'Shaarli\Plugin\ReadItLater\ReadItLaterController:toggleFilterBookmarkList',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'method' => 'GET',
|
||||||
|
'route' => '/toggle/{id:[\d]+}',
|
||||||
|
'callable' => 'Shaarli\Plugin\ReadItLater\ReadItLaterController:toggleBookmark',
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes: add plugin CSS file
|
||||||
|
*/
|
||||||
|
function hook_readitlater_render_includes(array $data, ConfigManager $conf): array
|
||||||
|
{
|
||||||
|
if (!($data['_LOGGEDIN_'] ?? false) || $conf->get('resource.theme') !== 'default') {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data['css_files'][] = PluginManager::$PLUGINS_PATH . '/readitlater/readitlater.default.css';
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Footer: add plugin JS file
|
||||||
|
*/
|
||||||
|
function hook_readitlater_render_footer(array $data, ConfigManager $conf): array
|
||||||
|
{
|
||||||
|
if (!($data['_LOGGEDIN_'] ?? false) || $conf->get('resource.theme') !== 'default') {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data['js_files'][] = PluginManager::$PLUGINS_PATH . '/readitlater/readitlater.default.js';
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edit link: add the 'Read it later' checkbox only for bookmark creation.
|
||||||
|
* It doesn't seem useful to add it on edit mode, because it can be toggled from the linklist.
|
||||||
|
*/
|
||||||
|
function hook_readitlater_render_editlink(array $data, ConfigManager $conf): array
|
||||||
|
{
|
||||||
|
if (!$data['link_is_new']) {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$default = filter_var($conf->get('plugins.READITLATER_DEFAULT_CHECK', false), FILTER_VALIDATE_BOOLEAN);
|
||||||
|
|
||||||
|
// Load HTML into a string
|
||||||
|
$html = file_get_contents(PluginManager::$PLUGINS_PATH . '/readitlater/readitlater_editlink.html');
|
||||||
|
|
||||||
|
// Replace value in HTML if it exists in $data
|
||||||
|
$html = sprintf($html, $default ? 'checked' : '');
|
||||||
|
|
||||||
|
// field_plugin
|
||||||
|
$data['edit_link_plugin'][] = $html;
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save link: if the flag is already defined, do nothing, otherwise rely on the checkbox value.
|
||||||
|
*/
|
||||||
|
function hook_readitlater_save_link(array $data): array
|
||||||
|
{
|
||||||
|
if (array_key_exists('readitlater', $data['additional_content'] ?? [])) {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data['additional_content']['readitlater'] = !!($_POST['readitlater'] ?? false);
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Linklist:
|
||||||
|
* - no effect for logged out users
|
||||||
|
* - if the flag is set to true, we add the readitlater class to format the bookmark
|
||||||
|
* - otherwise we only add the toggle button
|
||||||
|
* - also include a filter to display all bookmark to read
|
||||||
|
*/
|
||||||
|
function hook_readitlater_render_linklist(array $data, ConfigManager $conf): array
|
||||||
|
{
|
||||||
|
if (!($data['_LOGGEDIN_'] ?? false)) {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$basePath = $data['_BASE_PATH_'] ?? __DIR__ . '/../../';
|
||||||
|
$buttonHtml = file_get_contents(PluginManager::$PLUGINS_PATH . '/readitlater/readitlater_button.html');
|
||||||
|
$toggleUrl = $basePath . '/plugin/readitlater/toggle/';
|
||||||
|
|
||||||
|
// Display a toggle icon for each link and a label for unread links
|
||||||
|
foreach ($data['links'] as &$link) {
|
||||||
|
$isUnread = $link['additional_content']['readitlater'] ?? false;
|
||||||
|
$link['link_plugin'][] = sprintf(
|
||||||
|
$buttonHtml,
|
||||||
|
$toggleUrl,
|
||||||
|
$link['id'],
|
||||||
|
$isUnread ? t('Mark as Read') : t('Read it later'),
|
||||||
|
readitlater_get_icon($conf, $isUnread)
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($isUnread) {
|
||||||
|
$link['class'] = ($link['class'] ?? '') . ' readitlater-unread ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$data['action_plugin'][] = [
|
||||||
|
'attr' => [
|
||||||
|
'href' => $basePath . '/plugin/readitlater/toggle-filter',
|
||||||
|
'title' => t('Filter ReadItLater bookmarks'),
|
||||||
|
],
|
||||||
|
'on' => $_SESSION['readitlater-only'] ?? false,
|
||||||
|
'html' => readitlater_get_icon($conf, true),
|
||||||
|
];
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If search through only readitlater entries is enabled, add custom filter.
|
||||||
|
*/
|
||||||
|
function hook_readitlater_filter_search_entry(Bookmark $bookmark, array $context): bool
|
||||||
|
{
|
||||||
|
if (($_SESSION['readitlater-only'] ?? false) !== true) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $bookmark->getAdditionalContentEntry('readitlater') === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get ForkAwesome icon for the default theme, failback on text.
|
||||||
|
*/
|
||||||
|
function readitlater_get_icon(ConfigManager $conf, bool $isUnread): string
|
||||||
|
{
|
||||||
|
if ($conf->get('resource.theme') === 'default') {
|
||||||
|
return '<i class="fa fa-eye' . ($isUnread ? '-slash' : '') . '" aria-hidden="true"></i>';
|
||||||
|
} else {
|
||||||
|
return $isUnread ? 'Mark as Read' : 'Read it later';
|
||||||
|
}
|
||||||
|
}
|
3
plugins/readitlater/readitlater_button.html
Normal file
3
plugins/readitlater/readitlater_button.html
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<a href="%s%d" class="readitlater-toggle" title="%s">
|
||||||
|
<span class="readitlater-icon">%s</span>
|
||||||
|
</a>
|
1
plugins/readitlater/readitlater_editlink.html
Normal file
1
plugins/readitlater/readitlater_editlink.html
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<input type="checkbox" name="readitlater" id="readitlater" %s /><label for="readitlater"> Read it later</label><br>
|
|
@ -988,7 +988,7 @@ public function testFilterHashWithPrivateKey()
|
||||||
$privateKey = 'this is usually auto generated';
|
$privateKey = 'this is usually auto generated';
|
||||||
|
|
||||||
$bookmark = $this->privateLinkDB->findByHash($hash);
|
$bookmark = $this->privateLinkDB->findByHash($hash);
|
||||||
$bookmark->addAdditionalContentEntry('private_key', $privateKey);
|
$bookmark->setAdditionalContentEntry('private_key', $privateKey);
|
||||||
$this->privateLinkDB->save();
|
$this->privateLinkDB->save();
|
||||||
|
|
||||||
$this->privateLinkDB = new BookmarkFileService(
|
$this->privateLinkDB = new BookmarkFileService(
|
||||||
|
|
|
@ -184,7 +184,7 @@ public function testFormatTitleHtmlWithSearchHighlight(): void
|
||||||
|
|
||||||
$bookmark = new Bookmark();
|
$bookmark = new Bookmark();
|
||||||
$bookmark->setTitle('PSR-2: Coding Style Guide');
|
$bookmark->setTitle('PSR-2: Coding Style Guide');
|
||||||
$bookmark->addAdditionalContentEntry(
|
$bookmark->setAdditionalContentEntry(
|
||||||
'search_highlight',
|
'search_highlight',
|
||||||
['title' => [
|
['title' => [
|
||||||
['start' => 0, 'end' => 5], // "psr-2"
|
['start' => 0, 'end' => 5], // "psr-2"
|
||||||
|
@ -215,7 +215,7 @@ public function testFormatDescriptionWithSearchHighlight(): void
|
||||||
'This guide extends and expands on PSR-1, the basic coding standard.' . PHP_EOL .
|
'This guide extends and expands on PSR-1, the basic coding standard.' . PHP_EOL .
|
||||||
'https://www.php-fig.org/psr/psr-1/'
|
'https://www.php-fig.org/psr/psr-1/'
|
||||||
);
|
);
|
||||||
$bookmark->addAdditionalContentEntry(
|
$bookmark->setAdditionalContentEntry(
|
||||||
'search_highlight',
|
'search_highlight',
|
||||||
['description' => [
|
['description' => [
|
||||||
['start' => 0, 'end' => 10], // "This guide"
|
['start' => 0, 'end' => 10], // "This guide"
|
||||||
|
@ -247,7 +247,7 @@ public function testFormatUrlHtmlWithSearchHighlight(): void
|
||||||
|
|
||||||
$bookmark = new Bookmark();
|
$bookmark = new Bookmark();
|
||||||
$bookmark->setUrl('http://www.php-fig.org/psr/psr-2/');
|
$bookmark->setUrl('http://www.php-fig.org/psr/psr-2/');
|
||||||
$bookmark->addAdditionalContentEntry(
|
$bookmark->setAdditionalContentEntry(
|
||||||
'search_highlight',
|
'search_highlight',
|
||||||
['url' => [
|
['url' => [
|
||||||
['start' => 0, 'end' => 4], // http
|
['start' => 0, 'end' => 4], // http
|
||||||
|
@ -275,7 +275,7 @@ public function testFormatTagListHtmlWithSearchHighlight(): void
|
||||||
|
|
||||||
$bookmark = new Bookmark();
|
$bookmark = new Bookmark();
|
||||||
$bookmark->setTagsString('coding-style standards quality assurance');
|
$bookmark->setTagsString('coding-style standards quality assurance');
|
||||||
$bookmark->addAdditionalContentEntry(
|
$bookmark->setAdditionalContentEntry(
|
||||||
'search_highlight',
|
'search_highlight',
|
||||||
['tags' => [
|
['tags' => [
|
||||||
['start' => 0, 'end' => 12], // coding-style
|
['start' => 0, 'end' => 12], // coding-style
|
||||||
|
|
|
@ -145,7 +145,7 @@ public function testFormatDescriptionWithSearchHighlight()
|
||||||
|
|
||||||
$bookmark = new Bookmark();
|
$bookmark = new Bookmark();
|
||||||
$bookmark->setDescription($description);
|
$bookmark->setDescription($description);
|
||||||
$bookmark->addAdditionalContentEntry(
|
$bookmark->setAdditionalContentEntry(
|
||||||
'search_highlight',
|
'search_highlight',
|
||||||
['description' => [
|
['description' => [
|
||||||
['start' => 18, 'end' => 26], // cription
|
['start' => 18, 'end' => 26], // cription
|
||||||
|
|
|
@ -84,7 +84,7 @@ public function testSharePrivateWithExistingPrivateBookmark(): void
|
||||||
->setUrl('http://domain.tld')
|
->setUrl('http://domain.tld')
|
||||||
->setTitle('Title 123')
|
->setTitle('Title 123')
|
||||||
->setPrivate(true)
|
->setPrivate(true)
|
||||||
->addAdditionalContentEntry('private_key', $existingKey)
|
->setAdditionalContentEntry('private_key', $existingKey)
|
||||||
;
|
;
|
||||||
|
|
||||||
$this->container->bookmarkService
|
$this->container->bookmarkService
|
||||||
|
|
419
tests/plugins/PluginReadItLaterTest.php
Normal file
419
tests/plugins/PluginReadItLaterTest.php
Normal file
|
@ -0,0 +1,419 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Shaarli\Plugin\ReadItLater;
|
||||||
|
|
||||||
|
use Shaarli\Bookmark\Bookmark;
|
||||||
|
use Shaarli\Config\ConfigManager;
|
||||||
|
use Shaarli\Plugin\PluginManager;
|
||||||
|
use Shaarli\TestCase;
|
||||||
|
|
||||||
|
require_once 'plugins/readitlater/readitlater.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class PluginQrcodeTest
|
||||||
|
* Unit test for the ReadItLater plugin
|
||||||
|
*/
|
||||||
|
class PluginQrcodeTest extends TestCase
|
||||||
|
{
|
||||||
|
/** @var ConfigManager */
|
||||||
|
protected $confDefaultTheme;
|
||||||
|
|
||||||
|
/** @var ConfigManager */
|
||||||
|
protected $confOtherTheme;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset plugin path
|
||||||
|
*/
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
PluginManager::$PLUGINS_PATH = 'plugins';
|
||||||
|
$this->confDefaultTheme = $this->createMock(ConfigManager::class);
|
||||||
|
$this->confDefaultTheme->method('get')->willReturnCallback(function (string $parameter, $default) {
|
||||||
|
if ($parameter === 'resource.theme') {
|
||||||
|
return 'default';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $default;
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->confOtherTheme = $this->createMock(ConfigManager::class);
|
||||||
|
$this->confDefaultTheme->method('get')->willReturnCallback(function (string $parameter, $default) {
|
||||||
|
if ($parameter === 'resource.theme') {
|
||||||
|
return 'other';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $default;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test hook_readitlater_render_linklist while logged in.
|
||||||
|
*/
|
||||||
|
public function testReadItLaterLinklistLoggedInDefaultTheme(): void
|
||||||
|
{
|
||||||
|
$url = 'http://randomstr.com/test';
|
||||||
|
$data = [
|
||||||
|
'_LOGGEDIN_' => true,
|
||||||
|
'links' => [
|
||||||
|
[
|
||||||
|
'id' => 1,
|
||||||
|
'url' => $url . '1',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => 2,
|
||||||
|
'url' => $url . '2',
|
||||||
|
'additional_content' => [
|
||||||
|
'readitlater' => false,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => 3,
|
||||||
|
'url' => $url . '3',
|
||||||
|
'additional_content' => [
|
||||||
|
'readitlater' => true,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$data = hook_readitlater_render_linklist($data, $this->confDefaultTheme);
|
||||||
|
|
||||||
|
$link = $data['links'][0];
|
||||||
|
static::assertEquals($url . '1', $link['url']);
|
||||||
|
static::assertNotEmpty($link['link_plugin']);
|
||||||
|
static::assertContainsPolyfill('Read it later', $link['link_plugin'][0]);
|
||||||
|
|
||||||
|
$link = $data['links'][1];
|
||||||
|
static::assertEquals($url . '2', $link['url']);
|
||||||
|
static::assertNotEmpty($link['link_plugin']);
|
||||||
|
static::assertContainsPolyfill('Read it later', $link['link_plugin'][0]);
|
||||||
|
|
||||||
|
$link = $data['links'][2];
|
||||||
|
static::assertEquals($url . '3', $link['url']);
|
||||||
|
static::assertNotEmpty($link['link_plugin']);
|
||||||
|
static::assertContainsPolyfill('Mark as Read', $link['link_plugin'][0]);
|
||||||
|
|
||||||
|
static::assertNotEmpty($data['action_plugin']);
|
||||||
|
static::assertContainsPolyfill('readitlater/toggle-filter', $data['action_plugin'][0]['attr']['href']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test hook_readitlater_render_linklist while logged in.
|
||||||
|
*/
|
||||||
|
public function testReadItLaterLinklistLoggedInOtherTheme(): void
|
||||||
|
{
|
||||||
|
$url = 'http://randomstr.com/test';
|
||||||
|
$data = [
|
||||||
|
'_LOGGEDIN_' => true,
|
||||||
|
'links' => [
|
||||||
|
[
|
||||||
|
'id' => 1,
|
||||||
|
'url' => $url . '1',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => 2,
|
||||||
|
'url' => $url . '2',
|
||||||
|
'additional_content' => [
|
||||||
|
'readitlater' => false,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => 3,
|
||||||
|
'url' => $url . '3',
|
||||||
|
'additional_content' => [
|
||||||
|
'readitlater' => true,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$data = hook_readitlater_render_linklist($data, $this->confOtherTheme);
|
||||||
|
|
||||||
|
$link = $data['links'][0];
|
||||||
|
static::assertEquals($url . '1', $link['url']);
|
||||||
|
static::assertNotEmpty($link['link_plugin']);
|
||||||
|
static::assertContainsPolyfill('Read it later', $link['link_plugin'][0]);
|
||||||
|
|
||||||
|
$link = $data['links'][1];
|
||||||
|
static::assertEquals($url . '2', $link['url']);
|
||||||
|
static::assertNotEmpty($link['link_plugin']);
|
||||||
|
static::assertContainsPolyfill('Read it later', $link['link_plugin'][0]);
|
||||||
|
|
||||||
|
$link = $data['links'][2];
|
||||||
|
static::assertEquals($url . '3', $link['url']);
|
||||||
|
static::assertNotEmpty($link['link_plugin']);
|
||||||
|
static::assertContainsPolyfill('Mark as Read', $link['link_plugin'][0]);
|
||||||
|
|
||||||
|
static::assertNotEmpty($data['action_plugin']);
|
||||||
|
static::assertContainsPolyfill('readitlater/toggle-filter', $data['action_plugin'][0]['attr']['href']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test hook_readitlater_render_linklist while logged out: nothing should happen.
|
||||||
|
*/
|
||||||
|
public function testReadItLaterLinklistLoggedOut(): void
|
||||||
|
{
|
||||||
|
$url = 'http://randomstr.com/test';
|
||||||
|
$originalData = [
|
||||||
|
'_LOGGEDIN_' => false,
|
||||||
|
'links' => [
|
||||||
|
[
|
||||||
|
'id' => 1,
|
||||||
|
'url' => $url . '1',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => 2,
|
||||||
|
'url' => $url . '2',
|
||||||
|
'additional_content' => [
|
||||||
|
'readitlater' => false,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => 3,
|
||||||
|
'url' => $url . '3',
|
||||||
|
'additional_content' => [
|
||||||
|
'readitlater' => true,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$data = hook_readitlater_render_linklist($originalData, $this->confDefaultTheme);
|
||||||
|
|
||||||
|
static::assertSame($originalData, $data);
|
||||||
|
|
||||||
|
unset($originalData['_LOGGEDIN_']);
|
||||||
|
|
||||||
|
$data = hook_readitlater_render_linklist($originalData, $this->confDefaultTheme);
|
||||||
|
|
||||||
|
static::assertSame($originalData, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test readitlater_register_routes
|
||||||
|
*/
|
||||||
|
public function testReadItLaterRoutesRegister(): void
|
||||||
|
{
|
||||||
|
$routes = readitlater_register_routes();
|
||||||
|
|
||||||
|
static::assertCount(2, $routes);
|
||||||
|
foreach ($routes as $route) {
|
||||||
|
static::assertSame('GET', $route['method']);
|
||||||
|
static::assertContainsPolyfill('ReadItLaterController', $route['callable']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test hook_readitlater_render_includes while logged in
|
||||||
|
*/
|
||||||
|
public function testReadItLaterRenderIncludesLoggedInDefaultTheme(): void
|
||||||
|
{
|
||||||
|
$data = hook_readitlater_render_includes(['_LOGGEDIN_' => true], $this->confDefaultTheme);
|
||||||
|
|
||||||
|
static::assertSame('plugins/readitlater/readitlater.default.css', $data['css_files'][0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test hook_readitlater_render_includes while logged in
|
||||||
|
*/
|
||||||
|
public function testReadItLaterRenderIncludesLoggedInOtherTheme(): void
|
||||||
|
{
|
||||||
|
$data = hook_readitlater_render_includes($originalData = ['_LOGGEDIN_' => true], $this->confOtherTheme);
|
||||||
|
|
||||||
|
static::assertSame($originalData, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test hook_readitlater_render_includes while logged out
|
||||||
|
*/
|
||||||
|
public function testReadItLaterRenderIncludesLoggedOut(): void
|
||||||
|
{
|
||||||
|
$data = hook_readitlater_render_includes([], $this->confDefaultTheme);
|
||||||
|
|
||||||
|
static::assertSame([], $data);
|
||||||
|
|
||||||
|
$data = hook_readitlater_render_includes($originalData = ['_LOGGEDIN_' => false], $this->confDefaultTheme);
|
||||||
|
|
||||||
|
static::assertSame($originalData, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test hook_readitlater_render_footer while logged in
|
||||||
|
*/
|
||||||
|
public function testReadItLaterRenderFooterLoggedInDefaultTheme(): void
|
||||||
|
{
|
||||||
|
$data = hook_readitlater_render_footer(['_LOGGEDIN_' => true], $this->confDefaultTheme);
|
||||||
|
|
||||||
|
static::assertSame('plugins/readitlater/readitlater.default.js', $data['js_files'][0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test hook_readitlater_render_footer while logged in
|
||||||
|
*/
|
||||||
|
public function testReadItLaterRenderFooterLoggedInOtherTheme(): void
|
||||||
|
{
|
||||||
|
$data = hook_readitlater_render_footer($originalData = ['_LOGGEDIN_' => true], $this->confOtherTheme);
|
||||||
|
|
||||||
|
static::assertSame($originalData, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test hook_readitlater_render_footer while logged out
|
||||||
|
*/
|
||||||
|
public function testReadItLaterRenderFooterLoggedOut(): void
|
||||||
|
{
|
||||||
|
$data = hook_readitlater_render_footer([], $this->confDefaultTheme);
|
||||||
|
|
||||||
|
static::assertSame([], $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test hook_readitlater_render_editlink with a new link: checkbox added (unchecked)
|
||||||
|
*/
|
||||||
|
public function testReadItLaterRenderEditLinkDefaultOff(): void
|
||||||
|
{
|
||||||
|
$originalData = [
|
||||||
|
'link_is_new' => true,
|
||||||
|
'link' => [],
|
||||||
|
];
|
||||||
|
$data = hook_readitlater_render_editlink($originalData, $this->confDefaultTheme);
|
||||||
|
|
||||||
|
static::assertContainsPolyfill(
|
||||||
|
'<input type="checkbox" name="readitlater" id="readitlater" />',
|
||||||
|
$data['edit_link_plugin'][0]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->confDefaultTheme = $this->createMock(ConfigManager::class);
|
||||||
|
$this->confDefaultTheme->method('get')->with('plugins.READITLATER_DEFAULT_CHECK')->willReturn('0');
|
||||||
|
|
||||||
|
$data = hook_readitlater_render_editlink($originalData, $this->confDefaultTheme);
|
||||||
|
|
||||||
|
static::assertContainsPolyfill(
|
||||||
|
'<input type="checkbox" name="readitlater" id="readitlater" />',
|
||||||
|
$data['edit_link_plugin'][0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test hook_readitlater_render_editlink with a new link: checkbox added (checked)
|
||||||
|
*/
|
||||||
|
public function testReadItLaterRenderEditLinkDefaultOn(): void
|
||||||
|
{
|
||||||
|
$this->confDefaultTheme = $this->createMock(ConfigManager::class);
|
||||||
|
$this->confDefaultTheme->method('get')->with('plugins.READITLATER_DEFAULT_CHECK')->willReturn('1');
|
||||||
|
$originalData = [
|
||||||
|
'link_is_new' => true,
|
||||||
|
'link' => [],
|
||||||
|
];
|
||||||
|
$data = hook_readitlater_render_editlink($originalData, $this->confDefaultTheme);
|
||||||
|
|
||||||
|
static::assertContainsPolyfill(
|
||||||
|
'<input type="checkbox" name="readitlater" id="readitlater" checked />',
|
||||||
|
$data['edit_link_plugin'][0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test hook_readitlater_render_editlink with an existing link: we don't do anything
|
||||||
|
*/
|
||||||
|
public function testReadItLaterRenderEditLinkNotNew(): void
|
||||||
|
{
|
||||||
|
$originalData = [
|
||||||
|
'link_is_new' => false,
|
||||||
|
'link' => [],
|
||||||
|
];
|
||||||
|
$data = hook_readitlater_render_editlink($originalData, $this->confDefaultTheme);
|
||||||
|
|
||||||
|
static::assertSame($originalData, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test hook_readitlater_save_link with readitlater not already set and multiple values (defaults to false).
|
||||||
|
*/
|
||||||
|
public function testReadItLaterSaveLinkNewSetting(): void
|
||||||
|
{
|
||||||
|
$_POST['readitlater'] = true;
|
||||||
|
$data = hook_readitlater_save_link([]);
|
||||||
|
|
||||||
|
static::assertTrue($data['additional_content']['readitlater']);
|
||||||
|
|
||||||
|
$_POST['readitlater'] = 'on';
|
||||||
|
$data = hook_readitlater_save_link([]);
|
||||||
|
|
||||||
|
static::assertTrue($data['additional_content']['readitlater']);
|
||||||
|
|
||||||
|
$_POST['readitlater'] = false;
|
||||||
|
$data = hook_readitlater_save_link([]);
|
||||||
|
|
||||||
|
static::assertFalse($data['additional_content']['readitlater']);
|
||||||
|
|
||||||
|
unset($_POST['readitlater']);
|
||||||
|
$data = hook_readitlater_save_link([]);
|
||||||
|
|
||||||
|
static::assertFalse($data['additional_content']['readitlater']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test hook_readitlater_save_link with readitlater setting already set.
|
||||||
|
*/
|
||||||
|
public function testReadItLaterSaveLinkExistingSetting(): void
|
||||||
|
{
|
||||||
|
$data = hook_readitlater_save_link(['additional_content' => ['readitlater' => true]]);
|
||||||
|
static::assertTrue($data['additional_content']['readitlater']);
|
||||||
|
|
||||||
|
$data = hook_readitlater_save_link(['additional_content' => ['readitlater' => false]]);
|
||||||
|
static::assertFalse($data['additional_content']['readitlater']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test hook_readitlater_filter_search_entry
|
||||||
|
*/
|
||||||
|
public function testReadItLaterFilterSearchEntry(): void
|
||||||
|
{
|
||||||
|
$_SESSION['readitlater-only'] = true;
|
||||||
|
|
||||||
|
$bookmark = new Bookmark();
|
||||||
|
static::assertFalse(hook_readitlater_filter_search_entry($bookmark, []));
|
||||||
|
|
||||||
|
$bookmark = new Bookmark();
|
||||||
|
$bookmark->setAdditionalContentEntry('readitlater', false);
|
||||||
|
static::assertFalse(hook_readitlater_filter_search_entry($bookmark, []));
|
||||||
|
|
||||||
|
$bookmark = new Bookmark();
|
||||||
|
$bookmark->setAdditionalContentEntry('readitlater', true);
|
||||||
|
static::assertTrue(hook_readitlater_filter_search_entry($bookmark, []));
|
||||||
|
|
||||||
|
$_SESSION['readitlater-only'] = false;
|
||||||
|
|
||||||
|
$bookmark = new Bookmark();
|
||||||
|
static::assertTrue(hook_readitlater_filter_search_entry($bookmark, []));
|
||||||
|
|
||||||
|
$bookmark = new Bookmark();
|
||||||
|
$bookmark->setAdditionalContentEntry('readitlater', false);
|
||||||
|
static::assertTrue(hook_readitlater_filter_search_entry($bookmark, []));
|
||||||
|
|
||||||
|
$bookmark = new Bookmark();
|
||||||
|
$bookmark->setAdditionalContentEntry('readitlater', true);
|
||||||
|
static::assertTrue(hook_readitlater_filter_search_entry($bookmark, []));
|
||||||
|
|
||||||
|
unset($_SESSION['readitlater-only']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReadItLaterGetIconDefaultTheme(): void
|
||||||
|
{
|
||||||
|
$result = readitlater_get_icon($this->confDefaultTheme, true);
|
||||||
|
static::assertSame('<i class="fa fa-eye-slash" aria-hidden="true"></i>', $result);
|
||||||
|
|
||||||
|
$result = readitlater_get_icon($this->confDefaultTheme, false);
|
||||||
|
static::assertSame('<i class="fa fa-eye" aria-hidden="true"></i>', $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReadItLaterGetIconOtherTheme(): void
|
||||||
|
{
|
||||||
|
$result = readitlater_get_icon($this->confOtherTheme, true);
|
||||||
|
static::assertSame('Mark as Read', $result);
|
||||||
|
|
||||||
|
$result = readitlater_get_icon($this->confOtherTheme, false);
|
||||||
|
static::assertSame('Read it later', $result);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,8 +4,8 @@ function test_route_invalid_register_routes(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
[
|
[
|
||||||
'method' => 'GET',
|
'method' => 'I_INVENT_MY_HTTP_METHODS',
|
||||||
'route' => 'not a route',
|
'route' => '/hello',
|
||||||
'callable' => 'getFunction',
|
'callable' => 'getFunction',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
Loading…
Reference in a new issue