From bf8bec322bab7e040d836abe6d73319c01a2aeed Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Sat, 3 Apr 2021 15:56:55 +0200 Subject: [PATCH 1/3] New Core Plugin: ReadItLater MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create a new core plugin allowing to mark bookmarks to read them later. When enabled: * checkbox is displayed in editlink view for new bookmarks * a plugin setting is available to check it or not it by default * in bookmark list: * new global filter to display only bookmark flagged as read it later * for each bookmarks, new action icon to toggle read it later status * for each « readitlater » bookmark, red label « To Read » added, and red line on the right of the bookmark added (default template) Fixes #143 Signed-off-by: ArthurHoaro --- application/bookmark/Bookmark.php | 3 +- application/bookmark/BookmarkFilter.php | 2 +- .../formatter/BookmarkDefaultFormatter.php | 14 + application/formatter/BookmarkFormatter.php | 13 + .../admin/ShaareManageController.php | 2 +- application/plugin/PluginManager.php | 6 +- composer.json | 1 + plugins/readitlater/ReadItLaterController.php | 46 ++ plugins/readitlater/readitlater.default.css | 21 + plugins/readitlater/readitlater.default.js | 14 + plugins/readitlater/readitlater.meta | 3 + plugins/readitlater/readitlater.php | 158 +++++++ plugins/readitlater/readitlater_button.html | 3 + plugins/readitlater/readitlater_editlink.html | 1 + tests/bookmark/BookmarkFileServiceTest.php | 2 +- .../BookmarkDefaultFormatterTest.php | 8 +- .../BookmarkMarkdownFormatterTest.php | 2 +- .../SharePrivateTest.php | 2 +- tests/plugins/PluginReadItLaterTest.php | 422 ++++++++++++++++++ .../test_route_invalid/test_route_invalid.php | 4 +- 20 files changed, 711 insertions(+), 16 deletions(-) create mode 100644 plugins/readitlater/ReadItLaterController.php create mode 100644 plugins/readitlater/readitlater.default.css create mode 100644 plugins/readitlater/readitlater.default.js create mode 100644 plugins/readitlater/readitlater.meta create mode 100644 plugins/readitlater/readitlater.php create mode 100644 plugins/readitlater/readitlater_button.html create mode 100644 plugins/readitlater/readitlater_editlink.html create mode 100644 tests/plugins/PluginReadItLaterTest.php 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/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..e9217aa8 --- /dev/null +++ b/tests/plugins/PluginReadItLaterTest.php @@ -0,0 +1,422 @@ +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', ], ]; From 39c89d84f5cde2add4236da24e9a8a8edac9083f Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Sat, 3 Apr 2021 17:20:10 +0200 Subject: [PATCH 2/3] Update official plugin list in the documentation --- doc/md/Plugins.md | 1 + 1 file changed, 1 insertion(+) 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. From 11cf6e9b4998b56ded4ea209f5fedabd757d3d69 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Thu, 13 May 2021 12:00:46 +0200 Subject: [PATCH 3/3] Fix PHPCS rules --- tests/plugins/PluginReadItLaterTest.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/plugins/PluginReadItLaterTest.php b/tests/plugins/PluginReadItLaterTest.php index e9217aa8..2e145cb3 100644 --- a/tests/plugins/PluginReadItLaterTest.php +++ b/tests/plugins/PluginReadItLaterTest.php @@ -1,9 +1,6 @@