diff --git a/application/bookmark/Bookmark.php b/application/bookmark/Bookmark.php
index 4238ef25..316ae693 100644
--- a/application/bookmark/Bookmark.php
+++ b/application/bookmark/Bookmark.php
@@ -85,6 +85,7 @@ public function fromArray(array $data, string $tagsSeparator = ' '): Bookmark
$this->updated = $data['updated'];
}
$this->private = ($data['private'] ?? false) ? true : false;
+ $this->additionalContent = $data['additional_content'] ?? [];
return $this;
}
@@ -483,7 +484,7 @@ public function getAdditionalContent(): array
*
* @return $this
*/
- public function addAdditionalContentEntry(string $key, $value): self
+ public function setAdditionalContentEntry(string $key, $value): self
{
$this->additionalContent[$key] = $value;
diff --git a/application/bookmark/BookmarkFilter.php b/application/bookmark/BookmarkFilter.php
index e6619057..00cbba06 100644
--- a/application/bookmark/BookmarkFilter.php
+++ b/application/bookmark/BookmarkFilter.php
@@ -290,7 +290,7 @@ private function filterFulltext(string $searchterms, string $visibility = 'all')
}
if ($found !== false) {
- $bookmark->addAdditionalContentEntry(
+ $bookmark->setAdditionalContentEntry(
'search_highlight',
$this->postProcessFoundPositions($lengths, $foundPositions)
);
diff --git a/application/formatter/BookmarkDefaultFormatter.php b/application/formatter/BookmarkDefaultFormatter.php
index 12a4db72..91985ee1 100644
--- a/application/formatter/BookmarkDefaultFormatter.php
+++ b/application/formatter/BookmarkDefaultFormatter.php
@@ -2,6 +2,8 @@
namespace Shaarli\Formatter;
+use Shaarli\Bookmark\Bookmark;
+
/**
* Class BookmarkDefaultFormatter
*
@@ -145,6 +147,18 @@ protected function formatThumbnail($bookmark)
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
*
diff --git a/application/formatter/BookmarkFormatter.php b/application/formatter/BookmarkFormatter.php
index 124ce78b..a43ac1df 100644
--- a/application/formatter/BookmarkFormatter.php
+++ b/application/formatter/BookmarkFormatter.php
@@ -95,6 +95,7 @@ public function format($bookmark)
$out['updated'] = $this->formatUpdated($bookmark);
$out['timestamp'] = $this->formatCreatedTimestamp($bookmark);
$out['updated_timestamp'] = $this->formatUpdatedTimestamp($bookmark);
+ $out['additional_content'] = $this->formatAdditionalContent($bookmark);
return $out;
}
@@ -349,6 +350,18 @@ protected function formatUpdatedTimestamp(Bookmark $bookmark)
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.
* TODO: this method is called multiple time to format tags, the result should be cached.
diff --git a/application/front/controller/admin/ShaareManageController.php b/application/front/controller/admin/ShaareManageController.php
index 9633cd51..d29353a4 100644
--- a/application/front/controller/admin/ShaareManageController.php
+++ b/application/front/controller/admin/ShaareManageController.php
@@ -194,7 +194,7 @@ public function sharePrivate(Request $request, Response $response, array $args):
if (empty($bookmark->getAdditionalContentEntry('private_key'))) {
$privateKey = bin2hex(random_bytes(16));
- $bookmark->addAdditionalContentEntry('private_key', $privateKey);
+ $bookmark->setAdditionalContentEntry('private_key', $privateKey);
$this->container->bookmarkService->set($bookmark);
}
diff --git a/application/plugin/PluginManager.php b/application/plugin/PluginManager.php
index 939db1ea..b2cede28 100644
--- a/application/plugin/PluginManager.php
+++ b/application/plugin/PluginManager.php
@@ -343,6 +343,8 @@ protected function loadFilterSearchEntryHooks(): void
* Checks whether provided input is valid to register a new route.
* It must contain keys `method`, `route`, `callable` (all strings).
*
+ * We do not check the format because Slim routes support regexes.
+ *
* @param string[] $input
*
* @return bool
@@ -356,10 +358,6 @@ protected static function validateRouteRegistration(array $input): bool
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;
}
diff --git a/composer.json b/composer.json
index 2563906f..d2781223 100644
--- a/composer.json
+++ b/composer.json
@@ -66,6 +66,7 @@
"Shaarli\\Plugin\\": "application/plugin",
"Shaarli\\Plugin\\Exception\\": "application/plugin/exception",
"Shaarli\\Plugin\\Wallabag\\": "plugins/wallabag",
+ "Shaarli\\Plugin\\ReadItLater\\": "plugins/readitlater",
"Shaarli\\Render\\": "application/render",
"Shaarli\\Security\\": "application/security",
"Shaarli\\Updater\\": "application/updater",
diff --git a/doc/md/Plugins.md b/doc/md/Plugins.md
index 22dd53f4..f7e0baa7 100644
--- a/doc/md/Plugins.md
+++ b/doc/md/Plugins.md
@@ -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.
* `pubsubhubbub`: Enable PubSubHubbub feed publishing
* `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.
diff --git a/plugins/readitlater/ReadItLaterController.php b/plugins/readitlater/ReadItLaterController.php
new file mode 100644
index 00000000..9e1a4a8e
--- /dev/null
+++ b/plugins/readitlater/ReadItLaterController.php
@@ -0,0 +1,46 @@
+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']);
+ }
+}
diff --git a/plugins/readitlater/readitlater.default.css b/plugins/readitlater/readitlater.default.css
new file mode 100644
index 00000000..59adcdfd
--- /dev/null
+++ b/plugins/readitlater/readitlater.default.css
@@ -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;
+}
diff --git a/plugins/readitlater/readitlater.default.js b/plugins/readitlater/readitlater.default.js
new file mode 100644
index 00000000..fc128cff
--- /dev/null
+++ b/plugins/readitlater/readitlater.default.js
@@ -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);
+ });
+ }
+ });
+})();
diff --git a/plugins/readitlater/readitlater.meta b/plugins/readitlater/readitlater.meta
new file mode 100644
index 00000000..cacda58d
--- /dev/null
+++ b/plugins/readitlater/readitlater.meta
@@ -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 1
) or not (with 0
)"
diff --git a/plugins/readitlater/readitlater.php b/plugins/readitlater/readitlater.php
new file mode 100644
index 00000000..e7a4a316
--- /dev/null
+++ b/plugins/readitlater/readitlater.php
@@ -0,0 +1,158 @@
+ '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 '';
+ } else {
+ return $isUnread ? 'Mark as Read' : 'Read it later';
+ }
+}
diff --git a/plugins/readitlater/readitlater_button.html b/plugins/readitlater/readitlater_button.html
new file mode 100644
index 00000000..432c6a10
--- /dev/null
+++ b/plugins/readitlater/readitlater_button.html
@@ -0,0 +1,3 @@
+
+ %s
+
diff --git a/plugins/readitlater/readitlater_editlink.html b/plugins/readitlater/readitlater_editlink.html
new file mode 100644
index 00000000..a7d37801
--- /dev/null
+++ b/plugins/readitlater/readitlater_editlink.html
@@ -0,0 +1 @@
+
diff --git a/tests/bookmark/BookmarkFileServiceTest.php b/tests/bookmark/BookmarkFileServiceTest.php
index c12cd2b0..0a265e3c 100644
--- a/tests/bookmark/BookmarkFileServiceTest.php
+++ b/tests/bookmark/BookmarkFileServiceTest.php
@@ -988,7 +988,7 @@ public function testFilterHashWithPrivateKey()
$privateKey = 'this is usually auto generated';
$bookmark = $this->privateLinkDB->findByHash($hash);
- $bookmark->addAdditionalContentEntry('private_key', $privateKey);
+ $bookmark->setAdditionalContentEntry('private_key', $privateKey);
$this->privateLinkDB->save();
$this->privateLinkDB = new BookmarkFileService(
diff --git a/tests/formatter/BookmarkDefaultFormatterTest.php b/tests/formatter/BookmarkDefaultFormatterTest.php
index 0ac5267d..c0ba83f3 100644
--- a/tests/formatter/BookmarkDefaultFormatterTest.php
+++ b/tests/formatter/BookmarkDefaultFormatterTest.php
@@ -184,7 +184,7 @@ public function testFormatTitleHtmlWithSearchHighlight(): void
$bookmark = new Bookmark();
$bookmark->setTitle('PSR-2: Coding Style Guide');
- $bookmark->addAdditionalContentEntry(
+ $bookmark->setAdditionalContentEntry(
'search_highlight',
['title' => [
['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 .
'https://www.php-fig.org/psr/psr-1/'
);
- $bookmark->addAdditionalContentEntry(
+ $bookmark->setAdditionalContentEntry(
'search_highlight',
['description' => [
['start' => 0, 'end' => 10], // "This guide"
@@ -247,7 +247,7 @@ public function testFormatUrlHtmlWithSearchHighlight(): void
$bookmark = new Bookmark();
$bookmark->setUrl('http://www.php-fig.org/psr/psr-2/');
- $bookmark->addAdditionalContentEntry(
+ $bookmark->setAdditionalContentEntry(
'search_highlight',
['url' => [
['start' => 0, 'end' => 4], // http
@@ -275,7 +275,7 @@ public function testFormatTagListHtmlWithSearchHighlight(): void
$bookmark = new Bookmark();
$bookmark->setTagsString('coding-style standards quality assurance');
- $bookmark->addAdditionalContentEntry(
+ $bookmark->setAdditionalContentEntry(
'search_highlight',
['tags' => [
['start' => 0, 'end' => 12], // coding-style
diff --git a/tests/formatter/BookmarkMarkdownFormatterTest.php b/tests/formatter/BookmarkMarkdownFormatterTest.php
index 92668510..cd471756 100644
--- a/tests/formatter/BookmarkMarkdownFormatterTest.php
+++ b/tests/formatter/BookmarkMarkdownFormatterTest.php
@@ -145,7 +145,7 @@ public function testFormatDescriptionWithSearchHighlight()
$bookmark = new Bookmark();
$bookmark->setDescription($description);
- $bookmark->addAdditionalContentEntry(
+ $bookmark->setAdditionalContentEntry(
'search_highlight',
['description' => [
['start' => 18, 'end' => 26], // cription
diff --git a/tests/front/controller/admin/ShaareManageControllerTest/SharePrivateTest.php b/tests/front/controller/admin/ShaareManageControllerTest/SharePrivateTest.php
index ae61dfb7..deb8b50e 100644
--- a/tests/front/controller/admin/ShaareManageControllerTest/SharePrivateTest.php
+++ b/tests/front/controller/admin/ShaareManageControllerTest/SharePrivateTest.php
@@ -84,7 +84,7 @@ public function testSharePrivateWithExistingPrivateBookmark(): void
->setUrl('http://domain.tld')
->setTitle('Title 123')
->setPrivate(true)
- ->addAdditionalContentEntry('private_key', $existingKey)
+ ->setAdditionalContentEntry('private_key', $existingKey)
;
$this->container->bookmarkService
diff --git a/tests/plugins/PluginReadItLaterTest.php b/tests/plugins/PluginReadItLaterTest.php
new file mode 100644
index 00000000..2e145cb3
--- /dev/null
+++ b/tests/plugins/PluginReadItLaterTest.php
@@ -0,0 +1,419 @@
+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(
+ '',
+ $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(
+ '',
+ $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(
+ '',
+ $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('', $result);
+
+ $result = readitlater_get_icon($this->confDefaultTheme, false);
+ static::assertSame('', $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);
+ }
+}
diff --git a/tests/plugins/test_route_invalid/test_route_invalid.php b/tests/plugins/test_route_invalid/test_route_invalid.php
index 0c5a5101..cdec9933 100644
--- a/tests/plugins/test_route_invalid/test_route_invalid.php
+++ b/tests/plugins/test_route_invalid/test_route_invalid.php
@@ -4,8 +4,8 @@ function test_route_invalid_register_routes(): array
{
return [
[
- 'method' => 'GET',
- 'route' => 'not a route',
+ 'method' => 'I_INVENT_MY_HTTP_METHODS',
+ 'route' => '/hello',
'callable' => 'getFunction',
],
];