+++ /dev/null
-<?php
-
-declare(strict_types=1);
-
-namespace Shaarli\Front\Controller\Admin;
-
-use Shaarli\Bookmark\Bookmark;
-use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
-use Shaarli\Formatter\BookmarkMarkdownFormatter;
-use Shaarli\Render\TemplatePage;
-use Shaarli\Thumbnailer;
-use Slim\Http\Request;
-use Slim\Http\Response;
-
-/**
- * Class PostBookmarkController
- *
- * Slim controller used to handle Shaarli create or edit bookmarks.
- */
-class ManageShaareController extends ShaarliAdminController
-{
- /**
- * GET /admin/add-shaare - Displays the form used to create a new bookmark from an URL
- */
- public function addShaare(Request $request, Response $response): Response
- {
- $this->assignView(
- 'pagetitle',
- t('Shaare a new link') .' - '. $this->container->conf->get('general.title', 'Shaarli')
- );
-
- return $response->write($this->render(TemplatePage::ADDLINK));
- }
-
- /**
- * GET /admin/shaare - Displays the bookmark form for creation.
- * Note that if the URL is found in existing bookmarks, then it will be in edit mode.
- */
- public function displayCreateForm(Request $request, Response $response): Response
- {
- $url = cleanup_url($request->getParam('post'));
-
- $linkIsNew = false;
- // Check if URL is not already in database (in this case, we will edit the existing link)
- $bookmark = $this->container->bookmarkService->findByUrl($url);
- if (null === $bookmark) {
- $linkIsNew = true;
- // Get shaare data if it was provided in URL (e.g.: by the bookmarklet).
- $title = $request->getParam('title');
- $description = $request->getParam('description');
- $tags = $request->getParam('tags');
- $private = filter_var($request->getParam('private'), FILTER_VALIDATE_BOOLEAN);
-
- // If this is an HTTP(S) link, we try go get the page to extract
- // the title (otherwise we will to straight to the edit form.)
- if (true !== $this->container->conf->get('general.enable_async_metadata', true)
- && empty($title)
- && strpos(get_url_scheme($url) ?: '', 'http') !== false
- ) {
- $metadata = $this->container->metadataRetriever->retrieve($url);
- }
-
- if (empty($url)) {
- $metadata['title'] = $this->container->conf->get('general.default_note_title', t('Note: '));
- }
-
- $link = [
- 'title' => $title ?? $metadata['title'] ?? '',
- 'url' => $url ?? '',
- 'description' => $description ?? $metadata['description'] ?? '',
- 'tags' => $tags ?? $metadata['tags'] ?? '',
- 'private' => $private,
- ];
- } else {
- $formatter = $this->container->formatterFactory->getFormatter('raw');
- $link = $formatter->format($bookmark);
- }
-
- return $this->displayForm($link, $linkIsNew, $request, $response);
- }
-
- /**
- * GET /admin/shaare/{id} - Displays the bookmark form in edition mode.
- */
- public function displayEditForm(Request $request, Response $response, array $args): Response
- {
- $id = $args['id'] ?? '';
- try {
- if (false === ctype_digit($id)) {
- throw new BookmarkNotFoundException();
- }
- $bookmark = $this->container->bookmarkService->get((int) $id); // Read database
- } catch (BookmarkNotFoundException $e) {
- $this->saveErrorMessage(sprintf(
- t('Bookmark with identifier %s could not be found.'),
- $id
- ));
-
- return $this->redirect($response, '/');
- }
-
- $formatter = $this->container->formatterFactory->getFormatter('raw');
- $link = $formatter->format($bookmark);
-
- return $this->displayForm($link, false, $request, $response);
- }
-
- /**
- * POST /admin/shaare
- */
- public function save(Request $request, Response $response): Response
- {
- $this->checkToken($request);
-
- // lf_id should only be present if the link exists.
- $id = $request->getParam('lf_id') !== null ? intval(escape($request->getParam('lf_id'))) : null;
- if (null !== $id && true === $this->container->bookmarkService->exists($id)) {
- // Edit
- $bookmark = $this->container->bookmarkService->get($id);
- } else {
- // New link
- $bookmark = new Bookmark();
- }
-
- $bookmark->setTitle($request->getParam('lf_title'));
- $bookmark->setDescription($request->getParam('lf_description'));
- $bookmark->setUrl($request->getParam('lf_url'), $this->container->conf->get('security.allowed_protocols', []));
- $bookmark->setPrivate(filter_var($request->getParam('lf_private'), FILTER_VALIDATE_BOOLEAN));
- $bookmark->setTagsString($request->getParam('lf_tags'));
-
- if ($this->container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE
- && true !== $this->container->conf->get('general.enable_async_metadata', true)
- && $bookmark->shouldUpdateThumbnail()
- ) {
- $bookmark->setThumbnail($this->container->thumbnailer->get($bookmark->getUrl()));
- }
- $this->container->bookmarkService->addOrSet($bookmark, false);
-
- // To preserve backward compatibility with 3rd parties, plugins still use arrays
- $formatter = $this->container->formatterFactory->getFormatter('raw');
- $data = $formatter->format($bookmark);
- $this->executePageHooks('save_link', $data);
-
- $bookmark->fromArray($data);
- $this->container->bookmarkService->set($bookmark);
-
- // If we are called from the bookmarklet, we must close the popup:
- if ($request->getParam('source') === 'bookmarklet') {
- return $response->write('<script>self.close();</script>');
- }
-
- if (!empty($request->getParam('returnurl'))) {
- $this->container->environment['HTTP_REFERER'] = escape($request->getParam('returnurl'));
- }
-
- return $this->redirectFromReferer(
- $request,
- $response,
- ['/admin/add-shaare', '/admin/shaare'], ['addlink', 'post', 'edit_link'],
- $bookmark->getShortUrl()
- );
- }
-
- /**
- * GET /admin/shaare/delete - Delete one or multiple bookmarks (depending on `id` query parameter).
- */
- public function deleteBookmark(Request $request, Response $response): Response
- {
- $this->checkToken($request);
-
- $ids = escape(trim($request->getParam('id') ?? ''));
- if (empty($ids) || strpos($ids, ' ') !== false) {
- // multiple, space-separated ids provided
- $ids = array_values(array_filter(preg_split('/\s+/', $ids), 'ctype_digit'));
- } else {
- $ids = [$ids];
- }
-
- // assert at least one id is given
- if (0 === count($ids)) {
- $this->saveErrorMessage(t('Invalid bookmark ID provided.'));
-
- return $this->redirectFromReferer($request, $response, [], ['delete-shaare']);
- }
-
- $formatter = $this->container->formatterFactory->getFormatter('raw');
- $count = 0;
- foreach ($ids as $id) {
- try {
- $bookmark = $this->container->bookmarkService->get((int) $id);
- } catch (BookmarkNotFoundException $e) {
- $this->saveErrorMessage(sprintf(
- t('Bookmark with identifier %s could not be found.'),
- $id
- ));
-
- continue;
- }
-
- $data = $formatter->format($bookmark);
- $this->executePageHooks('delete_link', $data);
- $this->container->bookmarkService->remove($bookmark, false);
- ++ $count;
- }
-
- if ($count > 0) {
- $this->container->bookmarkService->save();
- }
-
- // If we are called from the bookmarklet, we must close the popup:
- if ($request->getParam('source') === 'bookmarklet') {
- return $response->write('<script>self.close();</script>');
- }
-
- // Don't redirect to where we were previously because the datastore has changed.
- return $this->redirect($response, '/');
- }
-
- /**
- * GET /admin/shaare/visibility
- *
- * Change visibility (public/private) of one or multiple bookmarks (depending on `id` query parameter).
- */
- public function changeVisibility(Request $request, Response $response): Response
- {
- $this->checkToken($request);
-
- $ids = trim(escape($request->getParam('id') ?? ''));
- if (empty($ids) || strpos($ids, ' ') !== false) {
- // multiple, space-separated ids provided
- $ids = array_values(array_filter(preg_split('/\s+/', $ids), 'ctype_digit'));
- } else {
- // only a single id provided
- $ids = [$ids];
- }
-
- // assert at least one id is given
- if (0 === count($ids)) {
- $this->saveErrorMessage(t('Invalid bookmark ID provided.'));
-
- return $this->redirectFromReferer($request, $response, [], ['change_visibility']);
- }
-
- // assert that the visibility is valid
- $visibility = $request->getParam('newVisibility');
- if (null === $visibility || false === in_array($visibility, ['public', 'private'], true)) {
- $this->saveErrorMessage(t('Invalid visibility provided.'));
-
- return $this->redirectFromReferer($request, $response, [], ['change_visibility']);
- } else {
- $isPrivate = $visibility === 'private';
- }
-
- $formatter = $this->container->formatterFactory->getFormatter('raw');
- $count = 0;
-
- foreach ($ids as $id) {
- try {
- $bookmark = $this->container->bookmarkService->get((int) $id);
- } catch (BookmarkNotFoundException $e) {
- $this->saveErrorMessage(sprintf(
- t('Bookmark with identifier %s could not be found.'),
- $id
- ));
-
- continue;
- }
-
- $bookmark->setPrivate($isPrivate);
-
- // To preserve backward compatibility with 3rd parties, plugins still use arrays
- $data = $formatter->format($bookmark);
- $this->executePageHooks('save_link', $data);
- $bookmark->fromArray($data);
-
- $this->container->bookmarkService->set($bookmark, false);
- ++$count;
- }
-
- if ($count > 0) {
- $this->container->bookmarkService->save();
- }
-
- return $this->redirectFromReferer($request, $response, ['/visibility'], ['change_visibility']);
- }
-
- /**
- * GET /admin/shaare/{id}/pin - Pin or unpin a bookmark.
- */
- public function pinBookmark(Request $request, Response $response, array $args): Response
- {
- $this->checkToken($request);
-
- $id = $args['id'] ?? '';
- try {
- if (false === ctype_digit($id)) {
- throw new BookmarkNotFoundException();
- }
- $bookmark = $this->container->bookmarkService->get((int) $id); // Read database
- } catch (BookmarkNotFoundException $e) {
- $this->saveErrorMessage(sprintf(
- t('Bookmark with identifier %s could not be found.'),
- $id
- ));
-
- return $this->redirectFromReferer($request, $response, ['/pin'], ['pin']);
- }
-
- $formatter = $this->container->formatterFactory->getFormatter('raw');
-
- $bookmark->setSticky(!$bookmark->isSticky());
-
- // To preserve backward compatibility with 3rd parties, plugins still use arrays
- $data = $formatter->format($bookmark);
- $this->executePageHooks('save_link', $data);
- $bookmark->fromArray($data);
-
- $this->container->bookmarkService->set($bookmark);
-
- return $this->redirectFromReferer($request, $response, ['/pin'], ['pin']);
- }
-
- /**
- * GET /admin/shaare/private/{hash} - Attach a private key to given bookmark, then redirect to the sharing URL.
- */
- public function sharePrivate(Request $request, Response $response, array $args): Response
- {
- $this->checkToken($request);
-
- $hash = $args['hash'] ?? '';
- $bookmark = $this->container->bookmarkService->findByHash($hash);
-
- if ($bookmark->isPrivate() !== true) {
- return $this->redirect($response, '/shaare/' . $hash);
- }
-
- if (empty($bookmark->getAdditionalContentEntry('private_key'))) {
- $privateKey = bin2hex(random_bytes(16));
- $bookmark->addAdditionalContentEntry('private_key', $privateKey);
- $this->container->bookmarkService->set($bookmark);
- }
-
- return $this->redirect(
- $response,
- '/shaare/' . $hash . '?key=' . $bookmark->getAdditionalContentEntry('private_key')
- );
- }
-
- /**
- * Helper function used to display the shaare form whether it's a new or existing bookmark.
- *
- * @param array $link data used in template, either from parameters or from the data store
- */
- protected function displayForm(array $link, bool $isNew, Request $request, Response $response): Response
- {
- $tags = $this->container->bookmarkService->bookmarksCountPerTag();
- if ($this->container->conf->get('formatter') === 'markdown') {
- $tags[BookmarkMarkdownFormatter::NO_MD_TAG] = 1;
- }
-
- $data = escape([
- 'link' => $link,
- 'link_is_new' => $isNew,
- 'http_referer' => $this->container->environment['HTTP_REFERER'] ?? '',
- 'source' => $request->getParam('source') ?? '',
- 'tags' => $tags,
- 'default_private_links' => $this->container->conf->get('privacy.default_private_links', false),
- 'async_metadata' => $this->container->conf->get('general.enable_async_metadata', true),
- 'retrieve_description' => $this->container->conf->get('general.retrieve_description', false),
- ]);
-
- $this->executePageHooks('render_editlink', $data, TemplatePage::EDIT_LINK);
-
- foreach ($data as $key => $value) {
- $this->assignView($key, $value);
- }
-
- $editLabel = false === $isNew ? t('Edit') .' ' : '';
- $this->assignView(
- 'pagetitle',
- $editLabel . t('Shaare') .' - '. $this->container->conf->get('general.title', 'Shaarli')
- );
-
- return $response->write($this->render(TemplatePage::EDIT_LINK));
- }
-}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Shaarli\Front\Controller\Admin;
+
+use Shaarli\Formatter\BookmarkMarkdownFormatter;
+use Shaarli\Render\TemplatePage;
+use Slim\Http\Request;
+use Slim\Http\Response;
+
+class ShaareAddController extends ShaarliAdminController
+{
+ /**
+ * GET /admin/add-shaare - Displays the form used to create a new bookmark from an URL
+ */
+ public function addShaare(Request $request, Response $response): Response
+ {
+ $tags = $this->container->bookmarkService->bookmarksCountPerTag();
+ if ($this->container->conf->get('formatter') === 'markdown') {
+ $tags[BookmarkMarkdownFormatter::NO_MD_TAG] = 1;
+ }
+
+ $this->assignView(
+ 'pagetitle',
+ t('Shaare a new link') .' - '. $this->container->conf->get('general.title', 'Shaarli')
+ );
+ $this->assignView('tags', $tags);
+ $this->assignView('default_private_links', $this->container->conf->get('privacy.default_private_links', false));
+ $this->assignView('async_metadata', $this->container->conf->get('general.enable_async_metadata', true));
+
+ return $response->write($this->render(TemplatePage::ADDLINK));
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Shaarli\Front\Controller\Admin;
+
+use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
+use Slim\Http\Request;
+use Slim\Http\Response;
+
+/**
+ * Class PostBookmarkController
+ *
+ * Slim controller used to handle Shaarli create or edit bookmarks.
+ */
+class ShaareManageController extends ShaarliAdminController
+{
+ /**
+ * GET /admin/shaare/delete - Delete one or multiple bookmarks (depending on `id` query parameter).
+ */
+ public function deleteBookmark(Request $request, Response $response): Response
+ {
+ $this->checkToken($request);
+
+ $ids = escape(trim($request->getParam('id') ?? ''));
+ if (empty($ids) || strpos($ids, ' ') !== false) {
+ // multiple, space-separated ids provided
+ $ids = array_values(array_filter(preg_split('/\s+/', $ids), 'ctype_digit'));
+ } else {
+ $ids = [$ids];
+ }
+
+ // assert at least one id is given
+ if (0 === count($ids)) {
+ $this->saveErrorMessage(t('Invalid bookmark ID provided.'));
+
+ return $this->redirectFromReferer($request, $response, [], ['delete-shaare']);
+ }
+
+ $formatter = $this->container->formatterFactory->getFormatter('raw');
+ $count = 0;
+ foreach ($ids as $id) {
+ try {
+ $bookmark = $this->container->bookmarkService->get((int) $id);
+ } catch (BookmarkNotFoundException $e) {
+ $this->saveErrorMessage(sprintf(
+ t('Bookmark with identifier %s could not be found.'),
+ $id
+ ));
+
+ continue;
+ }
+
+ $data = $formatter->format($bookmark);
+ $this->executePageHooks('delete_link', $data);
+ $this->container->bookmarkService->remove($bookmark, false);
+ ++ $count;
+ }
+
+ if ($count > 0) {
+ $this->container->bookmarkService->save();
+ }
+
+ // If we are called from the bookmarklet, we must close the popup:
+ if ($request->getParam('source') === 'bookmarklet') {
+ return $response->write('<script>self.close();</script>');
+ }
+
+ // Don't redirect to where we were previously because the datastore has changed.
+ return $this->redirect($response, '/');
+ }
+
+ /**
+ * GET /admin/shaare/visibility
+ *
+ * Change visibility (public/private) of one or multiple bookmarks (depending on `id` query parameter).
+ */
+ public function changeVisibility(Request $request, Response $response): Response
+ {
+ $this->checkToken($request);
+
+ $ids = trim(escape($request->getParam('id') ?? ''));
+ if (empty($ids) || strpos($ids, ' ') !== false) {
+ // multiple, space-separated ids provided
+ $ids = array_values(array_filter(preg_split('/\s+/', $ids), 'ctype_digit'));
+ } else {
+ // only a single id provided
+ $ids = [$ids];
+ }
+
+ // assert at least one id is given
+ if (0 === count($ids)) {
+ $this->saveErrorMessage(t('Invalid bookmark ID provided.'));
+
+ return $this->redirectFromReferer($request, $response, [], ['change_visibility']);
+ }
+
+ // assert that the visibility is valid
+ $visibility = $request->getParam('newVisibility');
+ if (null === $visibility || false === in_array($visibility, ['public', 'private'], true)) {
+ $this->saveErrorMessage(t('Invalid visibility provided.'));
+
+ return $this->redirectFromReferer($request, $response, [], ['change_visibility']);
+ } else {
+ $isPrivate = $visibility === 'private';
+ }
+
+ $formatter = $this->container->formatterFactory->getFormatter('raw');
+ $count = 0;
+
+ foreach ($ids as $id) {
+ try {
+ $bookmark = $this->container->bookmarkService->get((int) $id);
+ } catch (BookmarkNotFoundException $e) {
+ $this->saveErrorMessage(sprintf(
+ t('Bookmark with identifier %s could not be found.'),
+ $id
+ ));
+
+ continue;
+ }
+
+ $bookmark->setPrivate($isPrivate);
+
+ // To preserve backward compatibility with 3rd parties, plugins still use arrays
+ $data = $formatter->format($bookmark);
+ $this->executePageHooks('save_link', $data);
+ $bookmark->fromArray($data);
+
+ $this->container->bookmarkService->set($bookmark, false);
+ ++$count;
+ }
+
+ if ($count > 0) {
+ $this->container->bookmarkService->save();
+ }
+
+ return $this->redirectFromReferer($request, $response, ['/visibility'], ['change_visibility']);
+ }
+
+ /**
+ * GET /admin/shaare/{id}/pin - Pin or unpin a bookmark.
+ */
+ public function pinBookmark(Request $request, Response $response, array $args): Response
+ {
+ $this->checkToken($request);
+
+ $id = $args['id'] ?? '';
+ try {
+ if (false === ctype_digit($id)) {
+ throw new BookmarkNotFoundException();
+ }
+ $bookmark = $this->container->bookmarkService->get((int) $id); // Read database
+ } catch (BookmarkNotFoundException $e) {
+ $this->saveErrorMessage(sprintf(
+ t('Bookmark with identifier %s could not be found.'),
+ $id
+ ));
+
+ return $this->redirectFromReferer($request, $response, ['/pin'], ['pin']);
+ }
+
+ $formatter = $this->container->formatterFactory->getFormatter('raw');
+
+ $bookmark->setSticky(!$bookmark->isSticky());
+
+ // To preserve backward compatibility with 3rd parties, plugins still use arrays
+ $data = $formatter->format($bookmark);
+ $this->executePageHooks('save_link', $data);
+ $bookmark->fromArray($data);
+
+ $this->container->bookmarkService->set($bookmark);
+
+ return $this->redirectFromReferer($request, $response, ['/pin'], ['pin']);
+ }
+
+ /**
+ * GET /admin/shaare/private/{hash} - Attach a private key to given bookmark, then redirect to the sharing URL.
+ */
+ public function sharePrivate(Request $request, Response $response, array $args): Response
+ {
+ $this->checkToken($request);
+
+ $hash = $args['hash'] ?? '';
+ $bookmark = $this->container->bookmarkService->findByHash($hash);
+
+ if ($bookmark->isPrivate() !== true) {
+ return $this->redirect($response, '/shaare/' . $hash);
+ }
+
+ if (empty($bookmark->getAdditionalContentEntry('private_key'))) {
+ $privateKey = bin2hex(random_bytes(16));
+ $bookmark->addAdditionalContentEntry('private_key', $privateKey);
+ $this->container->bookmarkService->set($bookmark);
+ }
+
+ return $this->redirect(
+ $response,
+ '/shaare/' . $hash . '?key=' . $bookmark->getAdditionalContentEntry('private_key')
+ );
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Shaarli\Front\Controller\Admin;
+
+use Shaarli\Bookmark\Bookmark;
+use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
+use Shaarli\Formatter\BookmarkMarkdownFormatter;
+use Shaarli\Render\TemplatePage;
+use Shaarli\Thumbnailer;
+use Slim\Http\Request;
+use Slim\Http\Response;
+
+class ShaarePublishController extends ShaarliAdminController
+{
+ /**
+ * GET /admin/shaare - Displays the bookmark form for creation.
+ * Note that if the URL is found in existing bookmarks, then it will be in edit mode.
+ */
+ public function displayCreateForm(Request $request, Response $response): Response
+ {
+ $url = cleanup_url($request->getParam('post'));
+ $link = $this->buildLinkDataFromUrl($request, $url);
+
+ return $this->displayForm($link, $link['linkIsNew'], $request, $response);
+ }
+
+ /**
+ * POST /admin/shaare-batch - Displays multiple creation/edit forms from bulk add in add-link page.
+ */
+ public function displayCreateBatchForms(Request $request, Response $response): Response
+ {
+ $urls = array_map('cleanup_url', explode(PHP_EOL, $request->getParam('urls')));
+
+ $links = [];
+ foreach ($urls as $url) {
+ $link = $this->buildLinkDataFromUrl($request, $url);
+ $data = $this->buildFormData($link, $link['linkIsNew'], $request);
+ $data['token'] = $this->container->sessionManager->generateToken();
+ $data['source'] = 'batch';
+
+ $this->executePageHooks('render_editlink', $data, TemplatePage::EDIT_LINK);
+
+ $links[] = $data;
+ }
+
+ $this->assignView('links', $links);
+ $this->assignView('batch_mode', true);
+ $this->assignView('async_metadata', $this->container->conf->get('general.enable_async_metadata', true));
+
+ return $response->write($this->render(TemplatePage::EDIT_LINK_BATCH));
+ }
+
+ /**
+ * GET /admin/shaare/{id} - Displays the bookmark form in edition mode.
+ */
+ public function displayEditForm(Request $request, Response $response, array $args): Response
+ {
+ $id = $args['id'] ?? '';
+ try {
+ if (false === ctype_digit($id)) {
+ throw new BookmarkNotFoundException();
+ }
+ $bookmark = $this->container->bookmarkService->get((int) $id); // Read database
+ } catch (BookmarkNotFoundException $e) {
+ $this->saveErrorMessage(sprintf(
+ t('Bookmark with identifier %s could not be found.'),
+ $id
+ ));
+
+ return $this->redirect($response, '/');
+ }
+
+ $formatter = $this->container->formatterFactory->getFormatter('raw');
+ $link = $formatter->format($bookmark);
+
+ return $this->displayForm($link, false, $request, $response);
+ }
+
+ /**
+ * POST /admin/shaare
+ */
+ public function save(Request $request, Response $response): Response
+ {
+ $this->checkToken($request);
+
+ // lf_id should only be present if the link exists.
+ $id = $request->getParam('lf_id') !== null ? intval(escape($request->getParam('lf_id'))) : null;
+ if (null !== $id && true === $this->container->bookmarkService->exists($id)) {
+ // Edit
+ $bookmark = $this->container->bookmarkService->get($id);
+ } else {
+ // New link
+ $bookmark = new Bookmark();
+ }
+
+ $bookmark->setTitle($request->getParam('lf_title'));
+ $bookmark->setDescription($request->getParam('lf_description'));
+ $bookmark->setUrl($request->getParam('lf_url'), $this->container->conf->get('security.allowed_protocols', []));
+ $bookmark->setPrivate(filter_var($request->getParam('lf_private'), FILTER_VALIDATE_BOOLEAN));
+ $bookmark->setTagsString($request->getParam('lf_tags'));
+
+ if ($this->container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE
+ && true !== $this->container->conf->get('general.enable_async_metadata', true)
+ && $bookmark->shouldUpdateThumbnail()
+ ) {
+ $bookmark->setThumbnail($this->container->thumbnailer->get($bookmark->getUrl()));
+ }
+ $this->container->bookmarkService->addOrSet($bookmark, false);
+
+ // To preserve backward compatibility with 3rd parties, plugins still use arrays
+ $formatter = $this->container->formatterFactory->getFormatter('raw');
+ $data = $formatter->format($bookmark);
+ $this->executePageHooks('save_link', $data);
+
+ $bookmark->fromArray($data);
+ $this->container->bookmarkService->set($bookmark);
+
+ // If we are called from the bookmarklet, we must close the popup:
+ if ($request->getParam('source') === 'bookmarklet') {
+ return $response->write('<script>self.close();</script>');
+ } elseif ($request->getParam('source') === 'batch') {
+ return $response;
+ }
+
+ if (!empty($request->getParam('returnurl'))) {
+ $this->container->environment['HTTP_REFERER'] = escape($request->getParam('returnurl'));
+ }
+
+ return $this->redirectFromReferer(
+ $request,
+ $response,
+ ['/admin/add-shaare', '/admin/shaare'], ['addlink', 'post', 'edit_link'],
+ $bookmark->getShortUrl()
+ );
+ }
+
+ /**
+ * Helper function used to display the shaare form whether it's a new or existing bookmark.
+ *
+ * @param array $link data used in template, either from parameters or from the data store
+ */
+ protected function displayForm(array $link, bool $isNew, Request $request, Response $response): Response
+ {
+ $data = $this->buildFormData($link, $isNew, $request);
+
+ $this->executePageHooks('render_editlink', $data, TemplatePage::EDIT_LINK);
+
+ foreach ($data as $key => $value) {
+ $this->assignView($key, $value);
+ }
+
+ $editLabel = false === $isNew ? t('Edit') .' ' : '';
+ $this->assignView(
+ 'pagetitle',
+ $editLabel . t('Shaare') .' - '. $this->container->conf->get('general.title', 'Shaarli')
+ );
+
+ return $response->write($this->render(TemplatePage::EDIT_LINK));
+ }
+
+ protected function buildLinkDataFromUrl(Request $request, string $url): array
+ {
+ // Check if URL is not already in database (in this case, we will edit the existing link)
+ $bookmark = $this->container->bookmarkService->findByUrl($url);
+ if (null === $bookmark) {
+ // Get shaare data if it was provided in URL (e.g.: by the bookmarklet).
+ $title = $request->getParam('title');
+ $description = $request->getParam('description');
+ $tags = $request->getParam('tags');
+ $private = filter_var($request->getParam('private'), FILTER_VALIDATE_BOOLEAN);
+
+ // If this is an HTTP(S) link, we try go get the page to extract
+ // the title (otherwise we will to straight to the edit form.)
+ if (true !== $this->container->conf->get('general.enable_async_metadata', true)
+ && empty($title)
+ && strpos(get_url_scheme($url) ?: '', 'http') !== false
+ ) {
+ $metadata = $this->container->metadataRetriever->retrieve($url);
+ }
+
+ if (empty($url)) {
+ $metadata['title'] = $this->container->conf->get('general.default_note_title', t('Note: '));
+ }
+
+ return [
+ 'title' => $title ?? $metadata['title'] ?? '',
+ 'url' => $url ?? '',
+ 'description' => $description ?? $metadata['description'] ?? '',
+ 'tags' => $tags ?? $metadata['tags'] ?? '',
+ 'private' => $private,
+ 'linkIsNew' => true,
+ ];
+ }
+
+ $formatter = $this->container->formatterFactory->getFormatter('raw');
+ $link = $formatter->format($bookmark);
+ $link['linkIsNew'] = false;
+
+ return $link;
+ }
+
+ protected function buildFormData(array $link, bool $isNew, Request $request): array
+ {
+ $tags = $this->container->bookmarkService->bookmarksCountPerTag();
+ if ($this->container->conf->get('formatter') === 'markdown') {
+ $tags[BookmarkMarkdownFormatter::NO_MD_TAG] = 1;
+ }
+
+ return escape([
+ 'link' => $link,
+ 'link_is_new' => $isNew,
+ 'http_referer' => $this->container->environment['HTTP_REFERER'] ?? '',
+ 'source' => $request->getParam('source') ?? '',
+ 'tags' => $tags,
+ 'default_private_links' => $this->container->conf->get('privacy.default_private_links', false),
+ 'async_metadata' => $this->container->conf->get('general.enable_async_metadata', true),
+ 'retrieve_description' => $this->container->conf->get('general.retrieve_description', false),
+ ]);
+ }
+}
public const DAILY = 'daily';
public const DAILY_RSS = 'dailyrss';
public const EDIT_LINK = 'editlink';
+ public const EDIT_LINK_BATCH = 'editlink.batch';
public const ERROR = 'error';
public const EXPORT = 'export';
public const NETSCAPE_EXPORT_BOOKMARKS = 'export.bookmarks';
(() => {
const basePath = document.querySelector('input[name="js_base_path"]').value;
- const loaders = document.querySelectorAll('.loading-input');
/*
* METADATA FOR EDIT BOOKMARK PAGE
*/
- const inputTitle = document.querySelector('input[name="lf_title"]');
- if (inputTitle != null) {
- if (inputTitle.value.length > 0) {
- clearLoaders(loaders);
- return;
- }
+ const inputTitles = document.querySelectorAll('input[name="lf_title"]');
+ if (inputTitles != null) {
+ [...inputTitles].forEach((inputTitle) => {
+ const form = inputTitle.closest('form[name="linkform"]');
+ const loaders = form.querySelectorAll('.loading-input');
+
+ if (inputTitle.value.length > 0) {
+ clearLoaders(loaders);
+ return;
+ }
- const url = document.querySelector('input[name="lf_url"]').value;
+ const url = form.querySelector('input[name="lf_url"]').value;
- const xhr = new XMLHttpRequest();
- xhr.open('GET', `${basePath}/admin/metadata?url=${encodeURI(url)}`, true);
- xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
- xhr.onload = () => {
- const result = JSON.parse(xhr.response);
- Object.keys(result).forEach((key) => {
- if (result[key] !== null && result[key].length) {
- const element = document.querySelector(`input[name="lf_${key}"], textarea[name="lf_${key}"]`);
- if (element != null && element.value.length === 0) {
- element.value = he.decode(result[key]);
+ const xhr = new XMLHttpRequest();
+ xhr.open('GET', `${basePath}/admin/metadata?url=${encodeURI(url)}`, true);
+ xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+ xhr.onload = () => {
+ const result = JSON.parse(xhr.response);
+ Object.keys(result).forEach((key) => {
+ if (result[key] !== null && result[key].length) {
+ const element = form.querySelector(`input[name="lf_${key}"], textarea[name="lf_${key}"]`);
+ if (element != null && element.value.length === 0) {
+ element.value = he.decode(result[key]);
+ }
}
- }
- });
- clearLoaders(loaders);
- };
+ });
+ clearLoaders(loaders);
+ };
- xhr.send();
+ xhr.send();
+ });
}
/*
--- /dev/null
+const sendBookmarkForm = (basePath, formElement) => {
+ const inputs = formElement
+ .querySelectorAll('input[type="text"], textarea, input[type="checkbox"], input[type="hidden"]');
+
+ const formData = new FormData();
+ [...inputs].forEach((input) => {
+ formData.append(input.getAttribute('name'), input.value);
+ });
+
+ return new Promise((resolve, reject) => {
+ const xhr = new XMLHttpRequest();
+ xhr.open('POST', `${basePath}/admin/shaare`);
+ xhr.onload = () => {
+ if (xhr.status !== 200) {
+ alert(`An error occurred. Return code: ${xhr.status}`);
+ reject();
+ } else {
+ formElement.remove();
+ resolve();
+ }
+ };
+ xhr.send(formData);
+ });
+};
+
+const sendBookmarkDelete = (buttonElement, formElement) => (
+ new Promise((resolve, reject) => {
+ const xhr = new XMLHttpRequest();
+ xhr.open('GET', buttonElement.href);
+ xhr.onload = () => {
+ if (xhr.status !== 200) {
+ alert(`An error occurred. Return code: ${xhr.status}`);
+ reject();
+ } else {
+ formElement.remove();
+ resolve();
+ }
+ };
+ xhr.send();
+ })
+);
+
+const redirectIfEmptyBatch = (basePath, formElements, path) => {
+ if (formElements == null || formElements.length === 0) {
+ window.location.href = `${basePath}${path}`;
+ }
+};
+
+(() => {
+ const basePath = document.querySelector('input[name="js_base_path"]').value;
+ const getForms = () => document.querySelectorAll('form[name="linkform"]');
+
+ const cancelButtons = document.querySelectorAll('[name="cancel-batch-link"]');
+ if (cancelButtons != null) {
+ [...cancelButtons].forEach((cancelButton) => {
+ cancelButton.addEventListener('click', (e) => {
+ e.preventDefault();
+ e.target.closest('form[name="linkform"]').remove();
+ redirectIfEmptyBatch(basePath, getForms(), '/admin/add-shaare');
+ });
+ });
+ }
+
+ const saveButtons = document.querySelectorAll('[name="save_edit"]');
+ if (saveButtons != null) {
+ [...saveButtons].forEach((saveButton) => {
+ saveButton.addEventListener('click', (e) => {
+ e.preventDefault();
+
+ const formElement = e.target.closest('form[name="linkform"]');
+ sendBookmarkForm(basePath, formElement)
+ .then(() => redirectIfEmptyBatch(basePath, getForms(), '/'));
+ });
+ });
+ }
+
+ const saveAllButtons = document.querySelectorAll('[name="save_edit_batch"]');
+ if (saveAllButtons != null) {
+ [...saveAllButtons].forEach((saveAllButton) => {
+ saveAllButton.addEventListener('click', (e) => {
+ e.preventDefault();
+
+ const promises = [];
+ [...getForms()].forEach((formElement) => {
+ promises.push(sendBookmarkForm(basePath, formElement));
+ });
+
+ Promise.all(promises).then(() => {
+ window.location.href = basePath || '/';
+ });
+ });
+ });
+ }
+
+ const deleteButtons = document.querySelectorAll('[name="delete_link"]');
+ if (deleteButtons != null) {
+ [...deleteButtons].forEach((deleteButton) => {
+ deleteButton.addEventListener('click', (e) => {
+ e.preventDefault();
+
+ const formElement = e.target.closest('form[name="linkform"]');
+ sendBookmarkDelete(e.target, formElement)
+ .then(() => redirectIfEmptyBatch(basePath, getForms(), '/'));
+ });
+ });
+ }
+})();
});
});
}
+
+ const bulkCreationButton = document.querySelector('.addlink-batch-show-more-block');
+ if (bulkCreationButton != null) {
+ const toggleBulkCreationVisibility = (showMoreBlockElement, formElement) => {
+ if (bulkCreationButton.classList.contains('pure-u-0')) {
+ showMoreBlockElement.classList.remove('pure-u-0');
+ formElement.classList.add('pure-u-0');
+ } else {
+ showMoreBlockElement.classList.add('pure-u-0');
+ formElement.classList.remove('pure-u-0');
+ }
+ };
+
+ const bulkCreationForm = document.querySelector('.addlink-batch-form-block');
+
+ toggleBulkCreationVisibility(bulkCreationButton, bulkCreationForm);
+ bulkCreationButton.querySelector('a').addEventListener('click', (e) => {
+ e.preventDefault();
+ toggleBulkCreationVisibility(bulkCreationButton, bulkCreationForm);
+ });
+ }
})();
&.button-red {
background: $red;
}
+
+ &.button-grey {
+ background: $light-grey;
+ }
}
.submit-buttons {
position: absolute;
right: 5%;
}
+
+ &.button-grey {
+ position: absolute;
+ left: 5%;
+ }
}
}
}
}
}
+// Batch creation
+input[name='save_edit_batch'] {
+ @extend %page-form-button;
+}
+
+.addlink-batch-show-more {
+ display: flex;
+ align-items: center;
+ margin: 20px 0 8px;
+
+ a {
+ color: var(--main-color);
+ text-decoration: none;
+ }
+
+ &::before,
+ &::after {
+ content: "";
+ flex-grow: 1;
+ background: rgba(0, 0, 0, 0.35);
+ height: 1px;
+ font-size: 0;
+ line-height: 0;
+ }
+
+ &::before {
+ margin: 0 16px 0 0;
+ }
+
+ &::after {
+ margin: 0 0 0 16px;
+ }
+}
+
+.addlink-batch-form-block {
+ .pure-alert {
+ margin: 25px 0 0 0;
+ }
+}
+
// Print rules
@media print {
.shaarli-menu {
"le serveur web peut accepter (%s). Merci de l'envoyer en parties plus "
"légères."
-#: application/front/controller/admin/ManageShaareController.php:29
-#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
-msgid "Shaare a new link"
-msgstr "Partager un nouveau lien"
-
#: application/front/controller/admin/ManageShaareController.php:64
-msgid "Note: "
-msgstr "Note : "
-
#: application/front/controller/admin/ManageShaareController.php:95
#: application/front/controller/admin/ManageShaareController.php:193
#: application/front/controller/admin/ManageShaareController.php:262
#: application/front/controller/admin/ManageShaareController.php:302
-#, php-format
-msgid "Bookmark with identifier %s could not be found."
-msgstr "Le lien avec l'identifiant %s n'a pas pu être trouvé."
-
#: application/front/controller/admin/ManageShaareController.php:181
#: application/front/controller/admin/ManageShaareController.php:239
-msgid "Invalid bookmark ID provided."
-msgstr "ID du lien non valide."
-
#: application/front/controller/admin/ManageShaareController.php:247
-msgid "Invalid visibility provided."
-msgstr "Visibilité du lien non valide."
-
#: application/front/controller/admin/ManageShaareController.php:378
-#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:171
-msgid "Edit"
-msgstr "Modifier"
-
#: application/front/controller/admin/ManageShaareController.php:381
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:28
-msgid "Shaare"
-msgstr "Shaare"
-
#: application/front/controller/admin/ManageTagController.php:29
#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42
msgid "Shaarli's cache folder has been cleared!"
msgstr "Le dossier de cache de Shaarli a été vidé !"
+#, php-format
+msgid "Bookmark with identifier %s could not be found."
+msgstr "Le lien avec l'identifiant %s n'a pas pu être trouvé."
+
+#: application/front/controller/admin/ShaareManageController.php:101
+msgid "Invalid visibility provided."
+msgstr "Visibilité du lien non valide."
+
+#: application/front/controller/admin/ShaarePublishController.php:154
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:171
+msgid "Edit"
+msgstr "Modifier"
+
+#: application/front/controller/admin/ShaarePublishController.php:157
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:28
+msgid "Shaare"
+msgstr "Shaare"
+
+#: application/front/controller/admin/ShaarePublishController.php:184
+msgid "Note: "
+msgstr "Note : "
+
#: application/front/controller/admin/ThumbnailsController.php:37
#: tmp/thumbnails.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
msgid "Thumbnails update"
msgid "URL or leave empty to post a note"
msgstr "URL ou laisser vide pour créer une note"
+#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29
+msgid "BULK CREATION"
+msgstr "CRÉATION DE MASSE"
+
+#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:40
+msgid "Metadata asynchronous retrieval is disabled."
+msgstr "La récupération asynchrone des meta-données est désactivée."
+
+#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42
+msgid ""
+"We recommend that you enable the setting <em>general > "
+"enable_async_metadata</em> in your configuration file to use bulk link "
+"creation."
+msgstr ""
+"Nous recommandons d'activer le paramètre <em>general > "
+"enable_async_metadata</em> dans votre fichier de configuration pour utiliser "
+"la création de masse."
+
+#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:56
+msgid "Shaare multiple new links"
+msgstr "Partagez plusieurs nouveaux liens"
+
+#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:59
+msgid "Add one URL per line to create multiple bookmarks."
+msgstr "Ajouter une URL par ligne pour créer plusieurs marque-pages."
+
+#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:63
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:67
+msgid "Tags"
+msgstr "Tags"
+
+#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:73
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:83
+#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169
+msgid "Private"
+msgstr "Privé"
+
+#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:78
+msgid "Add links"
+msgstr "Ajouter des liens"
+
#: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
msgid "Current password"
msgstr "Mot de passe actuel"
msgstr "Description"
#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:58
-msgid "Tags"
-msgstr "Tags"
-
#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74
-#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35
-#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169
-msgid "Private"
-msgstr "Privé"
-
#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:80
msgid "Description will be rendered with"
msgstr "La description sera générée avec"
msgstr "la syntaxe Markdown"
#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102
+msgid "Cancel"
+msgstr "Annuler"
+
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:121
msgid "Apply Changes"
msgstr "Appliquer les changements"
+#: tmp/editlink.batch.b91ef64efc3688266305ea9b42e5017e.rtpl.php:12
+#: tmp/editlink.batch.b91ef64efc3688266305ea9b42e5017e.rtpl.php:23
+msgid "Save all"
+msgstr "Tout enregistrer"
+
#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:107
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:173
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:147
$this->post('/configure', '\Shaarli\Front\Controller\Admin\ConfigureController:save');
$this->get('/tags', '\Shaarli\Front\Controller\Admin\ManageTagController:index');
$this->post('/tags', '\Shaarli\Front\Controller\Admin\ManageTagController:save');
- $this->get('/add-shaare', '\Shaarli\Front\Controller\Admin\ManageShaareController:addShaare');
- $this->get('/shaare', '\Shaarli\Front\Controller\Admin\ManageShaareController:displayCreateForm');
- $this->get('/shaare/{id:[0-9]+}', '\Shaarli\Front\Controller\Admin\ManageShaareController:displayEditForm');
- $this->get('/shaare/private/{hash}', '\Shaarli\Front\Controller\Admin\ManageShaareController:sharePrivate');
- $this->post('/shaare', '\Shaarli\Front\Controller\Admin\ManageShaareController:save');
- $this->get('/shaare/delete', '\Shaarli\Front\Controller\Admin\ManageShaareController:deleteBookmark');
- $this->get('/shaare/visibility', '\Shaarli\Front\Controller\Admin\ManageShaareController:changeVisibility');
- $this->get('/shaare/{id:[0-9]+}/pin', '\Shaarli\Front\Controller\Admin\ManageShaareController:pinBookmark');
+ $this->get('/add-shaare', '\Shaarli\Front\Controller\Admin\ShaareAddController:addShaare');
+ $this->get('/shaare', '\Shaarli\Front\Controller\Admin\ShaarePublishController:displayCreateForm');
+ $this->get('/shaare/{id:[0-9]+}', '\Shaarli\Front\Controller\Admin\ShaarePublishController:displayEditForm');
+ $this->get('/shaare/private/{hash}', '\Shaarli\Front\Controller\Admin\ShaareManageController:sharePrivate');
+ $this->post('/shaare-batch', '\Shaarli\Front\Controller\Admin\ShaarePublishController:displayCreateBatchForms');
+ $this->post('/shaare', '\Shaarli\Front\Controller\Admin\ShaarePublishController:save');
+ $this->get('/shaare/delete', '\Shaarli\Front\Controller\Admin\ShaareManageController:deleteBookmark');
+ $this->get('/shaare/visibility', '\Shaarli\Front\Controller\Admin\ShaareManageController:changeVisibility');
+ $this->get('/shaare/{id:[0-9]+}/pin', '\Shaarli\Front\Controller\Admin\ShaareManageController:pinBookmark');
$this->patch(
'/shaare/{id:[0-9]+}/update-thumbnail',
'\Shaarli\Front\Controller\Admin\ThumbnailsController:ajaxUpdate'
+++ /dev/null
-<?php
-
-declare(strict_types=1);
-
-namespace Shaarli\Front\Controller\Admin\ManageShaareControllerTest;
-
-use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper;
-use Shaarli\Front\Controller\Admin\ManageShaareController;
-use Shaarli\Http\HttpAccess;
-use Shaarli\TestCase;
-use Slim\Http\Request;
-use Slim\Http\Response;
-
-class AddShaareTest extends TestCase
-{
- use FrontAdminControllerMockHelper;
-
- /** @var ManageShaareController */
- protected $controller;
-
- public function setUp(): void
- {
- $this->createContainer();
-
- $this->container->httpAccess = $this->createMock(HttpAccess::class);
- $this->controller = new ManageShaareController($this->container);
- }
-
- /**
- * Test displaying add link page
- */
- public function testAddShaare(): void
- {
- $assignedVariables = [];
- $this->assignTemplateVars($assignedVariables);
-
- $request = $this->createMock(Request::class);
- $response = new Response();
-
- $result = $this->controller->addShaare($request, $response);
-
- static::assertSame(200, $result->getStatusCode());
- static::assertSame('addlink', (string) $result->getBody());
-
- static::assertSame('Shaare a new link - Shaarli', $assignedVariables['pagetitle']);
- }
-}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Shaarli\Front\Controller\Admin;
+
+use Shaarli\Config\ConfigManager;
+use Shaarli\Formatter\BookmarkMarkdownFormatter;
+use Shaarli\Http\HttpAccess;
+use Shaarli\TestCase;
+use Slim\Http\Request;
+use Slim\Http\Response;
+
+class ShaareAddControllerTest extends TestCase
+{
+ use FrontAdminControllerMockHelper;
+
+ /** @var ShaareAddController */
+ protected $controller;
+
+ public function setUp(): void
+ {
+ $this->createContainer();
+
+ $this->container->httpAccess = $this->createMock(HttpAccess::class);
+ $this->controller = new ShaareAddController($this->container);
+ }
+
+ /**
+ * Test displaying add link page
+ */
+ public function testAddShaare(): void
+ {
+ $assignedVariables = [];
+ $this->assignTemplateVars($assignedVariables);
+
+ $request = $this->createMock(Request::class);
+ $response = new Response();
+
+ $expectedTags = [
+ 'tag1' => 32,
+ 'tag2' => 24,
+ 'tag3' => 1,
+ ];
+ $this->container->bookmarkService
+ ->expects(static::once())
+ ->method('bookmarksCountPerTag')
+ ->willReturn($expectedTags)
+ ;
+ $expectedTags = array_merge($expectedTags, [BookmarkMarkdownFormatter::NO_MD_TAG => 1]);
+
+ $this->container->conf = $this->createMock(ConfigManager::class);
+ $this->container->conf->method('get')->willReturnCallback(function (string $key, $default) {
+ return $key === 'formatter' ? 'markdown' : $default;
+ });
+
+ $result = $this->controller->addShaare($request, $response);
+
+ static::assertSame(200, $result->getStatusCode());
+ static::assertSame('addlink', (string) $result->getBody());
+
+ static::assertSame('Shaare a new link - Shaarli', $assignedVariables['pagetitle']);
+ static::assertFalse($assignedVariables['default_private_links']);
+ static::assertTrue($assignedVariables['async_metadata']);
+ static::assertSame($expectedTags, $assignedVariables['tags']);
+ }
+
+ /**
+ * Test displaying add link page
+ */
+ public function testAddShaareWithoutMd(): void
+ {
+ $assignedVariables = [];
+ $this->assignTemplateVars($assignedVariables);
+
+ $request = $this->createMock(Request::class);
+ $response = new Response();
+
+ $expectedTags = [
+ 'tag1' => 32,
+ 'tag2' => 24,
+ 'tag3' => 1,
+ ];
+ $this->container->bookmarkService
+ ->expects(static::once())
+ ->method('bookmarksCountPerTag')
+ ->willReturn($expectedTags)
+ ;
+
+ $result = $this->controller->addShaare($request, $response);
+
+ static::assertSame(200, $result->getStatusCode());
+ static::assertSame('addlink', (string) $result->getBody());
+
+ static::assertSame($expectedTags, $assignedVariables['tags']);
+ }
+}
declare(strict_types=1);
-namespace Shaarli\Front\Controller\Admin\ManageShaareControllerTest;
+namespace Shaarli\Front\Controller\Admin\ShaareManageControllerTest;
use Shaarli\Bookmark\Bookmark;
use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
use Shaarli\Formatter\BookmarkRawFormatter;
use Shaarli\Formatter\FormatterFactory;
use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper;
-use Shaarli\Front\Controller\Admin\ManageShaareController;
+use Shaarli\Front\Controller\Admin\ShaareManageController;
use Shaarli\Http\HttpAccess;
use Shaarli\Security\SessionManager;
use Shaarli\TestCase;
{
use FrontAdminControllerMockHelper;
- /** @var ManageShaareController */
+ /** @var ShaareManageController */
protected $controller;
public function setUp(): void
$this->createContainer();
$this->container->httpAccess = $this->createMock(HttpAccess::class);
- $this->controller = new ManageShaareController($this->container);
+ $this->controller = new ShaareManageController($this->container);
}
/**
declare(strict_types=1);
-namespace Shaarli\Front\Controller\Admin\ManageShaareControllerTest;
+namespace Shaarli\Front\Controller\Admin\ShaareManageControllerTest;
use Shaarli\Bookmark\Bookmark;
use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
use Shaarli\Formatter\BookmarkFormatter;
use Shaarli\Formatter\FormatterFactory;
use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper;
-use Shaarli\Front\Controller\Admin\ManageShaareController;
+use Shaarli\Front\Controller\Admin\ShaareManageController;
use Shaarli\Http\HttpAccess;
use Shaarli\Security\SessionManager;
use Shaarli\TestCase;
{
use FrontAdminControllerMockHelper;
- /** @var ManageShaareController */
+ /** @var ShaareManageController */
protected $controller;
public function setUp(): void
$this->createContainer();
$this->container->httpAccess = $this->createMock(HttpAccess::class);
- $this->controller = new ManageShaareController($this->container);
+ $this->controller = new ShaareManageController($this->container);
}
/**
declare(strict_types=1);
-namespace Shaarli\Front\Controller\Admin\ManageShaareControllerTest;
+namespace Shaarli\Front\Controller\Admin\ShaareManageControllerTest;
use Shaarli\Bookmark\Bookmark;
use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper;
-use Shaarli\Front\Controller\Admin\ManageShaareController;
+use Shaarli\Front\Controller\Admin\ShaareManageController;
use Shaarli\Http\HttpAccess;
use Shaarli\Security\SessionManager;
use Shaarli\TestCase;
{
use FrontAdminControllerMockHelper;
- /** @var ManageShaareController */
+ /** @var ShaareManageController */
protected $controller;
public function setUp(): void
$this->createContainer();
$this->container->httpAccess = $this->createMock(HttpAccess::class);
- $this->controller = new ManageShaareController($this->container);
+ $this->controller = new ShaareManageController($this->container);
}
/**
declare(strict_types=1);
-namespace Shaarli\Front\Controller\Admin\ManageShaareControllerTest;
+namespace Shaarli\Front\Controller\Admin\ShaareManageControllerTest;
use Shaarli\Bookmark\Bookmark;
use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper;
-use Shaarli\Front\Controller\Admin\ManageShaareController;
+use Shaarli\Front\Controller\Admin\ShaareManageController;
use Shaarli\Http\HttpAccess;
use Shaarli\TestCase;
use Slim\Http\Request;
{
use FrontAdminControllerMockHelper;
- /** @var ManageShaareController */
+ /** @var ShaareManageController */
protected $controller;
public function setUp(): void
$this->createContainer();
$this->container->httpAccess = $this->createMock(HttpAccess::class);
- $this->controller = new ManageShaareController($this->container);
+ $this->controller = new ShaareManageController($this->container);
}
/**
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Shaarli\Front\Controller\Admin\ShaarePublishControllerTest;
+
+use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper;
+use Shaarli\Front\Controller\Admin\ShaarePublishController;
+use Shaarli\Http\HttpAccess;
+use Shaarli\Http\MetadataRetriever;
+use Shaarli\TestCase;
+use Slim\Http\Request;
+use Slim\Http\Response;
+
+class DisplayCreateBatchFormTest extends TestCase
+{
+ use FrontAdminControllerMockHelper;
+
+ /** @var ShaarePublishController */
+ protected $controller;
+
+ public function setUp(): void
+ {
+ $this->createContainer();
+
+ $this->container->httpAccess = $this->createMock(HttpAccess::class);
+ $this->container->metadataRetriever = $this->createMock(MetadataRetriever::class);
+ $this->controller = new ShaarePublishController($this->container);
+ }
+
+ /**
+ * TODO
+ */
+ public function testDisplayCreateFormBatch(): void
+ {
+ $urls = [
+ 'https://domain1.tld/url1',
+ 'https://domain2.tld/url2',
+ 'https://domain3.tld/url3',
+ ];
+
+ $request = $this->createMock(Request::class);
+ $request->method('getParam')->willReturnCallback(function (string $key) use ($urls): ?string {
+ return $key === 'urls' ? implode(PHP_EOL, $urls) : null;
+ });
+ $response = new Response();
+
+ $assignedVariables = [];
+ $this->assignTemplateVars($assignedVariables);
+
+ $result = $this->controller->displayCreateBatchForms($request, $response);
+
+ static::assertSame(200, $result->getStatusCode());
+ static::assertSame('editlink.batch', (string) $result->getBody());
+
+ static::assertTrue($assignedVariables['batch_mode']);
+ static::assertCount(3, $assignedVariables['links']);
+ static::assertSame($urls[0], $assignedVariables['links'][0]['link']['url']);
+ static::assertSame($urls[1], $assignedVariables['links'][1]['link']['url']);
+ static::assertSame($urls[2], $assignedVariables['links'][2]['link']['url']);
+ }
+}
declare(strict_types=1);
-namespace Shaarli\Front\Controller\Admin\ManageShaareControllerTest;
+namespace Shaarli\Front\Controller\Admin\ShaarePublishControllerTest;
use Shaarli\Bookmark\Bookmark;
use Shaarli\Config\ConfigManager;
use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper;
-use Shaarli\Front\Controller\Admin\ManageShaareController;
+use Shaarli\Front\Controller\Admin\ShaarePublishController;
use Shaarli\Http\HttpAccess;
use Shaarli\Http\MetadataRetriever;
use Shaarli\TestCase;
{
use FrontAdminControllerMockHelper;
- /** @var ManageShaareController */
+ /** @var ShaarePublishController */
protected $controller;
public function setUp(): void
$this->container->httpAccess = $this->createMock(HttpAccess::class);
$this->container->metadataRetriever = $this->createMock(MetadataRetriever::class);
- $this->controller = new ManageShaareController($this->container);
+ $this->controller = new ShaarePublishController($this->container);
}
/**
declare(strict_types=1);
-namespace Shaarli\Front\Controller\Admin\ManageShaareControllerTest;
+namespace Shaarli\Front\Controller\Admin\ShaarePublishControllerTest;
use Shaarli\Bookmark\Bookmark;
use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper;
-use Shaarli\Front\Controller\Admin\ManageShaareController;
+use Shaarli\Front\Controller\Admin\ShaarePublishController;
use Shaarli\Http\HttpAccess;
use Shaarli\Security\SessionManager;
use Shaarli\TestCase;
{
use FrontAdminControllerMockHelper;
- /** @var ManageShaareController */
+ /** @var ShaarePublishController */
protected $controller;
public function setUp(): void
$this->createContainer();
$this->container->httpAccess = $this->createMock(HttpAccess::class);
- $this->controller = new ManageShaareController($this->container);
+ $this->controller = new ShaarePublishController($this->container);
}
/**
declare(strict_types=1);
-namespace Shaarli\Front\Controller\Admin\ManageShaareControllerTest;
+namespace Shaarli\Front\Controller\Admin\ShaarePublishControllerTest;
use Shaarli\Bookmark\Bookmark;
use Shaarli\Config\ConfigManager;
use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper;
-use Shaarli\Front\Controller\Admin\ManageShaareController;
+use Shaarli\Front\Controller\Admin\ShaarePublishController;
use Shaarli\Front\Exception\WrongTokenException;
use Shaarli\Http\HttpAccess;
use Shaarli\Security\SessionManager;
{
use FrontAdminControllerMockHelper;
- /** @var ManageShaareController */
+ /** @var ShaarePublishController */
protected $controller;
public function setUp(): void
$this->createContainer();
$this->container->httpAccess = $this->createMock(HttpAccess::class);
- $this->controller = new ManageShaareController($this->container);
+ $this->controller = new ShaarePublishController($this->container);
}
/**
</form>
</div>
</div>
+
+<div class="pure-g addlink-batch-show-more-block pure-u-0">
+ <div class="pure-u-lg-1-3 pure-u-1-24"></div>
+ <div class="pure-u-lg-1-3 pure-u-22-24 addlink-batch-show-more">
+ <a href="#">{'BULK CREATION'|t} <i class="fa fa-plus-circle" aria-hidden="true"></i></a>
+ </div>
+</div>
+
+<div class="addlink-batch-form-block">
+ {if="empty($async_metadata)"}
+ <div class="pure-g pure-alert pure-alert-warning pure-alert-closable">
+ <div class="pure-u-2-24"></div>
+ <div class="pure-u-20-24">
+ <p>
+ {'Metadata asynchronous retrieval is disabled.'|t}
+ {'We recommend that you enable the setting <em>general > enable_async_metadata</em> in your configuration file to use bulk link creation.'|t}
+ </p>
+ </div>
+ <div class="pure-u-2-24">
+ <i class="fa fa-times pure-alert-close"></i>
+ </div>
+ </div>
+ {/if}
+
+ <div class="pure-g">
+ <div class="pure-u-lg-1-3 pure-u-1-24"></div>
+ <div id="batch-addlink-form" class="page-form page-form-light pure-u-lg-1-3 pure-u-22-24">
+ <h2 class="window-title">{"Shaare multiple new links"|t}</h2>
+ <form method="POST" action="{$base_path}/admin/shaare-batch" name="batch-addform" class="batch-addform">
+ <div>
+ <label for="urls">{'Add one URL per line to create multiple bookmarks.'|t}</label>
+ <textarea name="urls" id="urls"></textarea>
+
+ <div>
+ <label for="tags">{'Tags'|t}</label>
+ </div>
+ <div>
+ <input type="text" name="tags" id="tags" class="lf_input"
+ data-list="{loop="$tags"}{$key}, {/loop}" data-multiple data-autofirst autocomplete="off">
+ </div>
+
+ <div>
+ <input type="checkbox" name="private"
+ {if="$default_private_links"} checked="checked"{/if}>
+ <label for="lf_private">{'Private'|t}</label>
+ </div>
+ </div>
+ <div>
+ <input type="hidden" name="token" value="{$token}">
+ <input type="submit" value="{'Add links'|t}">
+ </div>
+ </form>
+ </div>
+ </div>
+</div>
+
{include="page.footer"}
</body>
</html>
--- /dev/null
+<!DOCTYPE html>
+<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
+<head>
+ {include="includes"}
+</head>
+<body>
+{include="page.header"}
+
+<div class="center">
+ <input type="submit" name="save_edit_batch" class="pure-button-shaarli" value="{'Save all'|t}">
+</div>
+
+{loop="$links"}
+ {include="editlink"}
+{/loop}
+
+<div class="center">
+ <input type="submit" name="save_edit_batch" class="pure-button-shaarli" value="{'Save all'|t}">
+</div>
+
+{include="page.footer"}
+{if="$async_metadata"}<script src="{$asset_path}/js/metadata.min.js?v={$version_hash}#"></script>{/if}
+<script src="{$asset_path}/js/shaare_batch.min.js?v={$version_hash}#"></script>
+{if="empty($batch_mode)"}
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
</head>
<body>
{include="page.header"}
+{else}
+ {ignore}Lil hack: when included in a loop in batch mode, `$value` is assigned by RainTPL with template vars.{/ignore}
+ {function="extract($value) ? '' : ''"}
+{/if}
<div id="editlinkform" class="edit-link-container" class="pure-g">
<div class="pure-u-lg-1-5 pure-u-1-24"></div>
<form method="post"
<div class="submit-buttons center">
+ {if="!empty($batch_mode)"}
+ <a href="#" class="button button-grey" name="cancel-batch-link"
+ title="{'Remove this bookmark from batch creation/modification.'}"
+ >
+ {'Cancel'|t}
+ </a>
+ {/if}
<input type="submit" name="save_edit" class="" id="button-save-edit"
value="{if="$link_is_new"}{'Save'|t}{else}{'Apply Changes'|t}{/if}">
{if="!$link_is_new"}
{/if}
</form>
</div>
+
+{if="empty($batch_mode)"}
{include="page.footer"}
{if="$link_is_new && $async_metadata"}<script src="{$asset_path}/js/metadata.min.js?v={$version_hash}#"></script>{/if}
</body>
</html>
+{/if}
{
mode: 'production',
entry: {
+ shaare_batch: './assets/common/js/shaare-batch.js',
thumbnails: './assets/common/js/thumbnails.js',
thumbnails_update: './assets/common/js/thumbnails-update.js',
metadata: './assets/common/js/metadata.js',