' . $processedDescription . '
';
+ }
+
+ return $processedDescription;
+ }
+
+ /**
+ * Remove the NO markdown tag if it is present
+ *
+ * @inheritdoc
+ */
+ protected function formatTagList($bookmark)
+ {
+ $out = parent::formatTagList($bookmark);
+ if ($this->isLoggedIn === false && ($pos = array_search(self::NO_MD_TAG, $out)) !== false) {
+ unset($out[$pos]);
+ return array_values($out);
+ }
+ return $out;
+ }
+
+ /**
+ * Replace not whitelisted protocols with http:// in given description.
+ * Also adds `index_url` to relative links if it's specified
+ *
+ * @param string $description input description text.
+ *
+ * @return string $description without malicious link.
+ */
+ protected function filterProtocols($description)
+ {
+ $allowedProtocols = $this->allowedProtocols;
+ $indexUrl = ! empty($this->contextData['index_url']) ? $this->contextData['index_url'] : '';
+
+ return preg_replace_callback(
+ '#]\((.*?)\)#is',
+ function ($match) use ($allowedProtocols, $indexUrl) {
+ $link = startsWith($match[1], '?') || startsWith($match[1], '/') ? $indexUrl : '';
+ $link .= whitelist_protocols($match[1], $allowedProtocols);
+ return '](' . $link . ')';
+ },
+ $description
+ );
+ }
+
+ /**
+ * Replace hashtag in Markdown links format
+ * E.g. `#hashtag` becomes `[#hashtag](./add-tag/hashtag)`
+ * It includes the index URL if specified.
+ *
+ * @param string $description
+ *
+ * @return string
+ */
+ protected function formatHashTags($description)
+ {
+ $indexUrl = ! empty($this->contextData['index_url']) ? $this->contextData['index_url'] : '';
+ $tokens = '(?:' . BookmarkDefaultFormatter::SEARCH_HIGHLIGHT_OPEN . ')' .
+ '(?:' . BookmarkDefaultFormatter::SEARCH_HIGHLIGHT_CLOSE . ')'
+ ;
+
+ /*
+ * To support unicode: http://stackoverflow.com/a/35498078/1484919
+ * \p{Pc} - to match underscore
+ * \p{N} - numeric character in any script
+ * \p{L} - letter from any language
+ * \p{Mn} - any non marking space (accents, umlauts, etc)
+ */
+ $regex = '/(^|\s)#([\p{Pc}\p{N}\p{L}\p{Mn}' . $tokens . ']+)/mui';
+ $replacement = function (array $match) use ($indexUrl): string {
+ $cleanMatch = str_replace(
+ BookmarkDefaultFormatter::SEARCH_HIGHLIGHT_OPEN,
+ '',
+ str_replace(BookmarkDefaultFormatter::SEARCH_HIGHLIGHT_CLOSE, '', $match[2])
+ );
+ return $match[1] . '[#' . $match[2] . '](' . $indexUrl . './add-tag/' . $cleanMatch . ')';
+ };
+
+ $descriptionLines = explode(PHP_EOL, $description);
+ $descriptionOut = '';
+ $codeBlockOn = false;
+ $lineCount = 0;
+
+ foreach ($descriptionLines as $descriptionLine) {
+ // Detect line of code: starting with 4 spaces,
+ // except lists which can start with +/*/- or `2.` after spaces.
+ $codeLineOn = preg_match('/^ +(?=[^\+\*\-])(?=(?!\d\.).)/', $descriptionLine) > 0;
+ // Detect and toggle block of code
+ if (!$codeBlockOn) {
+ $codeBlockOn = preg_match('/^```/', $descriptionLine) > 0;
+ } elseif (preg_match('/^```/', $descriptionLine) > 0) {
+ $codeBlockOn = false;
+ }
+
+ if (!$codeBlockOn && !$codeLineOn) {
+ $descriptionLine = preg_replace_callback($regex, $replacement, $descriptionLine);
+ }
+
+ $descriptionOut .= $descriptionLine;
+ if ($lineCount++ < count($descriptionLines) - 1) {
+ $descriptionOut .= PHP_EOL;
+ }
+ }
+
+ return $descriptionOut;
+ }
+
+ /**
+ * Remove dangerous HTML tags (tags, iframe, etc.).
+ * Doesn't affect content (already escaped by Parsedown).
+ *
+ * @param string $description input description text.
+ *
+ * @return string given string escaped.
+ */
+ protected function sanitizeHtml($description)
+ {
+ $escapeTags = [
+ 'script',
+ 'style',
+ 'link',
+ 'iframe',
+ 'frameset',
+ 'frame',
+ ];
+ foreach ($escapeTags as $tag) {
+ $description = preg_replace_callback(
+ '#<\s*' . $tag . '[^>]*>(.*\s*' . $tag . '[^>]*>)?#is',
+ function ($match) {
+ return escape($match[0]);
+ },
+ $description
+ );
+ }
+ $description = preg_replace(
+ '#(<[^>]+\s)on[a-z]*="?[^ "]*"?#is',
+ '$1',
+ $description
+ );
+ return $description;
+ }
+
+ protected function reverseEscapedHtml($description)
+ {
+ return unescape($description);
+ }
+}
diff --git a/application/formatter/BookmarkRawFormatter.php b/application/formatter/BookmarkRawFormatter.php
new file mode 100644
index 00000000..4ff07cdf
--- /dev/null
+++ b/application/formatter/BookmarkRawFormatter.php
@@ -0,0 +1,15 @@
+conf = $conf;
+ $this->isLoggedIn = $isLoggedIn;
+ }
+
+ /**
+ * Instanciate a BookmarkFormatter depending on the configuration or provided formatter type.
+ *
+ * @param string|null $type force a specific type regardless of the configuration
+ *
+ * @return BookmarkFormatter instance.
+ */
+ public function getFormatter(string $type = null): BookmarkFormatter
+ {
+ $type = $type ? $type : $this->conf->get('formatter', 'default');
+ $className = '\\Shaarli\\Formatter\\Bookmark' . ucfirst($type) . 'Formatter';
+ if (!class_exists($className)) {
+ $className = '\\Shaarli\\Formatter\\BookmarkDefaultFormatter';
+ }
+
+ return new $className($this->conf, $this->isLoggedIn);
+ }
+}
diff --git a/application/formatter/Parsedown/ShaarliParsedown.php b/application/formatter/Parsedown/ShaarliParsedown.php
new file mode 100644
index 00000000..8eb48fda
--- /dev/null
+++ b/application/formatter/Parsedown/ShaarliParsedown.php
@@ -0,0 +1,15 @@
+shaarliFormatLink(parent::inlineLink($excerpt), true);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function inlineUrl($excerpt)
+ {
+ return $this->shaarliFormatLink(parent::inlineUrl($excerpt), false);
+ }
+
+ /**
+ * Properly format markdown link:
+ * - remove highlight tags from HREF attribute
+ * - (optional) add highlight tags to link caption
+ *
+ * @param array|null $link Parsedown formatted link array.
+ * It can be empty.
+ * @param bool $fullWrap Add highlight tags the whole link caption
+ *
+ * @return array|null
+ */
+ protected function shaarliFormatLink(?array $link, bool $fullWrap): ?array
+ {
+ // If open and clean search tokens are found in the link, process.
+ if (
+ is_array($link)
+ && strpos($link['element']['attributes']['href'] ?? '', Formatter::SEARCH_HIGHLIGHT_OPEN) !== false
+ && strpos($link['element']['attributes']['href'] ?? '', Formatter::SEARCH_HIGHLIGHT_CLOSE) !== false
+ ) {
+ $link['element']['attributes']['href'] = $this->shaarliRemoveSearchTokens(
+ $link['element']['attributes']['href']
+ );
+
+ if ($fullWrap) {
+ $link['element']['text'] = Formatter::SEARCH_HIGHLIGHT_OPEN .
+ $link['element']['text'] .
+ Formatter::SEARCH_HIGHLIGHT_CLOSE
+ ;
+ }
+ }
+
+ return $link;
+ }
+
+ /**
+ * Remove open and close tags from provided string.
+ *
+ * @param string $entry input
+ *
+ * @return string Striped input
+ */
+ protected function shaarliRemoveSearchTokens(string $entry): string
+ {
+ $entry = str_replace(Formatter::SEARCH_HIGHLIGHT_OPEN, '', $entry);
+ $entry = str_replace(Formatter::SEARCH_HIGHLIGHT_CLOSE, '', $entry);
+
+ return $entry;
+ }
+}
diff --git a/application/front/ShaarliAdminMiddleware.php b/application/front/ShaarliAdminMiddleware.php
new file mode 100644
index 00000000..35ce4a3b
--- /dev/null
+++ b/application/front/ShaarliAdminMiddleware.php
@@ -0,0 +1,27 @@
+initBasePath($request);
+
+ if (true !== $this->container->loginManager->isLoggedIn()) {
+ $returnUrl = urlencode($this->container->environment['REQUEST_URI']);
+
+ return $response->withRedirect($this->container->basePath . '/login?returnurl=' . $returnUrl);
+ }
+
+ return parent::__invoke($request, $response, $next);
+ }
+}
diff --git a/application/front/ShaarliMiddleware.php b/application/front/ShaarliMiddleware.php
new file mode 100644
index 00000000..164217f4
--- /dev/null
+++ b/application/front/ShaarliMiddleware.php
@@ -0,0 +1,116 @@
+container = $container;
+ }
+
+ /**
+ * Middleware execution:
+ * - run updates
+ * - if not logged in open shaarli, redirect to login
+ * - execute the controller
+ * - return the response
+ *
+ * In case of error, the error template will be displayed with the exception message.
+ *
+ * @param Request $request Slim request
+ * @param Response $response Slim response
+ * @param callable $next Next action
+ *
+ * @return Response response.
+ */
+ public function __invoke(Request $request, Response $response, callable $next): Response
+ {
+ $this->initBasePath($request);
+
+ try {
+ if (
+ !is_file($this->container->conf->getConfigFileExt())
+ && !in_array($next->getName(), ['displayInstall', 'saveInstall'], true)
+ ) {
+ return $response->withRedirect($this->container->basePath . '/install');
+ }
+
+ $this->runUpdates();
+ $this->checkOpenShaarli($request, $response, $next);
+
+ return $next($request, $response);
+ } catch (UnauthorizedException $e) {
+ $returnUrl = urlencode($this->container->environment['REQUEST_URI']);
+
+ return $response->withRedirect($this->container->basePath . '/login?returnurl=' . $returnUrl);
+ }
+ // Other exceptions are handled by ErrorController
+ }
+
+ /**
+ * Run the updater for every requests processed while logged in.
+ */
+ protected function runUpdates(): void
+ {
+ if ($this->container->loginManager->isLoggedIn() !== true) {
+ return;
+ }
+
+ $this->container->updater->setBasePath($this->container->basePath);
+ $newUpdates = $this->container->updater->update();
+ if (!empty($newUpdates)) {
+ $this->container->updater->writeUpdates(
+ $this->container->conf->get('resource.updates'),
+ $this->container->updater->getDoneUpdates()
+ );
+
+ $this->container->pageCacheManager->invalidateCaches();
+ }
+ }
+
+ /**
+ * Access is denied to most pages with `hide_public_links` + `force_login` settings.
+ */
+ protected function checkOpenShaarli(Request $request, Response $response, callable $next): bool
+ {
+ if (
+// if the user isn't logged in
+ !$this->container->loginManager->isLoggedIn()
+ // and Shaarli doesn't have public content...
+ && $this->container->conf->get('privacy.hide_public_links')
+ // and is configured to enforce the login
+ && $this->container->conf->get('privacy.force_login')
+ // and the current page isn't already the login page
+ // and the user is not requesting a feed (which would lead to a different content-type as expected)
+ && !in_array($next->getName(), ['login', 'processLogin', 'atom', 'rss'], true)
+ ) {
+ throw new UnauthorizedException();
+ }
+
+ return true;
+ }
+
+ /**
+ * Initialize the URL base path if it hasn't been defined yet.
+ */
+ protected function initBasePath(Request $request): void
+ {
+ if (null === $this->container->basePath) {
+ $this->container->basePath = rtrim($request->getUri()->getBasePath(), '/');
+ }
+ }
+}
diff --git a/application/front/controller/admin/ConfigureController.php b/application/front/controller/admin/ConfigureController.php
new file mode 100644
index 00000000..dc421661
--- /dev/null
+++ b/application/front/controller/admin/ConfigureController.php
@@ -0,0 +1,132 @@
+assignView('title', $this->container->conf->get('general.title', 'Shaarli'));
+ $this->assignView('theme', $this->container->conf->get('resource.theme'));
+ $this->assignView(
+ 'theme_available',
+ ThemeUtils::getThemes($this->container->conf->get('resource.raintpl_tpl'))
+ );
+ $this->assignView('formatter_available', ['default', 'markdown', 'markdownExtra']);
+ list($continents, $cities) = generateTimeZoneData(
+ timezone_identifiers_list(),
+ $this->container->conf->get('general.timezone')
+ );
+ $this->assignView('continents', $continents);
+ $this->assignView('cities', $cities);
+ $this->assignView('retrieve_description', $this->container->conf->get('general.retrieve_description', false));
+ $this->assignView('private_links_default', $this->container->conf->get('privacy.default_private_links', false));
+ $this->assignView(
+ 'session_protection_disabled',
+ $this->container->conf->get('security.session_protection_disabled', false)
+ );
+ $this->assignView('enable_rss_permalinks', $this->container->conf->get('feed.rss_permalinks', false));
+ $this->assignView('enable_update_check', $this->container->conf->get('updates.check_updates', true));
+ $this->assignView('hide_public_links', $this->container->conf->get('privacy.hide_public_links', false));
+ $this->assignView('api_enabled', $this->container->conf->get('api.enabled', true));
+ $this->assignView('api_secret', $this->container->conf->get('api.secret'));
+ $this->assignView('languages', Languages::getAvailableLanguages());
+ $this->assignView('gd_enabled', extension_loaded('gd'));
+ $this->assignView('thumbnails_mode', $this->container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE));
+ $this->assignView(
+ 'pagetitle',
+ t('Configure') . ' - ' . $this->container->conf->get('general.title', 'Shaarli')
+ );
+
+ return $response->write($this->render(TemplatePage::CONFIGURE));
+ }
+
+ /**
+ * POST /admin/configure - Update Shaarli's configuration
+ */
+ public function save(Request $request, Response $response): Response
+ {
+ $this->checkToken($request);
+
+ $continent = $request->getParam('continent');
+ $city = $request->getParam('city');
+ $tz = 'UTC';
+ if (null !== $continent && null !== $city && isTimeZoneValid($continent, $city)) {
+ $tz = $continent . '/' . $city;
+ }
+
+ $this->container->conf->set('general.timezone', $tz);
+ $this->container->conf->set('general.title', escape($request->getParam('title')));
+ $this->container->conf->set('general.header_link', escape($request->getParam('titleLink')));
+ $this->container->conf->set('general.retrieve_description', !empty($request->getParam('retrieveDescription')));
+ $this->container->conf->set('resource.theme', escape($request->getParam('theme')));
+ $this->container->conf->set(
+ 'security.session_protection_disabled',
+ !empty($request->getParam('disablesessionprotection'))
+ );
+ $this->container->conf->set(
+ 'privacy.default_private_links',
+ !empty($request->getParam('privateLinkByDefault'))
+ );
+ $this->container->conf->set('feed.rss_permalinks', !empty($request->getParam('enableRssPermalinks')));
+ $this->container->conf->set('updates.check_updates', !empty($request->getParam('updateCheck')));
+ $this->container->conf->set('privacy.hide_public_links', !empty($request->getParam('hidePublicLinks')));
+ $this->container->conf->set('api.enabled', !empty($request->getParam('enableApi')));
+ $this->container->conf->set('api.secret', escape($request->getParam('apiSecret')));
+ $this->container->conf->set('formatter', escape($request->getParam('formatter')));
+
+ if (!empty($request->getParam('language'))) {
+ $this->container->conf->set('translation.language', escape($request->getParam('language')));
+ }
+
+ $thumbnailsMode = extension_loaded('gd') ? $request->getParam('enableThumbnails') : Thumbnailer::MODE_NONE;
+ if (
+ $thumbnailsMode !== Thumbnailer::MODE_NONE
+ && $thumbnailsMode !== $this->container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE)
+ ) {
+ $this->saveWarningMessage(
+ t('You have enabled or changed thumbnails mode.') .
+ '' .
+ t('Please synchronize them.') .
+ ''
+ );
+ }
+ $this->container->conf->set('thumbnails.mode', $thumbnailsMode);
+
+ try {
+ $this->container->conf->write($this->container->loginManager->isLoggedIn());
+ $this->container->history->updateSettings();
+ $this->container->pageCacheManager->invalidateCaches();
+ } catch (Throwable $e) {
+ $this->assignView('message', t('Error while writing config file after configuration update.'));
+
+ if ($this->container->conf->get('dev.debug', false)) {
+ $this->assignView('stacktrace', $e->getMessage() . PHP_EOL . $e->getTraceAsString());
+ }
+
+ return $response->write($this->render('error'));
+ }
+
+ $this->saveSuccessMessage(t('Configuration was saved.'));
+
+ return $this->redirect($response, '/admin/configure');
+ }
+}
diff --git a/application/front/controller/admin/ExportController.php b/application/front/controller/admin/ExportController.php
new file mode 100644
index 00000000..f01d7e9b
--- /dev/null
+++ b/application/front/controller/admin/ExportController.php
@@ -0,0 +1,80 @@
+assignView('pagetitle', t('Export') . ' - ' . $this->container->conf->get('general.title', 'Shaarli'));
+
+ return $response->write($this->render(TemplatePage::EXPORT));
+ }
+
+ /**
+ * POST /admin/export - Process export, and serve download file named
+ * bookmarks_(all|private|public)_datetime.html
+ */
+ public function export(Request $request, Response $response): Response
+ {
+ $this->checkToken($request);
+
+ $selection = $request->getParam('selection');
+
+ if (empty($selection)) {
+ $this->saveErrorMessage(t('Please select an export mode.'));
+
+ return $this->redirect($response, '/admin/export');
+ }
+
+ $prependNoteUrl = filter_var($request->getParam('prepend_note_url') ?? false, FILTER_VALIDATE_BOOLEAN);
+
+ try {
+ $formatter = $this->container->formatterFactory->getFormatter('raw');
+
+ $this->assignView(
+ 'links',
+ $this->container->netscapeBookmarkUtils->filterAndFormat(
+ $formatter,
+ $selection,
+ $prependNoteUrl,
+ index_url($this->container->environment)
+ )
+ );
+ } catch (\Exception $exc) {
+ $this->saveErrorMessage($exc->getMessage());
+
+ return $this->redirect($response, '/admin/export');
+ }
+
+ $now = new DateTime();
+ $response = $response->withHeader('Content-Type', 'text/html; charset=utf-8');
+ $response = $response->withHeader(
+ 'Content-disposition',
+ 'attachment; filename=bookmarks_' . $selection . '_' . $now->format(Bookmark::LINK_DATE_FORMAT) . '.html'
+ );
+
+ $this->assignView('date', $now->format(DateTime::RFC822));
+ $this->assignView('eol', PHP_EOL);
+ $this->assignView('selection', $selection);
+
+ return $response->write($this->render(TemplatePage::NETSCAPE_EXPORT_BOOKMARKS));
+ }
+}
diff --git a/application/front/controller/admin/ImportController.php b/application/front/controller/admin/ImportController.php
new file mode 100644
index 00000000..c2ad6a09
--- /dev/null
+++ b/application/front/controller/admin/ImportController.php
@@ -0,0 +1,82 @@
+assignView(
+ 'maxfilesize',
+ get_max_upload_size(
+ ini_get('post_max_size'),
+ ini_get('upload_max_filesize'),
+ false
+ )
+ );
+ $this->assignView(
+ 'maxfilesizeHuman',
+ get_max_upload_size(
+ ini_get('post_max_size'),
+ ini_get('upload_max_filesize'),
+ true
+ )
+ );
+ $this->assignView('pagetitle', t('Import') . ' - ' . $this->container->conf->get('general.title', 'Shaarli'));
+
+ return $response->write($this->render(TemplatePage::IMPORT));
+ }
+
+ /**
+ * POST /admin/import - Process import file provided and create bookmarks
+ */
+ public function import(Request $request, Response $response): Response
+ {
+ $this->checkToken($request);
+
+ $file = ($request->getUploadedFiles() ?? [])['filetoupload'] ?? null;
+ if (!$file instanceof UploadedFileInterface) {
+ $this->saveErrorMessage(t('No import file provided.'));
+
+ return $this->redirect($response, '/admin/import');
+ }
+
+
+ // Import bookmarks from an uploaded file
+ if (0 === $file->getSize()) {
+ // The file is too big or some form field may be missing.
+ $msg = sprintf(
+ t(
+ 'The file you are trying to upload is probably bigger than what this webserver can accept'
+ . ' (%s). Please upload in smaller chunks.'
+ ),
+ get_max_upload_size(ini_get('post_max_size'), ini_get('upload_max_filesize'))
+ );
+ $this->saveErrorMessage($msg);
+
+ return $this->redirect($response, '/admin/import');
+ }
+
+ $status = $this->container->netscapeBookmarkUtils->import($request->getParams(), $file);
+
+ $this->saveSuccessMessage($status);
+
+ return $this->redirect($response, '/admin/import');
+ }
+}
diff --git a/application/front/controller/admin/LogoutController.php b/application/front/controller/admin/LogoutController.php
new file mode 100644
index 00000000..28165129
--- /dev/null
+++ b/application/front/controller/admin/LogoutController.php
@@ -0,0 +1,33 @@
+container->pageCacheManager->invalidateCaches();
+ $this->container->sessionManager->logout();
+ $this->container->cookieManager->setCookieParameter(
+ CookieManager::STAY_SIGNED_IN,
+ 'false',
+ 0,
+ $this->container->basePath . '/'
+ );
+
+ return $this->redirect($response, '/');
+ }
+}
diff --git a/application/front/controller/admin/ManageTagController.php b/application/front/controller/admin/ManageTagController.php
new file mode 100644
index 00000000..1333cce7
--- /dev/null
+++ b/application/front/controller/admin/ManageTagController.php
@@ -0,0 +1,124 @@
+getParam('fromtag') ?? '';
+
+ $this->assignView('fromtag', escape($fromTag));
+ $separator = escape($this->container->conf->get('general.tags_separator', ' '));
+ if ($separator === ' ') {
+ $separator = ' ';
+ $this->assignView('tags_separator_desc', t('whitespace'));
+ }
+ $this->assignView('tags_separator', $separator);
+ $this->assignView(
+ 'pagetitle',
+ t('Manage tags') . ' - ' . $this->container->conf->get('general.title', 'Shaarli')
+ );
+
+ return $response->write($this->render(TemplatePage::CHANGE_TAG));
+ }
+
+ /**
+ * POST /admin/tags - Update or delete provided tag
+ */
+ public function save(Request $request, Response $response): Response
+ {
+ $this->checkToken($request);
+
+ $isDelete = null !== $request->getParam('deletetag') && null === $request->getParam('renametag');
+
+ $fromTag = trim($request->getParam('fromtag') ?? '');
+ $toTag = trim($request->getParam('totag') ?? '');
+
+ if (0 === strlen($fromTag) || false === $isDelete && 0 === strlen($toTag)) {
+ $this->saveWarningMessage(t('Invalid tags provided.'));
+
+ return $this->redirect($response, '/admin/tags');
+ }
+
+ // TODO: move this to bookmark service
+ $searchResult = $this->container->bookmarkService->search(
+ ['searchtags' => $fromTag],
+ BookmarkFilter::$ALL,
+ true
+ );
+ foreach ($searchResult->getBookmarks() as $bookmark) {
+ if (false === $isDelete) {
+ $bookmark->renameTag($fromTag, $toTag);
+ } else {
+ $bookmark->deleteTag($fromTag);
+ }
+
+ $this->container->bookmarkService->set($bookmark, false);
+ $this->container->history->updateLink($bookmark);
+ }
+
+ $this->container->bookmarkService->save();
+
+ $count = $searchResult->getResultCount();
+ if (true === $isDelete) {
+ $alert = sprintf(
+ t('The tag was removed from %d bookmark.', 'The tag was removed from %d bookmarks.', $count),
+ $count
+ );
+ } else {
+ $alert = sprintf(
+ t('The tag was renamed in %d bookmark.', 'The tag was renamed in %d bookmarks.', $count),
+ $count
+ );
+ }
+
+ $this->saveSuccessMessage($alert);
+
+ $redirect = true === $isDelete ? '/admin/tags' : '/?searchtags=' . urlencode($toTag);
+
+ return $this->redirect($response, $redirect);
+ }
+
+ /**
+ * POST /admin/tags/change-separator - Change tag separator
+ */
+ public function changeSeparator(Request $request, Response $response): Response
+ {
+ $this->checkToken($request);
+
+ $reservedCharacters = ['-', '.', '*'];
+ $newSeparator = $request->getParam('separator');
+ if ($newSeparator === null || mb_strlen($newSeparator) !== 1) {
+ $this->saveErrorMessage(t('Tags separator must be a single character.'));
+ } elseif (in_array($newSeparator, $reservedCharacters, true)) {
+ $reservedCharacters = implode(' ', array_map(function (string $character) {
+ return '' . $character . '
';
+ }, $reservedCharacters));
+ $this->saveErrorMessage(
+ t('These characters are reserved and can\'t be used as tags separator: ') . $reservedCharacters
+ );
+ } else {
+ $this->container->conf->set('general.tags_separator', $newSeparator, true, true);
+
+ $this->saveSuccessMessage('Your tags separator setting has been updated!');
+ }
+
+ return $this->redirect($response, '/admin/tags');
+ }
+}
diff --git a/application/front/controller/admin/MetadataController.php b/application/front/controller/admin/MetadataController.php
new file mode 100644
index 00000000..ff845944
--- /dev/null
+++ b/application/front/controller/admin/MetadataController.php
@@ -0,0 +1,29 @@
+getParam('url');
+
+ // Only try to extract metadata from URL with HTTP(s) scheme
+ if (!empty($url) && strpos(get_url_scheme($url) ?: '', 'http') !== false) {
+ return $response->withJson($this->container->metadataRetriever->retrieve($url));
+ }
+
+ return $response->withJson([]);
+ }
+}
diff --git a/application/front/controller/admin/PasswordController.php b/application/front/controller/admin/PasswordController.php
new file mode 100644
index 00000000..4aaf1f82
--- /dev/null
+++ b/application/front/controller/admin/PasswordController.php
@@ -0,0 +1,101 @@
+assignView(
+ 'pagetitle',
+ t('Change password') . ' - ' . $this->container->conf->get('general.title', 'Shaarli')
+ );
+ }
+
+ /**
+ * GET /admin/password - Displays the change password template
+ */
+ public function index(Request $request, Response $response): Response
+ {
+ return $response->write($this->render(TemplatePage::CHANGE_PASSWORD));
+ }
+
+ /**
+ * POST /admin/password - Change admin password - existing and new passwords need to be provided.
+ */
+ public function change(Request $request, Response $response): Response
+ {
+ $this->checkToken($request);
+
+ if ($this->container->conf->get('security.open_shaarli', false)) {
+ throw new OpenShaarliPasswordException();
+ }
+
+ $oldPassword = $request->getParam('oldpassword');
+ $newPassword = $request->getParam('setpassword');
+
+ if (empty($newPassword) || empty($oldPassword)) {
+ $this->saveErrorMessage(t('You must provide the current and new password to change it.'));
+
+ return $response
+ ->withStatus(400)
+ ->write($this->render(TemplatePage::CHANGE_PASSWORD))
+ ;
+ }
+
+ // Make sure old password is correct.
+ $oldHash = sha1(
+ $oldPassword .
+ $this->container->conf->get('credentials.login') .
+ $this->container->conf->get('credentials.salt')
+ );
+
+ if ($oldHash !== $this->container->conf->get('credentials.hash')) {
+ $this->saveErrorMessage(t('The old password is not correct.'));
+
+ return $response
+ ->withStatus(400)
+ ->write($this->render(TemplatePage::CHANGE_PASSWORD))
+ ;
+ }
+
+ // Save new password
+ // Salt renders rainbow-tables attacks useless.
+ $this->container->conf->set('credentials.salt', sha1(uniqid('', true) . '_' . mt_rand()));
+ $this->container->conf->set(
+ 'credentials.hash',
+ sha1(
+ $newPassword
+ . $this->container->conf->get('credentials.login')
+ . $this->container->conf->get('credentials.salt')
+ )
+ );
+
+ try {
+ $this->container->conf->write($this->container->loginManager->isLoggedIn());
+ } catch (Throwable $e) {
+ throw new ShaarliFrontException($e->getMessage(), 500, $e);
+ }
+
+ $this->saveSuccessMessage(t('Your password has been changed'));
+
+ return $response->write($this->render(TemplatePage::CHANGE_PASSWORD));
+ }
+}
diff --git a/application/front/controller/admin/PluginsController.php b/application/front/controller/admin/PluginsController.php
new file mode 100644
index 00000000..ae47c1af
--- /dev/null
+++ b/application/front/controller/admin/PluginsController.php
@@ -0,0 +1,85 @@
+container->pluginManager->getPluginsMeta();
+
+ // Split plugins into 2 arrays: ordered enabled plugins and disabled.
+ $enabledPlugins = array_filter($pluginMeta, function ($v) {
+ return ($v['order'] ?? false) !== false;
+ });
+ $enabledPlugins = load_plugin_parameter_values($enabledPlugins, $this->container->conf->get('plugins', []));
+ uasort(
+ $enabledPlugins,
+ function ($a, $b) {
+ return $a['order'] - $b['order'];
+ }
+ );
+ $disabledPlugins = array_filter($pluginMeta, function ($v) {
+ return ($v['order'] ?? false) === false;
+ });
+
+ $this->assignView('enabledPlugins', $enabledPlugins);
+ $this->assignView('disabledPlugins', $disabledPlugins);
+ $this->assignView(
+ 'pagetitle',
+ t('Plugin Administration') . ' - ' . $this->container->conf->get('general.title', 'Shaarli')
+ );
+
+ return $response->write($this->render(TemplatePage::PLUGINS_ADMIN));
+ }
+
+ /**
+ * POST /admin/plugins - Update Shaarli's configuration
+ */
+ public function save(Request $request, Response $response): Response
+ {
+ $this->checkToken($request);
+
+ try {
+ $parameters = $request->getParams() ?? [];
+
+ $this->executePageHooks('save_plugin_parameters', $parameters);
+
+ if (isset($parameters['parameters_form'])) {
+ unset($parameters['parameters_form']);
+ unset($parameters['token']);
+ foreach ($parameters as $param => $value) {
+ $this->container->conf->set('plugins.' . $param, escape($value));
+ }
+ } else {
+ $this->container->conf->set('general.enabled_plugins', save_plugin_config($parameters));
+ }
+
+ $this->container->conf->write($this->container->loginManager->isLoggedIn());
+ $this->container->history->updateSettings();
+
+ $this->saveSuccessMessage(t('Setting successfully saved.'));
+ } catch (Exception $e) {
+ $this->saveErrorMessage(
+ t('Error while saving plugin configuration: ') . PHP_EOL . $e->getMessage()
+ );
+ }
+
+ return $this->redirect($response, '/admin/plugins');
+ }
+}
diff --git a/application/front/controller/admin/ServerController.php b/application/front/controller/admin/ServerController.php
new file mode 100644
index 00000000..4b74f4a9
--- /dev/null
+++ b/application/front/controller/admin/ServerController.php
@@ -0,0 +1,101 @@
+container->conf->get('updates.check_updates', true)) {
+ $latestVersion = 'v' . ApplicationUtils::getVersion(
+ ApplicationUtils::$GIT_RAW_URL . '/latest/' . ApplicationUtils::$VERSION_FILE
+ );
+ $releaseUrl .= 'tag/' . $latestVersion;
+ } else {
+ $latestVersion = t('Check disabled');
+ }
+
+ $currentVersion = ApplicationUtils::getVersion('./shaarli_version.php');
+ $currentVersion = $currentVersion === 'dev' ? $currentVersion : 'v' . $currentVersion;
+ $phpEol = new \DateTimeImmutable(ApplicationUtils::getPhpEol(PHP_VERSION));
+
+ $permissions = array_merge(
+ ApplicationUtils::checkResourcePermissions($this->container->conf),
+ ApplicationUtils::checkDatastoreMutex()
+ );
+
+ $this->assignView('php_version', PHP_VERSION);
+ $this->assignView('php_eol', format_date($phpEol, false));
+ $this->assignView('php_has_reached_eol', $phpEol < new \DateTimeImmutable());
+ $this->assignView('php_extensions', ApplicationUtils::getPhpExtensionsRequirement());
+ $this->assignView('permissions', $permissions);
+ $this->assignView('release_url', $releaseUrl);
+ $this->assignView('latest_version', $latestVersion);
+ $this->assignView('current_version', $currentVersion);
+ $this->assignView('thumbnails_mode', $this->container->conf->get('thumbnails.mode'));
+ $this->assignView('index_url', index_url($this->container->environment));
+ $this->assignView('client_ip', client_ip_id($this->container->environment));
+ $this->assignView('trusted_proxies', $this->container->conf->get('security.trusted_proxies', []));
+
+ $this->assignView(
+ 'pagetitle',
+ t('Server administration') . ' - ' . $this->container->conf->get('general.title', 'Shaarli')
+ );
+
+ return $response->write($this->render('server'));
+ }
+
+ /**
+ * GET /admin/clear-cache?type={$type} - Action to trigger cache folder clearing (either main or thumbnails).
+ */
+ public function clearCache(Request $request, Response $response): Response
+ {
+ $exclude = ['.htaccess'];
+
+ if ($request->getQueryParam('type') === static::CACHE_THUMB) {
+ $folders = [$this->container->conf->get('resource.thumbnails_cache')];
+
+ $this->saveWarningMessage(
+ t('Thumbnails cache has been cleared.') . ' ' .
+ '' .
+ t('Please synchronize them.') .
+ ''
+ );
+ } else {
+ $folders = [
+ $this->container->conf->get('resource.page_cache'),
+ $this->container->conf->get('resource.raintpl_tmp'),
+ ];
+
+ $this->saveSuccessMessage(t('Shaarli\'s cache folder has been cleared!'));
+ }
+
+ // Make sure that we don't delete root cache folder
+ $folders = array_map('realpath', array_values(array_filter(array_map('trim', $folders))));
+ foreach ($folders as $folder) {
+ FileUtils::clearFolder($folder, false, $exclude);
+ }
+
+ return $this->redirect($response, '/admin/server');
+ }
+}
diff --git a/application/front/controller/admin/SessionFilterController.php b/application/front/controller/admin/SessionFilterController.php
new file mode 100644
index 00000000..0917b6d2
--- /dev/null
+++ b/application/front/controller/admin/SessionFilterController.php
@@ -0,0 +1,48 @@
+container->loginManager->isLoggedIn()) {
+ return $this->redirectFromReferer($request, $response, ['visibility']);
+ }
+
+ $newVisibility = $args['visibility'] ?? null;
+ if (false === in_array($newVisibility, [BookmarkFilter::$PRIVATE, BookmarkFilter::$PUBLIC], true)) {
+ $newVisibility = null;
+ }
+
+ $currentVisibility = $this->container->sessionManager->getSessionParameter(SessionManager::KEY_VISIBILITY);
+
+ // Visibility not set or not already expected value, set expected value, otherwise reset it
+ if ($newVisibility !== null && (null === $currentVisibility || $currentVisibility !== $newVisibility)) {
+ // See only public bookmarks
+ $this->container->sessionManager->setSessionParameter(
+ SessionManager::KEY_VISIBILITY,
+ $newVisibility
+ );
+ } else {
+ $this->container->sessionManager->deleteSessionParameter(SessionManager::KEY_VISIBILITY);
+ }
+
+ return $this->redirectFromReferer($request, $response, ['visibility']);
+ }
+}
diff --git a/application/front/controller/admin/ShaareAddController.php b/application/front/controller/admin/ShaareAddController.php
new file mode 100644
index 00000000..ab8e7f40
--- /dev/null
+++ b/application/front/controller/admin/ShaareAddController.php
@@ -0,0 +1,34 @@
+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));
+ }
+}
diff --git a/application/front/controller/admin/ShaareManageController.php b/application/front/controller/admin/ShaareManageController.php
new file mode 100644
index 00000000..05b81678
--- /dev/null
+++ b/application/front/controller/admin/ShaareManageController.php
@@ -0,0 +1,287 @@
+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('');
+ }
+
+ if ($request->getParam('source') === 'batch') {
+ return $response->withStatus(204);
+ }
+
+ // Don't redirect to permalink after deletion.
+ return $this->redirectFromReferer($request, $response, ['shaare/']);
+ }
+
+ /**
+ * 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->conf->get('general.tags_separator', ' '));
+
+ $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->conf->get('general.tags_separator', ' '));
+
+ $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->setAdditionalContentEntry('private_key', $privateKey);
+ $this->container->bookmarkService->set($bookmark);
+ }
+
+ return $this->redirect(
+ $response,
+ '/shaare/' . $hash . '?key=' . $bookmark->getAdditionalContentEntry('private_key')
+ );
+ }
+
+ /**
+ * POST /admin/shaare/update-tags
+ *
+ * Bulk add or delete a tags on one or multiple bookmarks.
+ */
+ public function addOrDeleteTags(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, ['/updateTag'], []);
+ }
+
+ // assert that the action is valid
+ $action = $request->getParam('action');
+ if (!in_array($action, ['add', 'delete'], true)) {
+ $this->saveErrorMessage(t('Invalid action provided.'));
+
+ return $this->redirectFromReferer($request, $response, ['/updateTag'], []);
+ }
+
+ // assert that the tag name is valid
+ $tagString = trim($request->getParam('tag'));
+ if (empty($tagString)) {
+ $this->saveErrorMessage(t('Invalid tag name provided.'));
+
+ return $this->redirectFromReferer($request, $response, ['/updateTag'], []);
+ }
+
+ $tags = tags_str2array($tagString, $this->container->conf->get('general.tags_separator', ' '));
+ $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;
+ }
+
+ foreach ($tags as $tag) {
+ if ($action === 'add') {
+ $bookmark->addTag($tag);
+ } else {
+ $bookmark->deleteTag($tag);
+ }
+ }
+
+ // 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->conf->get('general.tags_separator', ' '));
+
+ $this->container->bookmarkService->set($bookmark, false);
+ ++$count;
+ }
+
+ if ($count > 0) {
+ $this->container->bookmarkService->save();
+ }
+
+ return $this->redirectFromReferer($request, $response, ['/updateTag'], []);
+ }
+}
diff --git a/application/front/controller/admin/ShaarePublishController.php b/application/front/controller/admin/ShaarePublishController.php
new file mode 100644
index 00000000..fb9cacc2
--- /dev/null
+++ b/application/front/controller/admin/ShaarePublishController.php
@@ -0,0 +1,274 @@
+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) {
+ if (empty($url)) {
+ continue;
+ }
+ $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->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'),
+ $this->container->conf->get('general.tags_separator', ' ')
+ );
+
+ 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->getFormatter('raw');
+ $data = $formatter->format($bookmark);
+ $this->executePageHooks('save_link', $data);
+
+ $bookmark->fromArray($data, $this->container->conf->get('general.tags_separator', ' '));
+ $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('');
+ } elseif ($request->getParam('source') === 'batch') {
+ return $response;
+ }
+
+ if (!empty($request->getParam('returnurl'))) {
+ $this->container->environment['HTTP_REFERER'] = $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');
+ if ($request->getParam('private') !== null) {
+ $private = filter_var($request->getParam('private'), FILTER_VALIDATE_BOOLEAN);
+ } else {
+ $private = $this->container->conf->get('privacy.default_private_links', false);
+ }
+
+ // 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->getFormatter('raw');
+ $link = $formatter->format($bookmark);
+ $link['linkIsNew'] = false;
+
+ return $link;
+ }
+
+ protected function buildFormData(array $link, bool $isNew, Request $request): array
+ {
+ $link['tags'] = $link['tags'] !== null && strlen($link['tags']) > 0
+ ? $link['tags'] . $this->container->conf->get('general.tags_separator', ' ')
+ : $link['tags']
+ ;
+
+ return escape([
+ 'link' => $link,
+ 'link_is_new' => $isNew,
+ 'http_referer' => $this->container->environment['HTTP_REFERER'] ?? '',
+ 'source' => $request->getParam('source') ?? '',
+ 'tags' => $this->getTags(),
+ '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),
+ ]);
+ }
+
+ /**
+ * Memoize formatterFactory->getFormatter() calls.
+ */
+ protected function getFormatter(string $type): BookmarkFormatter
+ {
+ if (!array_key_exists($type, $this->formatters) || $this->formatters[$type] === null) {
+ $this->formatters[$type] = $this->container->formatterFactory->getFormatter($type);
+ }
+
+ return $this->formatters[$type];
+ }
+
+ /**
+ * Memoize bookmarkService->bookmarksCountPerTag() calls.
+ */
+ protected function getTags(): array
+ {
+ if ($this->tags === null) {
+ $this->tags = $this->container->bookmarkService->bookmarksCountPerTag();
+
+ if ($this->container->conf->get('formatter') === 'markdown') {
+ $this->tags[BookmarkMarkdownFormatter::NO_MD_TAG] = 1;
+ }
+ }
+
+ return $this->tags;
+ }
+}
diff --git a/application/front/controller/admin/ShaarliAdminController.php b/application/front/controller/admin/ShaarliAdminController.php
new file mode 100644
index 00000000..35fd1394
--- /dev/null
+++ b/application/front/controller/admin/ShaarliAdminController.php
@@ -0,0 +1,71 @@
+container->sessionManager->checkToken($request->getParam('token'))) {
+ throw new WrongTokenException();
+ }
+
+ return true;
+ }
+
+ /**
+ * Save a SUCCESS message in user session, which will be displayed on any template page.
+ */
+ protected function saveSuccessMessage(string $message): void
+ {
+ $this->saveMessage(SessionManager::KEY_SUCCESS_MESSAGES, $message);
+ }
+
+ /**
+ * Save a WARNING message in user session, which will be displayed on any template page.
+ */
+ protected function saveWarningMessage(string $message): void
+ {
+ $this->saveMessage(SessionManager::KEY_WARNING_MESSAGES, $message);
+ }
+
+ /**
+ * Save an ERROR message in user session, which will be displayed on any template page.
+ */
+ protected function saveErrorMessage(string $message): void
+ {
+ $this->saveMessage(SessionManager::KEY_ERROR_MESSAGES, $message);
+ }
+
+ /**
+ * Use the sessionManager to save the provided message using the proper type.
+ *
+ * @param string $type successes/warnings/errors
+ */
+ protected function saveMessage(string $type, string $message): void
+ {
+ $messages = $this->container->sessionManager->getSessionParameter($type) ?? [];
+ $messages[] = $message;
+
+ $this->container->sessionManager->setSessionParameter($type, $messages);
+ }
+}
diff --git a/application/front/controller/admin/ThumbnailsController.php b/application/front/controller/admin/ThumbnailsController.php
new file mode 100644
index 00000000..665dfd03
--- /dev/null
+++ b/application/front/controller/admin/ThumbnailsController.php
@@ -0,0 +1,65 @@
+container->bookmarkService->search()->getBookmarks() as $bookmark) {
+ // A note or not HTTP(S)
+ if ($bookmark->isNote() || !startsWith(strtolower($bookmark->getUrl()), 'http')) {
+ continue;
+ }
+
+ $ids[] = $bookmark->getId();
+ }
+
+ $this->assignView('ids', $ids);
+ $this->assignView(
+ 'pagetitle',
+ t('Thumbnails update') . ' - ' . $this->container->conf->get('general.title', 'Shaarli')
+ );
+
+ return $response->write($this->render(TemplatePage::THUMBNAILS));
+ }
+
+ /**
+ * PATCH /admin/shaare/{id}/thumbnail-update - Route for AJAX calls
+ */
+ public function ajaxUpdate(Request $request, Response $response, array $args): Response
+ {
+ $id = $args['id'] ?? '';
+
+ if (false === ctype_digit($id)) {
+ return $response->withStatus(400);
+ }
+
+ try {
+ $bookmark = $this->container->bookmarkService->get((int) $id);
+ } catch (BookmarkNotFoundException $e) {
+ return $response->withStatus(404);
+ }
+
+ $bookmark->setThumbnail($this->container->thumbnailer->get($bookmark->getUrl()));
+ $this->container->bookmarkService->set($bookmark);
+
+ return $response->withJson($this->container->formatterFactory->getFormatter('raw')->format($bookmark));
+ }
+}
diff --git a/application/front/controller/admin/TokenController.php b/application/front/controller/admin/TokenController.php
new file mode 100644
index 00000000..08d68d0a
--- /dev/null
+++ b/application/front/controller/admin/TokenController.php
@@ -0,0 +1,26 @@
+withHeader('Content-Type', 'text/plain');
+
+ return $response->write($this->container->sessionManager->generateToken());
+ }
+}
diff --git a/application/front/controller/admin/ToolsController.php b/application/front/controller/admin/ToolsController.php
new file mode 100644
index 00000000..560e5e3e
--- /dev/null
+++ b/application/front/controller/admin/ToolsController.php
@@ -0,0 +1,35 @@
+ index_url($this->container->environment),
+ 'sslenabled' => is_https($this->container->environment),
+ ];
+
+ $this->executePageHooks('render_tools', $data, TemplatePage::TOOLS);
+
+ foreach ($data as $key => $value) {
+ $this->assignView($key, $value);
+ }
+
+ $this->assignView('pagetitle', t('Tools') . ' - ' . $this->container->conf->get('general.title', 'Shaarli'));
+
+ return $response->write($this->render(TemplatePage::TOOLS));
+ }
+}
diff --git a/application/front/controller/visitor/BookmarkListController.php b/application/front/controller/visitor/BookmarkListController.php
new file mode 100644
index 00000000..4aae2652
--- /dev/null
+++ b/application/front/controller/visitor/BookmarkListController.php
@@ -0,0 +1,239 @@
+processLegacyController($request, $response);
+ if (null !== $legacyResponse) {
+ return $legacyResponse;
+ }
+
+ $formatter = $this->container->formatterFactory->getFormatter();
+ $formatter->addContextData('base_path', $this->container->basePath);
+ $formatter->addContextData('index_url', index_url($this->container->environment));
+
+ $searchTags = normalize_spaces($request->getParam('searchtags') ?? '');
+ $searchTerm = escape(normalize_spaces($request->getParam('searchterm') ?? ''));
+
+ // Filter bookmarks according search parameters.
+ $visibility = $this->container->sessionManager->getSessionParameter('visibility');
+ $search = [
+ 'searchtags' => $searchTags,
+ 'searchterm' => $searchTerm,
+ ];
+
+ // Select articles according to paging.
+ $page = (int) ($request->getParam('page') ?? 1);
+ $page = $page < 1 ? 1 : $page;
+ $linksPerPage = $this->container->sessionManager->getSessionParameter('LINKS_PER_PAGE', 20) ?: 20;
+
+ $searchResult = $this->container->bookmarkService->search(
+ $search,
+ $visibility,
+ false,
+ !!$this->container->sessionManager->getSessionParameter('untaggedonly'),
+ false,
+ ['offset' => $linksPerPage * ($page - 1), 'limit' => $linksPerPage]
+ ) ?? [];
+
+ $save = false;
+ $links = [];
+ foreach ($searchResult->getBookmarks() as $key => $bookmark) {
+ $save = $this->updateThumbnail($bookmark, false) || $save;
+ $links[$key] = $formatter->format($bookmark);
+ }
+
+ if ($save) {
+ $this->container->bookmarkService->save();
+ }
+
+ // Compute paging navigation
+ $searchtagsUrl = $searchTags === '' ? '' : '&searchtags=' . urlencode($searchTags);
+ $searchtermUrl = $searchTerm === '' ? '' : '&searchterm=' . urlencode($searchTerm);
+ $page = $searchResult->getPage();
+
+ $previousPageUrl = !$searchResult->isLastPage() ? '?page=' . ($page + 1) . $searchtermUrl . $searchtagsUrl : '';
+ $nextPageUrl = !$searchResult->isFirstPage() ? '?page=' . ($page - 1) . $searchtermUrl . $searchtagsUrl : '';
+
+ $tagsSeparator = $this->container->conf->get('general.tags_separator', ' ');
+ $searchTagsUrlEncoded = array_map('urlencode', tags_str2array($searchTags, $tagsSeparator));
+ $searchTags = !empty($searchTags) ? trim($searchTags, $tagsSeparator) . $tagsSeparator : '';
+
+ // Fill all template fields.
+ $data = array_merge(
+ $this->initializeTemplateVars(),
+ [
+ 'previous_page_url' => $previousPageUrl,
+ 'next_page_url' => $nextPageUrl,
+ 'page_current' => $page,
+ 'page_max' => $searchResult->getLastPage(),
+ 'result_count' => $searchResult->getTotalCount(),
+ 'search_term' => escape($searchTerm),
+ 'search_tags' => escape($searchTags),
+ 'search_tags_url' => $searchTagsUrlEncoded,
+ 'visibility' => $visibility,
+ 'links' => $links,
+ ]
+ );
+
+ if (!empty($searchTerm) || !empty($searchTags)) {
+ $data['pagetitle'] = t('Search: ');
+ $data['pagetitle'] .= ! empty($searchTerm) ? $searchTerm . ' ' : '';
+ $bracketWrap = function ($tag) {
+ return '[' . $tag . ']';
+ };
+ $data['pagetitle'] .= ! empty($searchTags)
+ ? implode(' ', array_map($bracketWrap, tags_str2array($searchTags, $tagsSeparator))) . ' '
+ : ''
+ ;
+ $data['pagetitle'] .= '- ';
+ }
+
+ $data['pagetitle'] = ($data['pagetitle'] ?? '') . $this->container->conf->get('general.title', 'Shaarli');
+
+ $this->executePageHooks('render_linklist', $data, TemplatePage::LINKLIST);
+ $this->assignAllView($data);
+
+ return $response->write($this->render(TemplatePage::LINKLIST));
+ }
+
+ /**
+ * GET /shaare/{hash} - Display a single shaare
+ */
+ public function permalink(Request $request, Response $response, array $args): Response
+ {
+ $privateKey = $request->getParam('key');
+
+ try {
+ $bookmark = $this->container->bookmarkService->findByHash($args['hash'], $privateKey);
+ } catch (BookmarkNotFoundException $e) {
+ $this->assignView('error_message', $e->getMessage());
+
+ return $response->write($this->render(TemplatePage::ERROR_404));
+ }
+
+ $this->updateThumbnail($bookmark);
+
+ $formatter = $this->container->formatterFactory->getFormatter();
+ $formatter->addContextData('base_path', $this->container->basePath);
+ $formatter->addContextData('index_url', index_url($this->container->environment));
+
+ $data = array_merge(
+ $this->initializeTemplateVars(),
+ [
+ 'pagetitle' => $bookmark->getTitle() . ' - ' . $this->container->conf->get('general.title', 'Shaarli'),
+ 'links' => [$formatter->format($bookmark)],
+ ]
+ );
+
+ $this->executePageHooks('render_linklist', $data, TemplatePage::LINKLIST);
+ $this->assignAllView($data);
+
+ return $response->write($this->render(TemplatePage::LINKLIST));
+ }
+
+ /**
+ * Update the thumbnail of a single bookmark if necessary.
+ */
+ protected function updateThumbnail(Bookmark $bookmark, bool $writeDatastore = true): bool
+ {
+ if (false === $this->container->loginManager->isLoggedIn()) {
+ return false;
+ }
+
+ // If thumbnail should be updated, we reset it to null
+ if ($bookmark->shouldUpdateThumbnail()) {
+ $bookmark->setThumbnail(null);
+
+ // Requires an update, not async retrieval, thumbnails enabled
+ if (
+ $bookmark->shouldUpdateThumbnail()
+ && true !== $this->container->conf->get('general.enable_async_metadata', true)
+ && $this->container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE
+ ) {
+ $bookmark->setThumbnail($this->container->thumbnailer->get($bookmark->getUrl()));
+ $this->container->bookmarkService->set($bookmark, $writeDatastore);
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @return string[] Default template variables without values.
+ */
+ protected function initializeTemplateVars(): array
+ {
+ return [
+ 'previous_page_url' => '',
+ 'next_page_url' => '',
+ 'page_max' => '',
+ 'search_tags' => '',
+ 'result_count' => '',
+ 'async_metadata' => $this->container->conf->get('general.enable_async_metadata', true)
+ ];
+ }
+
+ /**
+ * Process legacy routes if necessary. They used query parameters.
+ * If no legacy routes is passed, return null.
+ */
+ protected function processLegacyController(Request $request, Response $response): ?Response
+ {
+ // Legacy smallhash filter
+ $queryString = $this->container->environment['QUERY_STRING'] ?? null;
+ if (null !== $queryString && 1 === preg_match('/^([a-zA-Z0-9-_@]{6})($|&|#)/', $queryString, $match)) {
+ return $this->redirect($response, '/shaare/' . $match[1]);
+ }
+
+ // Legacy controllers (mostly used for redirections)
+ if (null !== $request->getQueryParam('do')) {
+ $legacyController = new LegacyController($this->container);
+
+ try {
+ return $legacyController->process($request, $response, $request->getQueryParam('do'));
+ } catch (UnknowLegacyRouteException $e) {
+ // We ignore legacy 404
+ return null;
+ }
+ }
+
+ // Legacy GET admin routes
+ $legacyGetRoutes = array_intersect(
+ LegacyController::LEGACY_GET_ROUTES,
+ array_keys($request->getQueryParams() ?? [])
+ );
+ if (1 === count($legacyGetRoutes)) {
+ $legacyController = new LegacyController($this->container);
+
+ return $legacyController->process($request, $response, $legacyGetRoutes[0]);
+ }
+
+ return null;
+ }
+}
diff --git a/application/front/controller/visitor/DailyController.php b/application/front/controller/visitor/DailyController.php
new file mode 100644
index 00000000..3739ec16
--- /dev/null
+++ b/application/front/controller/visitor/DailyController.php
@@ -0,0 +1,206 @@
+container->bookmarkService->getLatest();
+ $dateTime = DailyPageHelper::extractRequestedDateTime($type, $request->getQueryParam($type), $latestBookmark);
+ $start = DailyPageHelper::getStartDateTimeByType($type, $dateTime);
+ $end = DailyPageHelper::getEndDateTimeByType($type, $dateTime);
+ $dailyDesc = DailyPageHelper::getDescriptionByType($type, $dateTime);
+
+ $linksToDisplay = $this->container->bookmarkService->findByDate(
+ $start,
+ $end,
+ $previousDay,
+ $nextDay
+ );
+
+ $formatter = $this->container->formatterFactory->getFormatter();
+ $formatter->addContextData('base_path', $this->container->basePath);
+ // We pre-format some fields for proper output.
+ foreach ($linksToDisplay as $key => $bookmark) {
+ $linksToDisplay[$key] = $formatter->format($bookmark);
+ // This page is a bit specific, we need raw description to calculate the length
+ $linksToDisplay[$key]['formatedDescription'] = $linksToDisplay[$key]['description'];
+ $linksToDisplay[$key]['description'] = $bookmark->getDescription();
+ }
+
+ $data = [
+ 'linksToDisplay' => $linksToDisplay,
+ 'dayDate' => $start,
+ 'day' => $start->getTimestamp(),
+ 'previousday' => $previousDay ? $previousDay->format($format) : '',
+ 'nextday' => $nextDay ? $nextDay->format($format) : '',
+ 'dayDesc' => $dailyDesc,
+ 'type' => $type,
+ 'localizedType' => $this->translateType($type),
+ ];
+
+ // Hooks are called before column construction so that plugins don't have to deal with columns.
+ $this->executePageHooks('render_daily', $data, TemplatePage::DAILY);
+
+ $data['cols'] = $this->calculateColumns($data['linksToDisplay']);
+
+ $this->assignAllView($data);
+
+ $mainTitle = $this->container->conf->get('general.title', 'Shaarli');
+ $this->assignView(
+ 'pagetitle',
+ $data['localizedType'] . ' - ' . $data['dayDesc'] . ' - ' . $mainTitle
+ );
+
+ return $response->write($this->render(TemplatePage::DAILY));
+ }
+
+ /**
+ * Daily RSS feed: 1 RSS entry per day giving all the bookmarks on that day.
+ * Gives the last 7 days (which have bookmarks).
+ * This RSS feed cannot be filtered and does not trigger plugins yet.
+ */
+ public function rss(Request $request, Response $response): Response
+ {
+ $response = $response->withHeader('Content-Type', 'application/rss+xml; charset=utf-8');
+ $type = DailyPageHelper::extractRequestedType($request);
+ $cacheDuration = DailyPageHelper::getCacheDatePeriodByType($type);
+
+ $pageUrl = page_url($this->container->environment);
+ $cache = $this->container->pageCacheManager->getCachePage($pageUrl, $cacheDuration);
+
+ $cached = $cache->cachedVersion();
+ if (!empty($cached)) {
+ return $response->write($cached);
+ }
+
+ $days = [];
+ $format = DailyPageHelper::getFormatByType($type);
+ $length = DailyPageHelper::getRssLengthByType($type);
+ foreach ($this->container->bookmarkService->search()->getBookmarks() as $bookmark) {
+ $day = $bookmark->getCreated()->format($format);
+
+ // Stop iterating after DAILY_RSS_NB_DAYS entries
+ if (count($days) === $length && !isset($days[$day])) {
+ break;
+ }
+
+ $days[$day][] = $bookmark;
+ }
+
+ // Build the RSS feed.
+ $indexUrl = escape(index_url($this->container->environment));
+
+ $formatter = $this->container->formatterFactory->getFormatter();
+ $formatter->addContextData('index_url', $indexUrl);
+
+ $dataPerDay = [];
+
+ /** @var Bookmark[] $bookmarks */
+ foreach ($days as $day => $bookmarks) {
+ $dayDateTime = DailyPageHelper::extractRequestedDateTime($type, (string) $day);
+ $endDateTime = DailyPageHelper::getEndDateTimeByType($type, $dayDateTime);
+
+ // We only want the RSS entry to be published when the period is over.
+ if (new DateTime() < $endDateTime) {
+ continue;
+ }
+
+ $dataPerDay[$day] = [
+ 'date' => $endDateTime,
+ 'date_rss' => $endDateTime->format(DateTime::RSS),
+ 'date_human' => DailyPageHelper::getDescriptionByType($type, $dayDateTime, false),
+ 'absolute_url' => $indexUrl . 'daily?' . $type . '=' . $day,
+ 'links' => [],
+ ];
+
+ foreach ($bookmarks as $key => $bookmark) {
+ $dataPerDay[$day]['links'][$key] = $formatter->format($bookmark);
+
+ // Make permalink URL absolute
+ if ($bookmark->isNote()) {
+ $dataPerDay[$day]['links'][$key]['url'] = rtrim($indexUrl, '/') . $bookmark->getUrl();
+ }
+ }
+ }
+
+ $this->assignAllView([
+ 'title' => $this->container->conf->get('general.title', 'Shaarli'),
+ 'index_url' => $indexUrl,
+ 'page_url' => $pageUrl,
+ 'hide_timestamps' => $this->container->conf->get('privacy.hide_timestamps', false),
+ 'days' => $dataPerDay,
+ 'type' => $type,
+ 'localizedType' => $this->translateType($type),
+ ]);
+
+ $rssContent = $this->render(TemplatePage::DAILY_RSS);
+
+ $cache->cache($rssContent);
+
+ return $response->write($rssContent);
+ }
+
+ /**
+ * We need to spread the articles on 3 columns.
+ * did not want to use a JavaScript lib like http://masonry.desandro.com/
+ * so I manually spread entries with a simple method: I roughly evaluate the
+ * height of a div according to title and description length.
+ */
+ protected function calculateColumns(array $links): array
+ {
+ // Entries to display, for each column.
+ $columns = [[], [], []];
+ // Rough estimate of columns fill.
+ $fill = [0, 0, 0];
+ foreach ($links as $link) {
+ // Roughly estimate length of entry (by counting characters)
+ // Title: 30 chars = 1 line. 1 line is 30 pixels height.
+ // Description: 836 characters gives roughly 342 pixel height.
+ // This is not perfect, but it's usually OK.
+ $length = strlen($link['title'] ?? '') + (342 * strlen($link['description'] ?? '')) / 836;
+ if (! empty($link['thumbnail'])) {
+ $length += 100; // 1 thumbnails roughly takes 100 pixels height.
+ }
+ // Then put in column which is the less filled:
+ $smallest = min($fill); // find smallest value in array.
+ $index = array_search($smallest, $fill); // find index of this smallest value.
+ array_push($columns[$index], $link); // Put entry in this column.
+ $fill[$index] += $length;
+ }
+
+ return $columns;
+ }
+
+ protected function translateType($type): string
+ {
+ return [
+ t('day') => t('Daily'),
+ t('week') => t('Weekly'),
+ t('month') => t('Monthly'),
+ ][t($type)] ?? t('Daily');
+ }
+}
diff --git a/application/front/controller/visitor/ErrorController.php b/application/front/controller/visitor/ErrorController.php
new file mode 100644
index 00000000..428e8254
--- /dev/null
+++ b/application/front/controller/visitor/ErrorController.php
@@ -0,0 +1,47 @@
+container->pageBuilder->reset();
+
+ if ($throwable instanceof ShaarliFrontException) {
+ // Functional error
+ $this->assignView('message', nl2br($throwable->getMessage()));
+
+ $response = $response->withStatus($throwable->getCode());
+ } else {
+ // Internal error (any other Throwable)
+ if ($this->container->conf->get('dev.debug', false) || $this->container->loginManager->isLoggedIn()) {
+ $this->assignView('message', t('Error: ') . $throwable->getMessage());
+ $this->assignView(
+ 'text',
+ ''
+ . t('Please report it on Github.')
+ . ''
+ );
+ $this->assignView('stacktrace', exception2text($throwable));
+ } else {
+ $this->assignView('message', t('An unexpected error occurred.'));
+ }
+
+ $response = $response->withStatus(500);
+ }
+
+ return $response->write($this->render('error'));
+ }
+}
diff --git a/application/front/controller/visitor/ErrorNotFoundController.php b/application/front/controller/visitor/ErrorNotFoundController.php
new file mode 100644
index 00000000..758dd83b
--- /dev/null
+++ b/application/front/controller/visitor/ErrorNotFoundController.php
@@ -0,0 +1,29 @@
+getRequestTarget(), '/api/v1')) {
+ return $response->withStatus(404);
+ }
+
+ // This is required because the middleware is ignored if the route is not found.
+ $this->container->basePath = rtrim($request->getUri()->getBasePath(), '/');
+
+ $this->assignView('error_message', t('Requested page could not be found.'));
+
+ return $response->withStatus(404)->write($this->render('404'));
+ }
+}
diff --git a/application/front/controller/visitor/FeedController.php b/application/front/controller/visitor/FeedController.php
new file mode 100644
index 00000000..edc7ef43
--- /dev/null
+++ b/application/front/controller/visitor/FeedController.php
@@ -0,0 +1,58 @@
+processRequest(FeedBuilder::$FEED_ATOM, $request, $response);
+ }
+
+ public function rss(Request $request, Response $response): Response
+ {
+ return $this->processRequest(FeedBuilder::$FEED_RSS, $request, $response);
+ }
+
+ protected function processRequest(string $feedType, Request $request, Response $response): Response
+ {
+ $response = $response->withHeader('Content-Type', 'application/' . $feedType . '+xml; charset=utf-8');
+
+ $pageUrl = page_url($this->container->environment);
+ $cache = $this->container->pageCacheManager->getCachePage($pageUrl);
+
+ $cached = $cache->cachedVersion();
+ if (!empty($cached)) {
+ return $response->write($cached);
+ }
+
+ // Generate data.
+ $this->container->feedBuilder->setLocale(strtolower(setlocale(LC_COLLATE, 0)));
+ $this->container->feedBuilder->setHideDates($this->container->conf->get('privacy.hide_timestamps', false));
+ $this->container->feedBuilder->setUsePermalinks(
+ null !== $request->getParam('permalinks') || !$this->container->conf->get('feed.rss_permalinks')
+ );
+
+ $data = $this->container->feedBuilder->buildData($feedType, $request->getParams());
+
+ $this->executePageHooks('render_feed', $data, 'feed.' . $feedType);
+ $this->assignAllView($data);
+
+ $content = $this->render('feed.' . $feedType);
+
+ $cache->cache($content);
+
+ return $response->write($content);
+ }
+}
diff --git a/application/front/controller/visitor/InstallController.php b/application/front/controller/visitor/InstallController.php
new file mode 100644
index 00000000..6ffeb2db
--- /dev/null
+++ b/application/front/controller/visitor/InstallController.php
@@ -0,0 +1,183 @@
+container->conf->getConfigFileExt())) {
+ throw new AlreadyInstalledException();
+ }
+ }
+
+ /**
+ * Display the install template page.
+ * Also test file permissions and sessions beforehand.
+ */
+ public function index(Request $request, Response $response): Response
+ {
+ // Before installation, we'll make sure that permissions are set properly, and sessions are working.
+ $this->checkPermissions();
+
+ if (
+ static::SESSION_TEST_VALUE
+ !== $this->container->sessionManager->getSessionParameter(static::SESSION_TEST_KEY)
+ ) {
+ $this->container->sessionManager->setSessionParameter(static::SESSION_TEST_KEY, static::SESSION_TEST_VALUE);
+
+ return $this->redirect($response, '/install/session-test');
+ }
+
+ [$continents, $cities] = generateTimeZoneData(timezone_identifiers_list(), date_default_timezone_get());
+
+ $this->assignView('continents', $continents);
+ $this->assignView('cities', $cities);
+ $this->assignView('languages', Languages::getAvailableLanguages());
+
+ $phpEol = new \DateTimeImmutable(ApplicationUtils::getPhpEol(PHP_VERSION));
+
+ $permissions = array_merge(
+ ApplicationUtils::checkResourcePermissions($this->container->conf),
+ ApplicationUtils::checkDatastoreMutex()
+ );
+
+ $this->assignView('php_version', PHP_VERSION);
+ $this->assignView('php_eol', format_date($phpEol, false));
+ $this->assignView('php_has_reached_eol', $phpEol < new \DateTimeImmutable());
+ $this->assignView('php_extensions', ApplicationUtils::getPhpExtensionsRequirement());
+ $this->assignView('permissions', $permissions);
+
+ $this->assignView('pagetitle', t('Install Shaarli'));
+
+ return $response->write($this->render('install'));
+ }
+
+ /**
+ * Route checking that the session parameter has been properly saved between two distinct requests.
+ * If the session parameter is preserved, redirect to install template page, otherwise displays error.
+ */
+ public function sessionTest(Request $request, Response $response): Response
+ {
+ // This part makes sure sessions works correctly.
+ // (Because on some hosts, session.save_path may not be set correctly,
+ // or we may not have write access to it.)
+ if (
+ static::SESSION_TEST_VALUE
+ !== $this->container->sessionManager->getSessionParameter(static::SESSION_TEST_KEY)
+ ) {
+ // Step 2: Check if data in session is correct.
+ $msg = t(
+ 'Sessions do not seem to work correctly on your server.
' .
+ 'Make sure the variable "session.save_path" is set correctly in your PHP config, ' .
+ 'and that you have write access to it.
' .
+ 'It currently points to %s.
' .
+ 'On some browsers, accessing your server via a hostname like \'localhost\' ' .
+ 'or any custom hostname without a dot causes cookie storage to fail. ' .
+ 'We recommend accessing your server via it\'s IP address or Fully Qualified Domain Name.
'
+ );
+ $msg = sprintf($msg, $this->container->sessionManager->getSavePath());
+
+ $this->assignView('message', $msg);
+
+ return $response->write($this->render('error'));
+ }
+
+ return $this->redirect($response, '/install');
+ }
+
+ /**
+ * Save installation form and initialize config file and datastore if necessary.
+ */
+ public function save(Request $request, Response $response): Response
+ {
+ $timezone = 'UTC';
+ if (
+ !empty($request->getParam('continent'))
+ && !empty($request->getParam('city'))
+ && isTimeZoneValid($request->getParam('continent'), $request->getParam('city'))
+ ) {
+ $timezone = $request->getParam('continent') . '/' . $request->getParam('city');
+ }
+ $this->container->conf->set('general.timezone', $timezone);
+
+ $login = $request->getParam('setlogin');
+ $this->container->conf->set('credentials.login', $login);
+ $salt = sha1(uniqid('', true) . '_' . mt_rand());
+ $this->container->conf->set('credentials.salt', $salt);
+ $this->container->conf->set('credentials.hash', sha1($request->getParam('setpassword') . $login . $salt));
+
+ if (!empty($request->getParam('title'))) {
+ $this->container->conf->set('general.title', escape($request->getParam('title')));
+ } else {
+ $this->container->conf->set(
+ 'general.title',
+ t('Shared Bookmarks')
+ );
+ }
+
+ $this->container->conf->set('translation.language', escape($request->getParam('language')));
+ $this->container->conf->set('updates.check_updates', !empty($request->getParam('updateCheck')));
+ $this->container->conf->set('api.enabled', !empty($request->getParam('enableApi')));
+ $this->container->conf->set(
+ 'api.secret',
+ generate_api_secret(
+ $this->container->conf->get('credentials.login'),
+ $this->container->conf->get('credentials.salt')
+ )
+ );
+ $this->container->conf->set('general.header_link', $this->container->basePath . '/');
+
+ try {
+ // Everything is ok, let's create config file.
+ $this->container->conf->write($this->container->loginManager->isLoggedIn());
+ } catch (\Exception $e) {
+ $this->assignView('message', t('Error while writing config file after configuration update.'));
+ $this->assignView('stacktrace', $e->getMessage() . PHP_EOL . $e->getTraceAsString());
+
+ return $response->write($this->render('error'));
+ }
+
+ $this->container->sessionManager->setSessionParameter(
+ SessionManager::KEY_SUCCESS_MESSAGES,
+ [t('Shaarli is now configured. Please login and start shaaring your bookmarks!')]
+ );
+
+ return $this->redirect($response, '/login');
+ }
+
+ protected function checkPermissions(): bool
+ {
+ // Ensure Shaarli has proper access to its resources
+ $errors = ApplicationUtils::checkResourcePermissions($this->container->conf, true);
+ if (empty($errors)) {
+ return true;
+ }
+
+ $message = t('Insufficient permissions:') . PHP_EOL;
+ foreach ($errors as $error) {
+ $message .= PHP_EOL . $error;
+ }
+
+ throw new ResourcePermissionException($message);
+ }
+}
diff --git a/application/front/controller/visitor/LoginController.php b/application/front/controller/visitor/LoginController.php
new file mode 100644
index 00000000..4b881535
--- /dev/null
+++ b/application/front/controller/visitor/LoginController.php
@@ -0,0 +1,155 @@
+checkLoginState();
+ } catch (CantLoginException $e) {
+ return $this->redirect($response, '/');
+ }
+
+ if ($request->getParam('login') !== null) {
+ $this->assignView('username', escape($request->getParam('login')));
+ }
+
+ $returnUrl = $request->getParam('returnurl') ?? $this->container->environment['HTTP_REFERER'] ?? null;
+
+ $this
+ ->assignView('returnurl', escape($returnUrl))
+ ->assignView('remember_user_default', $this->container->conf->get('privacy.remember_user_default', true))
+ ->assignView('pagetitle', t('Login') . ' - ' . $this->container->conf->get('general.title', 'Shaarli'))
+ ;
+
+ return $response->write($this->render(TemplatePage::LOGIN));
+ }
+
+ /**
+ * POST /login - Process login
+ */
+ public function login(Request $request, Response $response): Response
+ {
+ if (!$this->container->sessionManager->checkToken($request->getParam('token'))) {
+ throw new WrongTokenException();
+ }
+
+ try {
+ $this->checkLoginState();
+ } catch (CantLoginException $e) {
+ return $this->redirect($response, '/');
+ }
+
+ if (
+ !$this->container->loginManager->checkCredentials(
+ client_ip_id($this->container->environment),
+ $request->getParam('login'),
+ $request->getParam('password')
+ )
+ ) {
+ $this->container->loginManager->handleFailedLogin($this->container->environment);
+
+ $this->container->sessionManager->setSessionParameter(
+ SessionManager::KEY_ERROR_MESSAGES,
+ [t('Wrong login/password.')]
+ );
+
+ // Call controller directly instead of unnecessary redirection
+ return $this->index($request, $response);
+ }
+
+ $this->container->loginManager->handleSuccessfulLogin($this->container->environment);
+
+ $cookiePath = $this->container->basePath . '/';
+ $expirationTime = $this->saveLongLastingSession($request, $cookiePath);
+ $this->renewUserSession($cookiePath, $expirationTime);
+
+ // Force referer from given return URL
+ $this->container->environment['HTTP_REFERER'] = $request->getParam('returnurl');
+
+ return $this->redirectFromReferer($request, $response, ['login', 'install']);
+ }
+
+ /**
+ * Make sure that the user is allowed to login and/or displaying the login page:
+ * - not already logged in
+ * - not open shaarli
+ * - not banned
+ */
+ protected function checkLoginState(): bool
+ {
+ if (
+ $this->container->loginManager->isLoggedIn()
+ || $this->container->conf->get('security.open_shaarli', false)
+ ) {
+ throw new CantLoginException();
+ }
+
+ if (true !== $this->container->loginManager->canLogin($this->container->environment)) {
+ throw new LoginBannedException();
+ }
+
+ return true;
+ }
+
+ /**
+ * @return int Session duration in seconds
+ */
+ protected function saveLongLastingSession(Request $request, string $cookiePath): int
+ {
+ if (empty($request->getParam('longlastingsession'))) {
+ // Standard session expiration (=when browser closes)
+ $expirationTime = 0;
+ } else {
+ // Keep the session cookie even after the browser closes
+ $this->container->sessionManager->setStaySignedIn(true);
+ $expirationTime = $this->container->sessionManager->extendSession();
+ }
+
+ $this->container->cookieManager->setCookieParameter(
+ CookieManager::STAY_SIGNED_IN,
+ $this->container->loginManager->getStaySignedInToken(),
+ $expirationTime,
+ $cookiePath
+ );
+
+ return $expirationTime;
+ }
+
+ protected function renewUserSession(string $cookiePath, int $expirationTime): void
+ {
+ // Send cookie with the new expiration date to the browser
+ $this->container->sessionManager->destroy();
+ $this->container->sessionManager->cookieParameters(
+ $expirationTime,
+ $cookiePath,
+ $this->container->environment['SERVER_NAME']
+ );
+ $this->container->sessionManager->start();
+ $this->container->sessionManager->regenerateId(true);
+ }
+}
diff --git a/application/front/controller/visitor/OpenSearchController.php b/application/front/controller/visitor/OpenSearchController.php
new file mode 100644
index 00000000..36d60acf
--- /dev/null
+++ b/application/front/controller/visitor/OpenSearchController.php
@@ -0,0 +1,27 @@
+withHeader('Content-Type', 'application/opensearchdescription+xml; charset=utf-8');
+
+ $this->assignView('serverurl', index_url($this->container->environment));
+
+ return $response->write($this->render(TemplatePage::OPEN_SEARCH));
+ }
+}
diff --git a/application/front/controller/visitor/PictureWallController.php b/application/front/controller/visitor/PictureWallController.php
new file mode 100644
index 00000000..9c8f07d7
--- /dev/null
+++ b/application/front/controller/visitor/PictureWallController.php
@@ -0,0 +1,54 @@
+container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) === Thumbnailer::MODE_NONE) {
+ throw new ThumbnailsDisabledException();
+ }
+
+ $this->assignView(
+ 'pagetitle',
+ t('Picture wall') . ' - ' . $this->container->conf->get('general.title', 'Shaarli')
+ );
+
+ // Optionally filter the results:
+ $bookmarks = $this->container->bookmarkService->search($request->getQueryParams())->getBookmarks();
+ $links = [];
+
+ // Get only bookmarks which have a thumbnail.
+ // Note: we do not retrieve thumbnails here, the request is too heavy.
+ $formatter = $this->container->formatterFactory->getFormatter('raw');
+ foreach ($bookmarks as $key => $bookmark) {
+ if (!empty($bookmark->getThumbnail())) {
+ $links[] = $formatter->format($bookmark);
+ }
+ }
+
+ $data = ['linksToDisplay' => $links];
+ $this->executePageHooks('render_picwall', $data, TemplatePage::PICTURE_WALL);
+
+ foreach ($data as $key => $value) {
+ $this->assignView($key, $value);
+ }
+
+ return $response->write($this->render(TemplatePage::PICTURE_WALL));
+ }
+}
diff --git a/application/front/controller/visitor/PublicSessionFilterController.php b/application/front/controller/visitor/PublicSessionFilterController.php
new file mode 100644
index 00000000..1a66362d
--- /dev/null
+++ b/application/front/controller/visitor/PublicSessionFilterController.php
@@ -0,0 +1,46 @@
+getParam('nb') ?? null;
+ if (null === $linksPerPage || false === is_numeric($linksPerPage)) {
+ $linksPerPage = $this->container->conf->get('general.links_per_page', 20);
+ }
+
+ $this->container->sessionManager->setSessionParameter(
+ SessionManager::KEY_LINKS_PER_PAGE,
+ abs(intval($linksPerPage))
+ );
+
+ return $this->redirectFromReferer($request, $response, ['linksperpage'], ['nb']);
+ }
+
+ /**
+ * GET /untagged-only: allows to display only bookmarks without any tag
+ */
+ public function untaggedOnly(Request $request, Response $response): Response
+ {
+ $this->container->sessionManager->setSessionParameter(
+ SessionManager::KEY_UNTAGGED_ONLY,
+ empty($this->container->sessionManager->getSessionParameter(SessionManager::KEY_UNTAGGED_ONLY))
+ );
+
+ return $this->redirectFromReferer($request, $response, ['untaggedonly', 'untagged-only']);
+ }
+}
diff --git a/application/front/controller/visitor/ShaarliVisitorController.php b/application/front/controller/visitor/ShaarliVisitorController.php
new file mode 100644
index 00000000..d3f28f2f
--- /dev/null
+++ b/application/front/controller/visitor/ShaarliVisitorController.php
@@ -0,0 +1,186 @@
+container = $container;
+ }
+
+ /**
+ * Assign variables to RainTPL template through the PageBuilder.
+ *
+ * @param mixed $value Value to assign to the template
+ */
+ protected function assignView(string $name, $value): self
+ {
+ $this->container->pageBuilder->assign($name, $value);
+
+ return $this;
+ }
+
+ /**
+ * Assign variables to RainTPL template through the PageBuilder.
+ *
+ * @param mixed $data Values to assign to the template and their keys
+ */
+ protected function assignAllView(array $data): self
+ {
+ foreach ($data as $key => $value) {
+ $this->assignView($key, $value);
+ }
+
+ return $this;
+ }
+
+ protected function render(string $template): string
+ {
+ // Legacy key that used to be injected by PluginManager
+ $this->assignView('_PAGE_', $template);
+ $this->assignView('template', $template);
+
+ $this->assignView('linkcount', $this->container->bookmarkService->count(BookmarkFilter::$ALL));
+ $this->assignView('privateLinkcount', $this->container->bookmarkService->count(BookmarkFilter::$PRIVATE));
+
+ $this->executeDefaultHooks($template);
+
+ $this->assignView('plugin_errors', $this->container->pluginManager->getErrors());
+
+ return $this->container->pageBuilder->render($template, $this->container->basePath);
+ }
+
+ /**
+ * Call plugin hooks for header, footer and includes, specifying which page will be rendered.
+ * Then assign generated data to RainTPL.
+ */
+ protected function executeDefaultHooks(string $template): void
+ {
+ $common_hooks = [
+ 'includes',
+ 'header',
+ 'footer',
+ ];
+
+ $parameters = $this->buildPluginParameters($template);
+
+ foreach ($common_hooks as $name) {
+ $pluginData = [];
+ $this->container->pluginManager->executeHooks(
+ 'render_' . $name,
+ $pluginData,
+ $parameters
+ );
+ $this->assignView('plugins_' . $name, $pluginData);
+ }
+ }
+
+ protected function executePageHooks(string $hook, array &$data, string $template = null): void
+ {
+ $this->container->pluginManager->executeHooks(
+ $hook,
+ $data,
+ $this->buildPluginParameters($template)
+ );
+ }
+
+ protected function buildPluginParameters(?string $template): array
+ {
+ return [
+ 'target' => $template,
+ 'loggedin' => $this->container->loginManager->isLoggedIn(),
+ 'basePath' => $this->container->basePath,
+ 'rootPath' => preg_replace('#/index\.php$#', '', $this->container->basePath),
+ 'bookmarkService' => $this->container->bookmarkService
+ ];
+ }
+
+ /**
+ * Simple helper which prepend the base path to redirect path.
+ *
+ * @param Response $response
+ * @param string $path Absolute path, e.g.: `/`, or `/admin/shaare/123` regardless of install directory
+ *
+ * @return Response updated
+ */
+ protected function redirect(Response $response, string $path): Response
+ {
+ return $response->withRedirect($this->container->basePath . $path);
+ }
+
+ /**
+ * Generates a redirection to the previous page, based on the HTTP_REFERER.
+ * It fails back to the home page.
+ *
+ * @param array $loopTerms Terms to remove from path and query string to prevent direction loop.
+ * @param array $clearParams List of parameter to remove from the query string of the referrer.
+ */
+ protected function redirectFromReferer(
+ Request $request,
+ Response $response,
+ array $loopTerms = [],
+ array $clearParams = [],
+ string $anchor = null
+ ): Response {
+ $defaultPath = $this->container->basePath . '/';
+ $referer = $this->container->environment['HTTP_REFERER'] ?? null;
+
+ if (null !== $referer) {
+ $currentUrl = parse_url($referer);
+ // If the referer is not related to Shaarli instance, redirect to default
+ if (
+ isset($currentUrl['host'])
+ && strpos(index_url($this->container->environment), $currentUrl['host']) === false
+ ) {
+ return $response->withRedirect($defaultPath);
+ }
+
+ parse_str($currentUrl['query'] ?? '', $params);
+ $path = $currentUrl['path'] ?? $defaultPath;
+ } else {
+ $params = [];
+ $path = $defaultPath;
+ }
+
+ // Prevent redirection loop
+ if (isset($currentUrl)) {
+ foreach ($clearParams as $value) {
+ unset($params[$value]);
+ }
+
+ $checkQuery = implode('', array_keys($params));
+ foreach ($loopTerms as $value) {
+ if (strpos($path . $checkQuery, $value) !== false) {
+ $params = [];
+ $path = $defaultPath;
+ break;
+ }
+ }
+ }
+
+ $queryString = count($params) > 0 ? '?' . http_build_query($params) : '';
+ $anchor = $anchor ? '#' . $anchor : '';
+
+ return $response->withRedirect($path . $queryString . $anchor);
+ }
+}
diff --git a/application/front/controller/visitor/TagCloudController.php b/application/front/controller/visitor/TagCloudController.php
new file mode 100644
index 00000000..46d62779
--- /dev/null
+++ b/application/front/controller/visitor/TagCloudController.php
@@ -0,0 +1,123 @@
+processRequest(static::TYPE_CLOUD, $request, $response);
+ }
+
+ /**
+ * Display the tag list through the template engine.
+ * This controller a few filters:
+ * - Visibility stored in the session for logged in users
+ * - `searchtags` query parameter: will return tags associated with filter in at least one bookmark
+ * - `sort` query parameters:
+ * + `usage` (default): most used tags first
+ * + `alpha`: alphabetical order
+ */
+ public function list(Request $request, Response $response): Response
+ {
+ return $this->processRequest(static::TYPE_LIST, $request, $response);
+ }
+
+ /**
+ * Process the request for both tag cloud and tag list endpoints.
+ */
+ protected function processRequest(string $type, Request $request, Response $response): Response
+ {
+ $tagsSeparator = $this->container->conf->get('general.tags_separator', ' ');
+ if ($this->container->loginManager->isLoggedIn() === true) {
+ $visibility = $this->container->sessionManager->getSessionParameter('visibility');
+ }
+
+ $sort = $request->getQueryParam('sort');
+ $searchTags = $request->getQueryParam('searchtags');
+ $filteringTags = $searchTags !== null ? explode($tagsSeparator, $searchTags) : [];
+
+ $tags = $this->container->bookmarkService->bookmarksCountPerTag($filteringTags, $visibility ?? null);
+
+ if (static::TYPE_CLOUD === $type || 'alpha' === $sort) {
+ // TODO: the sorting should be handled by bookmarkService instead of the controller
+ alphabetical_sort($tags, false, true);
+ }
+
+ if (static::TYPE_CLOUD === $type) {
+ $tags = $this->formatTagsForCloud($tags);
+ }
+
+ $tagsUrl = [];
+ foreach ($tags as $tag => $value) {
+ $tagsUrl[escape($tag)] = urlencode((string) $tag);
+ }
+
+ $searchTags = tags_array2str($filteringTags, $tagsSeparator);
+ $searchTags = !empty($searchTags) ? trim($searchTags, $tagsSeparator) . $tagsSeparator : '';
+ $searchTagsUrl = urlencode($searchTags);
+ $data = [
+ 'search_tags' => escape($searchTags),
+ 'search_tags_url' => $searchTagsUrl,
+ 'tags' => escape($tags),
+ 'tags_url' => $tagsUrl,
+ ];
+ $this->executePageHooks('render_tag' . $type, $data, 'tag.' . $type);
+ $this->assignAllView($data);
+
+ $searchTags = !empty($searchTags) ? trim(str_replace($tagsSeparator, ' ', $searchTags)) . ' - ' : '';
+ $this->assignView(
+ 'pagetitle',
+ $searchTags . t('Tag ' . $type) . ' - ' . $this->container->conf->get('general.title', 'Shaarli')
+ );
+
+ return $response->write($this->render('tag.' . $type));
+ }
+
+ /**
+ * Format the tags array for the tag cloud template.
+ *
+ * @param array $tags List of tags as key with count as value
+ *
+ * @return mixed[] List of tags as key, with count and expected font size in a subarray
+ */
+ protected function formatTagsForCloud(array $tags): array
+ {
+ // We sort tags alphabetically, then choose a font size according to count.
+ // First, find max value.
+ $maxCount = count($tags) > 0 ? max($tags) : 0;
+ $logMaxCount = $maxCount > 1 ? log($maxCount, 30) : 1;
+ $tagList = [];
+ foreach ($tags as $key => $value) {
+ // Tag font size scaling:
+ // default 15 and 30 logarithm bases affect scaling,
+ // 2.2 and 0.8 are arbitrary font sizes in em.
+ $size = log($value, 15) / $logMaxCount * 2.2 + 0.8;
+ $tagList[$key] = [
+ 'count' => $value,
+ 'size' => number_format($size, 2, '.', ''),
+ ];
+ }
+
+ return $tagList;
+ }
+}
diff --git a/application/front/controller/visitor/TagController.php b/application/front/controller/visitor/TagController.php
new file mode 100644
index 00000000..3aa58542
--- /dev/null
+++ b/application/front/controller/visitor/TagController.php
@@ -0,0 +1,120 @@
+container->environment['HTTP_REFERER'] ?? null;
+
+ // In case browser does not send HTTP_REFERER, we search a single tag
+ if (null === $referer) {
+ if (null !== $newTag) {
+ return $this->redirect($response, '/?searchtags=' . urlencode($newTag));
+ }
+
+ return $this->redirect($response, '/');
+ }
+
+ $currentUrl = parse_url($referer);
+ parse_str($currentUrl['query'] ?? '', $params);
+
+ if (null === $newTag) {
+ return $response->withRedirect(($currentUrl['path'] ?? './') . '?' . http_build_query($params));
+ }
+
+ // Prevent redirection loop
+ if (isset($params['addtag'])) {
+ unset($params['addtag']);
+ }
+
+ $tagsSeparator = $this->container->conf->get('general.tags_separator', ' ');
+ // Check if this tag is already in the search query and ignore it if it is.
+ // Each tag is always separated by a space
+ $currentTags = tags_str2array($params['searchtags'] ?? '', $tagsSeparator);
+
+ $addtag = true;
+ foreach ($currentTags as $value) {
+ if ($value === $newTag) {
+ $addtag = false;
+ break;
+ }
+ }
+
+ // Append the tag if necessary
+ if (true === $addtag) {
+ $currentTags[] = trim($newTag);
+ }
+
+ $params['searchtags'] = tags_array2str($currentTags, $tagsSeparator);
+
+ // We also remove page (keeping the same page has no sense, since the results are different)
+ unset($params['page']);
+
+ return $response->withRedirect(($currentUrl['path'] ?? './') . '?' . http_build_query($params));
+ }
+
+ /**
+ * Remove a tag from the current search through an HTTP redirection.
+ *
+ * @param array $args Should contain `tag` key as tag to remove from current search
+ */
+ public function removeTag(Request $request, Response $response, array $args): Response
+ {
+ $referer = $this->container->environment['HTTP_REFERER'] ?? null;
+
+ // If the referrer is not provided, we can update the search, so we failback on the bookmark list
+ if (empty($referer)) {
+ return $this->redirect($response, '/');
+ }
+
+ $tagToRemove = $args['tag'] ?? null;
+ $currentUrl = parse_url($referer);
+ parse_str($currentUrl['query'] ?? '', $params);
+
+ if (null === $tagToRemove) {
+ return $response->withRedirect(($currentUrl['path'] ?? './') . '?' . http_build_query($params));
+ }
+
+ // Prevent redirection loop
+ if (isset($params['removetag'])) {
+ unset($params['removetag']);
+ }
+
+ if (isset($params['searchtags'])) {
+ $tagsSeparator = $this->container->conf->get('general.tags_separator', ' ');
+ $tags = tags_str2array($params['searchtags'] ?? '', $tagsSeparator);
+ // Remove value from array $tags.
+ $tags = array_diff($tags, [$tagToRemove]);
+ $params['searchtags'] = tags_array2str($tags, $tagsSeparator);
+
+ if (empty($params['searchtags'])) {
+ unset($params['searchtags']);
+ }
+
+ // We also remove page (keeping the same page has no sense, since the results are different)
+ unset($params['page']);
+ }
+
+ $queryParams = count($params) > 0 ? '?' . http_build_query($params) : '';
+
+ return $response->withRedirect(($currentUrl['path'] ?? './') . $queryParams);
+ }
+}
diff --git a/application/front/exceptions/AlreadyInstalledException.php b/application/front/exceptions/AlreadyInstalledException.php
new file mode 100644
index 00000000..4add86cf
--- /dev/null
+++ b/application/front/exceptions/AlreadyInstalledException.php
@@ -0,0 +1,15 @@
+';
+
+ /**
+ * Gets the latest version code from the Git repository
+ *
+ * The code is read from the raw content of the version file on the Git server.
+ *
+ * @param string $url URL to reach to get the latest version.
+ * @param int $timeout Timeout to check the URL (in seconds).
+ *
+ * @return mixed the version code from the repository if available, else 'false'
+ */
+ public static function getLatestGitVersionCode($url, $timeout = 2)
+ {
+ list($headers, $data) = get_http_response($url, $timeout);
+
+ if (preg_match('#HTTP/[\d\.]+ 200(?: OK)?#', $headers[0]) !== 1) {
+ error_log('Failed to retrieve ' . $url);
+ return false;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Retrieve the version from a remote URL or a file.
+ *
+ * @param string $remote URL or file to fetch.
+ * @param int $timeout For URLs fetching.
+ *
+ * @return bool|string The version or false if it couldn't be retrieved.
+ */
+ public static function getVersion($remote, $timeout = 2)
+ {
+ if (startsWith($remote, 'http')) {
+ if (($data = static::getLatestGitVersionCode($remote, $timeout)) === false) {
+ return false;
+ }
+ } else {
+ if (!is_file($remote)) {
+ return false;
+ }
+ $data = file_get_contents($remote);
+ }
+
+ return str_replace(
+ [self::$VERSION_START_TAG, self::$VERSION_END_TAG, PHP_EOL],
+ ['', '', ''],
+ $data
+ );
+ }
+
+ /**
+ * Checks if a new Shaarli version has been published on the Git repository
+ *
+ * Updates checks are run periodically, according to the following criteria:
+ * - the update checks are enabled (install, global config);
+ * - the user is logged in (or this is an open instance);
+ * - the last check is older than a given interval;
+ * - the check is non-blocking if the HTTPS connection to Git fails;
+ * - in case of failure, the update file's modification date is updated,
+ * to avoid intempestive connection attempts.
+ *
+ * @param string $currentVersion the current version code
+ * @param string $updateFile the file where to store the latest version code
+ * @param int $checkInterval the minimum interval between update checks (in seconds
+ * @param bool $enableCheck whether to check for new versions
+ * @param bool $isLoggedIn whether the user is logged in
+ * @param string $branch check update for the given branch
+ *
+ * @throws Exception an invalid branch has been set for update checks
+ *
+ * @return mixed the new version code if available and greater, else 'false'
+ */
+ public static function checkUpdate(
+ $currentVersion,
+ $updateFile,
+ $checkInterval,
+ $enableCheck,
+ $isLoggedIn,
+ $branch = 'stable'
+ ) {
+ // Do not check versions for visitors
+ // Do not check if the user doesn't want to
+ // Do not check with dev version
+ if (!$isLoggedIn || empty($enableCheck) || $currentVersion === 'dev') {
+ return false;
+ }
+
+ if (is_file($updateFile) && (filemtime($updateFile) > time() - $checkInterval)) {
+ // Shaarli has checked for updates recently - skip HTTP query
+ $latestKnownVersion = file_get_contents($updateFile);
+
+ if (version_compare($latestKnownVersion, $currentVersion) == 1) {
+ return $latestKnownVersion;
+ }
+ return false;
+ }
+
+ if (!in_array($branch, self::$GIT_BRANCHES)) {
+ throw new Exception(
+ 'Invalid branch selected for updates: "' . $branch . '"'
+ );
+ }
+
+ // Late Static Binding allows overriding within tests
+ // See http://php.net/manual/en/language.oop5.late-static-bindings.php
+ $latestVersion = static::getVersion(
+ self::$GIT_RAW_URL . '/' . $branch . '/' . self::$VERSION_FILE
+ );
+
+ if (!$latestVersion) {
+ // Only update the file's modification date
+ file_put_contents($updateFile, $currentVersion);
+ return false;
+ }
+
+ // Update the file's content and modification date
+ file_put_contents($updateFile, $latestVersion);
+
+ if (version_compare($latestVersion, $currentVersion) == 1) {
+ return $latestVersion;
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks the PHP version to ensure Shaarli can run
+ *
+ * @param string $minVersion minimum PHP required version
+ * @param string $curVersion current PHP version (use PHP_VERSION)
+ *
+ * @return bool true on success
+ *
+ * @throws Exception the PHP version is not supported
+ */
+ public static function checkPHPVersion($minVersion, $curVersion)
+ {
+ if (version_compare($curVersion, $minVersion) < 0) {
+ $msg = t(
+ 'Your PHP version is obsolete!'
+ . ' Shaarli requires at least PHP %s, and thus cannot run.'
+ . ' Your PHP version has known security vulnerabilities and should be'
+ . ' updated as soon as possible.'
+ );
+ throw new Exception(sprintf($msg, $minVersion));
+ }
+ return true;
+ }
+
+ /**
+ * Checks Shaarli has the proper access permissions to its resources
+ *
+ * @param ConfigManager $conf Configuration Manager instance.
+ * @param bool $minimalMode In minimal mode we only check permissions to be able to display a template.
+ * Currently we only need to be able to read the theme and write in raintpl cache.
+ *
+ * @return array A list of the detected configuration issues
+ */
+ public static function checkResourcePermissions(ConfigManager $conf, bool $minimalMode = false): array
+ {
+ $errors = [];
+ $rainTplDir = rtrim($conf->get('resource.raintpl_tpl'), '/');
+
+ // Check script and template directories are readable
+ foreach (
+ [
+ 'application',
+ 'inc',
+ 'plugins',
+ $rainTplDir,
+ $rainTplDir . '/' . $conf->get('resource.theme'),
+ ] as $path
+ ) {
+ if (!is_readable(realpath($path))) {
+ $errors[] = '"' . $path . '" ' . t('directory is not readable');
+ }
+ }
+
+ // Check cache and data directories are readable and writable
+ if ($minimalMode) {
+ $folders = [
+ $conf->get('resource.raintpl_tmp'),
+ ];
+ } else {
+ $folders = [
+ $conf->get('resource.thumbnails_cache'),
+ $conf->get('resource.data_dir'),
+ $conf->get('resource.page_cache'),
+ $conf->get('resource.raintpl_tmp'),
+ ];
+ }
+
+ foreach ($folders as $path) {
+ if (!is_readable(realpath($path))) {
+ $errors[] = '"' . $path . '" ' . t('directory is not readable');
+ }
+ if (!is_writable(realpath($path))) {
+ $errors[] = '"' . $path . '" ' . t('directory is not writable');
+ }
+ }
+
+ if ($minimalMode) {
+ return $errors;
+ }
+
+ // Check configuration files are readable and writable
+ foreach (
+ [
+ $conf->getConfigFileExt(),
+ $conf->get('resource.datastore'),
+ $conf->get('resource.ban_file'),
+ $conf->get('resource.log'),
+ $conf->get('resource.update_check'),
+ ] as $path
+ ) {
+ if (!is_string($path) || !is_file(realpath($path))) {
+ # the file may not exist yet
+ continue;
+ }
+
+ if (!is_readable(realpath($path))) {
+ $errors[] = '"' . $path . '" ' . t('file is not readable');
+ }
+ if (!is_writable(realpath($path))) {
+ $errors[] = '"' . $path . '" ' . t('file is not writable');
+ }
+ }
+
+ return $errors;
+ }
+
+ public static function checkDatastoreMutex(): array
+ {
+ $mutex = new FlockMutex(fopen(SHAARLI_MUTEX_FILE, 'r'), 2);
+ try {
+ $mutex->synchronized(function () {
+ return true;
+ });
+ } catch (LockAcquireException $e) {
+ $errors[] = t('Lock can not be acquired on the datastore. You might encounter concurrent access issues.');
+ }
+
+ return $errors ?? [];
+ }
+
+ /**
+ * Returns a salted hash representing the current Shaarli version.
+ *
+ * Useful for assets browser cache.
+ *
+ * @param string $currentVersion of Shaarli
+ * @param string $salt User personal salt, also used for the authentication
+ *
+ * @return string version hash
+ */
+ public static function getVersionHash($currentVersion, $salt)
+ {
+ return hash_hmac('sha256', $currentVersion, $salt);
+ }
+
+ /**
+ * Get a list of PHP extensions used by Shaarli.
+ *
+ * @return array[] List of extension with following keys:
+ * - name: extension name
+ * - required: whether the extension is required to use Shaarli
+ * - desc: short description of extension usage in Shaarli
+ * - loaded: whether the extension is properly loaded or not
+ */
+ public static function getPhpExtensionsRequirement(): array
+ {
+ $extensions = [
+ ['name' => 'json', 'required' => true, 'desc' => t('Configuration parsing')],
+ ['name' => 'simplexml', 'required' => true, 'desc' => t('Slim Framework (routing, etc.)')],
+ ['name' => 'mbstring', 'required' => true, 'desc' => t('Multibyte (Unicode) string support')],
+ ['name' => 'gd', 'required' => false, 'desc' => t('Required to use thumbnails')],
+ ['name' => 'intl', 'required' => false, 'desc' => t('Localized text sorting (e.g. e->è->f)')],
+ ['name' => 'curl', 'required' => false, 'desc' => t('Better retrieval of bookmark metadata and thumbnail')],
+ ['name' => 'gettext', 'required' => false, 'desc' => t('Use the translation system in gettext mode')],
+ ['name' => 'ldap', 'required' => false, 'desc' => t('Login using LDAP server')],
+ ];
+
+ foreach ($extensions as &$extension) {
+ $extension['loaded'] = extension_loaded($extension['name']);
+ }
+
+ return $extensions;
+ }
+
+ /**
+ * Return the EOL date of given PHP version. If the version is unknown,
+ * we return today + 2 years.
+ *
+ * @param string $fullVersion PHP version, e.g. 7.4.7
+ *
+ * @return string Date format: YYYY-MM-DD
+ */
+ public static function getPhpEol(string $fullVersion): string
+ {
+ preg_match('/(\d+\.\d+)\.\d+/', $fullVersion, $matches);
+
+ return [
+ '7.1' => '2019-12-01',
+ '7.2' => '2020-11-30',
+ '7.3' => '2021-12-06',
+ '7.4' => '2022-11-28',
+ '8.0' => '2023-12-01',
+ ][$matches[1]] ?? (new \DateTime('+2 year'))->format('Y-m-d');
+ }
+}
diff --git a/application/helper/DailyPageHelper.php b/application/helper/DailyPageHelper.php
new file mode 100644
index 00000000..cb4494a8
--- /dev/null
+++ b/application/helper/DailyPageHelper.php
@@ -0,0 +1,241 @@
+getQueryParam(static::MONTH) !== null) {
+ return static::MONTH;
+ } elseif ($request->getQueryParam(static::WEEK) !== null) {
+ return static::WEEK;
+ }
+
+ return static::DAY;
+ }
+
+ /**
+ * Extracts a DateTimeImmutable from provided HTTP request.
+ * If no parameter is provided, we rely on the creation date of the latest provided created bookmark.
+ * If the datastore is empty or no bookmark is provided, we use the current date.
+ *
+ * @param string $type month/week/day
+ * @param string|null $requestedDate Input string extracted from the request
+ * @param Bookmark|null $latestBookmark Latest bookmark found in the datastore (by date)
+ *
+ * @return DateTimeImmutable from input or latest bookmark.
+ *
+ * @throws Exception Type not supported.
+ */
+ public static function extractRequestedDateTime(
+ string $type,
+ ?string $requestedDate,
+ Bookmark $latestBookmark = null
+ ): DateTimeImmutable {
+ $format = static::getFormatByType($type);
+ if (empty($requestedDate)) {
+ return $latestBookmark instanceof Bookmark
+ ? new DateTimeImmutable($latestBookmark->getCreated()->format(\DateTime::ATOM))
+ : new DateTimeImmutable()
+ ;
+ }
+
+ // Don't use today's day of month (github issue #1844)
+ if ($type === static::MONTH) {
+ $format = '!' . $format;
+ }
+
+ // W is not supported by createFromFormat...
+ if ($type === static::WEEK) {
+ return (new DateTimeImmutable())
+ ->setISODate((int) substr($requestedDate, 0, 4), (int) substr($requestedDate, 4, 2))
+ ;
+ }
+
+ return DateTimeImmutable::createFromFormat($format, $requestedDate);
+ }
+
+ /**
+ * Get the DateTime format used by provided type
+ * Examples:
+ * - day: 20201016 ()
+ * - week: 202041 ()
+ * - month: 202010 ()
+ *
+ * @param string $type month/week/day
+ *
+ * @return string DateTime compatible format
+ *
+ * @see https://www.php.net/manual/en/datetime.format.php
+ *
+ * @throws Exception Type not supported.
+ */
+ public static function getFormatByType(string $type): string
+ {
+ switch ($type) {
+ case static::MONTH:
+ return 'Ym';
+ case static::WEEK:
+ return 'YW';
+ case static::DAY:
+ return 'Ymd';
+ default:
+ throw new Exception('Unsupported daily format type');
+ }
+ }
+
+ /**
+ * Get the first DateTime of the time period depending on given datetime and type.
+ * Note: DateTimeImmutable is required because we rely heavily on DateTime->modify() syntax
+ * and we don't want to alter original datetime.
+ *
+ * @param string $type month/week/day
+ * @param DateTimeImmutable $requested DateTime extracted from request input
+ * (should come from extractRequestedDateTime)
+ *
+ * @return \DateTimeInterface First DateTime of the time period
+ *
+ * @throws Exception Type not supported.
+ */
+ public static function getStartDateTimeByType(string $type, DateTimeImmutable $requested): \DateTimeInterface
+ {
+ switch ($type) {
+ case static::MONTH:
+ return $requested->modify('first day of this month midnight');
+ case static::WEEK:
+ return $requested->modify('Monday this week midnight');
+ case static::DAY:
+ return $requested->modify('Today midnight');
+ default:
+ throw new Exception('Unsupported daily format type');
+ }
+ }
+
+ /**
+ * Get the last DateTime of the time period depending on given datetime and type.
+ * Note: DateTimeImmutable is required because we rely heavily on DateTime->modify() syntax
+ * and we don't want to alter original datetime.
+ *
+ * @param string $type month/week/day
+ * @param DateTimeImmutable $requested DateTime extracted from request input
+ * (should come from extractRequestedDateTime)
+ *
+ * @return \DateTimeInterface Last DateTime of the time period
+ *
+ * @throws Exception Type not supported.
+ */
+ public static function getEndDateTimeByType(string $type, DateTimeImmutable $requested): \DateTimeInterface
+ {
+ switch ($type) {
+ case static::MONTH:
+ return $requested->modify('last day of this month 23:59:59');
+ case static::WEEK:
+ return $requested->modify('Sunday this week 23:59:59');
+ case static::DAY:
+ return $requested->modify('Today 23:59:59');
+ default:
+ throw new Exception('Unsupported daily format type');
+ }
+ }
+
+ /**
+ * Get localized description of the time period depending on given datetime and type.
+ * Example: for a month period, it returns `October, 2020`.
+ *
+ * @param string $type month/week/day
+ * @param \DateTimeImmutable $requested DateTime extracted from request input
+ * (should come from extractRequestedDateTime)
+ * @param bool $includeRelative Include relative date description (today, yesterday, etc.)
+ *
+ * @return string Localized time period description
+ *
+ * @throws Exception Type not supported.
+ */
+ public static function getDescriptionByType(
+ string $type,
+ \DateTimeImmutable $requested,
+ bool $includeRelative = true
+ ): string {
+ switch ($type) {
+ case static::MONTH:
+ return $requested->format('F') . ', ' . $requested->format('Y');
+ case static::WEEK:
+ $requested = $requested->modify('Monday this week');
+ return t('Week') . ' ' . $requested->format('W') . ' (' . format_date($requested, false) . ')';
+ case static::DAY:
+ $out = '';
+ if ($includeRelative && $requested->format('Ymd') === date('Ymd')) {
+ $out = t('Today') . ' - ';
+ } elseif ($includeRelative && $requested->format('Ymd') === date('Ymd', strtotime('-1 days'))) {
+ $out = t('Yesterday') . ' - ';
+ }
+ return $out . format_date($requested, false);
+ default:
+ throw new Exception('Unsupported daily format type');
+ }
+ }
+
+ /**
+ * Get the number of items to display in the RSS feed depending on the given type.
+ *
+ * @param string $type month/week/day
+ *
+ * @return int number of elements
+ *
+ * @throws Exception Type not supported.
+ */
+ public static function getRssLengthByType(string $type): int
+ {
+ switch ($type) {
+ case static::MONTH:
+ return 12; // 1 year
+ case static::WEEK:
+ return 26; // ~6 months
+ case static::DAY:
+ return 30; // ~1 month
+ default:
+ throw new Exception('Unsupported daily format type');
+ }
+ }
+
+ /**
+ * Get the number of items to display in the RSS feed depending on the given type.
+ *
+ * @param string $type month/week/day
+ * @param ?DateTimeImmutable $requested Currently only used for UT
+ *
+ * @return DatePeriod number of elements
+ *
+ * @throws Exception Type not supported.
+ */
+ public static function getCacheDatePeriodByType(string $type, DateTimeImmutable $requested = null): DatePeriod
+ {
+ $requested = $requested ?? new DateTimeImmutable();
+
+ return new DatePeriod(
+ static::getStartDateTimeByType($type, $requested),
+ new \DateInterval('P1D'),
+ static::getEndDateTimeByType($type, $requested)
+ );
+ }
+}
diff --git a/application/helper/FileUtils.php b/application/helper/FileUtils.php
new file mode 100644
index 00000000..e8a2168c
--- /dev/null
+++ b/application/helper/FileUtils.php
@@ -0,0 +1,140 @@
+';
+
+ /**
+ * Write data into a file (Shaarli database format).
+ * The data is stored in a PHP file, as a comment, in compressed base64 format.
+ *
+ * The file will be created if it doesn't exist.
+ *
+ * @param string $file File path.
+ * @param mixed $content Content to write.
+ *
+ * @return int|bool Number of bytes written or false if it fails.
+ *
+ * @throws IOException The destination file can't be written.
+ */
+ public static function writeFlatDB($file, $content)
+ {
+ if (is_file($file) && !is_writeable($file)) {
+ // The datastore exists but is not writeable
+ throw new IOException($file);
+ } elseif (!is_file($file) && !is_writeable(dirname($file))) {
+ // The datastore does not exist and its parent directory is not writeable
+ throw new IOException(dirname($file));
+ }
+
+ return file_put_contents(
+ $file,
+ self::$phpPrefix . base64_encode(gzdeflate(serialize($content))) . self::$phpSuffix
+ );
+ }
+
+ /**
+ * Read data from a file containing Shaarli database format content.
+ *
+ * If the file isn't readable or doesn't exist, default data will be returned.
+ *
+ * @param string $file File path.
+ * @param mixed $default The default value to return if the file isn't readable.
+ *
+ * @return mixed The content unserialized, or default if the file isn't readable, or false if it fails.
+ */
+ public static function readFlatDB($file, $default = null)
+ {
+ // Note that gzinflate is faster than gzuncompress.
+ // See: http://www.php.net/manual/en/function.gzdeflate.php#96439
+ if (!is_readable($file)) {
+ return $default;
+ }
+
+ $data = file_get_contents($file);
+ if ($data == '') {
+ return $default;
+ }
+
+ return unserialize(
+ gzinflate(
+ base64_decode(
+ substr($data, strlen(self::$phpPrefix), -strlen(self::$phpSuffix))
+ )
+ )
+ );
+ }
+
+ /**
+ * Recursively deletes a folder content, and deletes itself optionally.
+ * If an excluded file is found, folders won't be deleted.
+ *
+ * Additional security: raise an exception if it tries to delete a folder outside of Shaarli directory.
+ *
+ * @param string $path
+ * @param bool $selfDelete Delete the provided folder if true, only its content if false.
+ * @param array $exclude
+ */
+ public static function clearFolder(string $path, bool $selfDelete, array $exclude = []): bool
+ {
+ $skipped = false;
+
+ if (!is_dir($path)) {
+ throw new IOException(t('Provided path is not a directory.'));
+ }
+
+ if (!static::isPathInShaarliFolder($path)) {
+ throw new IOException(t('Trying to delete a folder outside of Shaarli path.'));
+ }
+
+ foreach (new \DirectoryIterator($path) as $file) {
+ if ($file->isDot()) {
+ continue;
+ }
+
+ if (in_array($file->getBasename(), $exclude, true)) {
+ $skipped = true;
+ continue;
+ }
+
+ if ($file->isFile()) {
+ unlink($file->getPathname());
+ } elseif ($file->isDir()) {
+ $skipped = static::clearFolder($file->getRealPath(), true, $exclude) || $skipped;
+ }
+ }
+
+ if ($selfDelete && !$skipped) {
+ rmdir($path);
+ }
+
+ return $skipped;
+ }
+
+ /**
+ * Checks that the given path is inside Shaarli directory.
+ */
+ public static function isPathInShaarliFolder(string $path): bool
+ {
+ $rootDirectory = dirname(dirname(dirname(__FILE__)));
+
+ return strpos(realpath($path), $rootDirectory) !== false;
+ }
+}
diff --git a/application/http/HttpAccess.php b/application/http/HttpAccess.php
new file mode 100644
index 00000000..e80e0c01
--- /dev/null
+++ b/application/http/HttpAccess.php
@@ -0,0 +1,49 @@
+idnToAscii();
if (!filter_var($cleanUrl, FILTER_VALIDATE_URL) || !$urlObj->isHttp()) {
- return array(array(0 => 'Invalid HTTP UrlUtils'), false);
+ return [[0 => 'Invalid HTTP UrlUtils'], false];
}
$userAgent =
@@ -64,42 +71,39 @@ function get_http_response($url, $timeout = 30, $maxBytes = 4194304, $curlWriteF
$ch = curl_init($cleanUrl);
if ($ch === false) {
- return array(array(0 => 'curl_init() error'), false);
+ return [[0 => 'curl_init() error'], false];
}
// General cURL settings
curl_setopt($ch, CURLOPT_AUTOREFERER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
- curl_setopt($ch, CURLOPT_HEADER, true);
+ // Default header download if the $curlHeaderFunction is not defined
+ curl_setopt($ch, CURLOPT_HEADER, !is_callable($curlHeaderFunction));
curl_setopt(
$ch,
CURLOPT_HTTPHEADER,
- array('Accept-Language: ' . $acceptLanguage)
+ ['Accept-Language: ' . $acceptLanguage]
);
curl_setopt($ch, CURLOPT_MAXREDIRS, $maxRedirs);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_USERAGENT, $userAgent);
+ // Max download size management
+ curl_setopt($ch, CURLOPT_BUFFERSIZE, 1024 * 16);
+ curl_setopt($ch, CURLOPT_NOPROGRESS, false);
+ if (is_callable($curlHeaderFunction)) {
+ curl_setopt($ch, CURLOPT_HEADERFUNCTION, $curlHeaderFunction);
+ }
if (is_callable($curlWriteFunction)) {
curl_setopt($ch, CURLOPT_WRITEFUNCTION, $curlWriteFunction);
}
-
- // Max download size management
- curl_setopt($ch, CURLOPT_BUFFERSIZE, 1024*16);
- curl_setopt($ch, CURLOPT_NOPROGRESS, false);
curl_setopt(
$ch,
CURLOPT_PROGRESSFUNCTION,
- function ($arg0, $arg1, $arg2, $arg3, $arg4 = 0) use ($maxBytes) {
- if (version_compare(phpversion(), '5.5', '<')) {
- // PHP version lower than 5.5
- // Callback has 4 arguments
- $downloaded = $arg1;
- } else {
- // Callback has 5 arguments
- $downloaded = $arg2;
- }
+ function ($arg0, $arg1, $arg2, $arg3, $arg4) use ($maxBytes) {
+ $downloaded = $arg2;
+
// Non-zero return stops downloading
return ($downloaded > $maxBytes) ? 1 : 0;
}
@@ -118,9 +122,9 @@ function get_http_response($url, $timeout = 30, $maxBytes = 4194304, $curlWriteF
* Removing this would require updating
* GetHttpUrlTest::testGetInvalidRemoteUrl()
*/
- return array(false, false);
+ return [false, false];
}
- return array(array(0 => 'curl_exec() error: ' . $errorStr), false);
+ return [[0 => 'curl_exec() error: ' . $errorStr], false];
}
// Formatting output like the fallback method
@@ -131,7 +135,7 @@ function get_http_response($url, $timeout = 30, $maxBytes = 4194304, $curlWriteF
$rawHeadersLastRedir = end($rawHeadersArrayRedirs);
$content = substr($response, $headSize);
- $headers = array();
+ $headers = [];
foreach (preg_split('~[\r\n]+~', $rawHeadersLastRedir) as $line) {
if (empty($line) || ctype_space($line)) {
continue;
@@ -142,7 +146,7 @@ function get_http_response($url, $timeout = 30, $maxBytes = 4194304, $curlWriteF
$value = $splitLine[1];
if (array_key_exists($key, $headers)) {
if (!is_array($headers[$key])) {
- $headers[$key] = array(0 => $headers[$key]);
+ $headers[$key] = [0 => $headers[$key]];
}
$headers[$key][] = $value;
} else {
@@ -153,7 +157,7 @@ function get_http_response($url, $timeout = 30, $maxBytes = 4194304, $curlWriteF
}
}
- return array($headers, $content);
+ return [$headers, $content];
}
/**
@@ -184,15 +188,15 @@ function get_http_response_fallback(
$acceptLanguage,
$maxRedr
) {
- $options = array(
- 'http' => array(
+ $options = [
+ 'http' => [
'method' => 'GET',
'timeout' => $timeout,
'user_agent' => $userAgent,
'header' => "Accept: */*\r\n"
. 'Accept-Language: ' . $acceptLanguage
- )
- );
+ ]
+ ];
stream_context_set_default($options);
list($headers, $finalUrl) = get_redirected_headers($cleanUrl, $maxRedr);
@@ -203,7 +207,7 @@ function get_http_response_fallback(
}
if (! $headers) {
- return array($headers, false);
+ return [$headers, false];
}
try {
@@ -211,10 +215,10 @@ function get_http_response_fallback(
$context = stream_context_create($options);
$content = file_get_contents($finalUrl, false, $context, -1, $maxBytes);
} catch (Exception $exc) {
- return array(array(0 => 'HTTP Error'), $exc->getMessage());
+ return [[0 => 'HTTP Error'], $exc->getMessage()];
}
- return array($headers, $content);
+ return [$headers, $content];
}
/**
@@ -233,10 +237,12 @@ function get_redirected_headers($url, $redirectionLimit = 3)
}
// Headers found, redirection found, and limit not reached.
- if ($redirectionLimit-- > 0
+ if (
+ $redirectionLimit-- > 0
&& !empty($headers)
&& (strpos($headers[0], '301') !== false || strpos($headers[0], '302') !== false)
- && !empty($headers['Location'])) {
+ && !empty($headers['Location'])
+ ) {
$redirection = is_array($headers['Location']) ? end($headers['Location']) : $headers['Location'];
if ($redirection != $url) {
$redirection = getAbsoluteUrl($url, $redirection);
@@ -244,7 +250,7 @@ function get_redirected_headers($url, $redirectionLimit = 3)
}
}
- return array($headers, $url);
+ return [$headers, $url];
}
/**
@@ -266,7 +272,7 @@ function getAbsoluteUrl($originalUrl, $newUrl)
}
$parts = parse_url($originalUrl);
- $final = $parts['scheme'] .'://'. $parts['host'];
+ $final = $parts['scheme'] . '://' . $parts['host'];
$final .= (!empty($parts['port'])) ? $parts['port'] : '';
$final .= '/';
if ($newUrl[0] != '/') {
@@ -319,7 +325,8 @@ function server_url($server)
$scheme = 'https';
}
- if (($scheme == 'http' && $port != '80')
+ if (
+ ($scheme == 'http' && $port != '80')
|| ($scheme == 'https' && $port != '443')
) {
$port = ':' . $port;
@@ -340,22 +347,26 @@ function server_url($server)
$host = $server['SERVER_NAME'];
}
- return $scheme.'://'.$host.$port;
+ return $scheme . '://' . $host . $port;
}
// SSL detection
- if ((! empty($server['HTTPS']) && strtolower($server['HTTPS']) == 'on')
- || (isset($server['SERVER_PORT']) && $server['SERVER_PORT'] == '443')) {
+ if (
+ (! empty($server['HTTPS']) && strtolower($server['HTTPS']) == 'on')
+ || (isset($server['SERVER_PORT']) && $server['SERVER_PORT'] == '443')
+ ) {
$scheme = 'https';
}
// Do not append standard port values
- if (($scheme == 'http' && $server['SERVER_PORT'] != '80')
- || ($scheme == 'https' && $server['SERVER_PORT'] != '443')) {
- $port = ':'.$server['SERVER_PORT'];
+ if (
+ ($scheme == 'http' && $server['SERVER_PORT'] != '80')
+ || ($scheme == 'https' && $server['SERVER_PORT'] != '443')
+ ) {
+ $port = ':' . $server['SERVER_PORT'];
}
- return $scheme.'://'.$server['SERVER_NAME'].$port;
+ return $scheme . '://' . $server['SERVER_NAME'] . $port;
}
/**
@@ -369,7 +380,11 @@ function server_url($server)
*/
function index_url($server)
{
- $scriptname = $server['SCRIPT_NAME'];
+ if (defined('SHAARLI_ROOT_URL') && null !== SHAARLI_ROOT_URL) {
+ return rtrim(SHAARLI_ROOT_URL, '/') . '/';
+ }
+
+ $scriptname = !empty($server['SCRIPT_NAME']) ? $server['SCRIPT_NAME'] : '/';
if (endsWith($scriptname, 'index.php')) {
$scriptname = substr($scriptname, 0, -9);
}
@@ -377,7 +392,7 @@ function index_url($server)
}
/**
- * Returns the absolute URL of the current script, with the query
+ * Returns the absolute URL of the current script, with current route and query
*
* If the resource is "index.php", then it is removed (for better-looking URLs)
*
@@ -387,10 +402,17 @@ function index_url($server)
*/
function page_url($server)
{
- if (! empty($server['QUERY_STRING'])) {
- return index_url($server).'?'.$server['QUERY_STRING'];
+ $scriptname = $server['SCRIPT_NAME'] ?? '';
+ if (endsWith($scriptname, 'index.php')) {
+ $scriptname = substr($scriptname, 0, -9);
}
- return index_url($server);
+
+ $route = preg_replace('@^' . $scriptname . '@', '', $server['REQUEST_URI'] ?? '');
+ if (! empty($server['QUERY_STRING'])) {
+ return index_url($server) . $route . '?' . $server['QUERY_STRING'];
+ }
+
+ return index_url($server) . $route;
}
/**
@@ -477,3 +499,138 @@ function is_https($server)
return ! empty($server['HTTPS']);
}
+
+/**
+ * Get cURL callback function for CURLOPT_WRITEFUNCTION
+ *
+ * @param string $charset to extract from the downloaded page (reference)
+ * @param string $curlGetInfo Optionally overrides curl_getinfo function
+ *
+ * @return Closure
+ */
+function get_curl_header_callback(
+ &$charset,
+ $curlGetInfo = 'curl_getinfo'
+) {
+ $isRedirected = false;
+
+ return function ($ch, $data) use ($curlGetInfo, &$charset, &$isRedirected) {
+ $responseCode = $curlGetInfo($ch, CURLINFO_RESPONSE_CODE);
+ $chunkLength = strlen($data);
+ if (!empty($responseCode) && in_array($responseCode, [301, 302])) {
+ $isRedirected = true;
+ return $chunkLength;
+ }
+ if (!empty($responseCode) && $responseCode !== 200) {
+ return false;
+ }
+ // After a redirection, the content type will keep the previous request value
+ // until it finds the next content-type header.
+ if (! $isRedirected || strpos(strtolower($data), 'content-type') !== false) {
+ $contentType = $curlGetInfo($ch, CURLINFO_CONTENT_TYPE);
+ }
+ if (!empty($contentType) && strpos($contentType, 'text/html') === false) {
+ return false;
+ }
+ if (!empty($contentType) && empty($charset)) {
+ $charset = header_extract_charset($contentType);
+ }
+
+ return $chunkLength;
+ };
+}
+
+/**
+ * Get cURL callback function for CURLOPT_WRITEFUNCTION
+ *
+ * @param string $charset to extract from the downloaded page (reference)
+ * @param string $title to extract from the downloaded page (reference)
+ * @param string $description to extract from the downloaded page (reference)
+ * @param string $keywords to extract from the downloaded page (reference)
+ * @param bool $retrieveDescription Automatically tries to retrieve description and keywords from HTML content
+ * @param string $curlGetInfo Optionally overrides curl_getinfo function
+ *
+ * @return Closure
+ */
+function get_curl_download_callback(
+ &$charset,
+ &$title,
+ &$description,
+ &$keywords,
+ $retrieveDescription,
+ $tagsSeparator
+) {
+ $currentChunk = 0;
+ $foundChunk = null;
+
+ /**
+ * cURL callback function for CURLOPT_WRITEFUNCTION (called during the download).
+ *
+ * While downloading the remote page, we check that the HTTP code is 200 and content type is 'html/text'
+ * Then we extract the title and the charset and stop the download when it's done.
+ *
+ * @param resource $ch cURL resource
+ * @param string $data chunk of data being downloaded
+ *
+ * @return int|bool length of $data or false if we need to stop the download
+ */
+ return function (
+ $ch,
+ $data
+ ) use (
+ $retrieveDescription,
+ $tagsSeparator,
+ &$charset,
+ &$title,
+ &$description,
+ &$keywords,
+ &$currentChunk,
+ &$foundChunk
+ ) {
+ $chunkLength = strlen($data);
+ $currentChunk++;
+
+ if (empty($charset)) {
+ $charset = html_extract_charset($data);
+ }
+ if (empty($title)) {
+ $title = html_extract_title($data);
+ $foundChunk = ! empty($title) ? $currentChunk : $foundChunk;
+ }
+ if (empty($title)) {
+ $title = html_extract_tag('title', $data);
+ $foundChunk = ! empty($title) ? $currentChunk : $foundChunk;
+ }
+ if ($retrieveDescription && empty($description)) {
+ $description = html_extract_tag('description', $data);
+ $foundChunk = ! empty($description) ? $currentChunk : $foundChunk;
+ }
+ if ($retrieveDescription && empty($keywords)) {
+ $keywords = html_extract_tag('keywords', $data);
+ if (! empty($keywords)) {
+ $foundChunk = $currentChunk;
+ // Keywords use the format tag1, tag2 multiple words, tag
+ // So we split the result with `,`, then if a tag contains the separator we replace it by `-`.
+ $keywords = tags_array2str(array_map(function (string $keyword) use ($tagsSeparator): string {
+ return tags_array2str(tags_str2array($keyword, $tagsSeparator), '-');
+ }, tags_str2array($keywords, ',')), $tagsSeparator);
+ }
+ }
+
+ // We got everything we want, stop the download.
+ // If we already found either the title, description or keywords,
+ // it's highly unlikely that we'll found the other metas further than
+ // in the same chunk of data or the next one. So we also stop the download after that.
+ if (
+ (!empty($responseCode) && !empty($contentType) && !empty($charset)) && $foundChunk !== null
+ && (! $retrieveDescription
+ || $foundChunk < $currentChunk
+ || (!empty($title) && !empty($description) && !empty($keywords))
+ )
+ ) {
+ return false;
+ }
+
+ return $chunkLength;
+ };
+}
diff --git a/application/http/MetadataRetriever.php b/application/http/MetadataRetriever.php
new file mode 100644
index 00000000..cfc72583
--- /dev/null
+++ b/application/http/MetadataRetriever.php
@@ -0,0 +1,74 @@
+conf = $conf;
+ $this->httpAccess = $httpAccess;
+ }
+
+ /**
+ * Retrieve metadata for given URL.
+ *
+ * @return array [
+ * 'title' => ,
+ * 'description' => ,
+ * 'tags' => ,
+ * ]
+ */
+ public function retrieve(string $url): array
+ {
+ $charset = null;
+ $title = null;
+ $description = null;
+ $tags = null;
+
+ // Short timeout to keep the application responsive
+ // The callback will fill $charset and $title with data from the downloaded page.
+ $this->httpAccess->getHttpResponse(
+ $url,
+ $this->conf->get('general.download_timeout', 30),
+ $this->conf->get('general.download_max_size', 4194304),
+ $this->httpAccess->getCurlHeaderCallback($charset),
+ $this->httpAccess->getCurlDownloadCallback(
+ $charset,
+ $title,
+ $description,
+ $tags,
+ $this->conf->get('general.retrieve_description'),
+ $this->conf->get('general.tags_separator', ' ')
+ )
+ );
+
+ if (!empty($title) && strtolower($charset) !== 'utf-8') {
+ $title = mb_convert_encoding($title, 'utf-8', $charset);
+ }
+
+ return array_map([$this, 'cleanMetadata'], [
+ 'title' => $title,
+ 'description' => $description,
+ 'tags' => $tags,
+ ]);
+ }
+
+ protected function cleanMetadata($data): ?string
+ {
+ return !is_string($data) || empty(trim($data)) ? null : trim($data);
+ }
+}
diff --git a/application/http/Url.php b/application/http/Url.php
index 90444a2f..129957bf 100644
--- a/application/http/Url.php
+++ b/application/http/Url.php
@@ -17,7 +17,7 @@ namespace Shaarli\Http;
*/
class Url
{
- private static $annoyingQueryParams = array(
+ private static $annoyingQueryParams = [
// Facebook
'action_object_map=',
'action_ref_map=',
@@ -37,15 +37,15 @@ class Url
// Other
'campaign_'
- );
+ ];
- private static $annoyingFragments = array(
+ private static $annoyingFragments = [
// ATInternet
'xtor=RSS-',
// Misc.
'tk.rss_all'
- );
+ ];
/*
* URL parts represented as an array
@@ -61,6 +61,7 @@ class Url
*/
public function __construct($url)
{
+ $url = $url ?? '';
$url = self::cleanupUnparsedUrl(trim($url));
$this->parts = parse_url($url);
@@ -120,7 +121,7 @@ class Url
foreach (self::$annoyingQueryParams as $annoying) {
foreach ($queryParams as $param) {
if (startsWith($param, $annoying)) {
- $queryParams = array_diff($queryParams, array($param));
+ $queryParams = array_diff($queryParams, [$param]);
continue;
}
}
diff --git a/application/http/UrlUtils.php b/application/http/UrlUtils.php
index 4bc84b82..de5b7db1 100644
--- a/application/http/UrlUtils.php
+++ b/application/http/UrlUtils.php
@@ -1,4 +1,5 @@
{$action}($request, $response);
+ }
+
+ /** Legacy route: ?post= */
+ public function post(Request $request, Response $response): Response
+ {
+ $route = '/admin/shaare';
+ $buildParameters = function (?array $parameters, bool $encode) {
+ if ($encode) {
+ $parameters = array_map('urlencode', $parameters);
+ }
+
+ return count($parameters) > 0 ? '?' . http_build_query($parameters) : '';
+ };
+
+
+ if (!$this->container->loginManager->isLoggedIn()) {
+ $parameters = $buildParameters($request->getQueryParams(), true);
+ return $this->redirect($response, '/login?returnurl=' . $this->getBasePath() . $route . $parameters);
+ }
+
+ $parameters = $buildParameters($request->getQueryParams(), false);
+
+ return $this->redirect($response, $route . $parameters);
+ }
+
+ /** Legacy route: ?addlink= */
+ protected function addlink(Request $request, Response $response): Response
+ {
+ $route = '/admin/add-shaare';
+
+ if (!$this->container->loginManager->isLoggedIn()) {
+ return $this->redirect($response, '/login?returnurl=' . $this->getBasePath() . $route);
+ }
+
+ return $this->redirect($response, $route);
+ }
+
+ /** Legacy route: ?do=login */
+ protected function login(Request $request, Response $response): Response
+ {
+ $returnUrl = $request->getQueryParam('returnurl');
+
+ return $this->redirect($response, '/login' . ($returnUrl ? '?returnurl=' . $returnUrl : ''));
+ }
+
+ /** Legacy route: ?do=logout */
+ protected function logout(Request $request, Response $response): Response
+ {
+ return $this->redirect($response, '/admin/logout');
+ }
+
+ /** Legacy route: ?do=picwall */
+ protected function picwall(Request $request, Response $response): Response
+ {
+ return $this->redirect($response, '/picture-wall');
+ }
+
+ /** Legacy route: ?do=tagcloud */
+ protected function tagcloud(Request $request, Response $response): Response
+ {
+ return $this->redirect($response, '/tags/cloud');
+ }
+
+ /** Legacy route: ?do=taglist */
+ protected function taglist(Request $request, Response $response): Response
+ {
+ return $this->redirect($response, '/tags/list');
+ }
+
+ /** Legacy route: ?do=daily */
+ protected function daily(Request $request, Response $response): Response
+ {
+ $dayParam = !empty($request->getParam('day')) ? '?day=' . escape($request->getParam('day')) : '';
+
+ return $this->redirect($response, '/daily' . $dayParam);
+ }
+
+ /** Legacy route: ?do=rss */
+ protected function rss(Request $request, Response $response): Response
+ {
+ return $this->feed($request, $response, FeedBuilder::$FEED_RSS);
+ }
+
+ /** Legacy route: ?do=atom */
+ protected function atom(Request $request, Response $response): Response
+ {
+ return $this->feed($request, $response, FeedBuilder::$FEED_ATOM);
+ }
+
+ /** Legacy route: ?do=opensearch */
+ protected function opensearch(Request $request, Response $response): Response
+ {
+ return $this->redirect($response, '/open-search');
+ }
+
+ /** Legacy route: ?do=dailyrss */
+ protected function dailyrss(Request $request, Response $response): Response
+ {
+ return $this->redirect($response, '/daily-rss');
+ }
+
+ /** Legacy route: ?do=feed */
+ protected function feed(Request $request, Response $response, string $feedType): Response
+ {
+ $parameters = count($request->getQueryParams()) > 0 ? '?' . http_build_query($request->getQueryParams()) : '';
+
+ return $this->redirect($response, '/feed/' . $feedType . $parameters);
+ }
+
+ /** Legacy route: ?do=configure */
+ protected function configure(Request $request, Response $response): Response
+ {
+ $route = '/admin/configure';
+
+ if (!$this->container->loginManager->isLoggedIn()) {
+ return $this->redirect($response, '/login?returnurl=' . $this->getBasePath() . $route);
+ }
+
+ return $this->redirect($response, $route);
+ }
+
+ protected function getBasePath(): string
+ {
+ return $this->container->basePath ?: '';
+ }
+}
diff --git a/application/legacy/LegacyLinkDB.php b/application/legacy/LegacyLinkDB.php
new file mode 100644
index 00000000..cb19eda5
--- /dev/null
+++ b/application/legacy/LegacyLinkDB.php
@@ -0,0 +1,585 @@
+link offset)
+ private $urls;
+
+ /**
+ * @var array List of all bookmarks IDS mapped with their array offset.
+ * Map: id->offset.
+ */
+ protected $ids;
+
+ // List of offset keys (for the Iterator interface implementation)
+ private $keys;
+
+ // Position in the $this->keys array (for the Iterator interface)
+ private $position;
+
+ // Is the user logged in? (used to filter private bookmarks)
+ private $loggedIn;
+
+ // Hide public bookmarks
+ private $hidePublicLinks;
+
+ /**
+ * Creates a new LinkDB
+ *
+ * Checks if the datastore exists; else, attempts to create a dummy one.
+ *
+ * @param string $datastore datastore file path.
+ * @param boolean $isLoggedIn is the user logged in?
+ * @param boolean $hidePublicLinks if true all bookmarks are private.
+ */
+ public function __construct(
+ $datastore,
+ $isLoggedIn,
+ $hidePublicLinks
+ ) {
+
+ $this->datastore = $datastore;
+ $this->loggedIn = $isLoggedIn;
+ $this->hidePublicLinks = $hidePublicLinks;
+ $this->check();
+ $this->read();
+ }
+
+ /**
+ * Countable - Counts elements of an object
+ */
+ public function count(): int
+ {
+ return count($this->links);
+ }
+
+ /**
+ * ArrayAccess - Assigns a value to the specified offset
+ */
+ public function offsetSet($offset, $value): void
+ {
+ // TODO: use exceptions instead of "die"
+ if (!$this->loggedIn) {
+ die(t('You are not authorized to add a link.'));
+ }
+ if (!isset($value['id']) || empty($value['url'])) {
+ die(t('Internal Error: A link should always have an id and URL.'));
+ }
+ if (($offset !== null && !is_int($offset)) || !is_int($value['id'])) {
+ die(t('You must specify an integer as a key.'));
+ }
+ if ($offset !== null && $offset !== $value['id']) {
+ die(t('Array offset and link ID must be equal.'));
+ }
+
+ // If the link exists, we reuse the real offset, otherwise new entry
+ $existing = $this->getLinkOffset($offset);
+ if ($existing !== null) {
+ $offset = $existing;
+ } else {
+ $offset = count($this->links);
+ }
+ $this->links[$offset] = $value;
+ $this->urls[$value['url']] = $offset;
+ $this->ids[$value['id']] = $offset;
+ }
+
+ /**
+ * ArrayAccess - Whether or not an offset exists
+ */
+ public function offsetExists($offset): bool
+ {
+ return array_key_exists($this->getLinkOffset($offset), $this->links);
+ }
+
+ /**
+ * ArrayAccess - Unsets an offset
+ */
+ public function offsetUnset($offset): void
+ {
+ if (!$this->loggedIn) {
+ // TODO: raise an exception
+ die('You are not authorized to delete a link.');
+ }
+ $realOffset = $this->getLinkOffset($offset);
+ $url = $this->links[$realOffset]['url'];
+ unset($this->urls[$url]);
+ unset($this->ids[$realOffset]);
+ unset($this->links[$realOffset]);
+ }
+
+ /**
+ * ArrayAccess - Returns the value at specified offset
+ */
+ public function offsetGet($offset): ?array
+ {
+ $realOffset = $this->getLinkOffset($offset);
+ return isset($this->links[$realOffset]) ? $this->links[$realOffset] : null;
+ }
+
+ /**
+ * Iterator - Returns the current element
+ */
+ public function current(): array
+ {
+ return $this[$this->keys[$this->position]];
+ }
+
+ /**
+ * Iterator - Returns the key of the current element
+ *
+ * @return int|string
+ */
+ #[\ReturnTypeWillChange]
+ public function key()
+ {
+ return $this->keys[$this->position];
+ }
+
+ /**
+ * Iterator - Moves forward to next element
+ */
+ public function next(): void
+ {
+ ++$this->position;
+ }
+
+ /**
+ * Iterator - Rewinds the Iterator to the first element
+ *
+ * Entries are sorted by date (latest first)
+ */
+ public function rewind(): void
+ {
+ $this->keys = array_keys($this->ids);
+ $this->position = 0;
+ }
+
+ /**
+ * Iterator - Checks if current position is valid
+ */
+ public function valid(): bool
+ {
+ return isset($this->keys[$this->position]);
+ }
+
+ /**
+ * Checks if the DB directory and file exist
+ *
+ * If no DB file is found, creates a dummy DB.
+ */
+ private function check()
+ {
+ if (file_exists($this->datastore)) {
+ return;
+ }
+
+ // Create a dummy database for example
+ $this->links = [];
+ $link = [
+ 'id' => 1,
+ 'title' => t('The personal, minimalist, super-fast, database free, bookmarking service'),
+ 'url' => 'https://shaarli.readthedocs.io',
+ 'description' => t(
+ 'Welcome to Shaarli! This is your first public bookmark. '
+ . 'To edit or delete me, you must first login.
+
+To learn how to use Shaarli, consult the link "Documentation" at the bottom of this page.
+
+You use the community supported version of the original Shaarli project, by Sebastien Sauvage.'
+ ),
+ 'private' => 0,
+ 'created' => new DateTime(),
+ 'tags' => 'opensource software',
+ 'sticky' => false,
+ ];
+ $link['shorturl'] = link_small_hash($link['created'], $link['id']);
+ $this->links[1] = $link;
+
+ $link = [
+ 'id' => 0,
+ 'title' => t('My secret stuff... - Pastebin.com'),
+ 'url' => 'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=',
+ 'description' => t('Shhhh! I\'m a private link only YOU can see. You can delete me too.'),
+ 'private' => 1,
+ 'created' => new DateTime('1 minute ago'),
+ 'tags' => 'secretstuff',
+ 'sticky' => false,
+ ];
+ $link['shorturl'] = link_small_hash($link['created'], $link['id']);
+ $this->links[0] = $link;
+
+ // Write database to disk
+ $this->write();
+ }
+
+ /**
+ * Reads database from disk to memory
+ */
+ private function read()
+ {
+ // Public bookmarks are hidden and user not logged in => nothing to show
+ if ($this->hidePublicLinks && !$this->loggedIn) {
+ $this->links = [];
+ return;
+ }
+
+ $this->urls = [];
+ $this->ids = [];
+ $this->links = FileUtils::readFlatDB($this->datastore, []);
+
+ $toremove = [];
+ foreach ($this->links as $key => &$link) {
+ if (!$this->loggedIn && $link['private'] != 0) {
+ // Transition for not upgraded databases.
+ unset($this->links[$key]);
+ continue;
+ }
+
+ // Sanitize data fields.
+ sanitizeLink($link);
+
+ // Remove private tags if the user is not logged in.
+ if (!$this->loggedIn) {
+ $link['tags'] = preg_replace('/(^|\s+)\.[^($|\s)]+\s*/', ' ', $link['tags']);
+ }
+
+ $link['real_url'] = $link['url'];
+
+ $link['sticky'] = isset($link['sticky']) ? $link['sticky'] : false;
+
+ // To be able to load bookmarks before running the update, and prepare the update
+ if (!isset($link['created'])) {
+ $link['id'] = $link['linkdate'];
+ $link['created'] = DateTime::createFromFormat(self::LINK_DATE_FORMAT, $link['linkdate']);
+ if (!empty($link['updated'])) {
+ $link['updated'] = DateTime::createFromFormat(self::LINK_DATE_FORMAT, $link['updated']);
+ }
+ $link['shorturl'] = smallHash($link['linkdate']);
+ }
+
+ $this->urls[$link['url']] = $key;
+ $this->ids[$link['id']] = $key;
+ }
+ }
+
+ /**
+ * Saves the database from memory to disk
+ *
+ * @throws IOException the datastore is not writable
+ */
+ private function write()
+ {
+ $this->reorder();
+ FileUtils::writeFlatDB($this->datastore, $this->links);
+ }
+
+ /**
+ * Saves the database from memory to disk
+ *
+ * @param string $pageCacheDir page cache directory
+ */
+ public function save($pageCacheDir)
+ {
+ if (!$this->loggedIn) {
+ // TODO: raise an Exception instead
+ die('You are not authorized to change the database.');
+ }
+
+ $this->write();
+
+ $pageCacheManager = new PageCacheManager($pageCacheDir, $this->loggedIn);
+ $pageCacheManager->invalidateCaches();
+ }
+
+ /**
+ * Returns the link for a given URL, or False if it does not exist.
+ *
+ * @param string $url URL to search for
+ *
+ * @return mixed the existing link if it exists, else 'false'
+ */
+ public function getLinkFromUrl($url)
+ {
+ if (isset($this->urls[$url])) {
+ return $this->links[$this->urls[$url]];
+ }
+ return false;
+ }
+
+ /**
+ * Returns the shaare corresponding to a smallHash.
+ *
+ * @param string $request QUERY_STRING server parameter.
+ *
+ * @return array $filtered array containing permalink data.
+ *
+ * @throws BookmarkNotFoundException if the smallhash is malformed or doesn't match any link.
+ */
+ public function filterHash($request)
+ {
+ $request = substr($request, 0, 6);
+ $linkFilter = new LegacyLinkFilter($this->links);
+ return $linkFilter->filter(LegacyLinkFilter::$FILTER_HASH, $request);
+ }
+
+ /**
+ * Returns the list of articles for a given day.
+ *
+ * @param string $request day to filter. Format: YYYYMMDD.
+ *
+ * @return array list of shaare found.
+ */
+ public function filterDay($request)
+ {
+ $linkFilter = new LegacyLinkFilter($this->links);
+ return $linkFilter->filter(LegacyLinkFilter::$FILTER_DAY, $request);
+ }
+
+ /**
+ * Filter bookmarks according to search parameters.
+ *
+ * @param array $filterRequest Search request content. Supported keys:
+ * - searchtags: list of tags
+ * - searchterm: term search
+ * @param bool $casesensitive Optional: Perform case sensitive filter
+ * @param string $visibility return only all/private/public bookmarks
+ * @param bool $untaggedonly return only untagged bookmarks
+ *
+ * @return array filtered bookmarks, all bookmarks if no suitable filter was provided.
+ */
+ public function filterSearch(
+ $filterRequest = [],
+ $casesensitive = false,
+ $visibility = 'all',
+ $untaggedonly = false
+ ) {
+
+ // Filter link database according to parameters.
+ $searchtags = isset($filterRequest['searchtags']) ? escape($filterRequest['searchtags']) : '';
+ $searchterm = isset($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : '';
+
+ // Search tags + fullsearch - blank string parameter will return all bookmarks.
+ $type = LegacyLinkFilter::$FILTER_TAG | LegacyLinkFilter::$FILTER_TEXT; // == "vuotext"
+ $request = [$searchtags, $searchterm];
+
+ $linkFilter = new LegacyLinkFilter($this);
+ return $linkFilter->filter($type, $request, $casesensitive, $visibility, $untaggedonly);
+ }
+
+ /**
+ * Returns the list tags appearing in the bookmarks with the given tags
+ *
+ * @param array $filteringTags tags selecting the bookmarks to consider
+ * @param string $visibility process only all/private/public bookmarks
+ *
+ * @return array tag => linksCount
+ */
+ public function linksCountPerTag($filteringTags = [], $visibility = 'all')
+ {
+ $links = $this->filterSearch(['searchtags' => $filteringTags], false, $visibility);
+ $tags = [];
+ $caseMapping = [];
+ foreach ($links as $link) {
+ foreach (preg_split('/\s+/', $link['tags'], 0, PREG_SPLIT_NO_EMPTY) as $tag) {
+ if (empty($tag)) {
+ continue;
+ }
+ // The first case found will be displayed.
+ if (!isset($caseMapping[strtolower($tag)])) {
+ $caseMapping[strtolower($tag)] = $tag;
+ $tags[$caseMapping[strtolower($tag)]] = 0;
+ }
+ $tags[$caseMapping[strtolower($tag)]]++;
+ }
+ }
+
+ /*
+ * Formerly used arsort(), which doesn't define the sort behaviour for equal values.
+ * Also, this function doesn't produce the same result between PHP 5.6 and 7.
+ *
+ * So we now use array_multisort() to sort tags by DESC occurrences,
+ * then ASC alphabetically for equal values.
+ *
+ * @see https://github.com/shaarli/Shaarli/issues/1142
+ */
+ $keys = array_keys($tags);
+ $tmpTags = array_combine($keys, $keys);
+ array_multisort($tags, SORT_DESC, $tmpTags, SORT_ASC, $tags);
+ return $tags;
+ }
+
+ /**
+ * Rename or delete a tag across all bookmarks.
+ *
+ * @param string $from Tag to rename
+ * @param string $to New tag. If none is provided, the from tag will be deleted
+ *
+ * @return array|bool List of altered bookmarks or false on error
+ */
+ public function renameTag($from, $to)
+ {
+ if (empty($from)) {
+ return false;
+ }
+ $delete = empty($to);
+ // True for case-sensitive tag search.
+ $linksToAlter = $this->filterSearch(['searchtags' => $from], true);
+ foreach ($linksToAlter as $key => &$value) {
+ $tags = preg_split('/\s+/', trim($value['tags']));
+ if (($pos = array_search($from, $tags)) !== false) {
+ if ($delete) {
+ unset($tags[$pos]); // Remove tag.
+ } else {
+ $tags[$pos] = trim($to);
+ }
+ $value['tags'] = trim(implode(' ', array_unique($tags)));
+ $this[$value['id']] = $value;
+ }
+ }
+
+ return $linksToAlter;
+ }
+
+ /**
+ * Returns the list of days containing articles (oldest first)
+ * Output: An array containing days (in format YYYYMMDD).
+ */
+ public function days()
+ {
+ $linkDays = [];
+ foreach ($this->links as $link) {
+ $linkDays[$link['created']->format('Ymd')] = 0;
+ }
+ $linkDays = array_keys($linkDays);
+ sort($linkDays);
+
+ return $linkDays;
+ }
+
+ /**
+ * Reorder bookmarks by creation date (newest first).
+ *
+ * Also update the urls and ids mapping arrays.
+ *
+ * @param string $order ASC|DESC
+ */
+ public function reorder($order = 'DESC')
+ {
+ $order = $order === 'ASC' ? -1 : 1;
+ // Reorder array by dates.
+ usort($this->links, function ($a, $b) use ($order) {
+ if (isset($a['sticky']) && isset($b['sticky']) && $a['sticky'] !== $b['sticky']) {
+ return $a['sticky'] ? -1 : 1;
+ }
+ if ($a['created'] == $b['created']) {
+ return $a['id'] < $b['id'] ? 1 * $order : -1 * $order;
+ }
+ return $a['created'] < $b['created'] ? 1 * $order : -1 * $order;
+ });
+
+ $this->urls = [];
+ $this->ids = [];
+ foreach ($this->links as $key => $link) {
+ $this->urls[$link['url']] = $key;
+ $this->ids[$link['id']] = $key;
+ }
+ }
+
+ /**
+ * Return the next key for link creation.
+ * E.g. If the last ID is 597, the next will be 598.
+ *
+ * @return int next ID.
+ */
+ public function getNextId()
+ {
+ if (!empty($this->ids)) {
+ return max(array_keys($this->ids)) + 1;
+ }
+ return 0;
+ }
+
+ /**
+ * Returns a link offset in bookmarks array from its unique ID.
+ *
+ * @param int $id Persistent ID of a link.
+ *
+ * @return int Real offset in local array, or null if doesn't exist.
+ */
+ protected function getLinkOffset($id)
+ {
+ if (isset($this->ids[$id])) {
+ return $this->ids[$id];
+ }
+ return null;
+ }
+}
diff --git a/application/legacy/LegacyLinkFilter.php b/application/legacy/LegacyLinkFilter.php
new file mode 100644
index 00000000..e6d186c4
--- /dev/null
+++ b/application/legacy/LegacyLinkFilter.php
@@ -0,0 +1,451 @@
+links = $links;
+ }
+
+ /**
+ * Filter links according to parameters.
+ *
+ * @param string $type Type of filter (eg. tags, permalink, etc.).
+ * @param mixed $request Filter content.
+ * @param bool $casesensitive Optional: Perform case sensitive filter if true.
+ * @param string $visibility Optional: return only all/private/public links
+ * @param string $untaggedonly Optional: return only untagged links. Applies only if $type includes FILTER_TAG
+ *
+ * @return array filtered link list.
+ */
+ public function filter($type, $request, $casesensitive = false, $visibility = 'all', $untaggedonly = false)
+ {
+ if (!in_array($visibility, ['all', 'public', 'private'])) {
+ $visibility = 'all';
+ }
+
+ switch ($type) {
+ case self::$FILTER_HASH:
+ return $this->filterSmallHash($request);
+ case self::$FILTER_TAG | self::$FILTER_TEXT: // == "vuotext"
+ $noRequest = empty($request) || (empty($request[0]) && empty($request[1]));
+ if ($noRequest) {
+ if ($untaggedonly) {
+ return $this->filterUntagged($visibility);
+ }
+ return $this->noFilter($visibility);
+ }
+ if ($untaggedonly) {
+ $filtered = $this->filterUntagged($visibility);
+ } else {
+ $filtered = $this->links;
+ }
+ if (!empty($request[0])) {
+ $filtered = (new LegacyLinkFilter($filtered))->filterTags($request[0], $casesensitive, $visibility);
+ }
+ if (!empty($request[1])) {
+ $filtered = (new LegacyLinkFilter($filtered))->filterFulltext($request[1], $visibility);
+ }
+ return $filtered;
+ case self::$FILTER_TEXT:
+ return $this->filterFulltext($request, $visibility);
+ case self::$FILTER_TAG:
+ if ($untaggedonly) {
+ return $this->filterUntagged($visibility);
+ } else {
+ return $this->filterTags($request, $casesensitive, $visibility);
+ }
+ case self::$FILTER_DAY:
+ return $this->filterDay($request);
+ default:
+ return $this->noFilter($visibility);
+ }
+ }
+
+ /**
+ * Unknown filter, but handle private only.
+ *
+ * @param string $visibility Optional: return only all/private/public links
+ *
+ * @return array filtered links.
+ */
+ private function noFilter($visibility = 'all')
+ {
+ if ($visibility === 'all') {
+ return $this->links;
+ }
+
+ $out = [];
+ foreach ($this->links as $key => $value) {
+ if ($value['private'] && $visibility === 'private') {
+ $out[$key] = $value;
+ } elseif (!$value['private'] && $visibility === 'public') {
+ $out[$key] = $value;
+ }
+ }
+
+ return $out;
+ }
+
+ /**
+ * Returns the shaare corresponding to a smallHash.
+ *
+ * @param string $smallHash permalink hash.
+ *
+ * @return array $filtered array containing permalink data.
+ *
+ * @throws BookmarkNotFoundException if the smallhash doesn't match any link.
+ */
+ private function filterSmallHash($smallHash)
+ {
+ $filtered = [];
+ foreach ($this->links as $key => $l) {
+ if ($smallHash == $l['shorturl']) {
+ // Yes, this is ugly and slow
+ $filtered[$key] = $l;
+ return $filtered;
+ }
+ }
+
+ if (empty($filtered)) {
+ throw new BookmarkNotFoundException();
+ }
+
+ return $filtered;
+ }
+
+ /**
+ * Returns the list of links corresponding to a full-text search
+ *
+ * Searches:
+ * - in the URLs, title and description;
+ * - are case-insensitive;
+ * - terms surrounded by quotes " are exact terms search.
+ * - terms starting with a dash - are excluded (except exact terms).
+ *
+ * Example:
+ * print_r($mydb->filterFulltext('hollandais'));
+ *
+ * mb_convert_case($val, MB_CASE_LOWER, 'UTF-8')
+ * - allows to perform searches on Unicode text
+ * - see https://github.com/shaarli/Shaarli/issues/75 for examples
+ *
+ * @param string $searchterms search query.
+ * @param string $visibility Optional: return only all/private/public links.
+ *
+ * @return array search results.
+ */
+ private function filterFulltext($searchterms, $visibility = 'all')
+ {
+ if (empty($searchterms)) {
+ return $this->noFilter($visibility);
+ }
+
+ $filtered = [];
+ $search = mb_convert_case(html_entity_decode($searchterms), MB_CASE_LOWER, 'UTF-8');
+ $exactRegex = '/"([^"]+)"/';
+ // Retrieve exact search terms.
+ preg_match_all($exactRegex, $search, $exactSearch);
+ $exactSearch = array_values(array_filter($exactSearch[1]));
+
+ // Remove exact search terms to get AND terms search.
+ $explodedSearchAnd = explode(' ', trim(preg_replace($exactRegex, '', $search)));
+ $explodedSearchAnd = array_values(array_filter($explodedSearchAnd));
+
+ // Filter excluding terms and update andSearch.
+ $excludeSearch = [];
+ $andSearch = [];
+ foreach ($explodedSearchAnd as $needle) {
+ if ($needle[0] == '-' && strlen($needle) > 1) {
+ $excludeSearch[] = substr($needle, 1);
+ } else {
+ $andSearch[] = $needle;
+ }
+ }
+
+ $keys = ['title', 'description', 'url', 'tags'];
+
+ // Iterate over every stored link.
+ foreach ($this->links as $id => $link) {
+ // ignore non private links when 'privatonly' is on.
+ if ($visibility !== 'all') {
+ if (!$link['private'] && $visibility === 'private') {
+ continue;
+ } elseif ($link['private'] && $visibility === 'public') {
+ continue;
+ }
+ }
+
+ // Concatenate link fields to search across fields.
+ // Adds a '\' separator for exact search terms.
+ $content = '';
+ foreach ($keys as $key) {
+ $content .= mb_convert_case($link[$key], MB_CASE_LOWER, 'UTF-8') . '\\';
+ }
+
+ // Be optimistic
+ $found = true;
+
+ // First, we look for exact term search
+ for ($i = 0; $i < count($exactSearch) && $found; $i++) {
+ $found = strpos($content, $exactSearch[$i]) !== false;
+ }
+
+ // Iterate over keywords, if keyword is not found,
+ // no need to check for the others. We want all or nothing.
+ for ($i = 0; $i < count($andSearch) && $found; $i++) {
+ $found = strpos($content, $andSearch[$i]) !== false;
+ }
+
+ // Exclude terms.
+ for ($i = 0; $i < count($excludeSearch) && $found; $i++) {
+ $found = strpos($content, $excludeSearch[$i]) === false;
+ }
+
+ if ($found) {
+ $filtered[$id] = $link;
+ }
+ }
+
+ return $filtered;
+ }
+
+ /**
+ * generate a regex fragment out of a tag
+ *
+ * @param string $tag to to generate regexs from. may start with '-' to negate, contain '*' as wildcard
+ *
+ * @return string generated regex fragment
+ */
+ private static function tag2regex($tag)
+ {
+ $len = strlen($tag);
+ if (!$len || $tag === "-" || $tag === "*") {
+ // nothing to search, return empty regex
+ return '';
+ }
+ if ($tag[0] === "-") {
+ // query is negated
+ $i = 1; // use offset to start after '-' character
+ $regex = '(?!'; // create negative lookahead
+ } else {
+ $i = 0; // start at first character
+ $regex = '(?='; // use positive lookahead
+ }
+ $regex .= '.*(?:^| )'; // before tag may only be a space or the beginning
+ // iterate over string, separating it into placeholder and content
+ for (; $i < $len; $i++) {
+ if ($tag[$i] === '*') {
+ // placeholder found
+ $regex .= '[^ ]*?';
+ } else {
+ // regular characters
+ $offset = strpos($tag, '*', $i);
+ if ($offset === false) {
+ // no placeholder found, set offset to end of string
+ $offset = $len;
+ }
+ // subtract one, as we want to get before the placeholder or end of string
+ $offset -= 1;
+ // we got a tag name that we want to search for. escape any regex characters to prevent conflicts.
+ $regex .= preg_quote(substr($tag, $i, $offset - $i + 1), '/');
+ // move $i on
+ $i = $offset;
+ }
+ }
+ $regex .= '(?:$| ))'; // after the tag may only be a space or the end
+ return $regex;
+ }
+
+ /**
+ * Returns the list of links associated with a given list of tags
+ *
+ * You can specify one or more tags, separated by space or a comma, e.g.
+ * print_r($mydb->filterTags('linux programming'));
+ *
+ * @param string $tags list of tags separated by commas or blank spaces.
+ * @param bool $casesensitive ignore case if false.
+ * @param string $visibility Optional: return only all/private/public links.
+ *
+ * @return array filtered links.
+ */
+ public function filterTags($tags, $casesensitive = false, $visibility = 'all')
+ {
+ // get single tags (we may get passed an array, even though the docs say different)
+ $inputTags = $tags;
+ if (!is_array($tags)) {
+ // we got an input string, split tags
+ $inputTags = preg_split('/(?:\s+)|,/', $inputTags, -1, PREG_SPLIT_NO_EMPTY);
+ }
+
+ if (!count($inputTags)) {
+ // no input tags
+ return $this->noFilter($visibility);
+ }
+
+ // build regex from all tags
+ $re = '/^' . implode(array_map("self::tag2regex", $inputTags)) . '.*$/';
+ if (!$casesensitive) {
+ // make regex case insensitive
+ $re .= 'i';
+ }
+
+ // create resulting array
+ $filtered = [];
+
+ // iterate over each link
+ foreach ($this->links as $key => $link) {
+ // check level of visibility
+ // ignore non private links when 'privateonly' is on.
+ if ($visibility !== 'all') {
+ if (!$link['private'] && $visibility === 'private') {
+ continue;
+ } elseif ($link['private'] && $visibility === 'public') {
+ continue;
+ }
+ }
+ $search = $link['tags']; // build search string, start with tags of current link
+ if (strlen(trim($link['description'])) && strpos($link['description'], '#') !== false) {
+ // description given and at least one possible tag found
+ $descTags = [];
+ // find all tags in the form of #tag in the description
+ preg_match_all(
+ '/(?links as $key => $link) {
+ if ($visibility !== 'all') {
+ if (!$link['private'] && $visibility === 'private') {
+ continue;
+ } elseif ($link['private'] && $visibility === 'public') {
+ continue;
+ }
+ }
+
+ if (empty(trim($link['tags']))) {
+ $filtered[$key] = $link;
+ }
+ }
+
+ return $filtered;
+ }
+
+ /**
+ * Returns the list of articles for a given day, chronologically sorted
+ *
+ * Day must be in the form 'YYYYMMDD' (e.g. '20120125'), e.g.
+ * print_r($mydb->filterDay('20120125'));
+ *
+ * @param string $day day to filter.
+ *
+ * @return array all link matching given day.
+ *
+ * @throws Exception if date format is invalid.
+ */
+ public function filterDay($day)
+ {
+ if (!checkDateFormat('Ymd', $day)) {
+ throw new Exception('Invalid date format');
+ }
+
+ $filtered = [];
+ foreach ($this->links as $key => $l) {
+ if ($l['created']->format('Ymd') == $day) {
+ $filtered[$key] = $l;
+ }
+ }
+
+ // sort by date ASC
+ return array_reverse($filtered, true);
+ }
+
+ /**
+ * Convert a list of tags (str) to an array. Also
+ * - handle case sensitivity.
+ * - accepts spaces commas as separator.
+ *
+ * @param string $tags string containing a list of tags.
+ * @param bool $casesensitive will convert everything to lowercase if false.
+ *
+ * @return array filtered tags string.
+ */
+ public static function tagsStrToArray($tags, $casesensitive)
+ {
+ // We use UTF-8 conversion to handle various graphemes (i.e. cyrillic, or greek)
+ $tagsOut = $casesensitive ? $tags : mb_convert_case($tags, MB_CASE_LOWER, 'UTF-8');
+ $tagsOut = str_replace(',', ' ', $tagsOut);
+
+ return preg_split('/\s+/', $tagsOut, -1, PREG_SPLIT_NO_EMPTY);
+ }
+}
diff --git a/application/legacy/LegacyRouter.php b/application/legacy/LegacyRouter.php
new file mode 100644
index 00000000..0449c7e1
--- /dev/null
+++ b/application/legacy/LegacyRouter.php
@@ -0,0 +1,63 @@
+doneUpdates = $doneUpdates;
+ $this->linkDB = $linkDB;
+ $this->conf = $conf;
+ $this->isLoggedIn = $isLoggedIn;
+ $this->session = &$session;
+
+ // Retrieve all update methods.
+ $class = new ReflectionClass($this);
+ $this->methods = $class->getMethods();
+ }
+
+ /**
+ * Run all new updates.
+ * Update methods have to start with 'updateMethod' and return true (on success).
+ *
+ * @return array An array containing ran updates.
+ *
+ * @throws UpdaterException If something went wrong.
+ */
+ public function update()
+ {
+ $updatesRan = [];
+
+ // If the user isn't logged in, exit without updating.
+ if ($this->isLoggedIn !== true) {
+ return $updatesRan;
+ }
+
+ if ($this->methods === null) {
+ throw new UpdaterException(t('Couldn\'t retrieve updater class methods.'));
+ }
+
+ foreach ($this->methods as $method) {
+ // Not an update method or already done, pass.
+ if (
+ !startsWith($method->getName(), 'updateMethod')
+ || in_array($method->getName(), $this->doneUpdates)
+ ) {
+ continue;
+ }
+
+ try {
+ $method->setAccessible(true);
+ $res = $method->invoke($this);
+ // Update method must return true to be considered processed.
+ if ($res === true) {
+ $updatesRan[] = $method->getName();
+ }
+ } catch (Exception $e) {
+ throw new UpdaterException($method, $e);
+ }
+ }
+
+ $this->doneUpdates = array_merge($this->doneUpdates, $updatesRan);
+
+ return $updatesRan;
+ }
+
+ /**
+ * @return array Updates methods already processed.
+ */
+ public function getDoneUpdates()
+ {
+ return $this->doneUpdates;
+ }
+
+ /**
+ * Move deprecated options.php to config.php.
+ *
+ * Milestone 0.9 (old versioning) - shaarli/Shaarli#41:
+ * options.php is not supported anymore.
+ */
+ public function updateMethodMergeDeprecatedConfigFile()
+ {
+ if (is_file($this->conf->get('resource.data_dir') . '/options.php')) {
+ include $this->conf->get('resource.data_dir') . '/options.php';
+
+ // Load GLOBALS into config
+ $allowedKeys = array_merge(ConfigPhp::$ROOT_KEYS);
+ $allowedKeys[] = 'config';
+ foreach ($GLOBALS as $key => $value) {
+ if (in_array($key, $allowedKeys)) {
+ $this->conf->set($key, $value);
+ }
+ }
+ $this->conf->write($this->isLoggedIn);
+ unlink($this->conf->get('resource.data_dir') . '/options.php');
+ }
+
+ return true;
+ }
+
+ /**
+ * Move old configuration in PHP to the new config system in JSON format.
+ *
+ * Will rename 'config.php' into 'config.save.php' and create 'config.json.php'.
+ * It will also convert legacy setting keys to the new ones.
+ */
+ public function updateMethodConfigToJson()
+ {
+ // JSON config already exists, nothing to do.
+ if ($this->conf->getConfigIO() instanceof ConfigJson) {
+ return true;
+ }
+
+ $configPhp = new ConfigPhp();
+ $configJson = new ConfigJson();
+ $oldConfig = $configPhp->read($this->conf->getConfigFile() . '.php');
+ rename($this->conf->getConfigFileExt(), $this->conf->getConfigFile() . '.save.php');
+ $this->conf->setConfigIO($configJson);
+ $this->conf->reload();
+
+ $legacyMap = array_flip(ConfigPhp::$LEGACY_KEYS_MAPPING);
+ foreach (ConfigPhp::$ROOT_KEYS as $key) {
+ $this->conf->set($legacyMap[$key], $oldConfig[$key]);
+ }
+
+ // Set sub config keys (config and plugins)
+ $subConfig = ['config', 'plugins'];
+ foreach ($subConfig as $sub) {
+ foreach ($oldConfig[$sub] as $key => $value) {
+ if (isset($legacyMap[$sub . '.' . $key])) {
+ $configKey = $legacyMap[$sub . '.' . $key];
+ } else {
+ $configKey = $sub . '.' . $key;
+ }
+ $this->conf->set($configKey, $value);
+ }
+ }
+
+ try {
+ $this->conf->write($this->isLoggedIn);
+ return true;
+ } catch (IOException $e) {
+ error_log($e->getMessage());
+ return false;
+ }
+ }
+
+ /**
+ * Escape settings which have been manually escaped in every request in previous versions:
+ * - general.title
+ * - general.header_link
+ * - redirector.url
+ *
+ * @return bool true if the update is successful, false otherwise.
+ */
+ public function updateMethodEscapeUnescapedConfig()
+ {
+ try {
+ $this->conf->set('general.title', escape($this->conf->get('general.title')));
+ $this->conf->set('general.header_link', escape($this->conf->get('general.header_link')));
+ $this->conf->write($this->isLoggedIn);
+ } catch (Exception $e) {
+ error_log($e->getMessage());
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Update the database to use the new ID system, which replaces linkdate primary keys.
+ * Also, creation and update dates are now DateTime objects (done by LinkDB).
+ *
+ * Since this update is very sensitve (changing the whole database), the datastore will be
+ * automatically backed up into the file datastore..php.
+ *
+ * LinkDB also adds the field 'shorturl' with the precedent format (linkdate smallhash),
+ * which will be saved by this method.
+ *
+ * @return bool true if the update is successful, false otherwise.
+ */
+ public function updateMethodDatastoreIds()
+ {
+ $first = 'update';
+ foreach ($this->linkDB as $key => $link) {
+ $first = $key;
+ break;
+ }
+
+ // up to date database
+ if (is_int($first)) {
+ return true;
+ }
+
+ $save = $this->conf->get('resource.data_dir') . '/datastore.' . date('YmdHis') . '.php';
+ copy($this->conf->get('resource.datastore'), $save);
+
+ $links = [];
+ foreach ($this->linkDB as $offset => $value) {
+ $links[] = $value;
+ unset($this->linkDB[$offset]);
+ }
+ $links = array_reverse($links);
+ $cpt = 0;
+ foreach ($links as $l) {
+ unset($l['linkdate']);
+ $l['id'] = $cpt;
+ $this->linkDB[$cpt++] = $l;
+ }
+
+ $this->linkDB->save($this->conf->get('resource.page_cache'));
+ $this->linkDB->reorder();
+
+ return true;
+ }
+
+ /**
+ * Rename tags starting with a '-' to work with tag exclusion search.
+ */
+ public function updateMethodRenameDashTags()
+ {
+ $linklist = $this->linkDB->filterSearch();
+ foreach ($linklist as $key => $link) {
+ $link['tags'] = preg_replace('/(^| )\-/', '$1', $link['tags']);
+ $link['tags'] = implode(' ', array_unique(BookmarkFilter::tagsStrToArray($link['tags'], true)));
+ $this->linkDB[$key] = $link;
+ }
+ $this->linkDB->save($this->conf->get('resource.page_cache'));
+ return true;
+ }
+
+ /**
+ * Initialize API settings:
+ * - api.enabled: true
+ * - api.secret: generated secret
+ */
+ public function updateMethodApiSettings()
+ {
+ if ($this->conf->exists('api.secret')) {
+ return true;
+ }
+
+ $this->conf->set('api.enabled', true);
+ $this->conf->set(
+ 'api.secret',
+ generate_api_secret(
+ $this->conf->get('credentials.login'),
+ $this->conf->get('credentials.salt')
+ )
+ );
+ $this->conf->write($this->isLoggedIn);
+ return true;
+ }
+
+ /**
+ * New setting: theme name. If the default theme is used, nothing to do.
+ *
+ * If the user uses a custom theme, raintpl_tpl dir is updated to the parent directory,
+ * and the current theme is set as default in the theme setting.
+ *
+ * @return bool true if the update is successful, false otherwise.
+ */
+ public function updateMethodDefaultTheme()
+ {
+ // raintpl_tpl isn't the root template directory anymore.
+ // We run the update only if this folder still contains the template files.
+ $tplDir = $this->conf->get('resource.raintpl_tpl');
+ $tplFile = $tplDir . '/linklist.html';
+ if (!file_exists($tplFile)) {
+ return true;
+ }
+
+ $parent = dirname($tplDir);
+ $this->conf->set('resource.raintpl_tpl', $parent);
+ $this->conf->set('resource.theme', trim(str_replace($parent, '', $tplDir), '/'));
+ $this->conf->write($this->isLoggedIn);
+
+ // Dependency injection gore
+ RainTPL::$tpl_dir = $tplDir;
+
+ return true;
+ }
+
+ /**
+ * Move the file to inc/user.css to data/user.css.
+ *
+ * Note: Due to hardcoded paths, it's not unit testable. But one line of code should be fine.
+ *
+ * @return bool true if the update is successful, false otherwise.
+ */
+ public function updateMethodMoveUserCss()
+ {
+ if (!is_file('inc/user.css')) {
+ return true;
+ }
+
+ return rename('inc/user.css', 'data/user.css');
+ }
+
+ /**
+ * * `markdown_escape` is a new setting, set to true as default.
+ *
+ * If the markdown plugin was already enabled, escaping is disabled to avoid
+ * breaking existing entries.
+ */
+ public function updateMethodEscapeMarkdown()
+ {
+ if ($this->conf->exists('security.markdown_escape')) {
+ return true;
+ }
+
+ if (in_array('markdown', $this->conf->get('general.enabled_plugins'))) {
+ $this->conf->set('security.markdown_escape', false);
+ } else {
+ $this->conf->set('security.markdown_escape', true);
+ }
+ $this->conf->write($this->isLoggedIn);
+
+ return true;
+ }
+
+ /**
+ * Add 'http://' to Piwik URL the setting is set.
+ *
+ * @return bool true if the update is successful, false otherwise.
+ */
+ public function updateMethodPiwikUrl()
+ {
+ if (!$this->conf->exists('plugins.PIWIK_URL') || startsWith($this->conf->get('plugins.PIWIK_URL'), 'http')) {
+ return true;
+ }
+
+ $this->conf->set('plugins.PIWIK_URL', 'http://' . $this->conf->get('plugins.PIWIK_URL'));
+ $this->conf->write($this->isLoggedIn);
+
+ return true;
+ }
+
+ /**
+ * Use ATOM feed as default.
+ */
+ public function updateMethodAtomDefault()
+ {
+ if (!$this->conf->exists('feed.show_atom') || $this->conf->get('feed.show_atom') === true) {
+ return true;
+ }
+
+ $this->conf->set('feed.show_atom', true);
+ $this->conf->write($this->isLoggedIn);
+
+ return true;
+ }
+
+ /**
+ * Update updates.check_updates_branch setting.
+ *
+ * If the current major version digit matches the latest branch
+ * major version digit, we set the branch to `latest`,
+ * otherwise we'll check updates on the `stable` branch.
+ *
+ * No update required for the dev version.
+ *
+ * Note: due to hardcoded URL and lack of dependency injection, this is not unit testable.
+ *
+ * FIXME! This needs to be removed when we switch to first digit major version
+ * instead of the second one since the versionning process will change.
+ */
+ public function updateMethodCheckUpdateRemoteBranch()
+ {
+ if (SHAARLI_VERSION === 'dev' || $this->conf->get('updates.check_updates_branch') === 'latest') {
+ return true;
+ }
+
+ // Get latest branch major version digit
+ $latestVersion = ApplicationUtils::getLatestGitVersionCode(
+ 'https://raw.githubusercontent.com/shaarli/Shaarli/latest/shaarli_version.php',
+ 5
+ );
+ if (preg_match('/(\d+)\.\d+$/', $latestVersion, $matches) === false) {
+ return false;
+ }
+ $latestMajor = $matches[1];
+
+ // Get current major version digit
+ preg_match('/(\d+)\.\d+$/', SHAARLI_VERSION, $matches);
+ $currentMajor = $matches[1];
+
+ if ($currentMajor === $latestMajor) {
+ $branch = 'latest';
+ } else {
+ $branch = 'stable';
+ }
+ $this->conf->set('updates.check_updates_branch', $branch);
+ $this->conf->write($this->isLoggedIn);
+ return true;
+ }
+
+ /**
+ * Reset history store file due to date format change.
+ */
+ public function updateMethodResetHistoryFile()
+ {
+ if (is_file($this->conf->get('resource.history'))) {
+ unlink($this->conf->get('resource.history'));
+ }
+ return true;
+ }
+
+ /**
+ * Save the datastore -> the link order is now applied when bookmarks are saved.
+ */
+ public function updateMethodReorderDatastore()
+ {
+ $this->linkDB->save($this->conf->get('resource.page_cache'));
+ return true;
+ }
+
+ /**
+ * Change privateonly session key to visibility.
+ */
+ public function updateMethodVisibilitySession()
+ {
+ if (isset($_SESSION['privateonly'])) {
+ unset($_SESSION['privateonly']);
+ $_SESSION['visibility'] = 'private';
+ }
+ return true;
+ }
+
+ /**
+ * Add download size and timeout to the configuration file
+ *
+ * @return bool true if the update is successful, false otherwise.
+ */
+ public function updateMethodDownloadSizeAndTimeoutConf()
+ {
+ if (
+ $this->conf->exists('general.download_max_size')
+ && $this->conf->exists('general.download_timeout')
+ ) {
+ return true;
+ }
+
+ if (!$this->conf->exists('general.download_max_size')) {
+ $this->conf->set('general.download_max_size', 1024 * 1024 * 4);
+ }
+
+ if (!$this->conf->exists('general.download_timeout')) {
+ $this->conf->set('general.download_timeout', 30);
+ }
+
+ $this->conf->write($this->isLoggedIn);
+ return true;
+ }
+
+ /**
+ * * Move thumbnails management to WebThumbnailer, coming with new settings.
+ */
+ public function updateMethodWebThumbnailer()
+ {
+ if ($this->conf->exists('thumbnails.mode')) {
+ return true;
+ }
+
+ $thumbnailsEnabled = extension_loaded('gd') && $this->conf->get('thumbnail.enable_thumbnails', true);
+ $this->conf->set('thumbnails.mode', $thumbnailsEnabled ? Thumbnailer::MODE_ALL : Thumbnailer::MODE_NONE);
+ $this->conf->set('thumbnails.width', 125);
+ $this->conf->set('thumbnails.height', 90);
+ $this->conf->remove('thumbnail');
+ $this->conf->write(true);
+
+ if ($thumbnailsEnabled) {
+ $this->session['warnings'][] = t(
+ t('You have enabled or changed thumbnails mode.') .
+ '' . t('Please synchronize them.') . ''
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Set sticky = false on all bookmarks
+ *
+ * @return bool true if the update is successful, false otherwise.
+ */
+ public function updateMethodSetSticky()
+ {
+ foreach ($this->linkDB as $key => $link) {
+ if (isset($link['sticky'])) {
+ return true;
+ }
+ $link['sticky'] = false;
+ $this->linkDB[$key] = $link;
+ }
+
+ $this->linkDB->save($this->conf->get('resource.page_cache'));
+
+ return true;
+ }
+
+ /**
+ * Remove redirector settings.
+ */
+ public function updateMethodRemoveRedirector()
+ {
+ $this->conf->remove('redirector');
+ $this->conf->write(true);
+ return true;
+ }
+
+ /**
+ * Migrate the legacy arrays to Bookmark objects.
+ * Also make a backup of the datastore.
+ */
+ public function updateMethodMigrateDatabase()
+ {
+ $save = $this->conf->get('resource.data_dir') . '/datastore.' . date('YmdHis') . '_1.php';
+ if (! copy($this->conf->get('resource.datastore'), $save)) {
+ die('Could not backup the datastore.');
+ }
+
+ $linksArray = new BookmarkArray();
+ foreach ($this->linkDB as $key => $link) {
+ $linksArray[$key] = (new Bookmark())->fromArray($link, $this->conf->get('general.tags_separator', ' '));
+ }
+ $linksIo = new BookmarkIO($this->conf);
+ $linksIo->write($linksArray);
+
+ return true;
+ }
+
+ /**
+ * Write the `formatter` setting in config file.
+ * Use markdown if the markdown plugin is enabled, the default one otherwise.
+ * Also remove markdown plugin setting as it is now integrated to the core.
+ */
+ public function updateMethodFormatterSetting()
+ {
+ if (!$this->conf->exists('formatter') || $this->conf->get('formatter') === 'default') {
+ $enabledPlugins = $this->conf->get('general.enabled_plugins');
+ if (($pos = array_search('markdown', $enabledPlugins)) !== false) {
+ $formatter = 'markdown';
+ unset($enabledPlugins[$pos]);
+ $this->conf->set('general.enabled_plugins', array_values($enabledPlugins));
+ } else {
+ $formatter = 'default';
+ }
+ $this->conf->set('formatter', $formatter);
+ $this->conf->write(true);
+ }
+
+ return true;
+ }
+}
diff --git a/application/legacy/UnknowLegacyRouteException.php b/application/legacy/UnknowLegacyRouteException.php
new file mode 100644
index 00000000..ae1518ad
--- /dev/null
+++ b/application/legacy/UnknowLegacyRouteException.php
@@ -0,0 +1,9 @@
+bookmarkService = $bookmarkService;
+ $this->conf = $conf;
+ $this->history = $history;
+ }
/**
- * Filters links and adds Netscape-formatted fields
+ * Filters bookmarks and adds Netscape-formatted fields
*
* Added fields:
* - timestamp link addition date, using the Unix epoch format
* - taglist comma-separated tag list
*
- * @param LinkDB $linkDb Link datastore
- * @param string $selection Which links to export: (all|private|public)
- * @param bool $prependNoteUrl Prepend note permalinks with the server's URL
- * @param string $indexUrl Absolute URL of the Shaarli index page
+ * @param BookmarkFormatter $formatter instance
+ * @param string $selection Which bookmarks to export: (all|private|public)
+ * @param bool $prependNoteUrl Prepend note permalinks with the server's URL
+ * @param string $indexUrl Absolute URL of the Shaarli index page
+ *
+ * @return array The bookmarks to be exported, with additional fields
*
* @throws Exception Invalid export selection
- *
- * @return array The links to be exported, with additional fields
*/
- public static function filterAndFormat($linkDb, $selection, $prependNoteUrl, $indexUrl)
- {
+ public function filterAndFormat(
+ $formatter,
+ $selection,
+ $prependNoteUrl,
+ $indexUrl
+ ) {
// see tpl/export.html for possible values
- if (!in_array($selection, array('all', 'public', 'private'))) {
+ if (!in_array($selection, ['all', 'public', 'private'])) {
throw new Exception(t('Invalid export selection:') . ' "' . $selection . '"');
}
- $bookmarkLinks = array();
- foreach ($linkDb as $link) {
- if ($link['private'] != 0 && $selection == 'public') {
- continue;
- }
- if ($link['private'] == 0 && $selection == 'private') {
- continue;
- }
- $date = $link['created'];
- $link['timestamp'] = $date->getTimestamp();
- $link['taglist'] = str_replace(' ', ',', $link['tags']);
-
- if (is_note($link['url']) && $prependNoteUrl) {
- $link['url'] = $indexUrl . $link['url'];
+ $bookmarkLinks = [];
+ foreach ($this->bookmarkService->search([], $selection)->getBookmarks() as $bookmark) {
+ $link = $formatter->format($bookmark);
+ $link['taglist'] = implode(',', $bookmark->getTags());
+ if ($bookmark->isNote() && $prependNoteUrl) {
+ $link['url'] = rtrim($indexUrl, '/') . '/' . ltrim($link['url'], '/');
}
$bookmarkLinks[] = $link;
@@ -64,19 +77,132 @@ class NetscapeBookmarkUtils
return $bookmarkLinks;
}
+ /**
+ * Imports Web bookmarks from an uploaded Netscape bookmark dump
+ *
+ * @param array $post Server $_POST parameters
+ * @param UploadedFileInterface $file File in PSR-7 object format
+ *
+ * @return string Summary of the bookmark import status
+ */
+ public function import($post, UploadedFileInterface $file)
+ {
+ $start = time();
+ $filename = $file->getClientFilename();
+ $filesize = $file->getSize();
+ $data = (string) $file->getStream();
+
+ if (preg_match('//i', $data) === 0) {
+ return $this->importStatus($filename, $filesize);
+ }
+
+ // Overwrite existing bookmarks?
+ $overwrite = !empty($post['overwrite']);
+
+ // Add tags to all imported bookmarks?
+ if (empty($post['default_tags'])) {
+ $defaultTags = [];
+ } else {
+ $defaultTags = tags_str2array(
+ escape($post['default_tags']),
+ $this->conf->get('general.tags_separator', ' ')
+ );
+ }
+
+ // Optionally Force all imported link to be either public or private.
+ $forcedPrivateStatus = !empty($post['privacy']) ? (string) $post['privacy'] : null;
+
+ $logger = new Logger(
+ $this->conf->get('resource.data_dir'),
+ !$this->conf->get('dev.debug') ? LogLevel::INFO : LogLevel::DEBUG,
+ [
+ 'prefix' => 'import.',
+ 'extension' => 'log',
+ ]
+ );
+ $parser = new NetscapeBookmarkParser([], $logger);
+
+ $bookmarks = $parser->parseString($data);
+
+ $importCount = 0;
+ $overwriteCount = 0;
+ $skipCount = 0;
+
+ foreach ($bookmarks as $bkm) {
+ if ($forcedPrivateStatus == 'private') {
+ // all imported bookmarks are private
+ $isPrivate = true;
+ } elseif ($forcedPrivateStatus == 'public') {
+ // all imported bookmarks are public
+ $isPrivate = false;
+ } else {
+ // Use private value from imported file or default to public
+ $isPrivate = isset($bkm['public']) && !$bkm['public'];
+ }
+
+ $link = $this->bookmarkService->findByUrl($bkm['url']);
+ $existingLink = $link !== null;
+ if (! $existingLink) {
+ $link = new Bookmark();
+ }
+
+ if ($existingLink !== false) {
+ if ($overwrite === false) {
+ // Do not overwrite an existing link
+ $skipCount++;
+ continue;
+ }
+
+ $link->setUpdated(new DateTime());
+ $overwriteCount++;
+ } else {
+ $newLinkDate = new DateTime('@' . $bkm['dateCreated']);
+ $newLinkDate->setTimezone(new DateTimeZone(date_default_timezone_get()));
+ $link->setCreated($newLinkDate);
+ }
+
+ if (!empty($defaultTags)) {
+ $bkm['tags'] = array_merge($defaultTags, $bkm['tags']);
+ }
+
+ $link->setTitle($bkm['name']);
+ $link->setUrl($bkm['url'], $this->conf->get('security.allowed_protocols'));
+ $link->setDescription($bkm['description']);
+ $link->setPrivate($isPrivate);
+ $link->setTags($bkm['tags']);
+
+ $this->bookmarkService->addOrSet($link, false);
+ $importCount++;
+ }
+
+ $this->bookmarkService->save();
+ $this->history->importLinks();
+
+ $duration = time() - $start;
+
+ return $this->importStatus(
+ $filename,
+ $filesize,
+ $importCount,
+ $overwriteCount,
+ $skipCount,
+ $duration
+ );
+ }
+
/**
* Generates an import status summary
*
* @param string $filename name of the file to import
* @param int $filesize size of the file to import
- * @param int $importCount how many links were imported
- * @param int $overwriteCount how many links were overwritten
- * @param int $skipCount how many links were skipped
+ * @param int $importCount how many bookmarks were imported
+ * @param int $overwriteCount how many bookmarks were overwritten
+ * @param int $skipCount how many bookmarks were skipped
* @param int $duration how many seconds did the import take
*
* @return string Summary of the bookmark import status
*/
- private static function importStatus(
+ protected function importStatus(
$filename,
$filesize,
$importCount = 0,
@@ -91,135 +217,11 @@ class NetscapeBookmarkUtils
$status .= vsprintf(
t(
'was successfully processed in %d seconds: '
- . '%d links imported, %d links overwritten, %d links skipped.'
+ . '%d bookmarks imported, %d bookmarks overwritten, %d bookmarks skipped.'
),
[$duration, $importCount, $overwriteCount, $skipCount]
);
}
return $status;
}
-
- /**
- * Imports Web bookmarks from an uploaded Netscape bookmark dump
- *
- * @param array $post Server $_POST parameters
- * @param array $files Server $_FILES parameters
- * @param LinkDB $linkDb Loaded LinkDB instance
- * @param ConfigManager $conf instance
- * @param History $history History instance
- *
- * @return string Summary of the bookmark import status
- */
- public static function import($post, $files, $linkDb, $conf, $history)
- {
- $start = time();
- $filename = $files['filetoupload']['name'];
- $filesize = $files['filetoupload']['size'];
- $data = file_get_contents($files['filetoupload']['tmp_name']);
-
- if (preg_match('//i', $data) === 0) {
- return self::importStatus($filename, $filesize);
- }
-
- // Overwrite existing links?
- $overwrite = !empty($post['overwrite']);
-
- // Add tags to all imported links?
- if (empty($post['default_tags'])) {
- $defaultTags = array();
- } else {
- $defaultTags = preg_split(
- '/[\s,]+/',
- escape($post['default_tags'])
- );
- }
-
- // links are imported as public by default
- $defaultPrivacy = 0;
-
- $parser = new NetscapeBookmarkParser(
- true, // nested tag support
- $defaultTags, // additional user-specified tags
- strval(1 - $defaultPrivacy), // defaultPub = 1 - defaultPrivacy
- $conf->get('resource.data_dir') // log path, will be overridden
- );
- $logger = new Logger(
- $conf->get('resource.data_dir'),
- !$conf->get('dev.debug') ? LogLevel::INFO : LogLevel::DEBUG,
- [
- 'prefix' => 'import.',
- 'extension' => 'log',
- ]
- );
- $parser->setLogger($logger);
- $bookmarks = $parser->parseString($data);
-
- $importCount = 0;
- $overwriteCount = 0;
- $skipCount = 0;
-
- foreach ($bookmarks as $bkm) {
- $private = $defaultPrivacy;
- if (empty($post['privacy']) || $post['privacy'] == 'default') {
- // use value from the imported file
- $private = $bkm['pub'] == '1' ? 0 : 1;
- } elseif ($post['privacy'] == 'private') {
- // all imported links are private
- $private = 1;
- } elseif ($post['privacy'] == 'public') {
- // all imported links are public
- $private = 0;
- }
-
- $newLink = array(
- 'title' => $bkm['title'],
- 'url' => $bkm['uri'],
- 'description' => $bkm['note'],
- 'private' => $private,
- 'tags' => $bkm['tags']
- );
-
- $existingLink = $linkDb->getLinkFromUrl($bkm['uri']);
-
- if ($existingLink !== false) {
- if ($overwrite === false) {
- // Do not overwrite an existing link
- $skipCount++;
- continue;
- }
-
- // Overwrite an existing link, keep its date
- $newLink['id'] = $existingLink['id'];
- $newLink['created'] = $existingLink['created'];
- $newLink['updated'] = new DateTime();
- $newLink['shorturl'] = $existingLink['shorturl'];
- $linkDb[$existingLink['id']] = $newLink;
- $importCount++;
- $overwriteCount++;
- continue;
- }
-
- // Add a new link - @ used for UNIX timestamps
- $newLinkDate = new DateTime('@' . strval($bkm['time']));
- $newLinkDate->setTimezone(new DateTimeZone(date_default_timezone_get()));
- $newLink['created'] = $newLinkDate;
- $newLink['id'] = $linkDb->getNextId();
- $newLink['shorturl'] = link_small_hash($newLink['created'], $newLink['id']);
- $linkDb[$newLink['id']] = $newLink;
- $importCount++;
- }
-
- $linkDb->save($conf->get('resource.page_cache'));
- $history->importLinks();
-
- $duration = time() - $start;
- return self::importStatus(
- $filename,
- $filesize,
- $importCount,
- $overwriteCount,
- $skipCount,
- $duration
- );
- }
}
diff --git a/application/plugin/PluginManager.php b/application/plugin/PluginManager.php
index f7b24a8e..b2cede28 100644
--- a/application/plugin/PluginManager.php
+++ b/application/plugin/PluginManager.php
@@ -1,8 +1,11 @@
/`.
+ * - `callable` string, function name or FQN class's method, e.g. `demo_plugin_custom_controller`.
+ */
+ protected $registeredRoutes = [];
/**
* @var ConfigManager Configuration Manager instance.
@@ -35,6 +46,9 @@ class PluginManager
*/
protected $errors;
+ /** @var callable[]|null Preloaded list of hook function for filterSearchEntry() */
+ protected $filterSearchEntryHooks = null;
+
/**
* Plugins subdirectory.
*
@@ -57,7 +71,7 @@ class PluginManager
public function __construct(&$conf)
{
$this->conf = $conf;
- $this->errors = array();
+ $this->errors = [];
}
/**
@@ -85,6 +99,9 @@ class PluginManager
$this->loadPlugin($dirs[$index], $plugin);
} catch (PluginFileNotFoundException $e) {
error_log($e->getMessage());
+ } catch (\Throwable $e) {
+ $error = $plugin . t(' [plugin incompatibility]: ') . $e->getMessage();
+ $this->errors = array_unique(array_merge($this->errors, [$error]));
}
}
}
@@ -98,23 +115,38 @@ class PluginManager
*
* @return void
*/
- public function executeHooks($hook, &$data, $params = array())
+ public function executeHooks($hook, &$data, $params = [])
{
- if (!empty($params['target'])) {
- $data['_PAGE_'] = $params['target'];
- }
+ $metadataParameters = [
+ 'target' => '_PAGE_',
+ 'loggedin' => '_LOGGEDIN_',
+ 'basePath' => '_BASE_PATH_',
+ 'rootPath' => '_ROOT_PATH_',
+ 'bookmarkService' => '_BOOKMARK_SERVICE_',
+ ];
- if (isset($params['loggedin'])) {
- $data['_LOGGEDIN_'] = $params['loggedin'];
+ foreach ($metadataParameters as $parameter => $metaKey) {
+ if (array_key_exists($parameter, $params)) {
+ $data[$metaKey] = $params[$parameter];
+ }
}
foreach ($this->loadedPlugins as $plugin) {
$hookFunction = $this->buildHookName($hook, $plugin);
if (function_exists($hookFunction)) {
- $data = call_user_func($hookFunction, $data, $this->conf);
+ try {
+ $data = call_user_func($hookFunction, $data, $this->conf);
+ } catch (\Throwable $e) {
+ $error = $plugin . t(' [plugin incompatibility]: ') . $e->getMessage();
+ $this->errors = array_unique(array_merge($this->errors, [$error]));
+ }
}
}
+
+ foreach ($metadataParameters as $metaKey) {
+ unset($data[$metaKey]);
+ }
}
/**
@@ -150,6 +182,22 @@ class PluginManager
}
}
+ $registerRouteFunction = $pluginName . '_register_routes';
+ $routes = null;
+ if (function_exists($registerRouteFunction)) {
+ $routes = call_user_func($registerRouteFunction);
+ }
+
+ if ($routes !== null) {
+ foreach ($routes as $route) {
+ if (static::validateRouteRegistration($route)) {
+ $this->registeredRoutes[$pluginName][] = $route;
+ } else {
+ throw new PluginInvalidRouteException($pluginName);
+ }
+ }
+ }
+
$this->loadedPlugins[] = $pluginName;
}
@@ -181,7 +229,7 @@ class PluginManager
*/
public function getPluginsMeta()
{
- $metaData = array();
+ $metaData = [];
$dirs = glob(self::$PLUGINS_PATH . '/*', GLOB_ONLYDIR | GLOB_MARK);
// Browse all plugin directories.
@@ -202,9 +250,9 @@ class PluginManager
if (isset($metaData[$plugin]['parameters'])) {
$params = explode(';', $metaData[$plugin]['parameters']);
} else {
- $params = array();
+ $params = [];
}
- $metaData[$plugin]['parameters'] = array();
+ $metaData[$plugin]['parameters'] = [];
foreach ($params as $param) {
if (empty($param)) {
continue;
@@ -221,6 +269,22 @@ class PluginManager
return $metaData;
}
+ /**
+ * @return array List of registered custom routes by plugins.
+ */
+ public function getRegisteredRoutes(): array
+ {
+ return $this->registeredRoutes;
+ }
+
+ /**
+ * @return array List of registered filter_search_entry hooks
+ */
+ public function getFilterSearchEntryHooks(): ?array
+ {
+ return $this->filterSearchEntryHooks;
+ }
+
/**
* Return the list of encountered errors.
*
@@ -230,4 +294,74 @@ class PluginManager
{
return $this->errors;
}
+
+ /**
+ * Apply additional filter on every search result of BookmarkFilter calling plugins hooks.
+ *
+ * @param Bookmark $bookmark To check.
+ * @param array $context Additional info about search context, depends on the search source.
+ *
+ * @return bool True if the result must be kept in search results, false otherwise.
+ */
+ public function filterSearchEntry(Bookmark $bookmark, array $context): bool
+ {
+ if ($this->filterSearchEntryHooks === null) {
+ $this->loadFilterSearchEntryHooks();
+ }
+
+ if ($this->filterSearchEntryHooks === []) {
+ return true;
+ }
+
+ foreach ($this->filterSearchEntryHooks as $filterSearchEntryHook) {
+ if ($filterSearchEntryHook($bookmark, $context) === false) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * filterSearchEntry() method will be called for every search result,
+ * so for performances we preload existing functions to invoke them directly.
+ */
+ protected function loadFilterSearchEntryHooks(): void
+ {
+ $this->filterSearchEntryHooks = [];
+
+ foreach ($this->loadedPlugins as $plugin) {
+ $hookFunction = $this->buildHookName('filter_search_entry', $plugin);
+
+ if (function_exists($hookFunction)) {
+ $this->filterSearchEntryHooks[] = $hookFunction;
+ }
+ }
+ }
+
+ /**
+ * Checks whether provided input is valid to register a new route.
+ * It must contain keys `method`, `route`, `callable` (all strings).
+ *
+ * We do not check the format because Slim routes support regexes.
+ *
+ * @param string[] $input
+ *
+ * @return bool
+ */
+ protected static function validateRouteRegistration(array $input): bool
+ {
+ if (
+ !array_key_exists('method', $input)
+ || !in_array(strtoupper($input['method']), ['GET', 'PUT', 'PATCH', 'POST', 'DELETE'])
+ ) {
+ return false;
+ }
+
+ if (!array_key_exists('callable', $input)) {
+ return false;
+ }
+
+ return true;
+ }
}
diff --git a/application/plugin/exception/PluginFileNotFoundException.php b/application/plugin/exception/PluginFileNotFoundException.php
index e5386f02..21ac6604 100644
--- a/application/plugin/exception/PluginFileNotFoundException.php
+++ b/application/plugin/exception/PluginFileNotFoundException.php
@@ -1,4 +1,5 @@
message = 'trying to register invalid route.';
+ }
+}
diff --git a/application/render/PageBuilder.php b/application/render/PageBuilder.php
index 3f86fc26..bf0ae326 100644
--- a/application/render/PageBuilder.php
+++ b/application/render/PageBuilder.php
@@ -3,10 +3,12 @@
namespace Shaarli\Render;
use Exception;
+use Psr\Log\LoggerInterface;
use RainTPL;
-use Shaarli\ApplicationUtils;
-use Shaarli\Bookmark\LinkDB;
+use Shaarli\Bookmark\BookmarkServiceInterface;
use Shaarli\Config\ConfigManager;
+use Shaarli\Helper\ApplicationUtils;
+use Shaarli\Security\SessionManager;
use Shaarli\Thumbnailer;
/**
@@ -33,10 +35,13 @@ class PageBuilder
*/
protected $session;
+ /** @var LoggerInterface */
+ protected $logger;
+
/**
- * @var LinkDB $linkDB instance.
+ * @var BookmarkServiceInterface $bookmarkService instance.
*/
- protected $linkDB;
+ protected $bookmarkService;
/**
* @var null|string XSRF token
@@ -52,22 +57,39 @@ class PageBuilder
* PageBuilder constructor.
* $tpl is initialized at false for lazy loading.
*
- * @param ConfigManager $conf Configuration Manager instance (reference).
- * @param array $session $_SESSION array
- * @param LinkDB $linkDB instance.
- * @param string $token Session token
- * @param bool $isLoggedIn
+ * @param ConfigManager $conf Configuration Manager instance (reference).
+ * @param array $session $_SESSION array
+ * @param LoggerInterface $logger
+ * @param null $linkDB instance.
+ * @param null $token Session token
+ * @param bool $isLoggedIn
*/
- public function __construct(&$conf, $session, $linkDB = null, $token = null, $isLoggedIn = false)
- {
+ public function __construct(
+ ConfigManager &$conf,
+ array $session,
+ LoggerInterface $logger,
+ $linkDB = null,
+ $token = null,
+ $isLoggedIn = false
+ ) {
$this->tpl = false;
$this->conf = $conf;
$this->session = $session;
- $this->linkDB = $linkDB;
+ $this->logger = $logger;
+ $this->bookmarkService = $linkDB;
$this->token = $token;
$this->isLoggedIn = $isLoggedIn;
}
+ /**
+ * Reset current state of template rendering.
+ * Mostly useful for error handling. We remove everything, and display the error template.
+ */
+ public function reset(): void
+ {
+ $this->tpl = false;
+ }
+
/**
* Initialize all default tpl tags.
*/
@@ -87,7 +109,7 @@ class PageBuilder
$this->tpl->assign('newVersion', escape($version));
$this->tpl->assign('versionError', '');
} catch (Exception $exc) {
- logm($this->conf->get('resource.log'), $_SERVER['REMOTE_ADDR'], $exc->getMessage());
+ $this->logger->error(format_log('Error: ' . $exc->getMessage(), client_ip_id($_SERVER)));
$this->tpl->assign('newVersion', '');
$this->tpl->assign('versionError', escape($exc->getMessage()));
}
@@ -125,8 +147,8 @@ class PageBuilder
$this->tpl->assign('language', $this->conf->get('translation.language'));
- if ($this->linkDB !== null) {
- $this->tpl->assign('tags', $this->linkDB->linksCountPerTag());
+ if ($this->bookmarkService !== null) {
+ $this->tpl->assign('tags', escape($this->bookmarkService->bookmarksCountPerTag()));
}
$this->tpl->assign(
@@ -136,15 +158,45 @@ class PageBuilder
$this->tpl->assign('thumbnails_width', $this->conf->get('thumbnails.width'));
$this->tpl->assign('thumbnails_height', $this->conf->get('thumbnails.height'));
- if (!empty($_SESSION['warnings'])) {
- $this->tpl->assign('global_warnings', $_SESSION['warnings']);
- unset($_SESSION['warnings']);
- }
+ $this->tpl->assign('formatter', $this->conf->get('formatter', 'default'));
+
+ $this->tpl->assign('links_per_page', $this->session['LINKS_PER_PAGE'] ?? 20);
+ $this->tpl->assign('tags_separator', $this->conf->get('general.tags_separator', ' '));
// To be removed with a proper theme configuration.
$this->tpl->assign('conf', $this->conf);
}
+ /**
+ * Affect variable after controller processing.
+ * Used for alert messages.
+ */
+ protected function finalize(string $basePath): void
+ {
+ // TODO: use the SessionManager
+ $messageKeys = [
+ SessionManager::KEY_SUCCESS_MESSAGES,
+ SessionManager::KEY_WARNING_MESSAGES,
+ SessionManager::KEY_ERROR_MESSAGES
+ ];
+ foreach ($messageKeys as $messageKey) {
+ if (!empty($_SESSION[$messageKey])) {
+ $this->tpl->assign('global_' . $messageKey, $_SESSION[$messageKey]);
+ unset($_SESSION[$messageKey]);
+ }
+ }
+
+ $rootPath = preg_replace('#/index\.php$#', '', $basePath);
+ $this->assign('base_path', $basePath);
+ $this->assign('root_path', $rootPath);
+ $this->assign(
+ 'asset_path',
+ $rootPath . '/' .
+ rtrim($this->conf->get('resource.raintpl_tpl', 'tpl'), '/') . '/' .
+ $this->conf->get('resource.theme', 'default')
+ );
+ }
+
/**
* The following assign() method is basically the same as RainTPL (except lazy loading)
*
@@ -183,33 +235,21 @@ class PageBuilder
}
/**
- * Render a specific page (using a template file).
- * e.g. $pb->renderPage('picwall');
+ * Render a specific page as string (using a template file).
+ * e.g. $pb->render('picwall');
*
* @param string $page Template filename (without extension).
+ *
+ * @return string Processed template content
*/
- public function renderPage($page)
+ public function render(string $page, string $basePath): string
{
if ($this->tpl === false) {
$this->initialize();
}
- $this->tpl->draw($page);
- }
+ $this->finalize($basePath);
- /**
- * Render a 404 page (uses the template : tpl/404.tpl)
- * usage: $PAGE->render404('The link was deleted')
- *
- * @param string $message A message to display what is not found
- */
- public function render404($message = '')
- {
- if (empty($message)) {
- $message = t('The page you are trying to reach does not exist or has been deleted.');
- }
- header($_SERVER['SERVER_PROTOCOL'] . ' ' . t('404 Not Found'));
- $this->tpl->assign('error_message', $message);
- $this->renderPage('404');
+ return $this->tpl->draw($page, true);
}
}
diff --git a/application/render/PageCacheManager.php b/application/render/PageCacheManager.php
new file mode 100644
index 00000000..fe74bf27
--- /dev/null
+++ b/application/render/PageCacheManager.php
@@ -0,0 +1,70 @@
+pageCacheDir = $pageCacheDir;
+ $this->isLoggedIn = $isLoggedIn;
+ }
+
+ /**
+ * Purges all cached pages
+ *
+ * @return string|null an error string if the directory is missing
+ */
+ public function purgeCachedPages(): ?string
+ {
+ if (!is_dir($this->pageCacheDir)) {
+ $error = sprintf(t('Cannot purge %s: no directory'), $this->pageCacheDir);
+ error_log($error);
+
+ return $error;
+ }
+
+ array_map('unlink', glob($this->pageCacheDir . '/*.cache'));
+
+ return null;
+ }
+
+ /**
+ * Invalidates caches when the database is changed or the user logs out.
+ */
+ public function invalidateCaches(): void
+ {
+ // Purge page cache shared by sessions.
+ $this->purgeCachedPages();
+ }
+
+ /**
+ * Get CachedPage instance for provided URL.
+ *
+ * @param string $pageUrl
+ * @param ?DatePeriod $validityPeriod Optionally specify a time limit on requested cache
+ *
+ * @return CachedPage
+ */
+ public function getCachePage(string $pageUrl, DatePeriod $validityPeriod = null): CachedPage
+ {
+ return new CachedPage(
+ $this->pageCacheDir,
+ $pageUrl,
+ false === $this->isLoggedIn,
+ $validityPeriod
+ );
+ }
+}
diff --git a/application/render/TemplatePage.php b/application/render/TemplatePage.php
new file mode 100644
index 00000000..03b424f3
--- /dev/null
+++ b/application/render/TemplatePage.php
@@ -0,0 +1,34 @@
+trustedProxies = $trustedProxies;
$this->nbAttempts = $nbAttempts;
$this->banDuration = $banDuration;
$this->banFile = $banFile;
- $this->logFile = $logFile;
+ $this->logger = $logger;
+
$this->readBanFile();
}
@@ -78,11 +80,7 @@ class BanManager
if ($this->failures[$ip] >= $this->nbAttempts) {
$this->bans[$ip] = time() + $this->banDuration;
- logm(
- $this->logFile,
- $server['REMOTE_ADDR'],
- 'IP address banned from login: '. $ip
- );
+ $this->logger->info(format_log('IP address banned from login: ' . $ip, $ip));
}
$this->writeBanFile();
}
@@ -138,7 +136,7 @@ class BanManager
unset($this->failures[$ip]);
}
unset($this->bans[$ip]);
- logm($this->logFile, $server['REMOTE_ADDR'], 'Ban lifted for: '. $ip);
+ $this->logger->info(format_log('Ban lifted for: ' . $ip, $ip));
$this->writeBanFile();
return false;
diff --git a/application/security/CookieManager.php b/application/security/CookieManager.php
new file mode 100644
index 00000000..cde4746e
--- /dev/null
+++ b/application/security/CookieManager.php
@@ -0,0 +1,33 @@
+cookies = $cookies;
+ }
+
+ public function setCookieParameter(string $key, string $value, int $expires, string $path): self
+ {
+ $this->cookies[$key] = $value;
+
+ setcookie($key, $value, $expires, $path);
+
+ return $this;
+ }
+
+ public function getCookieParameter(string $key, string $default = null): ?string
+ {
+ return $this->cookies[$key] ?? $default;
+ }
+}
diff --git a/application/security/LoginManager.php b/application/security/LoginManager.php
index 0b0ce0b1..b795b80e 100644
--- a/application/security/LoginManager.php
+++ b/application/security/LoginManager.php
@@ -1,6 +1,9 @@
configManager = $configManager;
$this->sessionManager = $sessionManager;
- $this->banManager = new BanManager(
- $this->configManager->get('security.trusted_proxies', []),
- $this->configManager->get('security.ban_after'),
- $this->configManager->get('security.ban_duration'),
- $this->configManager->get('resource.ban_file', 'data/ipbans.php'),
- $this->configManager->get('resource.log')
- );
+ $this->cookieManager = $cookieManager;
+ $this->banManager = $banManager;
+ $this->logger = $logger;
if ($this->configManager->get('security.open_shaarli') === true) {
$this->openShaarli = true;
@@ -85,10 +93,9 @@ class LoginManager
/**
* Check user session state and validity (expiration)
*
- * @param array $cookie The $_COOKIE array
* @param string $clientIpId Client IP address identifier
*/
- public function checkLoginState($cookie, $clientIpId)
+ public function checkLoginState($clientIpId)
{
if (! $this->configManager->exists('credentials.login')) {
// Shaarli is not configured yet
@@ -96,13 +103,12 @@ class LoginManager
return;
}
- if (isset($cookie[self::$STAY_SIGNED_IN_COOKIE])
- && $cookie[self::$STAY_SIGNED_IN_COOKIE] === $this->staySignedInToken
- ) {
+ if ($this->staySignedInToken === $this->cookieManager->getCookieParameter(CookieManager::STAY_SIGNED_IN)) {
// The user client has a valid stay-signed-in cookie
// Session information is updated with the current client information
$this->sessionManager->storeLoginInfo($clientIpId);
- } elseif ($this->sessionManager->hasSessionExpired()
+ } elseif (
+ $this->sessionManager->hasSessionExpired()
|| $this->sessionManager->hasClientIpChanged($clientIpId)
) {
$this->sessionManager->logout();
@@ -119,7 +125,7 @@ class LoginManager
*
* @return true when the user is logged in, false otherwise
*/
- public function isLoggedIn()
+ public function isLoggedIn(): bool
{
if ($this->openShaarli) {
return true;
@@ -130,6 +136,58 @@ class LoginManager
/**
* Check user credentials are valid
*
+ * @param string $clientIpId Client IP address identifier
+ * @param string $login Username
+ * @param string $password Password
+ *
+ * @return bool true if the provided credentials are valid, false otherwise
+ */
+ public function checkCredentials($clientIpId, $login, $password)
+ {
+ // Check credentials
+ try {
+ $useLdapLogin = !empty($this->configManager->get('ldap.host'));
+ if (
+ $login === $this->configManager->get('credentials.login')
+ && (
+ (false === $useLdapLogin && $this->checkCredentialsFromLocalConfig($login, $password))
+ || (true === $useLdapLogin && $this->checkCredentialsFromLdap($login, $password))
+ )
+ ) {
+ $this->sessionManager->storeLoginInfo($clientIpId);
+ $this->logger->info(format_log('Login successful', $clientIpId));
+
+ return true;
+ }
+ } catch (Exception $exception) {
+ $this->logger->info(format_log('Exception while checking credentials: ' . $exception, $clientIpId));
+ }
+
+ $this->logger->info(format_log('Login failed for user ' . $login, $clientIpId));
+
+ return false;
+ }
+
+
+ /**
+ * Check user credentials from local config
+ *
+ * @param string $login Username
+ * @param string $password Password
+ *
+ * @return bool true if the provided credentials are valid, false otherwise
+ */
+ public function checkCredentialsFromLocalConfig($login, $password)
+ {
+ $hash = sha1($password . $login . $this->configManager->get('credentials.salt'));
+
+ return $login == $this->configManager->get('credentials.login')
+ && $hash == $this->configManager->get('credentials.hash');
+ }
+
+ /**
+ * Check user credentials are valid through LDAP bind
+ *
* @param string $remoteIp Remote client IP address
* @param string $clientIpId Client IP address identifier
* @param string $login Username
@@ -137,28 +195,24 @@ class LoginManager
*
* @return bool true if the provided credentials are valid, false otherwise
*/
- public function checkCredentials($remoteIp, $clientIpId, $login, $password)
+ public function checkCredentialsFromLdap($login, $password, $connect = null, $bind = null)
{
- $hash = sha1($password . $login . $this->configManager->get('credentials.salt'));
+ $connect = $connect ?? function ($host) {
+ $resource = ldap_connect($host);
- if ($login != $this->configManager->get('credentials.login')
- || $hash != $this->configManager->get('credentials.hash')
- ) {
- logm(
- $this->configManager->get('resource.log'),
- $remoteIp,
- 'Login failed for user ' . $login
- );
- return false;
- }
+ ldap_set_option($resource, LDAP_OPT_PROTOCOL_VERSION, 3);
- $this->sessionManager->storeLoginInfo($clientIpId);
- logm(
- $this->configManager->get('resource.log'),
- $remoteIp,
- 'Login successful'
+ return $resource;
+ };
+ $bind = $bind ?? function ($handle, $dn, $password) {
+ return ldap_bind($handle, $dn, $password);
+ };
+
+ return $bind(
+ $connect($this->configManager->get('ldap.host')),
+ sprintf($this->configManager->get('ldap.dn'), $login),
+ $password
);
- return true;
}
/**
diff --git a/application/security/SessionManager.php b/application/security/SessionManager.php
index b8b8ab8d..ca186626 100644
--- a/application/security/SessionManager.php
+++ b/application/security/SessionManager.php
@@ -1,4 +1,5 @@
session = &$session;
$this->conf = $conf;
+ $this->savePath = $savePath;
+ }
+
+ /**
+ * Initialize XSRF token and links per page session variables.
+ */
+ public function initialize(): void
+ {
+ if (!isset($this->session['tokens'])) {
+ $this->session['tokens'] = [];
+ }
+
+ if (!isset($this->session['LINKS_PER_PAGE'])) {
+ $this->session['LINKS_PER_PAGE'] = $this->conf->get('general.links_per_page', 20);
+ }
}
/**
@@ -52,7 +80,7 @@ class SessionManager
*/
public function generateToken()
{
- $token = sha1(uniqid('', true) .'_'. mt_rand() . $this->conf->get('credentials.salt'));
+ $token = sha1(uniqid('', true) . '_' . mt_rand() . $this->conf->get('credentials.salt'));
$this->session['tokens'][$token] = 1;
return $token;
}
@@ -156,7 +184,6 @@ class SessionManager
unset($this->session['expires_on']);
unset($this->session['username']);
unset($this->session['visibility']);
- unset($this->session['untaggedonly']);
}
}
@@ -196,4 +223,87 @@ class SessionManager
}
return true;
}
+
+ /** @return array Local reference to the global $_SESSION array */
+ public function getSession(): array
+ {
+ return $this->session;
+ }
+
+ /**
+ * @param mixed $default value which will be returned if the $key is undefined
+ *
+ * @return mixed Content stored in session
+ */
+ public function getSessionParameter(string $key, $default = null)
+ {
+ return $this->session[$key] ?? $default;
+ }
+
+ /**
+ * Store a variable in user session.
+ *
+ * @param string $key Session key
+ * @param mixed $value Session value to store
+ *
+ * @return $this
+ */
+ public function setSessionParameter(string $key, $value): self
+ {
+ $this->session[$key] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Delete a variable in user session.
+ *
+ * @param string $key Session key
+ *
+ * @return $this
+ */
+ public function deleteSessionParameter(string $key): self
+ {
+ unset($this->session[$key]);
+
+ return $this;
+ }
+
+ public function getSavePath(): string
+ {
+ return $this->savePath;
+ }
+
+ /*
+ * Next public functions wrapping native PHP session API.
+ */
+
+ public function destroy(): bool
+ {
+ $this->session = [];
+
+ return session_destroy();
+ }
+
+ public function start(): bool
+ {
+ if (session_status() === PHP_SESSION_ACTIVE) {
+ $this->destroy();
+ }
+
+ return session_start();
+ }
+
+ /**
+ * Be careful, return type of session_set_cookie_params() changed between PHP 7.1 and 7.2.
+ */
+ public function cookieParameters(int $lifeTime, string $path, string $domain): void
+ {
+ session_set_cookie_params($lifeTime, $path, $domain);
+ }
+
+ public function regenerateId(bool $deleteOldSession = false): bool
+ {
+ return session_regenerate_id($deleteOldSession);
+ }
}
diff --git a/application/updater/Updater.php b/application/updater/Updater.php
index beb9ea9b..11b6c051 100644
--- a/application/updater/Updater.php
+++ b/application/updater/Updater.php
@@ -2,25 +2,14 @@
namespace Shaarli\Updater;
-use Exception;
-use RainTPL;
-use ReflectionClass;
-use ReflectionException;
-use ReflectionMethod;
-use Shaarli\ApplicationUtils;
-use Shaarli\Bookmark\LinkDB;
-use Shaarli\Bookmark\LinkFilter;
-use Shaarli\Config\ConfigJson;
+use Shaarli\Bookmark\BookmarkServiceInterface;
use Shaarli\Config\ConfigManager;
-use Shaarli\Config\ConfigPhp;
-use Shaarli\Exceptions\IOException;
-use Shaarli\Thumbnailer;
use Shaarli\Updater\Exception\UpdaterException;
/**
- * Class updater.
+ * Class Updater.
* Used to update stuff when a new Shaarli's version is reached.
- * Update methods are ran only once, and the stored in a JSON file.
+ * Update methods are ran only once, and the stored in a TXT file.
*/
class Updater
{
@@ -30,9 +19,9 @@ class Updater
protected $doneUpdates;
/**
- * @var LinkDB instance.
+ * @var BookmarkServiceInterface instance.
*/
- protected $linkDB;
+ protected $bookmarkService;
/**
* @var ConfigManager $conf Configuration Manager instance.
@@ -45,36 +34,32 @@ class Updater
protected $isLoggedIn;
/**
- * @var array $_SESSION
- */
- protected $session;
-
- /**
- * @var ReflectionMethod[] List of current class methods.
+ * @var \ReflectionMethod[] List of current class methods.
*/
protected $methods;
+ /**
+ * @var string $basePath Shaarli root directory (from HTTP Request)
+ */
+ protected $basePath = null;
+
/**
* Object constructor.
*
- * @param array $doneUpdates Updates which are already done.
- * @param LinkDB $linkDB LinkDB instance.
- * @param ConfigManager $conf Configuration Manager instance.
- * @param boolean $isLoggedIn True if the user is logged in.
- * @param array $session $_SESSION (by reference)
- *
- * @throws ReflectionException
+ * @param array $doneUpdates Updates which are already done.
+ * @param BookmarkServiceInterface $linkDB LinksService instance.
+ * @param ConfigManager $conf Configuration Manager instance.
+ * @param boolean $isLoggedIn True if the user is logged in.
*/
- public function __construct($doneUpdates, $linkDB, $conf, $isLoggedIn, &$session = [])
+ public function __construct($doneUpdates, $linkDB, $conf, $isLoggedIn)
{
$this->doneUpdates = $doneUpdates;
- $this->linkDB = $linkDB;
+ $this->bookmarkService = $linkDB;
$this->conf = $conf;
$this->isLoggedIn = $isLoggedIn;
- $this->session = &$session;
// Retrieve all update methods.
- $class = new ReflectionClass($this);
+ $class = new \ReflectionClass($this);
$this->methods = $class->getMethods();
}
@@ -82,13 +67,15 @@ class Updater
* Run all new updates.
* Update methods have to start with 'updateMethod' and return true (on success).
*
+ * @param string $basePath Shaarli root directory (from HTTP Request)
+ *
* @return array An array containing ran updates.
*
* @throws UpdaterException If something went wrong.
*/
- public function update()
+ public function update(string $basePath = null)
{
- $updatesRan = array();
+ $updatesRan = [];
// If the user isn't logged in, exit without updating.
if ($this->isLoggedIn !== true) {
@@ -96,12 +83,13 @@ class Updater
}
if ($this->methods === null) {
- throw new UpdaterException(t('Couldn\'t retrieve updater class methods.'));
+ throw new UpdaterException('Couldn\'t retrieve LegacyUpdater class methods.');
}
foreach ($this->methods as $method) {
// Not an update method or already done, pass.
- if (!startsWith($method->getName(), 'updateMethod')
+ if (
+ ! startsWith($method->getName(), 'updateMethod')
|| in_array($method->getName(), $this->doneUpdates)
) {
continue;
@@ -114,7 +102,7 @@ class Updater
if ($res === true) {
$updatesRan[] = $method->getName();
}
- } catch (Exception $e) {
+ } catch (\Exception $e) {
throw new UpdaterException($method, $e);
}
}
@@ -132,431 +120,62 @@ class Updater
return $this->doneUpdates;
}
- /**
- * Move deprecated options.php to config.php.
- *
- * Milestone 0.9 (old versioning) - shaarli/Shaarli#41:
- * options.php is not supported anymore.
- */
- public function updateMethodMergeDeprecatedConfigFile()
+ public function readUpdates(string $updatesFilepath): array
{
- if (is_file($this->conf->get('resource.data_dir') . '/options.php')) {
- include $this->conf->get('resource.data_dir') . '/options.php';
+ return UpdaterUtils::readUpdatesFile($updatesFilepath);
+ }
- // Load GLOBALS into config
- $allowedKeys = array_merge(ConfigPhp::$ROOT_KEYS);
- $allowedKeys[] = 'config';
- foreach ($GLOBALS as $key => $value) {
- if (in_array($key, $allowedKeys)) {
- $this->conf->set($key, $value);
- }
- }
- $this->conf->write($this->isLoggedIn);
- unlink($this->conf->get('resource.data_dir') . '/options.php');
+ public function writeUpdates(string $updatesFilepath, array $updates): void
+ {
+ UpdaterUtils::writeUpdatesFile($updatesFilepath, $updates);
+ }
+
+ /**
+ * With the Slim routing system, default header link should be `/subfolder/` instead of `?`.
+ * Otherwise you can not go back to the home page.
+ * Example: `/subfolder/picture-wall` -> `/subfolder/picture-wall?` instead of `/subfolder/`.
+ */
+ public function updateMethodRelativeHomeLink(): bool
+ {
+ if ('?' === trim($this->conf->get('general.header_link'))) {
+ $this->conf->set('general.header_link', $this->basePath . '/', true, true);
}
return true;
}
/**
- * Move old configuration in PHP to the new config system in JSON format.
- *
- * Will rename 'config.php' into 'config.save.php' and create 'config.json.php'.
- * It will also convert legacy setting keys to the new ones.
+ * With the Slim routing system, note bookmarks URL formatted `?abcdef`
+ * should be replaced with `/shaare/abcdef`
*/
- public function updateMethodConfigToJson()
+ public function updateMethodMigrateExistingNotesUrl(): bool
{
- // JSON config already exists, nothing to do.
- if ($this->conf->getConfigIO() instanceof ConfigJson) {
- return true;
- }
+ $updated = false;
- $configPhp = new ConfigPhp();
- $configJson = new ConfigJson();
- $oldConfig = $configPhp->read($this->conf->getConfigFile() . '.php');
- rename($this->conf->getConfigFileExt(), $this->conf->getConfigFile() . '.save.php');
- $this->conf->setConfigIO($configJson);
- $this->conf->reload();
+ foreach ($this->bookmarkService->search()->getBookmarks() as $bookmark) {
+ if (
+ $bookmark->isNote()
+ && startsWith($bookmark->getUrl(), '?')
+ && 1 === preg_match('/^\?([a-zA-Z0-9-_@]{6})($|&|#)/', $bookmark->getUrl(), $match)
+ ) {
+ $updated = true;
+ $bookmark = $bookmark->setUrl('/shaare/' . $match[1]);
- $legacyMap = array_flip(ConfigPhp::$LEGACY_KEYS_MAPPING);
- foreach (ConfigPhp::$ROOT_KEYS as $key) {
- $this->conf->set($legacyMap[$key], $oldConfig[$key]);
- }
-
- // Set sub config keys (config and plugins)
- $subConfig = array('config', 'plugins');
- foreach ($subConfig as $sub) {
- foreach ($oldConfig[$sub] as $key => $value) {
- if (isset($legacyMap[$sub . '.' . $key])) {
- $configKey = $legacyMap[$sub . '.' . $key];
- } else {
- $configKey = $sub . '.' . $key;
- }
- $this->conf->set($configKey, $value);
+ $this->bookmarkService->set($bookmark, false);
}
}
- try {
- $this->conf->write($this->isLoggedIn);
- return true;
- } catch (IOException $e) {
- error_log($e->getMessage());
- return false;
- }
- }
-
- /**
- * Escape settings which have been manually escaped in every request in previous versions:
- * - general.title
- * - general.header_link
- * - redirector.url
- *
- * @return bool true if the update is successful, false otherwise.
- */
- public function updateMethodEscapeUnescapedConfig()
- {
- try {
- $this->conf->set('general.title', escape($this->conf->get('general.title')));
- $this->conf->set('general.header_link', escape($this->conf->get('general.header_link')));
- $this->conf->write($this->isLoggedIn);
- } catch (Exception $e) {
- error_log($e->getMessage());
- return false;
- }
- return true;
- }
-
- /**
- * Update the database to use the new ID system, which replaces linkdate primary keys.
- * Also, creation and update dates are now DateTime objects (done by LinkDB).
- *
- * Since this update is very sensitve (changing the whole database), the datastore will be
- * automatically backed up into the file datastore..php.
- *
- * LinkDB also adds the field 'shorturl' with the precedent format (linkdate smallhash),
- * which will be saved by this method.
- *
- * @return bool true if the update is successful, false otherwise.
- */
- public function updateMethodDatastoreIds()
- {
- // up to date database
- if (isset($this->linkDB[0])) {
- return true;
- }
-
- $save = $this->conf->get('resource.data_dir') . '/datastore.' . date('YmdHis') . '.php';
- copy($this->conf->get('resource.datastore'), $save);
-
- $links = array();
- foreach ($this->linkDB as $offset => $value) {
- $links[] = $value;
- unset($this->linkDB[$offset]);
- }
- $links = array_reverse($links);
- $cpt = 0;
- foreach ($links as $l) {
- unset($l['linkdate']);
- $l['id'] = $cpt;
- $this->linkDB[$cpt++] = $l;
- }
-
- $this->linkDB->save($this->conf->get('resource.page_cache'));
- $this->linkDB->reorder();
-
- return true;
- }
-
- /**
- * Rename tags starting with a '-' to work with tag exclusion search.
- */
- public function updateMethodRenameDashTags()
- {
- $linklist = $this->linkDB->filterSearch();
- foreach ($linklist as $key => $link) {
- $link['tags'] = preg_replace('/(^| )\-/', '$1', $link['tags']);
- $link['tags'] = implode(' ', array_unique(LinkFilter::tagsStrToArray($link['tags'], true)));
- $this->linkDB[$key] = $link;
- }
- $this->linkDB->save($this->conf->get('resource.page_cache'));
- return true;
- }
-
- /**
- * Initialize API settings:
- * - api.enabled: true
- * - api.secret: generated secret
- */
- public function updateMethodApiSettings()
- {
- if ($this->conf->exists('api.secret')) {
- return true;
- }
-
- $this->conf->set('api.enabled', true);
- $this->conf->set(
- 'api.secret',
- generate_api_secret(
- $this->conf->get('credentials.login'),
- $this->conf->get('credentials.salt')
- )
- );
- $this->conf->write($this->isLoggedIn);
- return true;
- }
-
- /**
- * New setting: theme name. If the default theme is used, nothing to do.
- *
- * If the user uses a custom theme, raintpl_tpl dir is updated to the parent directory,
- * and the current theme is set as default in the theme setting.
- *
- * @return bool true if the update is successful, false otherwise.
- */
- public function updateMethodDefaultTheme()
- {
- // raintpl_tpl isn't the root template directory anymore.
- // We run the update only if this folder still contains the template files.
- $tplDir = $this->conf->get('resource.raintpl_tpl');
- $tplFile = $tplDir . '/linklist.html';
- if (!file_exists($tplFile)) {
- return true;
- }
-
- $parent = dirname($tplDir);
- $this->conf->set('resource.raintpl_tpl', $parent);
- $this->conf->set('resource.theme', trim(str_replace($parent, '', $tplDir), '/'));
- $this->conf->write($this->isLoggedIn);
-
- // Dependency injection gore
- RainTPL::$tpl_dir = $tplDir;
-
- return true;
- }
-
- /**
- * Move the file to inc/user.css to data/user.css.
- *
- * Note: Due to hardcoded paths, it's not unit testable. But one line of code should be fine.
- *
- * @return bool true if the update is successful, false otherwise.
- */
- public function updateMethodMoveUserCss()
- {
- if (!is_file('inc/user.css')) {
- return true;
- }
-
- return rename('inc/user.css', 'data/user.css');
- }
-
- /**
- * * `markdown_escape` is a new setting, set to true as default.
- *
- * If the markdown plugin was already enabled, escaping is disabled to avoid
- * breaking existing entries.
- */
- public function updateMethodEscapeMarkdown()
- {
- if ($this->conf->exists('security.markdown_escape')) {
- return true;
- }
-
- if (in_array('markdown', $this->conf->get('general.enabled_plugins'))) {
- $this->conf->set('security.markdown_escape', false);
- } else {
- $this->conf->set('security.markdown_escape', true);
- }
- $this->conf->write($this->isLoggedIn);
-
- return true;
- }
-
- /**
- * Add 'http://' to Piwik URL the setting is set.
- *
- * @return bool true if the update is successful, false otherwise.
- */
- public function updateMethodPiwikUrl()
- {
- if (!$this->conf->exists('plugins.PIWIK_URL') || startsWith($this->conf->get('plugins.PIWIK_URL'), 'http')) {
- return true;
- }
-
- $this->conf->set('plugins.PIWIK_URL', 'http://' . $this->conf->get('plugins.PIWIK_URL'));
- $this->conf->write($this->isLoggedIn);
-
- return true;
- }
-
- /**
- * Use ATOM feed as default.
- */
- public function updateMethodAtomDefault()
- {
- if (!$this->conf->exists('feed.show_atom') || $this->conf->get('feed.show_atom') === true) {
- return true;
- }
-
- $this->conf->set('feed.show_atom', true);
- $this->conf->write($this->isLoggedIn);
-
- return true;
- }
-
- /**
- * Update updates.check_updates_branch setting.
- *
- * If the current major version digit matches the latest branch
- * major version digit, we set the branch to `latest`,
- * otherwise we'll check updates on the `stable` branch.
- *
- * No update required for the dev version.
- *
- * Note: due to hardcoded URL and lack of dependency injection, this is not unit testable.
- *
- * FIXME! This needs to be removed when we switch to first digit major version
- * instead of the second one since the versionning process will change.
- */
- public function updateMethodCheckUpdateRemoteBranch()
- {
- if (SHAARLI_VERSION === 'dev' || $this->conf->get('updates.check_updates_branch') === 'latest') {
- return true;
- }
-
- // Get latest branch major version digit
- $latestVersion = ApplicationUtils::getLatestGitVersionCode(
- 'https://raw.githubusercontent.com/shaarli/Shaarli/latest/shaarli_version.php',
- 5
- );
- if (preg_match('/(\d+)\.\d+$/', $latestVersion, $matches) === false) {
- return false;
- }
- $latestMajor = $matches[1];
-
- // Get current major version digit
- preg_match('/(\d+)\.\d+$/', SHAARLI_VERSION, $matches);
- $currentMajor = $matches[1];
-
- if ($currentMajor === $latestMajor) {
- $branch = 'latest';
- } else {
- $branch = 'stable';
- }
- $this->conf->set('updates.check_updates_branch', $branch);
- $this->conf->write($this->isLoggedIn);
- return true;
- }
-
- /**
- * Reset history store file due to date format change.
- */
- public function updateMethodResetHistoryFile()
- {
- if (is_file($this->conf->get('resource.history'))) {
- unlink($this->conf->get('resource.history'));
- }
- return true;
- }
-
- /**
- * Save the datastore -> the link order is now applied when links are saved.
- */
- public function updateMethodReorderDatastore()
- {
- $this->linkDB->save($this->conf->get('resource.page_cache'));
- return true;
- }
-
- /**
- * Change privateonly session key to visibility.
- */
- public function updateMethodVisibilitySession()
- {
- if (isset($_SESSION['privateonly'])) {
- unset($_SESSION['privateonly']);
- $_SESSION['visibility'] = 'private';
- }
- return true;
- }
-
- /**
- * Add download size and timeout to the configuration file
- *
- * @return bool true if the update is successful, false otherwise.
- */
- public function updateMethodDownloadSizeAndTimeoutConf()
- {
- if ($this->conf->exists('general.download_max_size')
- && $this->conf->exists('general.download_timeout')
- ) {
- return true;
- }
-
- if (!$this->conf->exists('general.download_max_size')) {
- $this->conf->set('general.download_max_size', 1024 * 1024 * 4);
- }
-
- if (!$this->conf->exists('general.download_timeout')) {
- $this->conf->set('general.download_timeout', 30);
- }
-
- $this->conf->write($this->isLoggedIn);
- return true;
- }
-
- /**
- * * Move thumbnails management to WebThumbnailer, coming with new settings.
- */
- public function updateMethodWebThumbnailer()
- {
- if ($this->conf->exists('thumbnails.mode')) {
- return true;
- }
-
- $thumbnailsEnabled = extension_loaded('gd') && $this->conf->get('thumbnail.enable_thumbnails', true);
- $this->conf->set('thumbnails.mode', $thumbnailsEnabled ? Thumbnailer::MODE_ALL : Thumbnailer::MODE_NONE);
- $this->conf->set('thumbnails.width', 125);
- $this->conf->set('thumbnails.height', 90);
- $this->conf->remove('thumbnail');
- $this->conf->write(true);
-
- if ($thumbnailsEnabled) {
- $this->session['warnings'][] = t(
- 'You have enabled or changed thumbnails mode. Please synchronize them.'
- );
+ if ($updated) {
+ $this->bookmarkService->save();
}
return true;
}
- /**
- * Set sticky = false on all links
- *
- * @return bool true if the update is successful, false otherwise.
- */
- public function updateMethodSetSticky()
+ public function setBasePath(string $basePath): self
{
- foreach ($this->linkDB as $key => $link) {
- if (isset($link['sticky'])) {
- return true;
- }
- $link['sticky'] = false;
- $this->linkDB[$key] = $link;
- }
+ $this->basePath = $basePath;
- $this->linkDB->save($this->conf->get('resource.page_cache'));
-
- return true;
- }
-
- /**
- * Remove redirector settings.
- */
- public function updateMethodRemoveRedirector()
- {
- $this->conf->remove('redirector');
- $this->conf->write(true);
- return true;
+ return $this;
}
}
diff --git a/application/updater/UpdaterUtils.php b/application/updater/UpdaterUtils.php
index 34d4f422..206f826e 100644
--- a/application/updater/UpdaterUtils.php
+++ b/application/updater/UpdaterUtils.php
@@ -1,39 +1,44 @@
+ * whom created the CSS which this file is based on.
+ * License: Unlicense
+ */
+
+.markdown p{
+ margin:0.75em 0;
+}
+
+.markdown img{
+ max-width:100%;
+}
+
+.markdown h1, .markdown h2, .markdown h3, .markdown h4, .markdown h5, .markdown h6{
+ font-weight:normal;
+ font-style:normal;
+ line-height:1em;
+ margin:0.75em 0;
+}
+.markdown h4, .markdown h5, .markdown h6{ font-weight: bold; }
+.markdown h1{ font-size:2.5em; }
+.markdown h2{ font-size:2em; }
+.markdown h3{ font-size:1.5em; }
+.markdown h4{ font-size:1.2em; }
+.markdown h5{ font-size:1em; }
+.markdown h6{ font-size:0.9em; }
+
+.markdown blockquote{
+ color:#666666;
+ padding-left: 3em;
+ border-left: 0.5em #EEE solid;
+ margin:0.75em 0;
+}
+.markdown hr { display: block; height: 2px; border: 0; border-top: 1px solid #aaa;border-bottom: 1px solid #eee; margin: 1em 0; padding: 0; }
+.markdown pre, .markdown code, .markdown kbd, .markdown samp {
+ font-family: monospace, 'courier new';
+ font-size: 0.98em;
+}
+.markdown pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; }
+
+.markdown b, .markdown strong { font-weight: bold; }
+
+.markdown dfn, .markdown em { font-style: italic; }
+
+.markdown ins { background: #ff9; color: #000; text-decoration: none; }
+
+.markdown mark { background: #ff0; color: #000; font-style: italic; font-weight: bold; }
+
+.markdown sub, .markdown sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; }
+.markdown sup { top: -0.5em; }
+.markdown sub { bottom: -0.25em; }
+
+.markdown ul, .markdown ol { margin: 1em 0; padding: 0 0 0 2em; }
+.markdown li p:last-child { margin:0 }
+.markdown dd { margin: 0 0 0 2em; }
+
+.markdown img { border: 0; -ms-interpolation-mode: bicubic; vertical-align: middle; }
+
+.markdown table { border-collapse: collapse; border-spacing: 0; }
+.markdown td { vertical-align: top; }
+
+@media only screen and (min-width: 480px) {
+ .markdown {font-size:0.9em;}
+}
+
+@media only screen and (min-width: 768px) {
+ .markdown {font-size:1em;}
+}
+
+#linklist .markdown li {
+ padding: 0;
+ border: none;
+ background: none;
+}
+
+#linklist .markdown ul li {
+ list-style: circle;
+}
+
+#linklist .markdown ol li {
+ list-style: decimal;
+}
+
+.markdown table {
+ padding: 0;
+}
+.markdown table tr {
+ border-top: 1px solid #cccccc;
+ background-color: white;
+ margin: 0;
+ padding: 0;
+}
+.markdown table tr:nth-child(2n) {
+ background-color: #f8f8f8;
+}
+.markdown table tr th {
+ font-weight: bold;
+ border: 1px solid #cccccc;
+ text-align: left;
+ margin: 0;
+ padding: 6px 13px;
+}
+.markdown table tr td {
+ border: 1px solid #cccccc;
+ text-align: left;
+ margin: 0;
+ padding: 6px 13px;
+}
+.markdown table tr th :first-child, .markdown table tr td :first-child {
+ margin-top: 0;
+}
+.markdown table tr th :last-child, table tr td :last-child {
+ margin-bottom: 0;
+}
+
+.markdown pre {
+ background-color: #eee;
+ padding: 4px 9px;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+ overflow: auto;
+ box-shadow: 0 -1px 0 #e5e5e5,0 0 1px rgba(0,0,0,0.12),0 1px 2px rgba(0,0,0,0.24);
+}
+
+.markdown pre code {
+ color: black;
+ font-family: 'Consolas', 'Monaco', 'Andale Mono', monospace;
+ direction: ltr;
+ text-align: left;
+ white-space: pre;
+ word-spacing: normal;
+ word-break: normal;
+ line-height: 1.7;
+ font-size: 11.5px;
+ -moz-tab-size: 4;
+ -o-tab-size: 4;
+ tab-size: 4;
+ -webkit-hyphens: none;
+ -moz-hyphens: none;
+ -ms-hyphens: none;
+ hyphens: none;
+}
+
+.markdown :not(pre) code {
+ background-color: #eee;
+ padding: 1px 3px;
+ border-radius: 1px;
+ box-shadow: 0 -1px 0 #e5e5e5,0 0 1px rgba(0,0,0,0.12),0 1px 1px rgba(0,0,0,0.24);
+}
+
+#pageheader .md_help {
+ color: white;
+}
+
+/*
+ Remove header bookmarks style
+ */
+#pageheader .md_help a {
+ color: lightgray;
+ font-weight: bold;
+ text-decoration: underline;
+
+ background: none;
+ box-shadow: none;
+ padding: 0;
+ margin: 0;
+}
+
+#pageheader .md_help a:hover {
+ color: white;
+}
diff --git a/assets/common/js/metadata.js b/assets/common/js/metadata.js
new file mode 100644
index 00000000..d5a28a35
--- /dev/null
+++ b/assets/common/js/metadata.js
@@ -0,0 +1,107 @@
+import he from 'he';
+
+/**
+ * This script is used to retrieve bookmarks metadata asynchronously:
+ * - title, description and keywords while creating a new bookmark
+ * - thumbnails while visiting the bookmark list
+ *
+ * Note: it should only be included if the user is logged in
+ * and the setting general.enable_async_metadata is enabled.
+ */
+
+/**
+ * Removes given input loaders - used in edit link template.
+ *
+ * @param {object} loaders List of input DOM element that need to be cleared
+ */
+function clearLoaders(loaders) {
+ if (loaders != null && loaders.length > 0) {
+ [...loaders].forEach((loader) => {
+ loader.classList.remove('loading-input');
+ });
+ }
+}
+
+/**
+ * AJAX request to update the thumbnail of a bookmark with the provided ID.
+ * If a thumbnail is retrieved, it updates the divElement with the image src, and displays it.
+ *
+ * @param {string} basePath Shaarli subfolder for XHR requests
+ * @param {object} divElement Main DOM element containing the thumbnail placeholder
+ * @param {int} id Bookmark ID to update
+ */
+function updateThumb(basePath, divElement, id) {
+ const xhr = new XMLHttpRequest();
+ xhr.open('PATCH', `${basePath}/admin/shaare/${id}/update-thumbnail`);
+ xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+ xhr.responseType = 'json';
+ xhr.onload = () => {
+ if (xhr.status !== 200) {
+ alert(`An error occurred. Return code: ${xhr.status}`);
+ } else {
+ const { response } = xhr;
+
+ if (response.thumbnail !== false) {
+ const imgElement = divElement.querySelector('img');
+
+ imgElement.src = response.thumbnail;
+ imgElement.dataset.src = response.thumbnail;
+ imgElement.style.opacity = '1';
+ divElement.classList.remove('hidden');
+ }
+ }
+ };
+ xhr.send();
+}
+
+(() => {
+ const basePath = document.querySelector('input[name="js_base_path"]').value;
+
+ /*
+ * METADATA FOR EDIT BOOKMARK PAGE
+ */
+ 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 = 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 = 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);
+ };
+
+ xhr.send();
+ });
+ }
+
+ /*
+ * METADATA FOR THUMBNAIL RETRIEVAL
+ */
+ const thumbsToLoad = document.querySelectorAll('div[data-async-thumbnail]');
+ if (thumbsToLoad != null) {
+ [...thumbsToLoad].forEach((divElement) => {
+ const { id } = divElement.closest('[data-id]').dataset;
+
+ updateThumb(basePath, divElement, id);
+ });
+ }
+})();
diff --git a/assets/common/js/shaare-batch.js b/assets/common/js/shaare-batch.js
new file mode 100644
index 00000000..6fc16faf
--- /dev/null
+++ b/assets/common/js/shaare-batch.js
@@ -0,0 +1,125 @@
+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) => {
+ if (input.getAttribute('type') === 'checkbox') {
+ formData.append(input.getAttribute('name'), input.checked);
+ } else {
+ formData.append(input.getAttribute('name'), input.value);
+ }
+ });
+
+ return new Promise((resolve, reject) => {
+ 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.closest('.edit-link-container').remove();
+ resolve();
+ }
+ };
+ xhr.send(formData);
+ });
+};
+
+const sendBookmarkDelete = (buttonElement, formElement) => (
+ new Promise((resolve, reject) => {
+ const xhr = new XMLHttpRequest();
+ xhr.open('GET', `${buttonElement.href}&source=batch`);
+ xhr.onload = () => {
+ if (xhr.status !== 204) {
+ alert(`An error occurred. Return code: ${xhr.status}`);
+ reject();
+ } else {
+ formElement.closest('.edit-link-container').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 forms = [...getForms()];
+ const nbForm = forms.length;
+ let current = 0;
+ const progressBar = document.querySelector('.progressbar > div');
+ const progressBarCurrent = document.querySelector('.progressbar-current');
+
+ document.querySelector('.dark-layer').style.display = 'block';
+ document.querySelector('.progressbar-max').innerHTML = nbForm;
+ progressBarCurrent.innerHTML = current;
+
+ const promises = [];
+ forms.forEach((formElement) => {
+ promises.push(sendBookmarkForm(basePath, formElement).then(() => {
+ current += 1;
+ progressBar.style.width = `${(current * 100) / nbForm}%`;
+ progressBarCurrent.innerHTML = current;
+ }));
+ });
+
+ 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(), '/'));
+ });
+ });
+ }
+})();
diff --git a/assets/common/js/thumbnails-update.js b/assets/common/js/thumbnails-update.js
index b66ca3ae..3cd4c2a7 100644
--- a/assets/common/js/thumbnails-update.js
+++ b/assets/common/js/thumbnails-update.js
@@ -10,13 +10,14 @@
* It contains a recursive call to retrieve the thumb of the next link when it succeed.
* It also update the progress bar and other visual feedback elements.
*
+ * @param {string} basePath Shaarli subfolder for XHR requests
* @param {array} ids List of LinkID to update
* @param {int} i Current index in ids
* @param {object} elements List of DOM element to avoid retrieving them at each iteration
*/
-function updateThumb(ids, i, elements) {
+function updateThumb(basePath, ids, i, elements) {
const xhr = new XMLHttpRequest();
- xhr.open('POST', '?do=ajax_thumb_update');
+ xhr.open('PATCH', `${basePath}/admin/shaare/${ids[i]}/update-thumbnail`);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.responseType = 'json';
xhr.onload = () => {
@@ -29,17 +30,18 @@ function updateThumb(ids, i, elements) {
elements.current.innerHTML = i;
elements.title.innerHTML = response.title;
if (response.thumbnail !== false) {
- elements.thumbnail.innerHTML = `

`;
+ elements.thumbnail.innerHTML = `

`;
}
if (i < ids.length) {
- updateThumb(ids, i, elements);
+ updateThumb(basePath, ids, i, elements);
}
}
};
- xhr.send(`id=${ids[i]}`);
+ xhr.send();
}
(() => {
+ const basePath = document.querySelector('input[name="js_base_path"]').value;
const ids = document.getElementsByName('ids')[0].value.split(',');
const elements = {
progressBar: document.querySelector('.progressbar > div'),
@@ -47,5 +49,5 @@ function updateThumb(ids, i, elements) {
thumbnail: document.querySelector('.thumbnail-placeholder'),
title: document.querySelector('.thumbnail-link-title'),
};
- updateThumb(ids, 0, elements);
+ updateThumb(basePath, ids, 0, elements);
})();
diff --git a/assets/default/js/base.js b/assets/default/js/base.js
index d5c29c69..48291d52 100644
--- a/assets/default/js/base.js
+++ b/assets/default/js/base.js
@@ -1,4 +1,5 @@
import Awesomplete from 'awesomplete';
+import he from 'he';
/**
* Find a parent element according to its tag and its attributes
@@ -10,7 +11,7 @@ import Awesomplete from 'awesomplete';
* @returns Found element or null.
*/
function findParent(element, tagName, attributes) {
- const parentMatch = key => attributes[key] !== '' && element.getAttribute(key).indexOf(attributes[key]) !== -1;
+ const parentMatch = (key) => attributes[key] !== '' && element.getAttribute(key).indexOf(attributes[key]) !== -1;
while (element) {
if (element.tagName.toLowerCase() === tagName) {
if (Object.keys(attributes).find(parentMatch)) {
@@ -25,29 +26,48 @@ function findParent(element, tagName, attributes) {
/**
* Ajax request to refresh the CSRF token.
*/
-function refreshToken() {
+function refreshToken(basePath, callback) {
const xhr = new XMLHttpRequest();
- xhr.open('GET', '?do=token');
+ xhr.open('GET', `${basePath}/admin/token`);
xhr.onload = () => {
- const token = document.getElementById('token');
- token.setAttribute('value', xhr.responseText);
+ const elements = document.querySelectorAll('input[name="token"]');
+ [...elements].forEach((element) => {
+ element.setAttribute('value', xhr.responseText);
+ });
+
+ if (callback) {
+ callback(xhr.response);
+ }
};
xhr.send();
}
-function createAwesompleteInstance(element, tags = []) {
+function createAwesompleteInstance(element, separator, tags = []) {
const awesome = new Awesomplete(Awesomplete.$(element));
- // Tags are separated by a space
- awesome.filter = (text, input) => Awesomplete.FILTER_CONTAINS(text, input.match(/[^ ]*$/)[0]);
+
+ // Tags are separated by separator. Ignore leading search flags
+ awesome.filter = (text, input) => {
+ let filterFunc = Awesomplete.FILTER_CONTAINS;
+ let term = input.match(new RegExp(`[^${separator}]*$`))[0];
+ const termFlagged = term.replace(/^[-~+]/, '');
+ if (term !== termFlagged) {
+ term = termFlagged;
+ filterFunc = Awesomplete.FILTER_STARTSWITH;
+ }
+
+ return filterFunc(text, term);
+ };
+
// Insert new selected tag in the input
awesome.replace = (text) => {
- const before = awesome.input.value.match(/^.+ \s*|/)[0];
- awesome.input.value = `${before}${text} `;
+ const before = awesome.input.value.match(new RegExp(`^(.+${separator}+)?[-~+]?|`))[0];
+ awesome.input.value = `${before}${text}${separator}`;
};
// Highlight found items
- awesome.item = (text, input) => Awesomplete.ITEM(text, input.match(/[^ ]*$/)[0]);
+ awesome.item = (text, input) => Awesomplete.ITEM(text, input.match(new RegExp(`[^${separator}]*$`))[0]);
// Don't display already selected items
- const reg = /(\w+) /g;
+ // WARNING: pseudo classes does not seem to work with string litterals...
+ const reg = new RegExp(`([^${separator}]+)${separator}`, 'g');
let match;
awesome.data = (item, input) => {
while ((match = reg.exec(input))) {
@@ -71,13 +91,14 @@ function createAwesompleteInstance(element, tags = []) {
* @param selector CSS selector
* @param tags Array of tags
* @param instances List of existing awesomplete instances
+ * @param separator Tags separator character
*/
-function updateAwesompleteList(selector, tags, instances) {
+function updateAwesompleteList(selector, tags, instances, separator) {
if (instances.length === 0) {
// First load: create Awesomplete instances
const elements = document.querySelectorAll(selector);
[...elements].forEach((element) => {
- instances.push(createAwesompleteInstance(element, tags));
+ instances.push(createAwesompleteInstance(element, separator, tags));
});
} else {
// Update awesomplete tag list
@@ -89,15 +110,6 @@ function updateAwesompleteList(selector, tags, instances) {
return instances;
}
-/**
- * html_entities in JS
- *
- * @see http://stackoverflow.com/questions/18749591/encode-html-entities-in-javascript
- */
-function htmlEntities(str) {
- return str.replace(/[\u00A0-\u9999<>&]/gim, i => `${i.charCodeAt(0)};`);
-}
-
/**
* Add the class 'hidden' to city options not attached to the current selected continent.
*
@@ -188,8 +200,8 @@ function removeClass(element, classname) {
function init(description) {
function resize() {
/* Fix jumpy resizing: https://stackoverflow.com/a/18262927/1484919 */
- const scrollTop = window.pageYOffset ||
- (document.documentElement || document.body.parentNode || document.body).scrollTop;
+ const scrollTop = window.pageYOffset
+ || (document.documentElement || document.body.parentNode || document.body).scrollTop;
description.style.height = 'auto';
description.style.height = `${description.scrollHeight + 10}px`;
@@ -215,6 +227,10 @@ function init(description) {
}
(() => {
+ const basePath = document.querySelector('input[name="js_base_path"]').value;
+ const tagsSeparatorElement = document.querySelector('input[name="tags_separator"]');
+ const tagsSeparator = tagsSeparatorElement ? tagsSeparatorElement.value || ' ' : ' ';
+
/**
* Handle responsive menu.
* Source: http://purecss.io/layouts/tucked-menu-vertical/
@@ -294,7 +310,8 @@ function init(description) {
const deleteLinks = document.querySelectorAll('.confirm-delete');
[...deleteLinks].forEach((deleteLink) => {
deleteLink.addEventListener('click', (event) => {
- if (!confirm(document.getElementById('translation-delete-link').innerHTML)) {
+ const type = event.currentTarget.getAttribute('data-type') || 'link';
+ if (!confirm(document.getElementById(`translation-delete-${type}`).innerHTML)) {
event.preventDefault();
}
});
@@ -366,6 +383,10 @@ function init(description) {
});
sub.classList.toggle('open');
+ const autofocus = sub.querySelector('.autofocus');
+ if (autofocus) {
+ autofocus.focus();
+ }
}
});
});
@@ -461,7 +482,7 @@ function init(description) {
});
if (window.confirm(message)) {
- window.location = `?delete_link&lf_linkdate=${ids.join('+')}&token=${token.value}`;
+ window.location = `${basePath}/admin/shaare/delete?id=${ids.join('+')}&token=${token.value}`;
}
});
}
@@ -482,12 +503,45 @@ function init(description) {
});
});
- const ids = links.map(item => item.id);
- window.location = `?change_visibility&token=${token.value}&newVisibility=${visibility}&ids=${ids.join('+')}`;
+ const ids = links.map((item) => item.id);
+ window.location = (
+ `${basePath}/admin/shaare/visibility?token=${token.value}&newVisibility=${visibility}&id=${ids.join('+')}`
+ );
});
});
}
+ ['add', 'delete'].forEach((action) => {
+ const subHeader = document.getElementById(`bulk-tag-action-${action}`);
+
+ if (subHeader) {
+ subHeader.querySelectorAll('a.button').forEach((link) => {
+ if (!link.classList.contains('action')) {
+ return;
+ }
+
+ subHeader.querySelector('input[name="tag"]').addEventListener('keypress', (event) => {
+ if (event.keyCode === 13) { // enter
+ link.click();
+ }
+ });
+
+ link.addEventListener('click', (event) => {
+ event.preventDefault();
+
+ const ids = [];
+ const linkCheckedCheckboxes = document.querySelectorAll('.link-checkbox:checked');
+ [...linkCheckedCheckboxes].forEach((checkbox) => {
+ ids.push(checkbox.value);
+ });
+
+ subHeader.querySelector('input[name="id"]').value = ids.join(' ');
+ subHeader.querySelector('form').submit();
+ });
+ });
+ }
+ });
+
/**
* Select all button
*/
@@ -545,8 +599,9 @@ function init(description) {
}
const refreshedToken = document.getElementById('token').value;
const fromtag = block.getAttribute('data-tag');
+ const fromtagUrl = block.getAttribute('data-tag-url');
const xhr = new XMLHttpRequest();
- xhr.open('POST', '?do=changetag');
+ xhr.open('POST', `${basePath}/admin/tags`);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onload = () => {
if (xhr.status !== 200) {
@@ -554,20 +609,28 @@ function init(description) {
location.reload();
} else {
block.setAttribute('data-tag', totag);
+ block.setAttribute('data-tag-url', encodeURIComponent(totag));
input.setAttribute('name', totag);
input.setAttribute('value', totag);
findParent(input, 'div', { class: 'rename-tag-form' }).style.display = 'none';
- block.querySelector('a.tag-link').innerHTML = htmlEntities(totag);
- block.querySelector('a.tag-link').setAttribute('href', `?searchtags=${encodeURIComponent(totag)}`);
- block.querySelector('a.rename-tag').setAttribute('href', `?do=changetag&fromtag=${encodeURIComponent(totag)}`);
+ block.querySelector('a.tag-link').innerHTML = he.encode(totag);
+ block
+ .querySelector('a.tag-link')
+ .setAttribute('href', `${basePath}/?searchtags=${encodeURIComponent(totag)}`);
+ block
+ .querySelector('a.count')
+ .setAttribute('href', `${basePath}/add-tag/${encodeURIComponent(totag)}`);
+ block
+ .querySelector('a.rename-tag')
+ .setAttribute('href', `${basePath}/admin/tags?fromtag=${encodeURIComponent(totag)}`);
// Refresh awesomplete values
- existingTags = existingTags.map(tag => (tag === fromtag ? totag : tag));
- awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes);
+ existingTags = existingTags.map((tag) => (tag === fromtag ? totag : tag));
+ awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes, tagsSeparator);
}
};
- xhr.send(`renametag=1&fromtag=${encodeURIComponent(fromtag)}&totag=${encodeURIComponent(totag)}&token=${refreshedToken}`);
- refreshToken();
+ xhr.send(`renametag=1&fromtag=${fromtagUrl}&totag=${encodeURIComponent(totag)}&token=${refreshedToken}`);
+ refreshToken(basePath);
});
});
@@ -589,26 +652,67 @@ function init(description) {
event.preventDefault();
const block = findParent(event.target, 'div', { class: 'tag-list-item' });
const tag = block.getAttribute('data-tag');
+ const tagUrl = block.getAttribute('data-tag-url');
const refreshedToken = document.getElementById('token').value;
if (confirm(`Are you sure you want to delete the tag "${tag}"?`)) {
const xhr = new XMLHttpRequest();
- xhr.open('POST', '?do=changetag');
+ xhr.open('POST', `${basePath}/admin/tags`);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onload = () => {
block.remove();
};
- xhr.send(encodeURI(`deletetag=1&fromtag=${tag}&token=${refreshedToken}`));
- refreshToken();
+ xhr.send(`deletetag=1&fromtag=${tagUrl}&token=${refreshedToken}`);
+ refreshToken(basePath);
- existingTags = existingTags.filter(tagItem => tagItem !== tag);
- awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes);
+ existingTags = existingTags.filter((tagItem) => tagItem !== tag);
+ awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes, tagsSeparator);
}
});
});
const autocompleteFields = document.querySelectorAll('input[data-multiple]');
[...autocompleteFields].forEach((autocompleteField) => {
- awesomepletes.push(createAwesompleteInstance(autocompleteField));
+ awesomepletes.push(createAwesompleteInstance(autocompleteField, tagsSeparator));
});
+
+ const exportForm = document.querySelector('#exportform');
+ if (exportForm != null) {
+ exportForm.addEventListener('submit', (event) => {
+ event.preventDefault();
+
+ refreshToken(basePath, () => {
+ event.target.submit();
+ });
+ });
+ }
+
+ 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);
+ });
+
+ // Force to send falsy value if the checkbox is not checked.
+ const privateButton = bulkCreationForm.querySelector('input[type="checkbox"][name="private"]');
+ const privateHiddenButton = bulkCreationForm.querySelector('input[type="hidden"][name="private"]');
+ privateButton.addEventListener('click', () => {
+ privateHiddenButton.disabled = !privateHiddenButton.disabled;
+ });
+ privateHiddenButton.disabled = privateButton.checked;
+ }
})();
diff --git a/assets/default/scss/shaarli.scss b/assets/default/scss/shaarli.scss
index 61e382b6..51ddff23 100644
--- a/assets/default/scss/shaarli.scss
+++ b/assets/default/scss/shaarli.scss
@@ -69,20 +69,20 @@ pre {
font-family: 'Roboto';
font-weight: 400;
font-style: normal;
- src: local('Roboto'),
- local('Roboto-Regular'),
- url('../fonts/Roboto-Regular.woff2') format('woff2'),
- url('../fonts/Roboto-Regular.woff') format('woff');
+ src:
+ local('Roboto-Regular'),
+ url('../fonts/Roboto-Regular.woff2') format('woff2'),
+ url('../fonts/Roboto-Regular.woff') format('woff');
}
@font-face {
font-family: 'Roboto';
font-weight: 700;
font-style: normal;
- src: local('Roboto'),
- local('Roboto-Bold'),
- url('../fonts/Roboto-Bold.woff2') format('woff2'),
- url('../fonts/Roboto-Bold.woff') format('woff');
+ src:
+ local('Roboto-Bold'),
+ url('../fonts/Roboto-Bold.woff2') format('woff2'),
+ url('../fonts/Roboto-Bold.woff') format('woff');
}
body,
@@ -137,6 +137,16 @@ body,
}
}
+.page-form,
+.pure-alert {
+ code {
+ display: inline-block;
+ padding: 0 2px;
+ color: $dark-grey;
+ background-color: var(--background-color);
+ }
+}
+
// Make pure-extras alert closable.
.pure-alert-closable {
.fa-times {
@@ -375,7 +385,7 @@ body,
}
@media screen and (max-width: 64em) {
- .header-search ,
+ .header-search,
.header-search * {
visibility: hidden;
}
@@ -490,6 +500,10 @@ body,
}
}
+.header-alert-message {
+ text-align: center;
+}
+
// CONTENT - GENERAL
.container {
position: relative;
@@ -550,7 +564,6 @@ body,
color: $dark-grey;
font-size: .9em;
-
a {
display: inline-block;
margin: 3px 0;
@@ -612,6 +625,11 @@ body,
padding: 5px;
text-decoration: none;
color: $dark-grey;
+
+ &.selected {
+ background: var(--main-color);
+ color: $white;
+ }
}
input {
@@ -661,6 +679,10 @@ body,
content: '';
}
}
+
+ .search-highlight {
+ background-color: yellow;
+ }
}
.linklist-item-buttons {
@@ -1009,6 +1031,10 @@ body,
&.button-red {
background: $red;
}
+
+ &.button-grey {
+ background: $light-grey;
+ }
}
.submit-buttons {
@@ -1033,7 +1059,7 @@ body,
}
table {
- margin: auto;
+ margin: 10px auto 25px auto;
width: 90%;
.order {
@@ -1069,6 +1095,11 @@ body,
position: absolute;
right: 5%;
}
+
+ &.button-grey {
+ position: absolute;
+ left: 5%;
+ }
}
}
}
@@ -1236,8 +1267,23 @@ form {
color: $dark-grey;
}
-.page404-container {
+.page-error-container {
color: $dark-grey;
+
+ h2 {
+ margin: 70px 0 25px;
+ }
+
+ a {
+ color: var(--main-color);
+ }
+
+ pre {
+ margin: 0 20%;
+ padding: 20px 0;
+ text-align: left;
+ line-height: 1em;
+ }
}
// EDIT LINK
@@ -1248,6 +1294,57 @@ form {
}
}
+.loading-input {
+ position: relative;
+
+ @keyframes around {
+ 0% {
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ transform: rotate(360deg);
+ }
+ }
+
+ .icon-container {
+ position: absolute;
+ right: 60px;
+ top: calc(50% - 10px);
+ }
+
+ .loader {
+ position: relative;
+ height: 20px;
+ width: 20px;
+ display: inline-block;
+ animation: around 5.4s infinite;
+
+ &::after,
+ &::before {
+ content: "";
+ background: $form-input-background;
+ position: absolute;
+ display: inline-block;
+ width: 100%;
+ height: 100%;
+ border-width: 2px;
+ border-color: #333 #333 transparent transparent;
+ border-style: solid;
+ border-radius: 20px;
+ box-sizing: border-box;
+ top: 0;
+ left: 0;
+ animation: around 0.7s ease-in-out infinite;
+ }
+
+ &::after {
+ animation: around 0.7s ease-in-out 0.1s infinite;
+ background: transparent;
+ }
+ }
+}
+
// LOGIN
.login-form-container {
.remember-me {
@@ -1436,6 +1533,8 @@ form {
-webkit-transition: opacity 500ms ease-in-out;
-moz-transition: opacity 500ms ease-in-out;
-o-transition: opacity 500ms ease-in-out;
+ min-width: 1px;
+ min-height: 1px;
&.b-loaded {
opacity: 1;
@@ -1535,11 +1634,11 @@ form {
text-align: center;
a {
+ background: $almost-white;
display: inline-block;
- margin: 0 15px;
+ padding: 5px;
text-decoration: none;
- color: $white;
- font-weight: bold;
+ color: $dark-grey;
}
}
@@ -1587,13 +1686,14 @@ form {
> div {
border-radius: 10px;
- background: repeating-linear-gradient(
- -45deg,
- $almost-white,
- $almost-white 6px,
- var(--background-color) 6px,
- var(--background-color) 12px
- );
+ background:
+ repeating-linear-gradient(
+ -45deg,
+ $almost-white,
+ $almost-white 6px,
+ var(--background-color) 6px,
+ var(--background-color) 12px
+ );
width: 0%;
height: 10px;
}
@@ -1617,6 +1717,123 @@ form {
}
}
+// SERVER PAGE
+
+.server-tables-page,
+.server-tables {
+ .window-subtitle {
+ &::before {
+ display: block;
+ margin: 8px auto;
+ background: linear-gradient(to right, var(--background-color), $dark-grey, var(--background-color));
+ width: 50%;
+ height: 1px;
+ content: '';
+ }
+ }
+
+ .server-row {
+ p {
+ height: 25px;
+ padding: 0 10px;
+ }
+ }
+
+ .server-label {
+ text-align: right;
+ font-weight: bold;
+ }
+
+ i {
+ &.fa-color-green {
+ color: $main-green;
+ }
+
+ &.fa-color-orange {
+ color: $orange;
+ }
+
+ &.fa-color-red {
+ color: $red;
+ }
+ }
+
+ @media screen and (max-width: 64em) {
+ .server-label {
+ text-align: center;
+ }
+
+ .server-row {
+ p {
+ text-align: center;
+ }
+ }
+ }
+}
+
+// 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;
+ }
+}
+
+.dark-layer {
+ display: none;
+ position: fixed;
+ height: 100%;
+ width: 100%;
+ z-index: 998;
+ background-color: rgba(0, 0, 0, .75);
+ color: #fff;
+
+ .screen-center {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ text-align: center;
+ min-height: 100vh;
+ }
+
+ .progressbar {
+ width: 33%;
+ }
+}
+
+.addlink-batch-form-block {
+ .pure-alert {
+ margin: 25px 0 0 0;
+ }
+}
+
// Print rules
@media print {
.shaarli-menu {
diff --git a/assets/vintage/css/shaarli.css b/assets/vintage/css/shaarli.css
index 87c440c8..33e178af 100644
--- a/assets/vintage/css/shaarli.css
+++ b/assets/vintage/css/shaarli.css
@@ -746,8 +746,6 @@ a.bigbutton, #pageheader a.bigbutton {
text-align: left;
background-color: transparent;
background-color: rgba(0, 0, 0, 0.4);
- /* FF3+, Saf3+, Opera 10.10+, Chrome, IE9 */
- filter: progid: DXImageTransform.Microsoft.gradient(startColorstr=#66000000, endColorstr=#66000000);
/* IE6IE9 */
text-shadow: 2px 2px 1px #000000;
}
@@ -1124,6 +1122,16 @@ ul.errors {
float: left;
}
+ul.warnings {
+ color: orange;
+ float: left;
+}
+
+ul.successes {
+ color: green;
+ float: left;
+}
+
#pluginsadmin {
width: 80%;
padding: 20px 0 0 20px;
@@ -1250,3 +1258,54 @@ ul.errors {
width: 0%;
height: 10px;
}
+
+.loading-input {
+ position: relative;
+}
+
+@keyframes around {
+ 0% {
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+
+.loading-input .icon-container {
+ position: absolute;
+ right: 60px;
+ top: calc(50% - 10px);
+}
+
+.loading-input .loader {
+ position: relative;
+ height: 20px;
+ width: 20px;
+ display: inline-block;
+ animation: around 5.4s infinite;
+}
+
+.loading-input .loader::after,
+.loading-input .loader::before {
+ content: "";
+ background: #eee;
+ position: absolute;
+ display: inline-block;
+ width: 100%;
+ height: 100%;
+ border-width: 2px;
+ border-color: #333 #333 transparent transparent;
+ border-style: solid;
+ border-radius: 20px;
+ box-sizing: border-box;
+ top: 0;
+ left: 0;
+ animation: around 0.7s ease-in-out infinite;
+}
+
+.loading-input .loader::after {
+ animation: around 0.7s ease-in-out 0.1s infinite;
+ background: transparent;
+}
diff --git a/assets/vintage/js/base.js b/assets/vintage/js/base.js
index 66830b59..55f1c37d 100644
--- a/assets/vintage/js/base.js
+++ b/assets/vintage/js/base.js
@@ -2,29 +2,38 @@ import Awesomplete from 'awesomplete';
import 'awesomplete/awesomplete.css';
(() => {
- const awp = Awesomplete.$;
const autocompleteFields = document.querySelectorAll('input[data-multiple]');
- [...autocompleteFields].forEach((autocompleteField) => {
- const awesomplete = new Awesomplete(awp(autocompleteField));
- awesomplete.filter = (text, input) => Awesomplete.FILTER_CONTAINS(text, input.match(/[^ ]*$/)[0]);
- awesomplete.replace = (text) => {
- const before = awesomplete.input.value.match(/^.+ \s*|/)[0];
- awesomplete.input.value = `${before}${text} `;
- };
- awesomplete.minChars = 1;
+ const tagsSeparatorElement = document.querySelector('input[name="tags_separator"]');
+ const tagsSeparator = tagsSeparatorElement ? tagsSeparatorElement.value || ' ' : ' ';
- autocompleteField.addEventListener('input', () => {
- const proposedTags = autocompleteField.getAttribute('data-list').replace(/,/g, '').split(' ');
- const reg = /(\w+) /g;
- let match;
- while ((match = reg.exec(autocompleteField.value)) !== null) {
- const id = proposedTags.indexOf(match[1]);
- if (id !== -1) {
- proposedTags.splice(id, 1);
+ [...autocompleteFields].forEach((autocompleteField) => {
+ const awesome = new Awesomplete(Awesomplete.$(autocompleteField));
+
+ // Tags are separated by separator
+ awesome.filter = (text, input) => Awesomplete.FILTER_CONTAINS(
+ text,
+ input.match(new RegExp(`[^${tagsSeparator}]*$`))[0],
+ );
+ // Insert new selected tag in the input
+ awesome.replace = (text) => {
+ const before = awesome.input.value.match(new RegExp(`^.+${tagsSeparator}+|`))[0];
+ awesome.input.value = `${before}${text}${tagsSeparator}`;
+ };
+ // Highlight found items
+ awesome.item = (text, input) => Awesomplete.ITEM(text, input.match(new RegExp(`[^${tagsSeparator}]*$`))[0]);
+
+ // Don't display already selected items
+ // WARNING: pseudo classes does not seem to work with string litterals...
+ const reg = new RegExp(`([^${tagsSeparator}]+)${tagsSeparator}`, 'g');
+ let match;
+ awesome.data = (item, input) => {
+ while ((match = reg.exec(input))) {
+ if (item === match[1]) {
+ return '';
}
}
-
- awesomplete.list = proposedTags;
- });
+ return item;
+ };
+ awesome.minChars = 1;
});
})();
diff --git a/composer.json b/composer.json
index c23b8252..3015d8eb 100644
--- a/composer.json
+++ b/composer.json
@@ -10,26 +10,29 @@
},
"keywords": ["bookmark", "link", "share", "web"],
"config": {
+ "sort-packages": true,
"platform": {
- "php": "5.6.31"
+ "php": "7.1.29"
}
},
"require": {
- "php": ">=5.6",
+ "php": ">=7.1",
"ext-json": "*",
"ext-zlib": "*",
- "shaarli/netscape-bookmark-parser": "^2.1",
+ "arthurhoaro/web-thumbnailer": "^2.0",
"erusev/parsedown": "^1.6",
- "slim/slim": "^3.0",
- "arthurhoaro/web-thumbnailer": "^1.1",
+ "erusev/parsedown-extra": "^0.8.1",
+ "gettext/gettext": "^4.4",
+ "katzgrau/klogger": "^1.2",
+ "malkusch/lock": "^2.1",
"pubsubhubbub/publisher": "dev-master",
- "gettext/gettext": "^4.4"
+ "shaarli/netscape-bookmark-parser": "^4.0",
+ "slim/slim": "^3.0"
},
"require-dev": {
"roave/security-advisories": "dev-master",
- "phpunit/phpcov": "*",
- "phpunit/phpunit": "^5.0",
- "squizlabs/php_codesniffer": "2.*"
+ "squizlabs/php_codesniffer": "3.*",
+ "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
},
"suggest": {
"ext-curl": "Allows fetching web pages and thumbnails in a more robust way",
@@ -48,17 +51,32 @@
"Shaarli\\Bookmark\\Exception\\": "application/bookmark/exception",
"Shaarli\\Config\\": "application/config/",
"Shaarli\\Config\\Exception\\": "application/config/exception",
+ "Shaarli\\Container\\": "application/container",
"Shaarli\\Exceptions\\": "application/exceptions",
"Shaarli\\Feed\\": "application/feed",
+ "Shaarli\\Formatter\\": "application/formatter",
+ "Shaarli\\Front\\": "application/front",
+ "Shaarli\\Front\\Controller\\Admin\\": "application/front/controller/admin",
+ "Shaarli\\Front\\Controller\\Visitor\\": "application/front/controller/visitor",
+ "Shaarli\\Front\\Exception\\": "application/front/exceptions",
+ "Shaarli\\Helper\\": "application/helper",
"Shaarli\\Http\\": "application/http",
+ "Shaarli\\Legacy\\": "application/legacy",
"Shaarli\\Netscape\\": "application/netscape",
"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",
"Shaarli\\Updater\\Exception\\": "application/updater/exception"
}
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Shaarli\\Tests\\": "tests",
+ "Shaarli\\Tests\\Utils\\": "tests/utils"
+ }
}
}
diff --git a/composer.lock b/composer.lock
index 97b5e3bd..7b14f599 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,41 +4,37 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "432005c9db3e890f42fde27036d2a70f",
+ "content-hash": "326e743376bd043cd7de28c02b5ac1d5",
"packages": [
{
"name": "arthurhoaro/web-thumbnailer",
- "version": "v1.3.1",
+ "version": "v2.1.0",
"source": {
"type": "git",
"url": "https://github.com/ArthurHoaro/web-thumbnailer.git",
- "reference": "7142bd94ec93719a756a7012ebb8e1c5813c6860"
+ "reference": "47675fc58f6fd1dfd63f911b6e86ee5f31e8efd1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/ArthurHoaro/web-thumbnailer/zipball/7142bd94ec93719a756a7012ebb8e1c5813c6860",
- "reference": "7142bd94ec93719a756a7012ebb8e1c5813c6860",
+ "url": "https://api.github.com/repos/ArthurHoaro/web-thumbnailer/zipball/47675fc58f6fd1dfd63f911b6e86ee5f31e8efd1",
+ "reference": "47675fc58f6fd1dfd63f911b6e86ee5f31e8efd1",
"shasum": ""
},
"require": {
- "php": ">=5.6",
- "phpunit/php-text-template": "^1.2"
- },
- "conflict": {
- "phpunit/php-timer": ">=2"
+ "php": ">=7.1",
+ "phpunit/php-text-template": "^1.2 || ^2.0"
},
"require-dev": {
+ "gskema/phpcs-type-sniff": "^0.13.1",
"php-coveralls/php-coveralls": "^2.0",
- "phpunit/phpunit": "5.2.*",
- "squizlabs/php_codesniffer": "^3.2"
+ "phpstan/phpstan": "^0.12.9",
+ "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0 || dev-master",
+ "squizlabs/php_codesniffer": "^3.0"
},
"type": "library",
"autoload": {
- "psr-0": {
- "WebThumbnailer\\": [
- "src/",
- "tests/"
- ]
+ "psr-4": {
+ "WebThumbnailer\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -48,55 +44,28 @@
"authors": [
{
"name": "Arthur Hoaro",
- "homepage": "http://hoa.ro"
+ "homepage": "https://hoa.ro"
}
],
"description": "PHP library which will retrieve a thumbnail for any given URL",
- "time": "2018-08-11T12:21:52+00:00"
- },
- {
- "name": "container-interop/container-interop",
- "version": "1.2.0",
- "source": {
- "type": "git",
- "url": "https://github.com/container-interop/container-interop.git",
- "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8"
+ "support": {
+ "issues": "https://github.com/ArthurHoaro/web-thumbnailer/issues",
+ "source": "https://github.com/ArthurHoaro/web-thumbnailer/tree/v2.1.0"
},
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/container-interop/container-interop/zipball/79cbf1341c22ec75643d841642dd5d6acd83bdb8",
- "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8",
- "shasum": ""
- },
- "require": {
- "psr/container": "^1.0"
- },
- "type": "library",
- "autoload": {
- "psr-4": {
- "Interop\\Container\\": "src/Interop/Container/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "description": "Promoting the interoperability of container objects (DIC, SL, etc.)",
- "homepage": "https://github.com/container-interop/container-interop",
- "time": "2017-02-14T19:40:03+00:00"
+ "time": "2021-05-08T11:20:56+00:00"
},
{
"name": "erusev/parsedown",
- "version": "1.7.1",
+ "version": "1.7.4",
"source": {
"type": "git",
"url": "https://github.com/erusev/parsedown.git",
- "reference": "92e9c27ba0e74b8b028b111d1b6f956a15c01fc1"
+ "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/erusev/parsedown/zipball/92e9c27ba0e74b8b028b111d1b6f956a15c01fc1",
- "reference": "92e9c27ba0e74b8b028b111d1b6f956a15c01fc1",
+ "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3",
+ "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3",
"shasum": ""
},
"require": {
@@ -129,20 +98,75 @@
"markdown",
"parser"
],
- "time": "2018-03-08T01:11:30+00:00"
+ "support": {
+ "issues": "https://github.com/erusev/parsedown/issues",
+ "source": "https://github.com/erusev/parsedown/tree/1.7.x"
+ },
+ "time": "2019-12-30T22:54:17+00:00"
},
{
- "name": "gettext/gettext",
- "version": "v4.6.2",
+ "name": "erusev/parsedown-extra",
+ "version": "0.8.1",
"source": {
"type": "git",
- "url": "https://github.com/oscarotero/Gettext.git",
- "reference": "93176b272d61fb58a9767be71c50d19149cb1e48"
+ "url": "https://github.com/erusev/parsedown-extra.git",
+ "reference": "91ac3ff98f0cea243bdccc688df43810f044dcef"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/oscarotero/Gettext/zipball/93176b272d61fb58a9767be71c50d19149cb1e48",
- "reference": "93176b272d61fb58a9767be71c50d19149cb1e48",
+ "url": "https://api.github.com/repos/erusev/parsedown-extra/zipball/91ac3ff98f0cea243bdccc688df43810f044dcef",
+ "reference": "91ac3ff98f0cea243bdccc688df43810f044dcef",
+ "shasum": ""
+ },
+ "require": {
+ "erusev/parsedown": "^1.7.4"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8.35"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "ParsedownExtra": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Emanuil Rusev",
+ "email": "hello@erusev.com",
+ "homepage": "http://erusev.com"
+ }
+ ],
+ "description": "An extension of Parsedown that adds support for Markdown Extra.",
+ "homepage": "https://github.com/erusev/parsedown-extra",
+ "keywords": [
+ "markdown",
+ "markdown extra",
+ "parsedown",
+ "parser"
+ ],
+ "support": {
+ "issues": "https://github.com/erusev/parsedown-extra/issues",
+ "source": "https://github.com/erusev/parsedown-extra/tree/0.8.x"
+ },
+ "time": "2019-12-30T23:20:37+00:00"
+ },
+ {
+ "name": "gettext/gettext",
+ "version": "v4.8.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-gettext/Gettext.git",
+ "reference": "3f7bc5ef23302a9059e64934f3d59e454516bec0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-gettext/Gettext/zipball/3f7bc5ef23302a9059e64934f3d59e454516bec0",
+ "reference": "3f7bc5ef23302a9059e64934f3d59e454516bec0",
"shasum": ""
},
"require": {
@@ -150,7 +174,7 @@
"php": ">=5.4.0"
},
"require-dev": {
- "illuminate/view": "*",
+ "illuminate/view": "^5.0.x-dev",
"phpunit/phpunit": "^4.8|^5.7|^6.5",
"squizlabs/php_codesniffer": "^3.0",
"symfony/yaml": "~2",
@@ -191,31 +215,49 @@
"po",
"translation"
],
- "time": "2019-01-12T18:40:56+00:00"
+ "support": {
+ "email": "oom@oscarotero.com",
+ "issues": "https://github.com/oscarotero/Gettext/issues",
+ "source": "https://github.com/php-gettext/Gettext/tree/v4.8.7"
+ },
+ "funding": [
+ {
+ "url": "https://paypal.me/oscarotero",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/oscarotero",
+ "type": "github"
+ },
+ {
+ "url": "https://www.patreon.com/misteroom",
+ "type": "patreon"
+ }
+ ],
+ "time": "2022-08-02T09:42:10+00:00"
},
{
"name": "gettext/languages",
- "version": "2.5.0",
+ "version": "2.9.0",
"source": {
"type": "git",
- "url": "https://github.com/mlocati/cldr-to-gettext-plural-rules.git",
- "reference": "78db2d17933f0765a102f368a6663f057162ddbd"
+ "url": "https://github.com/php-gettext/Languages.git",
+ "reference": "ed56dd2c7f4024cc953ed180d25f02f2640e3ffa"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/mlocati/cldr-to-gettext-plural-rules/zipball/78db2d17933f0765a102f368a6663f057162ddbd",
- "reference": "78db2d17933f0765a102f368a6663f057162ddbd",
+ "url": "https://api.github.com/repos/php-gettext/Languages/zipball/ed56dd2c7f4024cc953ed180d25f02f2640e3ffa",
+ "reference": "ed56dd2c7f4024cc953ed180d25f02f2640e3ffa",
"shasum": ""
},
"require": {
"php": ">=5.3"
},
"require-dev": {
- "phpunit/phpunit": "^4"
+ "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5 || ^8.4"
},
"bin": [
- "bin/export-plural-rules",
- "bin/export-plural-rules.php"
+ "bin/export-plural-rules"
],
"type": "library",
"autoload": {
@@ -235,7 +277,7 @@
}
],
"description": "gettext languages with plural rules",
- "homepage": "https://github.com/mlocati/cldr-to-gettext-plural-rules",
+ "homepage": "https://github.com/php-gettext/Languages",
"keywords": [
"cldr",
"i18n",
@@ -252,20 +294,34 @@
"translations",
"unicode"
],
- "time": "2018-11-13T22:06:07+00:00"
+ "support": {
+ "issues": "https://github.com/php-gettext/Languages/issues",
+ "source": "https://github.com/php-gettext/Languages/tree/2.9.0"
+ },
+ "funding": [
+ {
+ "url": "https://paypal.me/mlocati",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/mlocati",
+ "type": "github"
+ }
+ ],
+ "time": "2021-11-11T17:30:39+00:00"
},
{
"name": "katzgrau/klogger",
- "version": "1.2.1",
+ "version": "1.2.2",
"source": {
"type": "git",
"url": "https://github.com/katzgrau/KLogger.git",
- "reference": "a4ed373fa8a214aa4ae7aa4f221fe2c6ce862ef1"
+ "reference": "36481c69db9305169a2ceadead25c2acaabd567c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/katzgrau/KLogger/zipball/a4ed373fa8a214aa4ae7aa4f221fe2c6ce862ef1",
- "reference": "a4ed373fa8a214aa4ae7aa4f221fe2c6ce862ef1",
+ "url": "https://api.github.com/repos/katzgrau/KLogger/zipball/36481c69db9305169a2ceadead25c2acaabd567c",
+ "reference": "36481c69db9305169a2ceadead25c2acaabd567c",
"shasum": ""
},
"require": {
@@ -273,7 +329,7 @@
"psr/log": "^1.0.0"
},
"require-dev": {
- "phpunit/phpunit": "4.0.*"
+ "phpunit/phpunit": "^6.0.0"
},
"type": "library",
"autoload": {
@@ -302,7 +358,96 @@
"keywords": [
"logging"
],
- "time": "2016-11-07T19:29:14+00:00"
+ "support": {
+ "issues": "https://github.com/katzgrau/KLogger/issues",
+ "source": "https://github.com/katzgrau/KLogger/tree/1.2.2"
+ },
+ "time": "2022-07-29T20:41:14+00:00"
+ },
+ {
+ "name": "malkusch/lock",
+ "version": "v2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-lock/lock.git",
+ "reference": "093f389ec2f38fc8686d2f70e23378182fce7714"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-lock/lock/zipball/093f389ec2f38fc8686d2f70e23378182fce7714",
+ "reference": "093f389ec2f38fc8686d2f70e23378182fce7714",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1",
+ "psr/log": "^1"
+ },
+ "require-dev": {
+ "eloquent/liberator": "^2.0",
+ "ext-memcached": "*",
+ "ext-pcntl": "*",
+ "ext-pdo_mysql": "*",
+ "ext-pdo_sqlite": "*",
+ "ext-redis": "*",
+ "ext-sysvsem": "*",
+ "johnkary/phpunit-speedtrap": "^3.0",
+ "kriswallsmith/spork": "^0.3",
+ "mikey179/vfsstream": "^1.6",
+ "php-mock/php-mock-phpunit": "^2.1",
+ "phpunit/phpunit": "^7.4",
+ "predis/predis": "^1.1",
+ "squizlabs/php_codesniffer": "^3.3"
+ },
+ "suggest": {
+ "ext-pnctl": "Enables locking with flock without busy waiting in CLI scripts.",
+ "ext-redis": "To use this library with the PHP Redis extension.",
+ "ext-sysvsem": "Enables locking using semaphores.",
+ "predis/predis": "To use this library with predis."
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "malkusch\\lock\\": "classes/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "WTFPL"
+ ],
+ "authors": [
+ {
+ "name": "Markus Malkusch",
+ "email": "markus@malkusch.de",
+ "homepage": "http://markus.malkusch.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Willem Stuursma-Ruwen",
+ "email": "willem@stuursma.name",
+ "role": "Developer"
+ }
+ ],
+ "description": "Mutex library for exclusive code execution.",
+ "homepage": "https://github.com/malkusch/lock",
+ "keywords": [
+ "advisory-locks",
+ "cas",
+ "flock",
+ "lock",
+ "locking",
+ "memcache",
+ "mutex",
+ "mysql",
+ "postgresql",
+ "redis",
+ "redlock",
+ "semaphore"
+ ],
+ "support": {
+ "issues": "https://github.com/php-lock/lock/issues",
+ "source": "https://github.com/php-lock/lock/tree/v2.1"
+ },
+ "time": "2018-12-12T19:53:29+00:00"
},
{
"name": "nikic/fast-route",
@@ -326,12 +471,12 @@
},
"type": "library",
"autoload": {
- "psr-4": {
- "FastRoute\\": "src/"
- },
"files": [
"src/functions.php"
- ]
+ ],
+ "psr-4": {
+ "FastRoute\\": "src/"
+ }
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@@ -348,6 +493,10 @@
"router",
"routing"
],
+ "support": {
+ "issues": "https://github.com/nikic/FastRoute/issues",
+ "source": "https://github.com/nikic/FastRoute/tree/master"
+ },
"time": "2018-02-13T20:26:39+00:00"
},
{
@@ -389,6 +538,10 @@
"keywords": [
"template"
],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
+ "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1"
+ },
"time": "2015-06-21T13:50:34+00:00"
},
{
@@ -439,6 +592,10 @@
"container",
"dependency injection"
],
+ "support": {
+ "issues": "https://github.com/silexphp/Pimple/issues",
+ "source": "https://github.com/silexphp/Pimple/tree/master"
+ },
"time": "2018-01-21T07:42:36+00:00"
},
{
@@ -488,6 +645,10 @@
"container-interop",
"psr"
],
+ "support": {
+ "issues": "https://github.com/php-fig/container/issues",
+ "source": "https://github.com/php-fig/container/tree/master"
+ },
"time": "2017-02-14T16:28:37+00:00"
},
{
@@ -538,20 +699,23 @@
"request",
"response"
],
+ "support": {
+ "source": "https://github.com/php-fig/http-message/tree/master"
+ },
"time": "2016-08-06T14:39:51+00:00"
},
{
"name": "psr/log",
- "version": "1.1.0",
+ "version": "1.1.4",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
- "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd"
+ "reference": "d49695b909c3b7628b6289db5479a1c204601f11"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
- "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11",
+ "reference": "d49695b909c3b7628b6289db5479a1c204601f11",
"shasum": ""
},
"require": {
@@ -560,7 +724,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.0.x-dev"
+ "dev-master": "1.1.x-dev"
}
},
"autoload": {
@@ -575,7 +739,7 @@
"authors": [
{
"name": "PHP-FIG",
- "homepage": "http://www.php-fig.org/"
+ "homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for logging libraries",
@@ -585,7 +749,10 @@
"psr",
"psr-3"
],
- "time": "2018-11-20T15:27:04+00:00"
+ "support": {
+ "source": "https://github.com/php-fig/log/tree/1.1.4"
+ },
+ "time": "2021-05-03T11:20:27+00:00"
},
{
"name": "pubsubhubbub/publisher",
@@ -593,18 +760,19 @@
"source": {
"type": "git",
"url": "https://github.com/pubsubhubbub/php-publisher.git",
- "reference": "047b0faf6219071527a45942d6fef4dbc6d1d884"
+ "reference": "60c7aa753b2908c7a924d3be8686eafcbeebbfd6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/pubsubhubbub/php-publisher/zipball/047b0faf6219071527a45942d6fef4dbc6d1d884",
- "reference": "047b0faf6219071527a45942d6fef4dbc6d1d884",
+ "url": "https://api.github.com/repos/pubsubhubbub/php-publisher/zipball/60c7aa753b2908c7a924d3be8686eafcbeebbfd6",
+ "reference": "60c7aa753b2908c7a924d3be8686eafcbeebbfd6",
"shasum": ""
},
"require": {
"ext-curl": "*",
- "php": "~5.4 || ~7.0"
+ "php": "~5.4 || ~7.0 || ~8.0"
},
+ "default-branch": true,
"type": "library",
"autoload": {
"psr-4": {
@@ -630,34 +798,39 @@
"pubsubhubbub",
"websub"
],
- "time": "2018-10-09T05:20:28+00:00"
+ "support": {
+ "issues": "https://github.com/pubsubhubbub/php-publisher/issues",
+ "source": "https://github.com/pubsubhubbub/php-publisher/tree/master"
+ },
+ "time": "2021-12-07T05:38:17+00:00"
},
{
"name": "shaarli/netscape-bookmark-parser",
- "version": "v2.1.0",
+ "version": "v4.0.0",
"source": {
"type": "git",
"url": "https://github.com/shaarli/netscape-bookmark-parser.git",
- "reference": "819008ee42c4dd7e45d988176a4a22d6ed689577"
+ "reference": "aa024e5731959966660d98fcefe27deada40d88e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/shaarli/netscape-bookmark-parser/zipball/819008ee42c4dd7e45d988176a4a22d6ed689577",
- "reference": "819008ee42c4dd7e45d988176a4a22d6ed689577",
+ "url": "https://api.github.com/repos/shaarli/netscape-bookmark-parser/zipball/aa024e5731959966660d98fcefe27deada40d88e",
+ "reference": "aa024e5731959966660d98fcefe27deada40d88e",
"shasum": ""
},
"require": {
- "katzgrau/klogger": "~1.0",
- "php": ">=5.6"
+ "php": ">=7.1",
+ "psr/log": "^1.1"
},
"require-dev": {
- "phpunit/phpunit": "^5.0"
+ "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
+ "squizlabs/php_codesniffer": "^3.5"
},
"type": "library",
"autoload": {
- "files": [
- "NetscapeBookmarkParser.php"
- ]
+ "psr-4": {
+ "Shaarli\\NetscapeBookmarkParser\\": "src/"
+ }
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@@ -675,34 +848,48 @@
"email": "virtualtam@flibidi.net",
"homepage": "https://github.com/virtualtam",
"role": "Developer"
+ },
+ {
+ "name": "Matthias Morin",
+ "email": "mat@tangoman.io",
+ "homepage": "https://github.com/TangoMan75",
+ "role": "Developer"
}
],
"description": "Generic Netscape bookmark parser",
"homepage": "https://github.com/shaarli/netscape-bookmark-parser",
"keywords": [
"bookmark",
+ "decoder",
+ "encoder",
"link",
"netscape",
- "parser"
+ "parse"
],
- "time": "2018-10-06T14:43:38+00:00"
+ "support": {
+ "issues": "https://github.com/shaarli/netscape-bookmark-parser/issues",
+ "source": "https://github.com/shaarli/netscape-bookmark-parser/tree/v4.0.0"
+ },
+ "time": "2022-08-13T09:57:26+00:00"
},
{
"name": "slim/slim",
- "version": "3.12.0",
+ "version": "3.12.4",
"source": {
"type": "git",
"url": "https://github.com/slimphp/Slim.git",
- "reference": "f4947cc900b6e51cbfda58b9f1247bca2f76f9f0"
+ "reference": "ce3cb65a06325fc9fe3d0223f2ae23113a767304"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/slimphp/Slim/zipball/f4947cc900b6e51cbfda58b9f1247bca2f76f9f0",
- "reference": "f4947cc900b6e51cbfda58b9f1247bca2f76f9f0",
+ "url": "https://api.github.com/repos/slimphp/Slim/zipball/ce3cb65a06325fc9fe3d0223f2ae23113a767304",
+ "reference": "ce3cb65a06325fc9fe3d0223f2ae23113a767304",
"shasum": ""
},
"require": {
- "container-interop/container-interop": "^1.2",
+ "ext-json": "*",
+ "ext-libxml": "*",
+ "ext-simplexml": "*",
"nikic/fast-route": "^1.0",
"php": ">=5.5.0",
"pimple/pimple": "^3.0",
@@ -714,7 +901,7 @@
},
"require-dev": {
"phpunit/phpunit": "^4.0",
- "squizlabs/php_codesniffer": "^2.5"
+ "squizlabs/php_codesniffer": "^3.6.0"
},
"type": "library",
"autoload": {
@@ -727,25 +914,25 @@
"MIT"
],
"authors": [
- {
- "name": "Rob Allen",
- "email": "rob@akrabat.com",
- "homepage": "http://akrabat.com"
- },
{
"name": "Josh Lockhart",
"email": "hello@joshlockhart.com",
"homepage": "https://joshlockhart.com"
},
- {
- "name": "Gabriel Manricks",
- "email": "gmanricks@me.com",
- "homepage": "http://gabrielmanricks.com"
- },
{
"name": "Andrew Smith",
"email": "a.smith@silentworks.co.uk",
"homepage": "http://silentworks.co.uk"
+ },
+ {
+ "name": "Rob Allen",
+ "email": "rob@akrabat.com",
+ "homepage": "http://akrabat.com"
+ },
+ {
+ "name": "Gabriel Manricks",
+ "email": "gmanricks@me.com",
+ "homepage": "http://gabrielmanricks.com"
}
],
"description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs",
@@ -756,40 +943,52 @@
"micro",
"router"
],
- "time": "2019-01-15T13:21:25+00:00"
+ "support": {
+ "issues": "https://github.com/slimphp/Slim/issues",
+ "source": "https://github.com/slimphp/Slim/tree/3.12.4"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/slimphp",
+ "type": "open_collective"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/slim/slim",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-10-02T19:38:22+00:00"
}
],
"packages-dev": [
{
"name": "doctrine/instantiator",
- "version": "1.0.5",
+ "version": "1.4.1",
"source": {
"type": "git",
"url": "https://github.com/doctrine/instantiator.git",
- "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
+ "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
- "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
+ "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc",
+ "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc",
"shasum": ""
},
"require": {
- "php": ">=5.3,<8.0-DEV"
+ "php": "^7.1 || ^8.0"
},
"require-dev": {
- "athletic/athletic": "~0.1.8",
+ "doctrine/coding-standard": "^9",
"ext-pdo": "*",
"ext-phar": "*",
- "phpunit/phpunit": "~4.0",
- "squizlabs/php_codesniffer": "~2.0"
+ "phpbench/phpbench": "^0.16 || ^1",
+ "phpstan/phpstan": "^1.4",
+ "phpstan/phpstan-phpunit": "^1",
+ "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
+ "vimeo/psalm": "^4.22"
},
"type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0.x-dev"
- }
- },
"autoload": {
"psr-4": {
"Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
@@ -803,47 +1002,69 @@
{
"name": "Marco Pivetta",
"email": "ocramius@gmail.com",
- "homepage": "http://ocramius.github.com/"
+ "homepage": "https://ocramius.github.io/"
}
],
"description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
- "homepage": "https://github.com/doctrine/instantiator",
+ "homepage": "https://www.doctrine-project.org/projects/instantiator.html",
"keywords": [
"constructor",
"instantiate"
],
- "time": "2015-06-14T21:17:01+00:00"
+ "support": {
+ "issues": "https://github.com/doctrine/instantiator/issues",
+ "source": "https://github.com/doctrine/instantiator/tree/1.4.1"
+ },
+ "funding": [
+ {
+ "url": "https://www.doctrine-project.org/sponsorship.html",
+ "type": "custom"
+ },
+ {
+ "url": "https://www.patreon.com/phpdoctrine",
+ "type": "patreon"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-03-03T08:28:38+00:00"
},
{
"name": "myclabs/deep-copy",
- "version": "1.7.0",
+ "version": "1.11.0",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
- "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e"
+ "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e",
- "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614",
+ "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614",
"shasum": ""
},
"require": {
- "php": "^5.6 || ^7.0"
+ "php": "^7.1 || ^8.0"
+ },
+ "conflict": {
+ "doctrine/collections": "<1.6.8",
+ "doctrine/common": "<2.13.3 || >=3,<3.2.2"
},
"require-dev": {
- "doctrine/collections": "^1.0",
- "doctrine/common": "^2.6",
- "phpunit/phpunit": "^4.1"
+ "doctrine/collections": "^1.6.8",
+ "doctrine/common": "^2.13.3 || ^3.2.2",
+ "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
},
"type": "library",
"autoload": {
- "psr-4": {
- "DeepCopy\\": "src/DeepCopy/"
- },
"files": [
"src/DeepCopy/deep_copy.php"
- ]
+ ],
+ "psr-4": {
+ "DeepCopy\\": "src/DeepCopy/"
+ }
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@@ -857,27 +1078,37 @@
"object",
"object graph"
],
- "time": "2017-10-19T19:58:43+00:00"
+ "support": {
+ "issues": "https://github.com/myclabs/DeepCopy/issues",
+ "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0"
+ },
+ "funding": [
+ {
+ "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-03-03T13:19:32+00:00"
},
{
- "name": "phpdocumentor/reflection-common",
- "version": "1.0.1",
+ "name": "phar-io/manifest",
+ "version": "1.0.3",
"source": {
"type": "git",
- "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
- "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6"
+ "url": "https://github.com/phar-io/manifest.git",
+ "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
- "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
+ "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4",
+ "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4",
"shasum": ""
},
"require": {
- "php": ">=5.5"
- },
- "require-dev": {
- "phpunit/phpunit": "^4.6"
+ "ext-dom": "*",
+ "ext-phar": "*",
+ "phar-io/version": "^2.0",
+ "php": "^5.6 || ^7.0"
},
"type": "library",
"extra": {
@@ -885,11 +1116,116 @@
"dev-master": "1.0.x-dev"
}
},
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
+ "support": {
+ "issues": "https://github.com/phar-io/manifest/issues",
+ "source": "https://github.com/phar-io/manifest/tree/master"
+ },
+ "time": "2018-07-08T19:23:20+00:00"
+ },
+ {
+ "name": "phar-io/version",
+ "version": "2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/version.git",
+ "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6",
+ "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.6 || ^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Library for handling version information and constraints",
+ "support": {
+ "issues": "https://github.com/phar-io/version/issues",
+ "source": "https://github.com/phar-io/version/tree/master"
+ },
+ "time": "2018-07-08T19:19:57+00:00"
+ },
+ {
+ "name": "phpdocumentor/reflection-common",
+ "version": "2.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
+ "reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/6568f4687e5b41b054365f9ae03fcb1ed5f2069b",
+ "reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.x-dev"
+ }
+ },
"autoload": {
"psr-4": {
- "phpDocumentor\\Reflection\\": [
- "src"
- ]
+ "phpDocumentor\\Reflection\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -911,33 +1247,44 @@
"reflection",
"static analysis"
],
- "time": "2017-09-11T18:02:19+00:00"
+ "support": {
+ "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues",
+ "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/master"
+ },
+ "time": "2020-04-27T09:25:28+00:00"
},
{
"name": "phpdocumentor/reflection-docblock",
- "version": "3.3.2",
+ "version": "4.3.4",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
- "reference": "bf329f6c1aadea3299f08ee804682b7c45b326a2"
+ "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/bf329f6c1aadea3299f08ee804682b7c45b326a2",
- "reference": "bf329f6c1aadea3299f08ee804682b7c45b326a2",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/da3fd972d6bafd628114f7e7e036f45944b62e9c",
+ "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c",
"shasum": ""
},
"require": {
- "php": "^5.6 || ^7.0",
- "phpdocumentor/reflection-common": "^1.0.0",
- "phpdocumentor/type-resolver": "^0.4.0",
+ "php": "^7.0",
+ "phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0",
+ "phpdocumentor/type-resolver": "~0.4 || ^1.0.0",
"webmozart/assert": "^1.0"
},
"require-dev": {
- "mockery/mockery": "^0.9.4",
- "phpunit/phpunit": "^4.4"
+ "doctrine/instantiator": "^1.0.5",
+ "mockery/mockery": "^1.0",
+ "phpdocumentor/type-resolver": "0.4.*",
+ "phpunit/phpunit": "^6.4"
},
"type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.x-dev"
+ }
+ },
"autoload": {
"psr-4": {
"phpDocumentor\\Reflection\\": [
@@ -956,41 +1303,44 @@
}
],
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
- "time": "2017-11-10T14:09:06+00:00"
+ "support": {
+ "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
+ "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/release/4.x"
+ },
+ "time": "2019-12-28T18:55:12+00:00"
},
{
"name": "phpdocumentor/type-resolver",
- "version": "0.4.0",
+ "version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git",
- "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7"
+ "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7",
- "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7",
+ "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/2e32a6d48972b2c1976ed5d8967145b6cec4a4a9",
+ "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9",
"shasum": ""
},
"require": {
- "php": "^5.5 || ^7.0",
- "phpdocumentor/reflection-common": "^1.0"
+ "php": "^7.1",
+ "phpdocumentor/reflection-common": "^2.0"
},
"require-dev": {
- "mockery/mockery": "^0.9.4",
- "phpunit/phpunit": "^5.2||^4.8.24"
+ "ext-tokenizer": "^7.1",
+ "mockery/mockery": "~1",
+ "phpunit/phpunit": "^7.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.0.x-dev"
+ "dev-master": "1.x-dev"
}
},
"autoload": {
"psr-4": {
- "phpDocumentor\\Reflection\\": [
- "src/"
- ]
+ "phpDocumentor\\Reflection\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -1003,42 +1353,47 @@
"email": "me@mikevanriel.com"
}
],
- "time": "2017-07-14T14:27:02+00:00"
+ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
+ "support": {
+ "issues": "https://github.com/phpDocumentor/TypeResolver/issues",
+ "source": "https://github.com/phpDocumentor/TypeResolver/tree/0.7.2"
+ },
+ "time": "2019-08-22T18:11:29+00:00"
},
{
"name": "phpspec/prophecy",
- "version": "1.8.0",
+ "version": "v1.10.3",
"source": {
"type": "git",
"url": "https://github.com/phpspec/prophecy.git",
- "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06"
+ "reference": "451c3cd1418cf640de218914901e51b064abb093"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06",
- "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06",
+ "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093",
+ "reference": "451c3cd1418cf640de218914901e51b064abb093",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.0.2",
"php": "^5.3|^7.0",
- "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0",
- "sebastian/comparator": "^1.1|^2.0|^3.0",
- "sebastian/recursion-context": "^1.0|^2.0|^3.0"
+ "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0",
+ "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0",
+ "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0"
},
"require-dev": {
- "phpspec/phpspec": "^2.5|^3.2",
+ "phpspec/phpspec": "^2.5 || ^3.2",
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.8.x-dev"
+ "dev-master": "1.10.x-dev"
}
},
"autoload": {
- "psr-0": {
- "Prophecy\\": "src/"
+ "psr-4": {
+ "Prophecy\\": "src/Prophecy"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -1066,44 +1421,48 @@
"spy",
"stub"
],
- "time": "2018-08-05T17:53:17+00:00"
+ "support": {
+ "issues": "https://github.com/phpspec/prophecy/issues",
+ "source": "https://github.com/phpspec/prophecy/tree/v1.10.3"
+ },
+ "time": "2020-03-05T15:02:03+00:00"
},
{
"name": "phpunit/php-code-coverage",
- "version": "4.0.8",
+ "version": "6.1.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
- "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d"
+ "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d",
- "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/807e6013b00af69b6c5d9ceb4282d0393dbb9d8d",
+ "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-xmlwriter": "*",
- "php": "^5.6 || ^7.0",
- "phpunit/php-file-iterator": "^1.3",
- "phpunit/php-text-template": "^1.2",
- "phpunit/php-token-stream": "^1.4.2 || ^2.0",
- "sebastian/code-unit-reverse-lookup": "^1.0",
- "sebastian/environment": "^1.3.2 || ^2.0",
- "sebastian/version": "^1.0 || ^2.0"
+ "php": "^7.1",
+ "phpunit/php-file-iterator": "^2.0",
+ "phpunit/php-text-template": "^1.2.1",
+ "phpunit/php-token-stream": "^3.0",
+ "sebastian/code-unit-reverse-lookup": "^1.0.1",
+ "sebastian/environment": "^3.1 || ^4.0",
+ "sebastian/version": "^2.0.1",
+ "theseer/tokenizer": "^1.1"
},
"require-dev": {
- "ext-xdebug": "^2.1.4",
- "phpunit/phpunit": "^5.7"
+ "phpunit/phpunit": "^7.0"
},
"suggest": {
- "ext-xdebug": "^2.5.1"
+ "ext-xdebug": "^2.6.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "4.0.x-dev"
+ "dev-master": "6.1-dev"
}
},
"autoload": {
@@ -1118,7 +1477,7 @@
"authors": [
{
"name": "Sebastian Bergmann",
- "email": "sb@sebastian-bergmann.de",
+ "email": "sebastian@phpunit.de",
"role": "lead"
}
],
@@ -1129,29 +1488,36 @@
"testing",
"xunit"
],
- "time": "2017-04-02T07:44:40+00:00"
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/master"
+ },
+ "time": "2018-10-31T16:06:48+00:00"
},
{
"name": "phpunit/php-file-iterator",
- "version": "1.4.5",
+ "version": "2.0.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-file-iterator.git",
- "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4"
+ "reference": "42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4",
- "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5",
+ "reference": "42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5",
"shasum": ""
},
"require": {
- "php": ">=5.3.3"
+ "php": ">=7.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^8.5"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.4.x-dev"
+ "dev-master": "2.0.x-dev"
}
},
"autoload": {
@@ -1166,7 +1532,7 @@
"authors": [
{
"name": "Sebastian Bergmann",
- "email": "sb@sebastian-bergmann.de",
+ "email": "sebastian@phpunit.de",
"role": "lead"
}
],
@@ -1176,32 +1542,42 @@
"filesystem",
"iterator"
],
- "time": "2017-11-27T13:52:08+00:00"
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
+ "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.5"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2021-12-02T12:42:26+00:00"
},
{
"name": "phpunit/php-timer",
- "version": "1.0.9",
+ "version": "2.1.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-timer.git",
- "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f"
+ "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
- "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/2454ae1765516d20c4ffe103d85a58a9a3bd5662",
+ "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662",
"shasum": ""
},
"require": {
- "php": "^5.3.3 || ^7.0"
+ "php": ">=7.1"
},
"require-dev": {
- "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
+ "phpunit/phpunit": "^8.5"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.0-dev"
+ "dev-master": "2.1-dev"
}
},
"autoload": {
@@ -1216,7 +1592,7 @@
"authors": [
{
"name": "Sebastian Bergmann",
- "email": "sb@sebastian-bergmann.de",
+ "email": "sebastian@phpunit.de",
"role": "lead"
}
],
@@ -1225,33 +1601,43 @@
"keywords": [
"timer"
],
- "time": "2017-02-26T11:10:40+00:00"
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-timer/issues",
+ "source": "https://github.com/sebastianbergmann/php-timer/tree/2.1.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-11-30T08:20:02+00:00"
},
{
"name": "phpunit/php-token-stream",
- "version": "1.4.12",
+ "version": "3.1.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-token-stream.git",
- "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16"
+ "reference": "9c1da83261628cb24b6a6df371b6e312b3954768"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1ce90ba27c42e4e44e6d8458241466380b51fa16",
- "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/9c1da83261628cb24b6a6df371b6e312b3954768",
+ "reference": "9c1da83261628cb24b6a6df371b6e312b3954768",
"shasum": ""
},
"require": {
"ext-tokenizer": "*",
- "php": ">=5.3.3"
+ "php": ">=7.1"
},
"require-dev": {
- "phpunit/phpunit": "~4.2"
+ "phpunit/phpunit": "^7.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.4-dev"
+ "dev-master": "3.1-dev"
}
},
"autoload": {
@@ -1274,107 +1660,68 @@
"keywords": [
"tokenizer"
],
- "time": "2017-12-04T08:55:13+00:00"
- },
- {
- "name": "phpunit/phpcov",
- "version": "3.1.0",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/phpcov.git",
- "reference": "2005bd90c2c8aae6d93ec82d9cda9d55dca96c3d"
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-token-stream/issues",
+ "source": "https://github.com/sebastianbergmann/php-token-stream/tree/3.1.3"
},
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpcov/zipball/2005bd90c2c8aae6d93ec82d9cda9d55dca96c3d",
- "reference": "2005bd90c2c8aae6d93ec82d9cda9d55dca96c3d",
- "shasum": ""
- },
- "require": {
- "php": "^5.6 || ^7.0",
- "phpunit/php-code-coverage": "^4.0",
- "phpunit/phpunit": "^5.0",
- "sebastian/diff": "^1.1",
- "sebastian/finder-facade": "^1.1",
- "sebastian/version": "^1.0|^2.0",
- "symfony/console": "^2|^3"
- },
- "bin": [
- "phpcov"
- ],
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.1.x-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
+ "funding": [
{
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de",
- "role": "lead"
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
}
],
- "description": "CLI frontend for PHP_CodeCoverage",
- "homepage": "https://github.com/sebastianbergmann/phpcov",
- "time": "2016-06-03T07:01:55+00:00"
+ "abandoned": true,
+ "time": "2021-07-26T12:15:06+00:00"
},
{
"name": "phpunit/phpunit",
- "version": "5.7.27",
+ "version": "7.5.20",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c"
+ "reference": "9467db479d1b0487c99733bb1e7944d32deded2c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c",
- "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9467db479d1b0487c99733bb1e7944d32deded2c",
+ "reference": "9467db479d1b0487c99733bb1e7944d32deded2c",
"shasum": ""
},
"require": {
+ "doctrine/instantiator": "^1.1",
"ext-dom": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-xml": "*",
- "myclabs/deep-copy": "~1.3",
- "php": "^5.6 || ^7.0",
- "phpspec/prophecy": "^1.6.2",
- "phpunit/php-code-coverage": "^4.0.4",
- "phpunit/php-file-iterator": "~1.4",
- "phpunit/php-text-template": "~1.2",
- "phpunit/php-timer": "^1.0.6",
- "phpunit/phpunit-mock-objects": "^3.2",
- "sebastian/comparator": "^1.2.4",
- "sebastian/diff": "^1.4.3",
- "sebastian/environment": "^1.3.4 || ^2.0",
- "sebastian/exporter": "~2.0",
- "sebastian/global-state": "^1.1",
- "sebastian/object-enumerator": "~2.0",
- "sebastian/resource-operations": "~1.0",
- "sebastian/version": "^1.0.6|^2.0.1",
- "symfony/yaml": "~2.1|~3.0|~4.0"
+ "myclabs/deep-copy": "^1.7",
+ "phar-io/manifest": "^1.0.2",
+ "phar-io/version": "^2.0",
+ "php": "^7.1",
+ "phpspec/prophecy": "^1.7",
+ "phpunit/php-code-coverage": "^6.0.7",
+ "phpunit/php-file-iterator": "^2.0.1",
+ "phpunit/php-text-template": "^1.2.1",
+ "phpunit/php-timer": "^2.1",
+ "sebastian/comparator": "^3.0",
+ "sebastian/diff": "^3.0",
+ "sebastian/environment": "^4.0",
+ "sebastian/exporter": "^3.1",
+ "sebastian/global-state": "^2.0",
+ "sebastian/object-enumerator": "^3.0.3",
+ "sebastian/resource-operations": "^2.0",
+ "sebastian/version": "^2.0.1"
},
"conflict": {
- "phpdocumentor/reflection-docblock": "3.0.2"
+ "phpunit/phpunit-mock-objects": "*"
},
"require-dev": {
"ext-pdo": "*"
},
"suggest": {
+ "ext-soap": "*",
"ext-xdebug": "*",
- "phpunit/php-invoker": "~1.1"
+ "phpunit/php-invoker": "^2.0"
},
"bin": [
"phpunit"
@@ -1382,7 +1729,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "5.7.x-dev"
+ "dev-master": "7.5-dev"
}
},
"autoload": {
@@ -1408,67 +1755,11 @@
"testing",
"xunit"
],
- "time": "2018-02-01T05:50:59+00:00"
- },
- {
- "name": "phpunit/phpunit-mock-objects",
- "version": "3.4.4",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
- "reference": "a23b761686d50a560cc56233b9ecf49597cc9118"
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/phpunit/issues",
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/7.5.20"
},
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/a23b761686d50a560cc56233b9ecf49597cc9118",
- "reference": "a23b761686d50a560cc56233b9ecf49597cc9118",
- "shasum": ""
- },
- "require": {
- "doctrine/instantiator": "^1.0.2",
- "php": "^5.6 || ^7.0",
- "phpunit/php-text-template": "^1.2",
- "sebastian/exporter": "^1.2 || ^2.0"
- },
- "conflict": {
- "phpunit/phpunit": "<5.4.0"
- },
- "require-dev": {
- "phpunit/phpunit": "^5.4"
- },
- "suggest": {
- "ext-soap": "*"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.2.x-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sb@sebastian-bergmann.de",
- "role": "lead"
- }
- ],
- "description": "Mock Object library for PHPUnit",
- "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
- "keywords": [
- "mock",
- "xunit"
- ],
- "abandoned": true,
- "time": "2017-06-30T09:13:00+00:00"
+ "time": "2020-01-08T08:45:45+00:00"
},
{
"name": "roave/security-advisories",
@@ -1476,172 +1767,463 @@
"source": {
"type": "git",
"url": "https://github.com/Roave/SecurityAdvisories.git",
- "reference": "d155baccb43ba2542941fbcba258b85ce7786419"
+ "reference": "773292d413a97c357a0b49635afd5fdb1d4f314a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/d155baccb43ba2542941fbcba258b85ce7786419",
- "reference": "d155baccb43ba2542941fbcba258b85ce7786419",
+ "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/773292d413a97c357a0b49635afd5fdb1d4f314a",
+ "reference": "773292d413a97c357a0b49635afd5fdb1d4f314a",
"shasum": ""
},
"conflict": {
"3f/pygmentize": "<1.2",
- "adodb/adodb-php": "<5.20.12",
+ "admidio/admidio": "<4.1.9",
+ "adodb/adodb-php": "<=5.20.20|>=5.21,<=5.21.3",
+ "akaunting/akaunting": "<2.1.13",
+ "alextselegidis/easyappointments": "<=1.4.3",
"alterphp/easyadmin-extension-bundle": ">=1.2,<1.2.11|>=1.3,<1.3.1",
+ "amazing/media2click": ">=1,<1.3.3",
"amphp/artax": "<1.0.6|>=2,<2.0.6",
"amphp/http": "<1.0.1",
+ "amphp/http-client": ">=4,<4.4",
+ "anchorcms/anchor-cms": "<=0.12.7",
+ "andreapollastri/cipi": "<=3.1.15",
"api-platform/core": ">=2.2,<2.2.10|>=2.3,<2.3.6",
+ "appwrite/server-ce": "<0.11.1|>=0.12,<0.12.2",
+ "area17/twill": "<1.2.5|>=2,<2.5.3",
"asymmetricrypt/asymmetricrypt": ">=0,<9.9.99",
"aws/aws-sdk-php": ">=3,<3.2.1",
+ "bagisto/bagisto": "<0.1.5",
+ "barrelstrength/sprout-base-email": "<1.2.7",
+ "barrelstrength/sprout-forms": "<3.9",
+ "barryvdh/laravel-translation-manager": "<0.6.2",
+ "baserproject/basercms": "<4.5.4",
+ "billz/raspap-webgui": "<=2.6.6",
+ "bk2k/bootstrap-package": ">=7.1,<7.1.2|>=8,<8.0.8|>=9,<9.0.4|>=9.1,<9.1.3|>=10,<10.0.10|>=11,<11.0.3",
+ "bmarshall511/wordpress_zero_spam": "<5.2.13",
+ "bolt/bolt": "<3.7.2",
+ "bolt/core": "<=4.2",
+ "bottelet/flarepoint": "<2.2.1",
"brightlocal/phpwhois": "<=4.2.5",
+ "brotkrueml/codehighlight": "<2.7",
+ "brotkrueml/schema": "<1.13.1|>=2,<2.5.1",
+ "brotkrueml/typo3-matomo-integration": "<1.3.2",
+ "buddypress/buddypress": "<7.2.1",
"bugsnag/bugsnag-laravel": ">=2,<2.0.2",
- "cakephp/cakephp": ">=1.3,<1.3.18|>=2,<2.4.99|>=2.5,<2.5.99|>=2.6,<2.6.12|>=2.7,<2.7.6|>=3,<3.0.15|>=3.1,<3.1.4|>=3.4,<3.4.14|>=3.5,<3.5.17|>=3.6,<3.6.4",
+ "bytefury/crater": "<6.0.2",
+ "cachethq/cachet": "<2.5.1",
+ "cakephp/cakephp": "<3.10.3|>=4,<4.0.6",
+ "cardgate/magento2": "<2.0.33",
"cart2quote/module-quotation": ">=4.1.6,<=4.4.5|>=5,<5.4.4",
"cartalyst/sentry": "<=2.1.6",
+ "catfan/medoo": "<1.7.5",
+ "centreon/centreon": "<20.10.7",
+ "cesnet/simplesamlphp-module-proxystatistics": "<3.1",
+ "codeception/codeception": "<3.1.3|>=4,<4.1.22",
"codeigniter/framework": "<=3.0.6",
- "composer/composer": "<=1.0.0-alpha11",
+ "codeigniter4/framework": "<4.1.9",
+ "codiad/codiad": "<=2.8.4",
+ "composer/composer": "<1.10.26|>=2-alpha.1,<2.2.12|>=2.3,<2.3.5",
+ "concrete5/concrete5": "<9",
+ "concrete5/core": "<8.5.8|>=9,<9.1",
"contao-components/mediaelement": ">=2.14.2,<2.21.1",
- "contao/core": ">=2,<3.5.35",
- "contao/core-bundle": ">=4,<4.4.18|>=4.5,<4.5.8",
+ "contao/contao": ">=4,<4.4.56|>=4.5,<4.9.18|>=4.10,<4.11.7|>=4.13,<4.13.3",
+ "contao/core": ">=2,<3.5.39",
+ "contao/core-bundle": "<4.9.18|>=4.10,<4.11.7|>=4.13,<4.13.3|= 4.10.0",
"contao/listing-bundle": ">=4,<4.4.8",
- "contao/newsletter-bundle": ">=4,<4.1",
+ "contao/managed-edition": "<=1.5",
+ "craftcms/cms": "<3.7.36",
+ "croogo/croogo": "<3.0.7",
+ "cuyz/valinor": "<0.12",
+ "czproject/git-php": "<4.0.3",
+ "darylldoyle/safe-svg": "<1.9.10",
+ "datadog/dd-trace": ">=0.30,<0.30.2",
"david-garcia/phpwhois": "<=4.3.1",
+ "derhansen/sf_event_mgt": "<4.3.1|>=5,<5.1.1",
+ "directmailteam/direct-mail": "<5.2.4",
"doctrine/annotations": ">=1,<1.2.7",
"doctrine/cache": ">=1,<1.3.2|>=1.4,<1.4.2",
"doctrine/common": ">=2,<2.4.3|>=2.5,<2.5.1",
- "doctrine/dbal": ">=2,<2.0.8|>=2.1,<2.1.2",
+ "doctrine/dbal": ">=2,<2.0.8|>=2.1,<2.1.2|>=3,<3.1.4",
"doctrine/doctrine-bundle": "<1.5.2",
"doctrine/doctrine-module": "<=0.7.1",
"doctrine/mongodb-odm": ">=1,<1.0.2",
"doctrine/mongodb-odm-bundle": ">=2,<3.0.1",
- "doctrine/orm": ">=2,<2.4.8|>=2.5,<2.5.1",
- "dompdf/dompdf": ">=0.6,<0.6.2",
- "drupal/core": ">=7,<7.60|>=8,<8.5.8|>=8.6,<8.6.2",
- "drupal/drupal": ">=7,<7.60|>=8,<8.5.8|>=8.6,<8.6.2",
- "erusev/parsedown": "<1.7",
- "ezsystems/ezpublish-kernel": ">=5.3,<5.3.12.1|>=5.4,<5.4.13.1|>=6,<6.7.9.1|>=6.8,<6.13.5.1|>=7,<7.2.4.1|>=7.3,<7.3.2.1",
- "ezsystems/ezpublish-legacy": ">=5.3,<5.3.12.6|>=5.4,<5.4.12.3|>=2011,<2017.12.4.3|>=2018.6,<2018.6.1.4|>=2018.9,<2018.9.1.3",
+ "doctrine/orm": ">=2,<2.4.8|>=2.5,<2.5.1|>=2.8.3,<2.8.4",
+ "dolibarr/dolibarr": "<16|= 12.0.5|>= 3.3.beta1, < 13.0.2",
+ "dompdf/dompdf": "<2",
+ "drupal/core": ">=7,<7.91|>=8,<9.3.19|>=9.4,<9.4.3",
+ "drupal/drupal": ">=7,<7.80|>=8,<8.9.16|>=9,<9.1.12|>=9.2,<9.2.4",
+ "dweeves/magmi": "<=0.7.24",
+ "ecodev/newsletter": "<=4",
+ "ectouch/ectouch": "<=2.7.2",
+ "elefant/cms": "<1.3.13",
+ "elgg/elgg": "<3.3.24|>=4,<4.0.5",
+ "endroid/qr-code-bundle": "<3.4.2",
+ "enshrined/svg-sanitize": "<0.15",
+ "erusev/parsedown": "<1.7.2",
+ "ether/logs": "<3.0.4",
+ "ezsystems/demobundle": ">=5.4,<5.4.6.1",
+ "ezsystems/ez-support-tools": ">=2.2,<2.2.3",
+ "ezsystems/ezdemo-ls-extension": ">=5.4,<5.4.2.1",
+ "ezsystems/ezfind-ls": ">=5.3,<5.3.6.1|>=5.4,<5.4.11.1|>=2017.12,<2017.12.0.1",
+ "ezsystems/ezplatform": "<=1.13.6|>=2,<=2.5.24",
+ "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6|>=1.5,<1.5.27",
+ "ezsystems/ezplatform-admin-ui-assets": ">=4,<4.2.1|>=5,<5.0.1|>=5.1,<5.1.1",
+ "ezsystems/ezplatform-kernel": "<=1.2.5|>=1.3,<1.3.19",
+ "ezsystems/ezplatform-rest": ">=1.2,<=1.2.2|>=1.3,<1.3.8",
+ "ezsystems/ezplatform-richtext": ">=2.3,<=2.3.7",
+ "ezsystems/ezplatform-user": ">=1,<1.0.1",
+ "ezsystems/ezpublish-kernel": "<=6.13.8.1|>=7,<7.5.29",
+ "ezsystems/ezpublish-legacy": "<=2017.12.7.3|>=2018.6,<=2019.3.5.1",
+ "ezsystems/platform-ui-assets-bundle": ">=4.2,<4.2.3",
"ezsystems/repository-forms": ">=2.3,<2.3.2.1",
"ezyang/htmlpurifier": "<4.1.1",
+ "facade/ignition": "<1.16.15|>=2,<2.4.2|>=2.5,<2.5.2",
+ "facturascripts/facturascripts": "<=2022.8",
+ "feehi/cms": "<=2.1.1",
+ "feehi/feehicms": "<=0.1.3",
+ "fenom/fenom": "<=2.12.1",
+ "filegator/filegator": "<7.8",
"firebase/php-jwt": "<2",
+ "flarum/core": ">=1,<=1.0.1",
+ "flarum/sticky": ">=0.1-beta.14,<=0.1-beta.15",
+ "flarum/tags": "<=0.1-beta.13",
+ "fluidtypo3/vhs": "<5.1.1",
+ "fof/byobu": ">=0.3-beta.2,<1.1.7",
+ "fof/upload": "<1.2.3",
"fooman/tcpdf": "<6.2.22",
+ "forkcms/forkcms": "<5.11.1",
"fossar/tcpdf-parser": "<6.2.22",
+ "francoisjacquet/rosariosis": "<9.1",
+ "friendsofsymfony/oauth2-php": "<1.3",
"friendsofsymfony/rest-bundle": ">=1.2,<1.2.2",
"friendsofsymfony/user-bundle": ">=1.2,<1.3.5",
+ "friendsoftypo3/mediace": ">=7.6.2,<7.6.5",
+ "froala/wysiwyg-editor": "<3.2.7",
+ "froxlor/froxlor": "<=0.10.22",
"fuel/core": "<1.8.1",
+ "gaoming13/wechat-php-sdk": "<=1.10.2",
+ "genix/cms": "<=1.1.11",
+ "getgrav/grav": "<1.7.34",
+ "getkirby/cms": "<3.5.8",
+ "getkirby/panel": "<2.5.14",
+ "gilacms/gila": "<=1.11.4",
+ "globalpayments/php-sdk": "<2",
+ "google/protobuf": "<3.15",
+ "gos/web-socket-bundle": "<1.10.4|>=2,<2.6.1|>=3,<3.3",
"gree/jose": "<=2.2",
"gregwar/rst": "<1.0.3",
- "guzzlehttp/guzzle": ">=6,<6.2.1|>=4.0.0-rc2,<4.2.4|>=5,<5.3.1",
+ "grumpydictator/firefly-iii": "<5.6.5",
+ "guzzlehttp/guzzle": "<6.5.8|>=7,<7.4.5",
+ "guzzlehttp/psr7": "<1.8.4|>=2,<2.1.1",
+ "helloxz/imgurl": "= 2.31|<=2.31",
+ "hillelcoren/invoice-ninja": "<5.3.35",
+ "hjue/justwriting": "<=1",
+ "hov/jobfair": "<1.0.13|>=2,<2.0.2",
+ "hyn/multi-tenant": ">=5.6,<5.7.2",
+ "ibexa/core": ">=4,<4.0.7|>=4.1,<4.1.4",
+ "ibexa/post-install": "<=1.0.4",
+ "icecoder/icecoder": "<=8.1",
+ "idno/known": "<=1.3.1",
"illuminate/auth": ">=4,<4.0.99|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.10",
- "illuminate/cookie": ">=4,<=4.0.11|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.42|>=5.6,<5.6.30",
- "illuminate/database": ">=4,<4.0.99|>=4.1,<4.1.29",
+ "illuminate/cookie": ">=4,<=4.0.11|>=4.1,<=4.1.99999|>=4.2,<=4.2.99999|>=5,<=5.0.99999|>=5.1,<=5.1.99999|>=5.2,<=5.2.99999|>=5.3,<=5.3.99999|>=5.4,<=5.4.99999|>=5.5,<=5.5.49|>=5.6,<=5.6.99999|>=5.7,<=5.7.99999|>=5.8,<=5.8.99999|>=6,<6.18.31|>=7,<7.22.4",
+ "illuminate/database": "<6.20.26|>=7,<7.30.5|>=8,<8.40",
"illuminate/encryption": ">=4,<=4.0.11|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.40|>=5.6,<5.6.15",
+ "illuminate/view": "<6.20.42|>=7,<7.30.6|>=8,<8.75",
+ "impresscms/impresscms": "<=1.4.3",
+ "in2code/femanager": "<5.5.1|>=6,<6.3.1",
+ "in2code/lux": "<17.6.1|>=18,<24.0.2",
+ "intelliants/subrion": "<=4.2.1",
+ "islandora/islandora": ">=2,<2.4.1",
"ivankristianto/phpwhois": "<=4.3",
- "james-heinrich/getid3": "<1.9.9",
+ "jackalope/jackalope-doctrine-dbal": "<1.7.4",
+ "james-heinrich/getid3": "<1.9.21",
+ "joomla/archive": "<1.1.12|>=2,<2.0.1",
+ "joomla/filesystem": "<1.6.2|>=2,<2.0.1",
+ "joomla/filter": "<1.4.4|>=2,<2.0.1",
+ "joomla/input": ">=2,<2.0.2",
"joomla/session": "<1.3.1",
+ "jsdecena/laracom": "<2.0.9",
"jsmitty12/phpwhois": "<5.1",
"kazist/phpwhois": "<=4.2.6",
+ "kevinpapst/kimai2": "<1.16.7",
+ "kitodo/presentation": "<3.1.2",
+ "klaviyo/magento2-extension": ">=1,<3",
+ "krayin/laravel-crm": "<1.2.2",
"kreait/firebase-php": ">=3.2,<3.8.1",
"la-haute-societe/tcpdf": "<6.2.22",
- "laravel/framework": ">=4,<4.0.99|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.42|>=5.6,<5.6.30",
+ "laminas/laminas-diactoros": "<2.11.1",
+ "laminas/laminas-form": "<2.17.1|>=3,<3.0.2|>=3.1,<3.1.1",
+ "laminas/laminas-http": "<2.14.2",
+ "laravel/fortify": "<1.11.1",
+ "laravel/framework": "<6.20.42|>=7,<7.30.6|>=8,<8.75",
+ "laravel/laravel": "<=9.1.8",
"laravel/socialite": ">=1,<1.0.99|>=2,<2.0.10",
- "league/commonmark": ">=0.15.6,<0.18.1",
- "magento/magento1ce": "<1.9.4",
- "magento/magento1ee": ">=1.9,<1.14.4",
- "magento/product-community-edition": ">=2,<2.2.7",
+ "latte/latte": "<2.10.8",
+ "lavalite/cms": "<=5.8",
+ "lcobucci/jwt": ">=3.4,<3.4.6|>=4,<4.0.4|>=4.1,<4.1.5",
+ "league/commonmark": "<0.18.3",
+ "league/flysystem": "<1.1.4|>=2,<2.1.1",
+ "lexik/jwt-authentication-bundle": "<2.10.7|>=2.11,<2.11.3",
+ "librenms/librenms": "<22.4",
+ "limesurvey/limesurvey": "<3.27.19",
+ "livehelperchat/livehelperchat": "<=3.91",
+ "livewire/livewire": ">2.2.4,<2.2.6",
+ "lms/routes": "<2.1.1",
+ "localizationteam/l10nmgr": "<7.4|>=8,<8.7|>=9,<9.2",
+ "luyadev/yii-helpers": "<1.2.1",
+ "magento/community-edition": ">=2,<2.2.10|>=2.3,<2.3.3",
+ "magento/magento1ce": "<1.9.4.3",
+ "magento/magento1ee": ">=1,<1.14.4.3",
+ "magento/product-community-edition": ">=2,<2.2.10|>=2.3,<2.3.2-p.2",
+ "marcwillmann/turn": "<0.3.3",
+ "matyhtf/framework": "<3.0.6",
+ "mautic/core": "<4.3|= 2.13.1",
+ "mediawiki/core": ">=1.27,<1.27.6|>=1.29,<1.29.3|>=1.30,<1.30.2|>=1.31,<1.31.9|>=1.32,<1.32.6|>=1.32.99,<1.33.3|>=1.33.99,<1.34.3|>=1.34.99,<1.35",
+ "mezzio/mezzio-swoole": "<3.7|>=4,<4.3",
+ "microweber/microweber": "<1.3.1",
+ "miniorange/miniorange-saml": "<1.4.3",
+ "mittwald/typo3_forum": "<1.2.1",
+ "modx/revolution": "<= 2.8.3-pl|<2.8",
+ "mojo42/jirafeau": "<4.4",
"monolog/monolog": ">=1.8,<1.12",
+ "moodle/moodle": "<4.0.1",
+ "mustache/mustache": ">=2,<2.14.1",
"namshi/jose": "<2.2",
+ "neoan3-apps/template": "<1.1.1",
+ "neorazorx/facturascripts": "<2022.4",
+ "neos/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6",
+ "neos/form": ">=1.2,<4.3.3|>=5,<5.0.9|>=5.1,<5.1.3",
+ "neos/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.9.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<5.3.10|>=7,<7.0.9|>=7.1,<7.1.7|>=7.2,<7.2.6|>=7.3,<7.3.4|>=8,<8.0.2",
+ "neos/swiftmailer": ">=4.1,<4.1.99|>=5.4,<5.4.5",
+ "netgen/tagsbundle": ">=3.4,<3.4.11|>=4,<4.0.15",
+ "nette/application": ">=2,<2.0.19|>=2.1,<2.1.13|>=2.2,<2.2.10|>=2.3,<2.3.14|>=2.4,<2.4.16|>=3,<3.0.6",
+ "nette/nette": ">=2,<2.0.19|>=2.1,<2.1.13",
+ "nilsteampassnet/teampass": "<=2.1.27.36",
+ "noumo/easyii": "<=0.9",
+ "nukeviet/nukeviet": "<4.5.2",
+ "nystudio107/craft-seomatic": "<3.4.12",
+ "nzo/url-encryptor-bundle": ">=4,<4.3.2|>=5,<5.0.1",
+ "october/backend": "<1.1.2",
+ "october/cms": "= 1.1.1|= 1.0.471|= 1.0.469|>=1.0.319,<1.0.469",
+ "october/october": ">=1.0.319,<1.0.466|>=2.1,<2.1.12",
+ "october/rain": "<1.0.472|>=1.1,<1.1.2",
+ "october/system": "<1.0.476|>=1.1,<1.1.12|>=2,<2.2.15",
"onelogin/php-saml": "<2.10.4",
+ "oneup/uploader-bundle": "<1.9.3|>=2,<2.1.5",
+ "open-web-analytics/open-web-analytics": "<1.7.4",
+ "opencart/opencart": "<=3.0.3.2",
"openid/php-openid": "<2.3",
- "oro/crm": ">=1.7,<1.7.4",
- "oro/platform": ">=1.7,<1.7.4",
+ "openmage/magento-lts": "<19.4.15|>=20,<20.0.13",
+ "orchid/platform": ">=9,<9.4.4",
+ "oro/commerce": ">=5,<5.0.4",
+ "oro/crm": ">=1.7,<1.7.4|>=3.1,<4.1.17|>=4.2,<4.2.7",
+ "oro/platform": ">=1.7,<1.7.4|>=3.1,<3.1.29|>=4.1,<4.1.17|>=4.2,<4.2.8",
+ "packbackbooks/lti-1-3-php-library": "<5",
"padraic/humbug_get_contents": "<1.1.2",
"pagarme/pagarme-php": ">=0,<3",
+ "pagekit/pagekit": "<=1.0.18",
"paragonie/random_compat": "<2",
+ "passbolt/passbolt_api": "<2.11",
"paypal/merchant-sdk-php": "<3.12",
- "pear/archive_tar": "<1.4.4",
- "phpmailer/phpmailer": ">=5,<5.2.27|>=6,<6.0.6",
- "phpoffice/phpexcel": "<=1.8.1",
- "phpoffice/phpspreadsheet": "<=1.5",
- "phpunit/phpunit": ">=4.8.19,<4.8.28|>=5.0.10,<5.6.3",
+ "pear/archive_tar": "<1.4.14",
+ "pear/crypt_gpg": "<1.6.7",
+ "pegasus/google-for-jobs": "<1.5.1|>=2,<2.1.1",
+ "personnummer/personnummer": "<3.0.2",
+ "phanan/koel": "<5.1.4",
+ "phpfastcache/phpfastcache": "<6.1.5|>=7,<7.1.2|>=8,<8.0.7",
+ "phpmailer/phpmailer": "<6.5",
+ "phpmussel/phpmussel": ">=1,<1.6",
+ "phpmyadmin/phpmyadmin": "<5.1.3",
+ "phpoffice/phpexcel": "<1.8",
+ "phpoffice/phpspreadsheet": "<1.16",
+ "phpseclib/phpseclib": "<2.0.31|>=3,<3.0.7",
+ "phpservermon/phpservermon": "<=3.5.2",
+ "phpunit/phpunit": ">=4.8.19,<4.8.28|>=5,<5.6.3",
"phpwhois/phpwhois": "<=4.2.5",
"phpxmlrpc/extras": "<0.6.1",
- "propel/propel": ">=2.0.0-alpha1,<=2.0.0-alpha7",
+ "pimcore/data-hub": "<1.2.4",
+ "pimcore/pimcore": "<10.4.4",
+ "pocketmine/bedrock-protocol": "<8.0.2",
+ "pocketmine/pocketmine-mp": ">= 4.0.0-BETA5, < 4.4.2|<4.2.10",
+ "pressbooks/pressbooks": "<5.18",
+ "prestashop/autoupgrade": ">=4,<4.10.1",
+ "prestashop/blockwishlist": ">=2,<2.1.1",
+ "prestashop/contactform": ">1.0.1,<4.3",
+ "prestashop/gamification": "<2.3.2",
+ "prestashop/prestashop": ">=1.6.0.10,<1.7.8.7",
+ "prestashop/productcomments": ">=4,<4.2.1",
+ "prestashop/ps_emailsubscription": "<2.6.1",
+ "prestashop/ps_facetedsearch": "<3.4.1",
+ "prestashop/ps_linklist": "<3.1",
+ "privatebin/privatebin": "<1.4",
+ "propel/propel": ">=2-alpha.1,<=2-alpha.7",
"propel/propel1": ">=1,<=1.7.1",
+ "pterodactyl/panel": "<1.7",
+ "ptrofimov/beanstalk_console": "<1.7.14",
"pusher/pusher-php-server": "<2.2.1",
- "robrichards/xmlseclibs": ">=1,<3.0.2",
+ "pwweb/laravel-core": "<=0.3.6-beta",
+ "rainlab/debugbar-plugin": "<3.1",
+ "remdex/livehelperchat": "<3.99",
+ "rmccue/requests": ">=1.6,<1.8",
+ "robrichards/xmlseclibs": "<3.0.4",
+ "rudloff/alltube": "<3.0.3",
+ "s-cart/core": "<6.9",
+ "s-cart/s-cart": "<6.9",
+ "sabberworm/php-css-parser": ">=1,<1.0.1|>=2,<2.0.1|>=3,<3.0.1|>=4,<4.0.1|>=5,<5.0.9|>=5.1,<5.1.3|>=5.2,<5.2.1|>=6,<6.0.2|>=7,<7.0.4|>=8,<8.0.1|>=8.1,<8.1.1|>=8.2,<8.2.1|>=8.3,<8.3.1",
"sabre/dav": ">=1.6,<1.6.99|>=1.7,<1.7.11|>=1.8,<1.8.9",
+ "scheb/two-factor-bundle": ">=0,<3.26|>=4,<4.11",
"sensiolabs/connect": "<4.2.3",
"serluck/phpwhois": "<=4.2.6",
- "shopware/shopware": "<5.3.7",
- "silverstripe/cms": ">=3,<=3.0.11|>=3.1,<3.1.11",
+ "shopware/core": "<=6.4.9",
+ "shopware/platform": "<=6.4.9",
+ "shopware/production": "<=6.3.5.2",
+ "shopware/shopware": "<=5.7.13",
+ "shopware/storefront": "<=6.4.8.1",
+ "shopxo/shopxo": "<2.2.6",
+ "showdoc/showdoc": "<2.10.4",
+ "silverstripe/admin": ">=1,<1.8.1",
+ "silverstripe/assets": ">=1,<1.10.1",
+ "silverstripe/cms": "<4.3.6|>=4.4,<4.4.4",
+ "silverstripe/comments": ">=1.3,<1.9.99|>=2,<2.9.99|>=3,<3.1.1",
"silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3",
- "silverstripe/framework": ">=3,<3.3",
+ "silverstripe/framework": "<4.10.9",
+ "silverstripe/graphql": "<3.5.2|>=4-alpha.1,<4-alpha.2|= 4.0.0-alpha1",
+ "silverstripe/hybridsessions": ">=1,<2.4.1|>=2.5,<2.5.1",
+ "silverstripe/registry": ">=2.1,<2.1.2|>=2.2,<2.2.1",
+ "silverstripe/restfulserver": ">=1,<1.0.9|>=2,<2.0.4",
+ "silverstripe/silverstripe-omnipay": "<2.5.2|>=3,<3.0.2|>=3.1,<3.1.4|>=3.2,<3.2.1",
+ "silverstripe/subsites": ">=2,<2.1.1",
+ "silverstripe/taxonomy": ">=1.3,<1.3.1|>=2,<2.0.1",
"silverstripe/userforms": "<3",
"simple-updates/phpwhois": "<=1",
"simplesamlphp/saml2": "<1.10.6|>=2,<2.3.8|>=3,<3.1.4",
- "simplesamlphp/simplesamlphp": "<1.16.3",
+ "simplesamlphp/simplesamlphp": "<1.18.6",
"simplesamlphp/simplesamlphp-module-infocard": "<1.0.1",
+ "simplito/elliptic-php": "<1.0.6",
"slim/slim": "<2.6",
- "smarty/smarty": "<3.1.33",
+ "smarty/smarty": "<3.1.45|>=4,<4.1.1",
+ "snipe/snipe-it": "<=6.0.2|>= 6.0.0-RC-1, <= 6.0.0-RC-5",
"socalnick/scn-social-auth": "<1.15.2",
+ "socialiteproviders/steam": "<1.1",
+ "spipu/html2pdf": "<5.2.4",
"spoonity/tcpdf": "<6.2.22",
"squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1",
+ "ssddanbrown/bookstack": "<22.2.3",
+ "statamic/cms": "<3.2.39|>=3.3,<3.3.2",
"stormpath/sdk": ">=0,<9.9.99",
+ "studio-42/elfinder": "<2.1.59",
+ "subrion/cms": "<=4.2.1",
+ "sulu/sulu": "= 2.4.0-RC1|<1.6.44|>=2,<2.2.18|>=2.3,<2.3.8",
"swiftmailer/swiftmailer": ">=4,<5.4.5",
"sylius/admin-bundle": ">=1,<1.0.17|>=1.1,<1.1.9|>=1.2,<1.2.2",
- "sylius/sylius": ">=1,<1.0.17|>=1.1,<1.1.9|>=1.2,<1.2.2",
- "symfony/dependency-injection": ">=2,<2.0.17",
+ "sylius/grid": ">=1,<1.1.19|>=1.2,<1.2.18|>=1.3,<1.3.13|>=1.4,<1.4.5|>=1.5,<1.5.1",
+ "sylius/grid-bundle": "<1.10.1",
+ "sylius/paypal-plugin": ">=1,<1.2.4|>=1.3,<1.3.1",
+ "sylius/resource-bundle": "<1.3.14|>=1.4,<1.4.7|>=1.5,<1.5.2|>=1.6,<1.6.4",
+ "sylius/sylius": "<1.9.10|>=1.10,<1.10.11|>=1.11,<1.11.2",
+ "symbiote/silverstripe-multivaluefield": ">=3,<3.0.99",
+ "symbiote/silverstripe-queuedjobs": ">=3,<3.0.2|>=3.1,<3.1.4|>=4,<4.0.7|>=4.1,<4.1.2|>=4.2,<4.2.4|>=4.3,<4.3.3|>=4.4,<4.4.3|>=4.5,<4.5.1|>=4.6,<4.6.4",
+ "symbiote/silverstripe-versionedfiles": "<=2.0.3",
+ "symfont/process": ">=0,<4",
+ "symfony/cache": ">=3.1,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8",
+ "symfony/dependency-injection": ">=2,<2.0.17|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7",
+ "symfony/error-handler": ">=4.4,<4.4.4|>=5,<5.0.4",
"symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1",
- "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2",
- "symfony/http-foundation": ">=2,<2.7.49|>=2.8,<2.8.44|>=3,<3.3.18|>=3.4,<3.4.14|>=4,<4.0.14|>=4.1,<4.1.3",
- "symfony/http-kernel": ">=2,<2.3.29|>=2.4,<2.5.12|>=2.6,<2.6.8",
+ "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7|>=5.3.14,<=5.3.14|>=5.4.3,<=5.4.3|>=6.0.3,<=6.0.3|= 6.0.3|= 5.4.3|= 5.3.14",
+ "symfony/http-foundation": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7",
+ "symfony/http-kernel": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.4.13|>=5,<5.1.5|>=5.2,<5.3.12",
"symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13",
+ "symfony/maker-bundle": ">=1.27,<1.29.2|>=1.30,<1.31.1",
+ "symfony/mime": ">=4.3,<4.3.8",
+ "symfony/phpunit-bridge": ">=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7",
"symfony/polyfill": ">=1,<1.10",
"symfony/polyfill-php55": ">=1,<1.10",
+ "symfony/proxy-manager-bridge": ">=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7",
"symfony/routing": ">=2,<2.0.19",
- "symfony/security": ">=2,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.19|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1",
- "symfony/security-bundle": ">=2,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11",
- "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<2.8.37|>=3,<3.3.17|>=3.4,<3.4.7|>=4,<4.0.7",
+ "symfony/security": ">=2,<2.7.51|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.8",
+ "symfony/security-bundle": ">=2,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11|>=5.3,<5.3.12",
+ "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.9",
"symfony/security-csrf": ">=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11",
- "symfony/security-guard": ">=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11",
- "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1",
- "symfony/serializer": ">=2,<2.0.11",
- "symfony/symfony": ">=2,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1",
+ "symfony/security-guard": ">=2.8,<3.4.48|>=4,<4.4.23|>=5,<5.2.8",
+ "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7|>=5.1,<5.2.8|>=5.3,<5.3.2",
+ "symfony/serializer": ">=2,<2.0.11|>=4.1,<4.4.35|>=5,<5.3.12",
+ "symfony/symfony": ">=2,<3.4.49|>=4,<4.4.35|>=5,<5.3.12|>=5.3.14,<=5.3.14|>=5.4.3,<=5.4.3|>=6.0.3,<=6.0.3",
"symfony/translation": ">=2,<2.0.17",
"symfony/validator": ">=2,<2.0.24|>=2.1,<2.1.12|>=2.2,<2.2.5|>=2.3,<2.3.3",
+ "symfony/var-exporter": ">=4.2,<4.2.12|>=4.3,<4.3.8",
"symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4",
"symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7",
+ "t3/dce": ">=2.2,<2.6.2",
+ "t3g/svg-sanitizer": "<1.0.3",
+ "tastyigniter/tastyigniter": "<3.3",
"tecnickcom/tcpdf": "<6.2.22",
+ "terminal42/contao-tablelookupwizard": "<3.3.5",
"thelia/backoffice-default-template": ">=2.1,<2.1.2",
- "thelia/thelia": ">=2.1.0-beta1,<2.1.3|>=2.1,<2.1.2",
+ "thelia/thelia": ">=2.1-beta.1,<2.1.3",
"theonedemon/phpwhois": "<=4.2.5",
+ "thinkcmf/thinkcmf": "<=5.1.7",
+ "tinymce/tinymce": "<5.10",
"titon/framework": ">=0,<9.9.99",
+ "topthink/framework": "<=6.0.12",
+ "topthink/think": "<=6.0.9",
+ "topthink/thinkphp": "<=3.2.3",
+ "tribalsystems/zenario": "<9.2.55826",
"truckersmp/phpwhois": "<=4.3.1",
- "twig/twig": "<1.20",
- "typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.32|>=8,<8.7.21|>=9,<9.5.2",
- "typo3/cms-core": ">=8,<8.7.21|>=9,<9.5.2",
- "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.10|>=3.1,<3.1.7|>=3.2,<3.2.7|>=3.3,<3.3.5",
- "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4",
+ "twig/twig": "<1.38|>=2,<2.14.11|>=3,<3.3.8",
+ "typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.32|>=8,<8.7.38|>=9,<9.5.29|>=10,<10.4.29|>=11,<11.5.11",
+ "typo3/cms-backend": ">=7,<=7.6.50|>=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.13|>=11,<=11.1",
+ "typo3/cms-core": ">=6.2,<=6.2.56|>=7,<7.6.57|>=8,<8.7.47|>=9,<9.5.35|>=10,<10.4.29|>=11,<11.5.11",
+ "typo3/cms-form": ">=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.13|>=11,<=11.1",
+ "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6",
+ "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.3.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<3.3.23|>=4,<4.0.17|>=4.1,<4.1.16|>=4.2,<4.2.12|>=4.3,<4.3.3",
+ "typo3/phar-stream-wrapper": ">=1,<2.1.1|>=3,<3.1.1",
+ "typo3/swiftmailer": ">=4.1,<4.1.99|>=5.4,<5.4.5",
+ "typo3fluid/fluid": ">=2,<2.0.8|>=2.1,<2.1.7|>=2.2,<2.2.4|>=2.3,<2.3.7|>=2.4,<2.4.4|>=2.5,<2.5.11|>=2.6,<2.6.10",
"ua-parser/uap-php": "<3.8",
+ "unisharp/laravel-filemanager": "<=2.3",
+ "userfrosting/userfrosting": ">=0.3.1,<4.6.3",
+ "usmanhalalit/pixie": "<1.0.3|>=2,<2.0.2",
+ "vanilla/safecurl": "<0.9.2",
+ "verot/class.upload.php": "<=1.0.3|>=2,<=2.0.4",
+ "vrana/adminer": "<4.8.1",
"wallabag/tcpdf": "<6.2.22",
+ "wanglelecc/laracms": "<=1.0.3",
+ "web-auth/webauthn-framework": ">=3.3,<3.3.4",
+ "webcoast/deferred-image-processing": "<1.0.2",
+ "wikimedia/parsoid": "<0.12.2",
"willdurand/js-translation-bundle": "<2.1.1",
+ "wintercms/winter": "<1.0.475|>=1.1,<1.1.9",
+ "woocommerce/woocommerce": "<6.6",
+ "wp-cli/wp-cli": "<2.5",
+ "wp-graphql/wp-graphql": "<0.3.5",
+ "wpanel/wpanel4-cms": "<=4.3.1",
+ "wwbn/avideo": "<=11.6",
+ "yeswiki/yeswiki": "<4.1",
+ "yetiforce/yetiforce-crm": "<6.4",
+ "yidashi/yii2cmf": "<=2",
+ "yii2mod/yii2-cms": "<1.9.2",
"yiisoft/yii": ">=1.1.14,<1.1.15",
- "yiisoft/yii2": "<2.0.15",
+ "yiisoft/yii2": "<2.0.38",
"yiisoft/yii2-bootstrap": "<2.0.4",
- "yiisoft/yii2-dev": "<2.0.15",
+ "yiisoft/yii2-dev": "<2.0.43",
"yiisoft/yii2-elasticsearch": "<2.0.5",
"yiisoft/yii2-gii": "<2.0.4",
"yiisoft/yii2-jui": "<2.0.4",
"yiisoft/yii2-redis": "<2.0.8",
+ "yoast-seo-for-typo3/yoast_seo": "<7.2.3",
+ "yourls/yourls": "<=1.8.2",
+ "zendesk/zendesk_api_client_php": "<2.2.11",
"zendframework/zend-cache": ">=2.4,<2.4.8|>=2.5,<2.5.3",
"zendframework/zend-captcha": ">=2,<2.4.9|>=2.5,<2.5.2",
"zendframework/zend-crypt": ">=2,<2.4.9|>=2.5,<2.5.2",
"zendframework/zend-db": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.10|>=2.3,<2.3.5",
- "zendframework/zend-diactoros": ">=1,<1.8.4",
- "zendframework/zend-feed": ">=1,<2.10.3",
+ "zendframework/zend-developer-tools": ">=1.2.2,<1.2.3",
+ "zendframework/zend-diactoros": "<1.8.4",
+ "zendframework/zend-feed": "<2.10.3",
"zendframework/zend-form": ">=2,<2.2.7|>=2.3,<2.3.1",
- "zendframework/zend-http": ">=1,<2.8.1",
+ "zendframework/zend-http": "<2.8.1",
"zendframework/zend-json": ">=2.1,<2.1.6|>=2.2,<2.2.6",
"zendframework/zend-ldap": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.8|>=2.3,<2.3.3",
"zendframework/zend-mail": ">=2,<2.4.11|>=2.5,<2.7.2",
@@ -1650,14 +2232,15 @@
"zendframework/zend-validator": ">=2.3,<2.3.6",
"zendframework/zend-view": ">=2,<2.2.7|>=2.3,<2.3.1",
"zendframework/zend-xmlrpc": ">=2.1,<2.1.6|>=2.2,<2.2.6",
- "zendframework/zendframework": "<2.5.1",
+ "zendframework/zendframework": "<=3",
"zendframework/zendframework1": "<1.12.20",
"zendframework/zendopenid": ">=2,<2.0.2",
"zendframework/zendxml": ">=1,<1.0.1",
"zetacomponents/mail": "<1.8.2",
"zf-commons/zfc-user": "<1.2.2",
"zfcampus/zf-apigility-doctrine": ">=1,<1.0.3",
- "zfr/zfr-oauth2-server-module": "<0.1.2"
+ "zfr/zfr-oauth2-server-module": "<0.1.2",
+ "zoujingli/thinkadmin": "<6.0.22"
},
"type": "metapackage",
"notification-url": "https://packagist.org/downloads/",
@@ -1669,30 +2252,49 @@
"name": "Marco Pivetta",
"email": "ocramius@gmail.com",
"role": "maintainer"
+ },
+ {
+ "name": "Ilya Tribusean",
+ "email": "slash3b@gmail.com",
+ "role": "maintainer"
}
],
"description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it",
- "time": "2019-01-15T19:39:37+00:00"
+ "support": {
+ "issues": "https://github.com/Roave/SecurityAdvisories/issues",
+ "source": "https://github.com/Roave/SecurityAdvisories/tree/latest"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/Ocramius",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/roave/security-advisories",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-08-12T16:04:45+00:00"
},
{
"name": "sebastian/code-unit-reverse-lookup",
- "version": "1.0.1",
+ "version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
- "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18"
+ "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18",
- "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619",
+ "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619",
"shasum": ""
},
"require": {
- "php": "^5.6 || ^7.0"
+ "php": ">=5.6"
},
"require-dev": {
- "phpunit/phpunit": "^5.7 || ^6.0"
+ "phpunit/phpunit": "^8.5"
},
"type": "library",
"extra": {
@@ -1717,34 +2319,44 @@
],
"description": "Looks up which function or method a line of code belongs to",
"homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
- "time": "2017-03-04T06:30:41+00:00"
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
+ "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-11-30T08:15:22+00:00"
},
{
"name": "sebastian/comparator",
- "version": "1.2.4",
+ "version": "3.0.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
- "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be"
+ "reference": "1071dfcef776a57013124ff35e1fc41ccd294758"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
- "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1071dfcef776a57013124ff35e1fc41ccd294758",
+ "reference": "1071dfcef776a57013124ff35e1fc41ccd294758",
"shasum": ""
},
"require": {
- "php": ">=5.3.3",
- "sebastian/diff": "~1.2",
- "sebastian/exporter": "~1.2 || ~2.0"
+ "php": ">=7.1",
+ "sebastian/diff": "^3.0",
+ "sebastian/exporter": "^3.1"
},
"require-dev": {
- "phpunit/phpunit": "~4.4"
+ "phpunit/phpunit": "^8.5"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.2.x-dev"
+ "dev-master": "3.0-dev"
}
},
"autoload": {
@@ -1757,6 +2369,10 @@
"BSD-3-Clause"
],
"authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
{
"name": "Jeff Welch",
"email": "whatthejeff@gmail.com"
@@ -1768,45 +2384,52 @@
{
"name": "Bernhard Schussek",
"email": "bschussek@2bepublished.at"
- },
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de"
}
],
"description": "Provides the functionality to compare PHP values for equality",
- "homepage": "http://www.github.com/sebastianbergmann/comparator",
+ "homepage": "https://github.com/sebastianbergmann/comparator",
"keywords": [
"comparator",
"compare",
"equality"
],
- "time": "2017-01-29T09:50:25+00:00"
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/comparator/issues",
+ "source": "https://github.com/sebastianbergmann/comparator/tree/3.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-11-30T08:04:30+00:00"
},
{
"name": "sebastian/diff",
- "version": "1.4.3",
+ "version": "3.0.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/diff.git",
- "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4"
+ "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4",
- "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/14f72dd46eaf2f2293cbe79c93cc0bc43161a211",
+ "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211",
"shasum": ""
},
"require": {
- "php": "^5.3.3 || ^7.0"
+ "php": ">=7.1"
},
"require-dev": {
- "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
+ "phpunit/phpunit": "^7.5 || ^8.0",
+ "symfony/process": "^2 || ^3.3 || ^4"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.4-dev"
+ "dev-master": "3.0-dev"
}
},
"autoload": {
@@ -1819,46 +2442,62 @@
"BSD-3-Clause"
],
"authors": [
- {
- "name": "Kore Nordmann",
- "email": "mail@kore-nordmann.de"
- },
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Kore Nordmann",
+ "email": "mail@kore-nordmann.de"
}
],
"description": "Diff implementation",
"homepage": "https://github.com/sebastianbergmann/diff",
"keywords": [
- "diff"
+ "diff",
+ "udiff",
+ "unidiff",
+ "unified diff"
],
- "time": "2017-05-22T07:24:03+00:00"
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/diff/issues",
+ "source": "https://github.com/sebastianbergmann/diff/tree/3.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-11-30T07:59:04+00:00"
},
{
"name": "sebastian/environment",
- "version": "2.0.0",
+ "version": "4.2.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
- "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac"
+ "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac",
- "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/d47bbbad83711771f167c72d4e3f25f7fcc1f8b0",
+ "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0",
"shasum": ""
},
"require": {
- "php": "^5.6 || ^7.0"
+ "php": ">=7.1"
},
"require-dev": {
- "phpunit/phpunit": "^5.0"
+ "phpunit/phpunit": "^7.5"
+ },
+ "suggest": {
+ "ext-posix": "*"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.0.x-dev"
+ "dev-master": "4.2-dev"
}
},
"autoload": {
@@ -1883,34 +2522,44 @@
"environment",
"hhvm"
],
- "time": "2016-11-26T07:53:53+00:00"
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/environment/issues",
+ "source": "https://github.com/sebastianbergmann/environment/tree/4.2.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-11-30T07:53:42+00:00"
},
{
"name": "sebastian/exporter",
- "version": "2.0.0",
+ "version": "3.1.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git",
- "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4"
+ "reference": "0c32ea2e40dbf59de29f3b49bf375176ce7dd8db"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4",
- "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0c32ea2e40dbf59de29f3b49bf375176ce7dd8db",
+ "reference": "0c32ea2e40dbf59de29f3b49bf375176ce7dd8db",
"shasum": ""
},
"require": {
- "php": ">=5.3.3",
- "sebastian/recursion-context": "~2.0"
+ "php": ">=7.0",
+ "sebastian/recursion-context": "^3.0"
},
"require-dev": {
"ext-mbstring": "*",
- "phpunit/phpunit": "~4.4"
+ "phpunit/phpunit": "^8.5"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.0.x-dev"
+ "dev-master": "3.1.x-dev"
}
},
"autoload": {
@@ -1923,6 +2572,10 @@
"BSD-3-Clause"
],
"authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
{
"name": "Jeff Welch",
"email": "whatthejeff@gmail.com"
@@ -1931,17 +2584,13 @@
"name": "Volker Dusch",
"email": "github@wallbash.com"
},
- {
- "name": "Bernhard Schussek",
- "email": "bschussek@2bepublished.at"
- },
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de"
- },
{
"name": "Adam Harvey",
"email": "aharvey@php.net"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
}
],
"description": "Provides the functionality to export PHP variables for visualization",
@@ -1950,66 +2599,37 @@
"export",
"exporter"
],
- "time": "2016-11-19T08:54:04+00:00"
- },
- {
- "name": "sebastian/finder-facade",
- "version": "1.2.2",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/finder-facade.git",
- "reference": "4a3174709c2dc565fe5fb26fcf827f6a1fc7b09f"
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/exporter/issues",
+ "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.4"
},
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/finder-facade/zipball/4a3174709c2dc565fe5fb26fcf827f6a1fc7b09f",
- "reference": "4a3174709c2dc565fe5fb26fcf827f6a1fc7b09f",
- "shasum": ""
- },
- "require": {
- "symfony/finder": "~2.3|~3.0|~4.0",
- "theseer/fdomdocument": "~1.3"
- },
- "type": "library",
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
+ "funding": [
{
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de",
- "role": "lead"
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
}
],
- "description": "FinderFacade is a convenience wrapper for Symfony's Finder component.",
- "homepage": "https://github.com/sebastianbergmann/finder-facade",
- "time": "2017-11-18T17:31:49+00:00"
+ "time": "2021-11-11T13:51:24+00:00"
},
{
"name": "sebastian/global-state",
- "version": "1.1.1",
+ "version": "2.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/global-state.git",
- "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4"
+ "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4",
- "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4",
+ "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4",
"shasum": ""
},
"require": {
- "php": ">=5.3.3"
+ "php": "^7.0"
},
"require-dev": {
- "phpunit/phpunit": "~4.2"
+ "phpunit/phpunit": "^6.0"
},
"suggest": {
"ext-uopz": "*"
@@ -2017,7 +2637,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.0-dev"
+ "dev-master": "2.0-dev"
}
},
"autoload": {
@@ -2040,33 +2660,38 @@
"keywords": [
"global state"
],
- "time": "2015-10-12T03:26:01+00:00"
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/global-state/issues",
+ "source": "https://github.com/sebastianbergmann/global-state/tree/2.0.0"
+ },
+ "time": "2017-04-27T15:39:26+00:00"
},
{
"name": "sebastian/object-enumerator",
- "version": "2.0.1",
+ "version": "3.0.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/object-enumerator.git",
- "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7"
+ "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1311872ac850040a79c3c058bea3e22d0f09cbb7",
- "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2",
+ "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2",
"shasum": ""
},
"require": {
- "php": ">=5.6",
- "sebastian/recursion-context": "~2.0"
+ "php": ">=7.0",
+ "sebastian/object-reflector": "^1.1.1",
+ "sebastian/recursion-context": "^3.0"
},
"require-dev": {
- "phpunit/phpunit": "~5"
+ "phpunit/phpunit": "^6.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.0.x-dev"
+ "dev-master": "3.0.x-dev"
}
},
"autoload": {
@@ -2086,32 +2711,42 @@
],
"description": "Traverses array structures and object graphs to enumerate all referenced objects",
"homepage": "https://github.com/sebastianbergmann/object-enumerator/",
- "time": "2017-02-18T15:18:39+00:00"
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
+ "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-11-30T07:40:27+00:00"
},
{
- "name": "sebastian/recursion-context",
- "version": "2.0.0",
+ "name": "sebastian/object-reflector",
+ "version": "1.1.2",
"source": {
"type": "git",
- "url": "https://github.com/sebastianbergmann/recursion-context.git",
- "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a"
+ "url": "https://github.com/sebastianbergmann/object-reflector.git",
+ "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a",
- "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/9b8772b9cbd456ab45d4a598d2dd1a1bced6363d",
+ "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d",
"shasum": ""
},
"require": {
- "php": ">=5.3.3"
+ "php": ">=7.0"
},
"require-dev": {
- "phpunit/phpunit": "~4.4"
+ "phpunit/phpunit": "^6.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.0.x-dev"
+ "dev-master": "1.1-dev"
}
},
"autoload": {
@@ -2125,13 +2760,68 @@
],
"authors": [
{
- "name": "Jeff Welch",
- "email": "whatthejeff@gmail.com"
- },
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Allows reflection of object attributes, including inherited and non-public ones",
+ "homepage": "https://github.com/sebastianbergmann/object-reflector/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-reflector/issues",
+ "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-11-30T07:37:18+00:00"
+ },
+ {
+ "name": "sebastian/recursion-context",
+ "version": "3.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
+ "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/367dcba38d6e1977be014dc4b22f47a484dac7fb",
+ "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
},
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
{
"name": "Adam Harvey",
"email": "aharvey@php.net"
@@ -2139,29 +2829,39 @@
],
"description": "Provides functionality to recursively process PHP variables",
"homepage": "http://www.github.com/sebastianbergmann/recursion-context",
- "time": "2016-11-19T07:33:16+00:00"
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
+ "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-11-30T07:34:24+00:00"
},
{
"name": "sebastian/resource-operations",
- "version": "1.0.0",
+ "version": "2.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/resource-operations.git",
- "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52"
+ "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52",
- "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52",
+ "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/31d35ca87926450c44eae7e2611d45a7a65ea8b3",
+ "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3",
"shasum": ""
},
"require": {
- "php": ">=5.6.0"
+ "php": ">=7.1"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.0.x-dev"
+ "dev-master": "2.0-dev"
}
},
"autoload": {
@@ -2181,7 +2881,17 @@
],
"description": "Provides a list of PHP built-in functions that operate on resources",
"homepage": "https://www.github.com/sebastianbergmann/resource-operations",
- "time": "2015-07-28T20:34:47+00:00"
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/resource-operations/issues",
+ "source": "https://github.com/sebastianbergmann/resource-operations/tree/2.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-11-30T07:30:19+00:00"
},
{
"name": "sebastian/version",
@@ -2224,68 +2934,45 @@
],
"description": "Library that helps with managing the version number of Git-hosted PHP projects",
"homepage": "https://github.com/sebastianbergmann/version",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/version/issues",
+ "source": "https://github.com/sebastianbergmann/version/tree/master"
+ },
"time": "2016-10-03T07:35:21+00:00"
},
{
"name": "squizlabs/php_codesniffer",
- "version": "2.9.2",
+ "version": "3.7.1",
"source": {
"type": "git",
"url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
- "reference": "2acf168de78487db620ab4bc524135a13cfe6745"
+ "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/2acf168de78487db620ab4bc524135a13cfe6745",
- "reference": "2acf168de78487db620ab4bc524135a13cfe6745",
+ "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/1359e176e9307e906dc3d890bcc9603ff6d90619",
+ "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619",
"shasum": ""
},
"require": {
"ext-simplexml": "*",
"ext-tokenizer": "*",
"ext-xmlwriter": "*",
- "php": ">=5.1.2"
+ "php": ">=5.4.0"
},
"require-dev": {
- "phpunit/phpunit": "~4.0"
+ "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
},
"bin": [
- "scripts/phpcs",
- "scripts/phpcbf"
+ "bin/phpcs",
+ "bin/phpcbf"
],
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.x-dev"
+ "dev-master": "3.x-dev"
}
},
- "autoload": {
- "classmap": [
- "CodeSniffer.php",
- "CodeSniffer/CLI.php",
- "CodeSniffer/Exception.php",
- "CodeSniffer/File.php",
- "CodeSniffer/Fixer.php",
- "CodeSniffer/Report.php",
- "CodeSniffer/Reporting.php",
- "CodeSniffer/Sniff.php",
- "CodeSniffer/Tokens.php",
- "CodeSniffer/Reports/",
- "CodeSniffer/Tokenizers/",
- "CodeSniffer/DocGenerators/",
- "CodeSniffer/Standards/AbstractPatternSniff.php",
- "CodeSniffer/Standards/AbstractScopeSniff.php",
- "CodeSniffer/Standards/AbstractVariableSniff.php",
- "CodeSniffer/Standards/IncorrectPatternException.php",
- "CodeSniffer/Standards/Generic/Sniffs/",
- "CodeSniffer/Standards/MySource/Sniffs/",
- "CodeSniffer/Standards/PEAR/Sniffs/",
- "CodeSniffer/Standards/PSR1/Sniffs/",
- "CodeSniffer/Standards/PSR2/Sniffs/",
- "CodeSniffer/Standards/Squiz/Sniffs/",
- "CodeSniffer/Standards/Zend/Sniffs/"
- ]
- },
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
@@ -2297,206 +2984,37 @@
}
],
"description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
- "homepage": "http://www.squizlabs.com/php-codesniffer",
+ "homepage": "https://github.com/squizlabs/PHP_CodeSniffer",
"keywords": [
"phpcs",
"standards"
],
- "time": "2018-11-07T22:31:41+00:00"
- },
- {
- "name": "symfony/console",
- "version": "v3.4.21",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/console.git",
- "reference": "a700b874d3692bc8342199adfb6d3b99f62cc61a"
+ "support": {
+ "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues",
+ "source": "https://github.com/squizlabs/PHP_CodeSniffer",
+ "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki"
},
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/a700b874d3692bc8342199adfb6d3b99f62cc61a",
- "reference": "a700b874d3692bc8342199adfb6d3b99f62cc61a",
- "shasum": ""
- },
- "require": {
- "php": "^5.5.9|>=7.0.8",
- "symfony/debug": "~2.8|~3.0|~4.0",
- "symfony/polyfill-mbstring": "~1.0"
- },
- "conflict": {
- "symfony/dependency-injection": "<3.4",
- "symfony/process": "<3.3"
- },
- "provide": {
- "psr/log-implementation": "1.0"
- },
- "require-dev": {
- "psr/log": "~1.0",
- "symfony/config": "~3.3|~4.0",
- "symfony/dependency-injection": "~3.4|~4.0",
- "symfony/event-dispatcher": "~2.8|~3.0|~4.0",
- "symfony/lock": "~3.4|~4.0",
- "symfony/process": "~3.3|~4.0"
- },
- "suggest": {
- "psr/log": "For using the console logger",
- "symfony/event-dispatcher": "",
- "symfony/lock": "",
- "symfony/process": ""
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.4-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\Console\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony Console Component",
- "homepage": "https://symfony.com",
- "time": "2019-01-04T04:42:43+00:00"
- },
- {
- "name": "symfony/debug",
- "version": "v3.4.21",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/debug.git",
- "reference": "26d7f23b9bd0b93bee5583e4d6ca5cb1ab31b186"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/debug/zipball/26d7f23b9bd0b93bee5583e4d6ca5cb1ab31b186",
- "reference": "26d7f23b9bd0b93bee5583e4d6ca5cb1ab31b186",
- "shasum": ""
- },
- "require": {
- "php": "^5.5.9|>=7.0.8",
- "psr/log": "~1.0"
- },
- "conflict": {
- "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2"
- },
- "require-dev": {
- "symfony/http-kernel": "~2.8|~3.0|~4.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.4-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\Debug\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony Debug Component",
- "homepage": "https://symfony.com",
- "time": "2019-01-01T13:45:19+00:00"
- },
- {
- "name": "symfony/finder",
- "version": "v3.4.21",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/finder.git",
- "reference": "3f2a2ab6315dd7682d4c16dcae1e7b95c8b8555e"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/3f2a2ab6315dd7682d4c16dcae1e7b95c8b8555e",
- "reference": "3f2a2ab6315dd7682d4c16dcae1e7b95c8b8555e",
- "shasum": ""
- },
- "require": {
- "php": "^5.5.9|>=7.0.8"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.4-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\Finder\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony Finder Component",
- "homepage": "https://symfony.com",
- "time": "2019-01-01T13:45:19+00:00"
+ "time": "2022-06-18T07:21:10+00:00"
},
{
"name": "symfony/polyfill-ctype",
- "version": "v1.10.0",
+ "version": "v1.26.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
- "reference": "e3d826245268269cd66f8326bd8bc066687b4a19"
+ "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19",
- "reference": "e3d826245268269cd66f8326bd8bc066687b4a19",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
+ "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
"shasum": ""
},
"require": {
- "php": ">=5.3.3"
+ "php": ">=7.1"
+ },
+ "provide": {
+ "ext-ctype": "*"
},
"suggest": {
"ext-ctype": "For best performance"
@@ -2504,29 +3022,33 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.9-dev"
+ "dev-main": "1.26-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
- "psr-4": {
- "Symfony\\Polyfill\\Ctype\\": ""
- },
"files": [
"bootstrap.php"
- ]
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Ctype\\": ""
+ }
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- },
{
"name": "Gert de Pagter",
"email": "BackEndTea@gmail.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for ctype functions",
@@ -2537,144 +3059,44 @@
"polyfill",
"portable"
],
- "time": "2018-08-06T14:22:27+00:00"
+ "support": {
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-05-24T11:49:31+00:00"
},
{
- "name": "symfony/polyfill-mbstring",
- "version": "v1.10.0",
+ "name": "theseer/tokenizer",
+ "version": "1.1.3",
"source": {
"type": "git",
- "url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "c79c051f5b3a46be09205c73b80b346e4153e494"
+ "url": "https://github.com/theseer/tokenizer.git",
+ "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494",
- "reference": "c79c051f5b3a46be09205c73b80b346e4153e494",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3.3"
- },
- "suggest": {
- "ext-mbstring": "For best performance"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.9-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Polyfill\\Mbstring\\": ""
- },
- "files": [
- "bootstrap.php"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Nicolas Grekas",
- "email": "p@tchwork.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony polyfill for the Mbstring extension",
- "homepage": "https://symfony.com",
- "keywords": [
- "compatibility",
- "mbstring",
- "polyfill",
- "portable",
- "shim"
- ],
- "time": "2018-09-21T13:07:52+00:00"
- },
- {
- "name": "symfony/yaml",
- "version": "v3.4.21",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/yaml.git",
- "reference": "554a59a1ccbaac238a89b19c8e551a556fd0e2ea"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/554a59a1ccbaac238a89b19c8e551a556fd0e2ea",
- "reference": "554a59a1ccbaac238a89b19c8e551a556fd0e2ea",
- "shasum": ""
- },
- "require": {
- "php": "^5.5.9|>=7.0.8",
- "symfony/polyfill-ctype": "~1.8"
- },
- "conflict": {
- "symfony/console": "<3.4"
- },
- "require-dev": {
- "symfony/console": "~3.4|~4.0"
- },
- "suggest": {
- "symfony/console": "For validating YAML files using the lint command"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.4-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\Yaml\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony Yaml Component",
- "homepage": "https://symfony.com",
- "time": "2019-01-01T13:45:19+00:00"
- },
- {
- "name": "theseer/fdomdocument",
- "version": "1.6.6",
- "source": {
- "type": "git",
- "url": "https://github.com/theseer/fDOMDocument.git",
- "reference": "6e8203e40a32a9c770bcb62fe37e68b948da6dca"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/theseer/fDOMDocument/zipball/6e8203e40a32a9c770bcb62fe37e68b948da6dca",
- "reference": "6e8203e40a32a9c770bcb62fe37e68b948da6dca",
+ "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9",
+ "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9",
"shasum": ""
},
"require": {
"ext-dom": "*",
- "lib-libxml": "*",
- "php": ">=5.3.3"
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": "^7.0"
},
"type": "library",
"autoload": {
@@ -2690,41 +3112,42 @@
{
"name": "Arne Blankerts",
"email": "arne@blankerts.de",
- "role": "lead"
+ "role": "Developer"
}
],
- "description": "The classes contained within this repository extend the standard DOM to use exceptions at all occasions of errors instead of PHP warnings or notices. They also add various custom methods and shortcuts for convenience and to simplify the usage of DOM.",
- "homepage": "https://github.com/theseer/fDOMDocument",
- "time": "2017-06-30T11:53:12+00:00"
+ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
+ "support": {
+ "issues": "https://github.com/theseer/tokenizer/issues",
+ "source": "https://github.com/theseer/tokenizer/tree/master"
+ },
+ "time": "2019-06-13T22:48:21+00:00"
},
{
"name": "webmozart/assert",
- "version": "1.4.0",
+ "version": "1.9.1",
"source": {
"type": "git",
- "url": "https://github.com/webmozart/assert.git",
- "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9"
+ "url": "https://github.com/webmozarts/assert.git",
+ "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/webmozart/assert/zipball/83e253c8e0be5b0257b881e1827274667c5c17a9",
- "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9",
+ "url": "https://api.github.com/repos/webmozarts/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389",
+ "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389",
"shasum": ""
},
"require": {
- "php": "^5.3.3 || ^7.0",
+ "php": "^5.3.3 || ^7.0 || ^8.0",
"symfony/polyfill-ctype": "^1.8"
},
+ "conflict": {
+ "phpstan/phpstan": "<0.12.20",
+ "vimeo/psalm": "<3.9.1"
+ },
"require-dev": {
- "phpunit/phpunit": "^4.6",
- "sebastian/version": "^1.0.1"
+ "phpunit/phpunit": "^4.8.36 || ^7.5.13"
},
"type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.3-dev"
- }
- },
"autoload": {
"psr-4": {
"Webmozart\\Assert\\": "src/"
@@ -2746,7 +3169,11 @@
"check",
"validate"
],
- "time": "2018-12-25T11:19:39+00:00"
+ "support": {
+ "issues": "https://github.com/webmozarts/assert/issues",
+ "source": "https://github.com/webmozarts/assert/tree/1.9.1"
+ },
+ "time": "2020-07-08T17:02:28+00:00"
}
],
"aliases": [],
@@ -2758,12 +3185,13 @@
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
- "php": ">=5.6",
+ "php": ">=7.1",
"ext-json": "*",
"ext-zlib": "*"
},
"platform-dev": [],
"platform-overrides": {
- "php": "5.6.31"
- }
+ "php": "7.1.29"
+ },
+ "plugin-api-version": "2.1.0"
}
diff --git a/doc/custom_theme/main.html b/doc/custom_theme/main.html
index cc2a703e..96bb153e 100644
--- a/doc/custom_theme/main.html
+++ b/doc/custom_theme/main.html
@@ -2,7 +2,7 @@
{#
The entry point for the ReadTheDocs Theme.
-
+
Any theme customisations should override this file to redefine blocks defined in
the various templates. The custom theme should only need to define a main.html
which `{% extends "base.html" %}` and defines various blocks which will replace
@@ -14,7 +14,7 @@ the blocks defined in base.html and its included child templates.
-{%- if 'media.readthedocs.org' not in config.extra_css[0] %}
+{%- if config.extra_css|length and 'media.readthedocs.org' not in config.extra_css[0] %}
{%- endif %}
diff --git a/doc/md/dev/images/poedit-1.jpg b/doc/md/dev/images/poedit-1.jpg
new file mode 100644
index 00000000..673ae6d6
Binary files /dev/null and b/doc/md/dev/images/poedit-1.jpg differ
diff --git a/doc/md/images/07-installation.jpg b/doc/md/images/07-installation.jpg
new file mode 100644
index 00000000..42cc9f10
Binary files /dev/null and b/doc/md/images/07-installation.jpg differ
diff --git a/inc/languages/de/LC_MESSAGES/shaarli.po b/inc/languages/de/LC_MESSAGES/shaarli.po
index 34d29ce8..f46f66fa 100644
--- a/inc/languages/de/LC_MESSAGES/shaarli.po
+++ b/inc/languages/de/LC_MESSAGES/shaarli.po
@@ -2,15 +2,15 @@ msgid ""
msgstr ""
"Project-Id-Version: Shaarli\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-03-31 09:09+0200\n"
-"PO-Revision-Date: 2018-03-31 09:12+0200\n"
+"POT-Creation-Date: 2021-01-23 23:57+0100\n"
+"PO-Revision-Date: 2021-01-24 00:37+0100\n"
"Last-Translator: \n"
"Language-Team: Shaarli\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Generator: Poedit 2.0.6\n"
+"X-Generator: Poedit 2.4.2\n"
"X-Poedit-Basepath: ../../../..\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Poedit-SourceCharset: UTF-8\n"
@@ -19,7 +19,614 @@ msgstr ""
"X-Poedit-SearchPathExcluded-0: node_modules\n"
"X-Poedit-SearchPathExcluded-1: vendor\n"
-#: application/ApplicationUtils.php:153
+#: application/History.php:181
+msgid "History file isn't readable or writable"
+msgstr "Protokolldatei nicht lesbar oder beschreibbar"
+
+#: application/History.php:192
+msgid "Could not parse history file"
+msgstr "Protokolldatei konnte nicht analysiert werden"
+
+#: application/Languages.php:184
+msgid "Automatic"
+msgstr "Automatisch"
+
+#: application/Languages.php:185
+msgid "German"
+msgstr "Deutsch"
+
+#: application/Languages.php:186
+msgid "English"
+msgstr "Englisch"
+
+#: application/Languages.php:187
+msgid "French"
+msgstr "Französisch"
+
+#: application/Languages.php:188
+msgid "Japanese"
+msgstr "Japanisch"
+
+#: application/Languages.php:189
+msgid "Russian"
+msgstr "Russisch"
+
+#: application/Thumbnailer.php:62
+msgid ""
+"php-gd extension must be loaded to use thumbnails. Thumbnails are now "
+"disabled. Please reload the page."
+msgstr ""
+"Die Erweiterung php-gd muss geladen werden, um Miniaturansichten "
+"(Thumbnails) verwenden zu können. Thumbnails sind jetzt deaktiviert. Bitte "
+"lade die Seite neu."
+
+#: application/Utils.php:405 tests/UtilsTest.php:327
+msgid "Setting not set"
+msgstr "Einstellung nicht gesetzt"
+
+#: application/Utils.php:412 tests/UtilsTest.php:325 tests/UtilsTest.php:326
+msgid "Unlimited"
+msgstr "Unbegrenzt"
+
+#: application/Utils.php:415 tests/UtilsTest.php:322 tests/UtilsTest.php:323
+#: tests/UtilsTest.php:337
+msgid "B"
+msgstr "B"
+
+#: application/Utils.php:415 tests/UtilsTest.php:316 tests/UtilsTest.php:317
+#: tests/UtilsTest.php:324
+msgid "kiB"
+msgstr "kiB"
+
+#: application/Utils.php:415 tests/UtilsTest.php:318 tests/UtilsTest.php:319
+#: tests/UtilsTest.php:335 tests/UtilsTest.php:336
+msgid "MiB"
+msgstr "MiB"
+
+#: application/Utils.php:415 tests/UtilsTest.php:320 tests/UtilsTest.php:321
+msgid "GiB"
+msgstr "GiB"
+
+#: application/bookmark/BookmarkFileService.php:185
+#: application/bookmark/BookmarkFileService.php:207
+#: application/bookmark/BookmarkFileService.php:229
+#: application/bookmark/BookmarkFileService.php:243
+msgid "You're not authorized to alter the datastore"
+msgstr "Du bist nicht berechtigt, den Datenspeicher zu ändern"
+
+#: application/bookmark/BookmarkFileService.php:210
+msgid "This bookmarks already exists"
+msgstr "Diese Lesezeichen sind bereits vorhanden"
+
+#: application/bookmark/BookmarkInitializer.php:42
+msgid "(private bookmark with thumbnail demo)"
+msgstr "(privates Lesezeichen mit Thumbnail-Demo)"
+
+#: application/bookmark/BookmarkInitializer.php:45
+msgid ""
+"Shaarli will automatically pick up the thumbnail for links to a variety of "
+"websites.\n"
+"\n"
+"Explore your new Shaarli instance by trying out controls and menus.\n"
+"Visit the project on [Github](https://github.com/shaarli/Shaarli) or [the "
+"documentation](https://shaarli.readthedocs.io/en/master/) to learn more "
+"about Shaarli.\n"
+"\n"
+"Now you can edit or delete the default shaares.\n"
+msgstr ""
+"Shaarli holt sich automatisch das Miniaturbild (Thumbnail) für Links zu "
+"verschiedenen Websites.\n"
+"\n"
+"Erkunde Deine neue Shaarli-Instanz, indem Du Steuerelemente und Menüs "
+"ausprobierst.\n"
+"Besuche das Projekt auf [Github](https://github.com/shaarli/Shaarli) oder "
+"[die Dokumentation](https://shaarli.readthedocs.io/en/master/), um mehr über "
+"Shaarli zu erfahren.\n"
+"\n"
+"Jetzt kannst Du die Standard-Shaares bearbeiten oder löschen.\n"
+
+#: application/bookmark/BookmarkInitializer.php:58
+msgid "Note: Shaare descriptions"
+msgstr "Hinweis: Shaare-Beschreibungen"
+
+#: application/bookmark/BookmarkInitializer.php:60
+msgid ""
+"Adding a shaare without entering a URL creates a text-only \"note\" post "
+"such as this one.\n"
+"This note is private, so you are the only one able to see it while logged "
+"in.\n"
+"\n"
+"You can use this to keep notes, post articles, code snippets, and much "
+"more.\n"
+"\n"
+"The Markdown formatting setting allows you to format your notes and bookmark "
+"description:\n"
+"\n"
+"### Title headings\n"
+"\n"
+"#### Multiple headings levels\n"
+" * bullet lists\n"
+" * _italic_ text\n"
+" * **bold** text\n"
+" * ~~strike through~~ text\n"
+" * `code` blocks\n"
+" * images\n"
+" * [links](https://en.wikipedia.org/wiki/Markdown)\n"
+"\n"
+"Markdown also supports tables:\n"
+"\n"
+"| Name | Type | Color | Qty |\n"
+"| ------- | --------- | ------ | ----- |\n"
+"| Orange | Fruit | Orange | 126 |\n"
+"| Apple | Fruit | Any | 62 |\n"
+"| Lemon | Fruit | Yellow | 30 |\n"
+"| Carrot | Vegetable | Red | 14 |\n"
+msgstr ""
+"Durch Hinzufügen eines Shaare ohne Eingabe einer URL wird ein Nur-Text-"
+"Notizbeitrag wie dieser erstellt.\n"
+"Diese Notiz ist privat, sodass Du sie als einziger sehen kannst, während Du "
+"angemeldet bist.\n"
+"\n"
+"Du kannst dies nutzen, um Notizen zu machen, Artikel, Codefragmente und "
+"vieles mehr zu veröffentlichen.\n"
+"\n"
+"Mit der Markdown-Formatierungseinstellung kannst Du Deine Notizen und die "
+"Lesezeichenbeschreibung formatieren:\n"
+"\n"
+"### Titel-Überschrift\n"
+"\n"
+"#### Mehrere Überschriftenebenen\n"
+" * bullet lists\n"
+" * _kursiver_ Text\n"
+" * **fetter** Text\n"
+" * ~~durchgestrichener~~ Text\n"
+" * `Code` Blöcke\n"
+" * Bilder\n"
+" * [Links](https://en.wikipedia.org/wiki/Markdown)\n"
+"\n"
+"Markdown unterstützt auch Tabellen:\n"
+"\n"
+"| Name | Typ | Farbe | Menge |\n"
+"| ------- | --------- | ------ | ----- |\n"
+"| Orange | Frucht | orange | 126 |\n"
+"| Apfel | Frucht | verschiedene | 62 |\n"
+"| Lemon | Frucht | gelb | 30 |\n"
+"| Karotte | Gemüse | rot | 14 |\n"
+
+#: application/bookmark/BookmarkInitializer.php:94
+#: application/legacy/LegacyLinkDB.php:246
+#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
+#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
+#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:15
+#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:48
+msgid ""
+"The personal, minimalist, super-fast, database free, bookmarking service"
+msgstr ""
+"Der persönliche, minimalistische, superschnelle, datenbankfreie "
+"Lesezeichenservice"
+
+#: application/bookmark/BookmarkInitializer.php:97
+msgid ""
+"Welcome to Shaarli!\n"
+"\n"
+"Shaarli allows you to bookmark your favorite pages, and share them with "
+"others or store them privately.\n"
+"You can add a description to your bookmarks, such as this one, and tag "
+"them.\n"
+"\n"
+"Create a new shaare by clicking the `+Shaare` button, or using any of the "
+"recommended tools (browser extension, mobile app, bookmarklet, REST API, "
+"etc.).\n"
+"\n"
+"You can easily retrieve your links, even with thousands of them, using the "
+"internal search engine, or search through tags (e.g. this Shaare is tagged "
+"with `shaarli` and `help`).\n"
+"Hashtags such as #shaarli #help are also supported.\n"
+"You can also filter the available [RSS feed](/feed/atom) and picture wall by "
+"tag or plaintext search.\n"
+"\n"
+"We hope that you will enjoy using Shaarli, maintained with ❤️ by the "
+"community!\n"
+"Feel free to open [an issue](https://github.com/shaarli/Shaarli/issues) if "
+"you have a suggestion or encounter an issue.\n"
+msgstr ""
+"Willkommen bei Shaarli!\n"
+"\n"
+"Mit Shaarli kannst Du Lesezeichen für Deine Lieblingsseiten anlegen und mit "
+"anderen teilen oder privat speichern.\n"
+"Du kannst Lesezeichen wie diesem eine Beschreibung hinzufügen und sie mit "
+"Tags versehen.\n"
+"\n"
+"Erstelle eine neue Shaare, indem Du auf die Schaltfläche \"+ Shaare\" "
+"klickst oder eines der empfohlenen Tools (Browsererweiterung, mobile App, "
+"Lesezeichen, REST-API usw.) verwendest.\n"
+"\n"
+"Du kannst Deine Links - auch mit Tausenden von ihnen- einfach über die "
+"interne Suchmaschine abrufen oder Tags durchsuchen (z. B. ist diese Shaare "
+"mit \"shaarli\" und \"help\" gekennzeichnet).\n"
+"Hashtags wie #shaarli #help werden ebenfalls unterstützt.\n"
+"Du kannst den verfügbaren [RSS-Feed] (/feed/atom) und die Bilderwand auch "
+"nach Tag- oder Klartextsuche filtern.\n"
+"\n"
+"Wir hoffen, dass Du Shaarli schätzen wirst, das von der Community mit ❤️ "
+"gepflegt wird!\n"
+"Du kannst gerne [ein Problem] (https://github.com/shaarli/Shaarli/issues) "
+"öffnen, wenn Du einen Vorschlag hast oder auf ein Problem stößt.\n"
+
+#: application/bookmark/exception/BookmarkNotFoundException.php:14
+msgid "The link you are trying to reach does not exist or has been deleted."
+msgstr ""
+"Den Link, den du versucht zu erreichen, existiert nicht oder wurde gelöscht."
+
+#: application/config/ConfigJson.php:52 application/config/ConfigPhp.php:131
+msgid ""
+"Shaarli could not create the config file. Please make sure Shaarli has the "
+"right to write in the folder is it installed in."
+msgstr ""
+"Shaarli konnte die Konfigurationsdatei nicht erstellen. Bitte stelle sicher, "
+"dass Shaarli das Recht hat, in den Ordner zu schreiben, in dem es "
+"installiert ist."
+
+#: application/config/ConfigManager.php:137
+#: application/config/ConfigManager.php:164
+msgid "Invalid setting key parameter. String expected, got: "
+msgstr ""
+"Ungültiger Parameter für den Einstellungsschlüssel. Zeichenfolge erwartet, "
+"erhalten: "
+
+#: application/config/exception/MissingFieldConfigException.php:20
+#, php-format
+msgid "Configuration value is required for %s"
+msgstr "Konfigurationswert erforderlich für %s"
+
+#: application/config/exception/PluginConfigOrderException.php:15
+msgid "An error occurred while trying to save plugins loading order."
+msgstr ""
+"Beim Versuch, die Ladereihenfolge der Plugins zu speichern, ist ein Fehler "
+"aufgetreten."
+
+#: application/config/exception/UnauthorizedConfigException.php:15
+msgid "You are not authorized to alter config."
+msgstr "Du bist nicht berechtigt, die Konfiguration zu ändern."
+
+#: application/exceptions/IOException.php:23
+msgid "Error accessing"
+msgstr "Fehler beim Zugriff"
+
+#: application/feed/FeedBuilder.php:180
+msgid "Direct link"
+msgstr "Direct Link"
+
+#: application/feed/FeedBuilder.php:182
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:103
+#: tmp/dailyrss.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:179
+msgid "Permalink"
+msgstr "Permalink"
+
+#: application/front/controller/admin/ConfigureController.php:56
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24
+msgid "Configure"
+msgstr "Konfigurieren"
+
+#: application/front/controller/admin/ConfigureController.php:106
+#: application/legacy/LegacyUpdater.php:539
+msgid "You have enabled or changed thumbnails mode."
+msgstr "Du hast den Miniaturansichten-Modus aktiviert oder geändert."
+
+#: application/front/controller/admin/ConfigureController.php:108
+#: application/front/controller/admin/ServerController.php:81
+#: application/legacy/LegacyUpdater.php:540
+msgid "Please synchronize them."
+msgstr "Bitte synchronisiere sie."
+
+#: application/front/controller/admin/ConfigureController.php:119
+#: application/front/controller/visitor/InstallController.php:154
+msgid "Error while writing config file after configuration update."
+msgstr ""
+"Fehler beim Schreiben der Einstellungsdatei nach der "
+"Konfigurationsaktualisierung."
+
+#: application/front/controller/admin/ConfigureController.php:128
+msgid "Configuration was saved."
+msgstr "Konfiguration wurde gespeichert."
+
+#: application/front/controller/admin/ExportController.php:26
+#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:64
+msgid "Export"
+msgstr "Exportieren"
+
+#: application/front/controller/admin/ExportController.php:42
+msgid "Please select an export mode."
+msgstr "Bitte wähle einen Export-Modus."
+
+#: application/front/controller/admin/ImportController.php:41
+#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:83
+msgid "Import"
+msgstr "Importieren"
+
+#: application/front/controller/admin/ImportController.php:55
+msgid "No import file provided."
+msgstr "Keine Import-Datei übergeben."
+
+#: application/front/controller/admin/ImportController.php:66
+#, php-format
+msgid ""
+"The file you are trying to upload is probably bigger than what this "
+"webserver can accept (%s). Please upload in smaller chunks."
+msgstr ""
+"Die Datei, die du hochladen möchtest, ist wahrscheinlich größer als das, was "
+"dieser Webserver akzeptieren kann (%s). Bitte lade in kleineren Blöcken hoch."
+
+#: application/front/controller/admin/ManageTagController.php:30
+msgid "whitespace"
+msgstr "Leerzeichen"
+
+#: application/front/controller/admin/ManageTagController.php:35
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42
+msgid "Manage tags"
+msgstr "Tags verwalten"
+
+#: application/front/controller/admin/ManageTagController.php:54
+msgid "Invalid tags provided."
+msgstr "Ungültige Tags übergeben."
+
+#: application/front/controller/admin/ManageTagController.php:78
+#, php-format
+msgid "The tag was removed from %d bookmark."
+msgid_plural "The tag was removed from %d bookmarks."
+msgstr[0] "Der Tag wurde aus dem Lesezeichen %d entfernt."
+msgstr[1] "Der Tag wurde aus den Lesezeichen %d entfernt."
+
+#: application/front/controller/admin/ManageTagController.php:83
+#, php-format
+msgid "The tag was renamed in %d bookmark."
+msgid_plural "The tag was renamed in %d bookmarks."
+msgstr[0] "Der Tag wurde im Lesezeichen %d umbenannt."
+msgstr[1] "Der Tag wurde in den Lesezeichen %d umbenannt."
+
+#: application/front/controller/admin/ManageTagController.php:105
+msgid "Tags separator must be a single character."
+msgstr "Tags müssen durch ein einzelnen Zeichen getrennt werden."
+
+#: application/front/controller/admin/ManageTagController.php:111
+msgid "These characters are reserved and can't be used as tags separator: "
+msgstr ""
+"Diese Zeichen sind reserviert und können nicht als Tag-Trennzeichen genutzt "
+"werden: "
+
+#: application/front/controller/admin/PasswordController.php:28
+#: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35
+msgid "Change password"
+msgstr "Passwort ändern"
+
+#: application/front/controller/admin/PasswordController.php:55
+msgid "You must provide the current and new password to change it."
+msgstr "Du musst das aktuelle und das neue Passwort angeben zur Änderung."
+
+#: application/front/controller/admin/PasswordController.php:71
+msgid "The old password is not correct."
+msgstr "Das alte Passwort ist nicht korrekt."
+
+#: application/front/controller/admin/PasswordController.php:97
+msgid "Your password has been changed"
+msgstr "Dein Passwort wurde geändert"
+
+#: application/front/controller/admin/PluginsController.php:45
+msgid "Plugin Administration"
+msgstr "Plugin Administration"
+
+#: application/front/controller/admin/PluginsController.php:76
+msgid "Setting successfully saved."
+msgstr "Einstellung wurde erfolgreich gespeichert."
+
+#: application/front/controller/admin/PluginsController.php:79
+msgid "Error while saving plugin configuration: "
+msgstr "Fehler beim Speichern der Plugin-Konfiguration: "
+
+#: application/front/controller/admin/ServerController.php:35
+msgid "Check disabled"
+msgstr "Prüfung deaktiviert"
+
+#: application/front/controller/admin/ServerController.php:62
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
+msgid "Server administration"
+msgstr "Server-Administration"
+
+#: application/front/controller/admin/ServerController.php:79
+msgid "Thumbnails cache has been cleared."
+msgstr "Zwischenspeicher der Miniaturansichten wurde geleert."
+
+#: application/front/controller/admin/ServerController.php:90
+msgid "Shaarli's cache folder has been cleared!"
+msgstr "Der Zwischenspeicher-Ordner von Shaarli wurde geleert!"
+
+#: application/front/controller/admin/ShaareAddController.php:26
+#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
+msgid "Shaare a new link"
+msgstr "Teile einen neuen Link"
+
+#: application/front/controller/admin/ShaareManageController.php:35
+#: application/front/controller/admin/ShaareManageController.php:93
+msgid "Invalid bookmark ID provided."
+msgstr "Ungültige Lesezeichen-ID bereitgestellt."
+
+#: application/front/controller/admin/ShaareManageController.php:47
+#: application/front/controller/admin/ShaareManageController.php:116
+#: application/front/controller/admin/ShaareManageController.php:156
+#: application/front/controller/admin/ShaarePublishController.php:82
+#, php-format
+msgid "Bookmark with identifier %s could not be found."
+msgstr "Lesezeichen mit der ID %s konnte nicht gefunden werden."
+
+#: application/front/controller/admin/ShaareManageController.php:101
+msgid "Invalid visibility provided."
+msgstr "Ungültige Sichtbarkeit angegeben."
+
+#: application/front/controller/admin/ShaarePublishController.php:173
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:171
+msgid "Edit"
+msgstr "Bearbeiten"
+
+#: application/front/controller/admin/ShaarePublishController.php:176
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:28
+msgid "Shaare"
+msgstr "Teilen"
+
+#: application/front/controller/admin/ShaarePublishController.php:208
+msgid "Note: "
+msgstr "Notiz: "
+
+#: application/front/controller/admin/ThumbnailsController.php:37
+#: tmp/thumbnails.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
+msgid "Thumbnails update"
+msgstr "Thumbnail-Aktualisierung"
+
+#: application/front/controller/admin/ToolsController.php:31
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:33
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:33
+msgid "Tools"
+msgstr "Tools"
+
+#: application/front/controller/visitor/BookmarkListController.php:121
+msgid "Search: "
+msgstr "Suche: "
+
+#: application/front/controller/visitor/DailyController.php:201
+msgid "day"
+msgstr "Tag"
+
+#: application/front/controller/visitor/DailyController.php:201
+#: application/front/controller/visitor/DailyController.php:204
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:48
+msgid "Daily"
+msgstr "Täglich"
+
+#: application/front/controller/visitor/DailyController.php:202
+msgid "week"
+msgstr "Woche"
+
+#: application/front/controller/visitor/DailyController.php:202
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
+msgid "Weekly"
+msgstr "Wöchentlich"
+
+#: application/front/controller/visitor/DailyController.php:203
+msgid "month"
+msgstr "Monat"
+
+#: application/front/controller/visitor/DailyController.php:203
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
+msgid "Monthly"
+msgstr "Monatlich"
+
+#: application/front/controller/visitor/ErrorController.php:30
+msgid "Error: "
+msgstr "Fehler: "
+
+#: application/front/controller/visitor/ErrorController.php:34
+msgid "Please report it on Github."
+msgstr "Bitte berichte es bei Github."
+
+#: application/front/controller/visitor/ErrorController.php:39
+msgid "An unexpected error occurred."
+msgstr "Ein unerwarteter Fehler ist aufgetreten."
+
+#: application/front/controller/visitor/ErrorNotFoundController.php:25
+msgid "Requested page could not be found."
+msgstr "Angefragte Seite kann nicht gefunden werden."
+
+#: application/front/controller/visitor/InstallController.php:70
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22
+msgid "Install Shaarli"
+msgstr "Installiere Shaarli"
+
+#: application/front/controller/visitor/InstallController.php:90
+#, php-format
+msgid ""
+"
Sessions do not seem to work correctly on your server.
Make sure the "
+"variable \"session.save_path\" is set correctly in your PHP config, and that "
+"you have write access to it.
It currently points to %s.
On some "
+"browsers, accessing your server via a hostname like 'localhost' or any "
+"custom hostname without a dot causes cookie storage to fail. We recommend "
+"accessing your server via it's IP address or Fully Qualified Domain Name.
"
+msgstr ""
+"Sessions scheinen auf deinem Server nicht korrekt zu funktionieren. "
+"
Stelle sicher, dass die Variable \"session.save_path\" in deiner PHP-"
+"Konfiguration richtig eingestellt ist und dass du Schreibzugriff darauf hast."
+"
Es verweist aktuell auf %s.
Bei einigen Browsern führt der Zugriff "
+"auf deinen Server über einen Hostnamen wie \"localhost\" oder einen "
+"beliebigen benutzerdefinierten Hostnamen ohne Punkt dazu, dass der Cookie-"
+"Speicher fehlschlägt. Wir empfehlen den Zugriff auf deinen Server über die "
+"IP-Adresse oder den Fully Qualified Domain Namen.
"
+
+#: application/front/controller/visitor/InstallController.php:162
+msgid ""
+"Shaarli is now configured. Please login and start shaaring your bookmarks!"
+msgstr ""
+"Shaarli ist nun konfiguriert. Bitte melden Dich an und teile Deine "
+"Lesezeichen!"
+
+#: application/front/controller/visitor/InstallController.php:176
+msgid "Insufficient permissions:"
+msgstr "Unzureichende Berechtigungen:"
+
+#: application/front/controller/visitor/LoginController.php:46
+#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
+#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:101
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:77
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:101
+msgid "Login"
+msgstr "Einloggen"
+
+#: application/front/controller/visitor/LoginController.php:78
+msgid "Wrong login/password."
+msgstr "Falscher Loging/Passwort."
+
+#: application/front/controller/visitor/PictureWallController.php:29
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:43
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:43
+msgid "Picture wall"
+msgstr "Bildwand"
+
+#: application/front/controller/visitor/TagCloudController.php:90
+msgid "Tag "
+msgstr "Tag Liste "
+
+#: application/front/exceptions/AlreadyInstalledException.php:11
+msgid "Shaarli has already been installed. Login to edit the configuration."
+msgstr ""
+"Shaarlie wurde bereits installiert. Melde Dich an zum Ändern der "
+"Konfiguration."
+
+#: application/front/exceptions/LoginBannedException.php:11
+msgid ""
+"You have been banned after too many failed login attempts. Try again later."
+msgstr ""
+"Du wurdest nach zu vielen fehlgeschlagenen Anmeldeversuchen gesperrt. "
+"Versuche es später noch einmal."
+
+#: application/front/exceptions/OpenShaarliPasswordException.php:16
+msgid "You are not supposed to change a password on an Open Shaarli."
+msgstr "Du darfst kein Passwort für ein offenes Shaarli ändern."
+
+#: application/front/exceptions/ThumbnailsDisabledException.php:11
+msgid "Picture wall unavailable (thumbnails are disabled)."
+msgstr "Bildwand ist nicht verfügbar (Miniaturansichten sind deaktiviert)."
+
+#: application/front/exceptions/WrongTokenException.php:16
+msgid "Wrong token."
+msgstr "Falsches Token."
+
+#: application/helper/ApplicationUtils.php:165
#, php-format
msgid ""
"Your PHP version is obsolete! Shaarli requires at least PHP %s, and thus "
@@ -30,89 +637,100 @@ msgstr ""
"daher nicht laufen. Deine PHP-Version hat bekannte Sicherheitslücken und "
"sollte so bald wie möglich aktualisiert werden."
-#: application/ApplicationUtils.php:183 application/ApplicationUtils.php:195
+#: application/helper/ApplicationUtils.php:200
+#: application/helper/ApplicationUtils.php:220
msgid "directory is not readable"
msgstr "Verzeichnis ist nicht lesbar"
-#: application/ApplicationUtils.php:198
+#: application/helper/ApplicationUtils.php:223
msgid "directory is not writable"
msgstr "Verzeichnis ist nicht beschreibbar"
-#: application/ApplicationUtils.php:216
+#: application/helper/ApplicationUtils.php:247
msgid "file is not readable"
msgstr "Datei ist nicht lesbar"
-#: application/ApplicationUtils.php:219
+#: application/helper/ApplicationUtils.php:250
msgid "file is not writable"
msgstr "Datei ist nicht beschreibbar"
-#: application/Cache.php:16
-#, php-format
-msgid "Cannot purge %s: no directory"
-msgstr "Kann nicht löschen, %s ist kein Verzeichnis"
+#: application/helper/ApplicationUtils.php:265
+msgid ""
+"Lock can not be acquired on the datastore. You might encounter concurrent "
+"access issues."
+msgstr ""
+"Der Datenspeicher kann nicht gesperrt werden. Möglicherweise treten Probleme "
+"beim gleichzeitigen Zugriff auf."
-#: application/FeedBuilder.php:151
-msgid "Direct link"
-msgstr "Direct Link"
+#: application/helper/ApplicationUtils.php:298
+msgid "Configuration parsing"
+msgstr "Konfigurationsanalyse"
-#: application/FeedBuilder.php:153
-#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:88
-#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:178
-msgid "Permalink"
-msgstr "Permalink"
+#: application/helper/ApplicationUtils.php:299
+msgid "Slim Framework (routing, etc.)"
+msgstr "Slim Framework (Routing usw.)"
-#: application/History.php:174
-msgid "History file isn't readable or writable"
-msgstr "Protokolldatei nicht lesbar oder beschreibbar"
+#: application/helper/ApplicationUtils.php:300
+msgid "Multibyte (Unicode) string support"
+msgstr "Unterstützung für Multibyte-Zeichenfolgen (Unicode)"
-#: application/History.php:185
-msgid "Could not parse history file"
-msgstr "Protokolldatei konnte nicht analysiert werden"
+#: application/helper/ApplicationUtils.php:301
+msgid "Required to use thumbnails"
+msgstr "Erforderlich, um Miniaturansichten (Thumbnails) zu verwenden"
-#: application/Languages.php:177
-msgid "Automatic"
-msgstr "Automatisch"
+#: application/helper/ApplicationUtils.php:302
+msgid "Localized text sorting (e.g. e->è->f)"
+msgstr "Lokalisierte Textsortierung (z. B. e->è->f)"
-#: application/Languages.php:178
-msgid "English"
-msgstr "Englisch"
+#: application/helper/ApplicationUtils.php:303
+msgid "Better retrieval of bookmark metadata and thumbnail"
+msgstr "Besserer Abruf von Lesezeichen-Metadaten und Miniaturansichten"
-#: application/Languages.php:179
-msgid "French"
-msgstr "Französisch"
+#: application/helper/ApplicationUtils.php:304
+msgid "Use the translation system in gettext mode"
+msgstr "Verwende das Übersetzungssystem im gettext-Modus"
-#: application/Languages.php:180
-msgid "German"
-msgstr "Deutsch"
+#: application/helper/ApplicationUtils.php:305
+msgid "Login using LDAP server"
+msgstr "Anmeldung mittels LDAP-Server"
-#: application/LinkDB.php:136
+#: application/helper/DailyPageHelper.php:179
+msgid "Week"
+msgstr "Woche"
+
+#: application/helper/DailyPageHelper.php:183
+msgid "Today"
+msgstr "Heute"
+
+#: application/helper/DailyPageHelper.php:185
+msgid "Yesterday"
+msgstr "Gestern"
+
+#: application/helper/FileUtils.php:100
+msgid "Provided path is not a directory."
+msgstr "Der angegebene Pfad ist kein Verzeichnis."
+
+#: application/helper/FileUtils.php:104
+msgid "Trying to delete a folder outside of Shaarli path."
+msgstr "Versuch, einen Ordner außerhalb des Shaarli-Pfads zu löschen."
+
+#: application/legacy/LegacyLinkDB.php:131
msgid "You are not authorized to add a link."
msgstr "Du bist nicht berechtigt einen Link hinzuzufügen."
-#: application/LinkDB.php:139
+#: application/legacy/LegacyLinkDB.php:134
msgid "Internal Error: A link should always have an id and URL."
msgstr "Interner Fehler: Ein Link sollte immer eine ID und URL haben."
-#: application/LinkDB.php:142
+#: application/legacy/LegacyLinkDB.php:137
msgid "You must specify an integer as a key."
msgstr "Du musst eine Ganzzahl als Schlüssel angeben."
-#: application/LinkDB.php:145
+#: application/legacy/LegacyLinkDB.php:140
msgid "Array offset and link ID must be equal."
msgstr "Array-Offset und Link-ID müssen gleich sein."
-#: application/LinkDB.php:251
-#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
-#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
-#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:14
-#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:48
-msgid ""
-"The personal, minimalist, super-fast, database free, bookmarking service"
-msgstr ""
-"Der persönliche, minimalistische, superschnelle, datenbankfreie "
-"Lesezeichenservice"
-
-#: application/LinkDB.php:253
+#: application/legacy/LegacyLinkDB.php:249
msgid ""
"Welcome to Shaarli! This is your first public bookmark. To edit or delete "
"me, you must first login.\n"
@@ -132,326 +750,115 @@ msgstr ""
"Du verwendest die von der Community unterstützte Version des ursprünglichen "
"Shaarli-Projekts von Sebastien Sauvage."
-#: application/LinkDB.php:267
+#: application/legacy/LegacyLinkDB.php:266
msgid "My secret stuff... - Pastebin.com"
msgstr "Meine geheimen Sachen... - Pastebin.com"
-#: application/LinkDB.php:269
+#: application/legacy/LegacyLinkDB.php:268
msgid "Shhhh! I'm a private link only YOU can see. You can delete me too."
msgstr ""
"Pssst Ich bin ein privater Link, den nur du sehen kannst. Du kannst mich "
"auch löschen."
-#: application/LinkFilter.php:452
-msgid "The link you are trying to reach does not exist or has been deleted."
-msgstr ""
-"Den Link, den du versucht zu erreichen, existiert nicht oder wurde gelöscht."
+#: application/legacy/LegacyUpdater.php:104
+msgid "Couldn't retrieve updater class methods."
+msgstr "Die Updater-Klassenmethoden konnten nicht abgerufen werden."
-#: application/NetscapeBookmarkUtils.php:35
+#: application/legacy/LegacyUpdater.php:540
+msgid ""
+msgstr ""
+
+#: application/netscape/NetscapeBookmarkUtils.php:63
msgid "Invalid export selection:"
msgstr "Ungültige Exportauswahl:"
-#: application/NetscapeBookmarkUtils.php:81
+#: application/netscape/NetscapeBookmarkUtils.php:215
#, php-format
msgid "File %s (%d bytes) "
msgstr "Datei %s (%d bytes) "
-#: application/NetscapeBookmarkUtils.php:83
+#: application/netscape/NetscapeBookmarkUtils.php:217
msgid "has an unknown file format. Nothing was imported."
msgstr "hat ein unbekanntes Dateiformat. Es wurde nichts importiert."
-#: application/NetscapeBookmarkUtils.php:86
+#: application/netscape/NetscapeBookmarkUtils.php:221
#, php-format
msgid ""
-"was successfully processed in %d seconds: %d links imported, %d links "
-"overwritten, %d links skipped."
+"was successfully processed in %d seconds: %d bookmarks imported, %d "
+"bookmarks overwritten, %d bookmarks skipped."
msgstr ""
-"wurde erfolgreich in %d Sekunden verarbeitet: %d Links importiert, %d Links "
-"überschrieben, %d Links übersprungen."
+"wurde erfolgreich in %d Sekunden verarbeitet: %d Lesezeichen importiert, %d "
+"Lesezeichen überschrieben, %d Lesezeichen übersprungen."
-#: application/PageBuilder.php:168
-msgid "The page you are trying to reach does not exist or has been deleted."
-msgstr ""
-"Die Seite, die du erreichen möchtest, existiert nicht oder wurde gelöscht."
+#: application/plugin/PluginManager.php:99
+#: application/plugin/PluginManager.php:137
+msgid " [plugin incompatibility]: "
+msgstr " [Plugin-Inkompatibiliät]: "
-#: application/PageBuilder.php:170
-msgid "404 Not Found"
-msgstr "404 Nicht gefunden"
-
-#: application/PluginManager.php:243
+#: application/plugin/exception/PluginFileNotFoundException.php:22
#, php-format
msgid "Plugin \"%s\" files not found."
msgstr "Plugin \"%s\" Dateien nicht gefunden."
-#: application/Updater.php:76
-msgid "Couldn't retrieve Updater class methods."
-msgstr "Die Updater-Klassenmethoden konnten nicht abgerufen werden."
+#: application/render/PageCacheManager.php:33
+#, php-format
+msgid "Cannot purge %s: no directory"
+msgstr "Kann nicht löschen, %s ist kein Verzeichnis"
-#: application/Updater.php:532
+#: application/updater/exception/UpdaterException.php:51
msgid "An error occurred while running the update "
msgstr "Beim Ausführen des Updates ist ein Fehler aufgetreten "
-#: application/Updater.php:572
-msgid "Updates file path is not set, can't write updates."
-msgstr ""
-"Der Update-Dateipfad ist nicht festgelegt, es können keine Updates "
-"geschrieben werden."
+#: index.php:82
+msgid "Shared bookmarks on "
+msgstr "Geteilte Lesezeichen auf "
-#: application/Updater.php:577
-msgid "Unable to write updates in "
-msgstr "Es ist nicht möglich Updates zu schreiben in "
-
-#: application/Utils.php:376 tests/UtilsTest.php:340
-msgid "Setting not set"
-msgstr "Einstellung nicht gesetzt"
-
-#: application/Utils.php:383 tests/UtilsTest.php:338 tests/UtilsTest.php:339
-msgid "Unlimited"
-msgstr "Unbegrenzt"
-
-#: application/Utils.php:386 tests/UtilsTest.php:335 tests/UtilsTest.php:336
-#: tests/UtilsTest.php:350
-msgid "B"
-msgstr "B"
-
-#: application/Utils.php:386 tests/UtilsTest.php:329 tests/UtilsTest.php:330
-#: tests/UtilsTest.php:337
-msgid "kiB"
-msgstr "kiB"
-
-#: application/Utils.php:386 tests/UtilsTest.php:331 tests/UtilsTest.php:332
-#: tests/UtilsTest.php:348 tests/UtilsTest.php:349
-msgid "MiB"
-msgstr "MiB"
-
-#: application/Utils.php:386 tests/UtilsTest.php:333 tests/UtilsTest.php:334
-msgid "GiB"
-msgstr "GiB"
-
-#: application/config/ConfigJson.php:52 application/config/ConfigPhp.php:121
-msgid ""
-"Shaarli could not create the config file. Please make sure Shaarli has the "
-"right to write in the folder is it installed in."
-msgstr ""
-"Shaarli konnte die Konfigurationsdatei nicht erstellen. Bitte stelle sicher, "
-"dass Shaarli das Recht hat, in den Ordner zu schreiben, in dem es "
-"installiert ist."
-
-#: application/config/ConfigManager.php:135
-msgid "Invalid setting key parameter. String expected, got: "
-msgstr ""
-"Ungültiger Parameter für den Einstellungsschlüssel. Zeichenfolge erwartet, "
-"erhalten: "
-
-#: application/config/exception/MissingFieldConfigException.php:21
-#, php-format
-msgid "Configuration value is required for %s"
-msgstr "Konfigurationswert erforderlich für %s"
-
-#: application/config/exception/PluginConfigOrderException.php:15
-msgid "An error occurred while trying to save plugins loading order."
-msgstr ""
-"Beim Versuch, die Ladereihenfolge der Plugins zu speichern, ist ein Fehler "
-"aufgetreten."
-
-#: application/config/exception/UnauthorizedConfigException.php:16
-msgid "You are not authorized to alter config."
-msgstr "Du bist nicht berechtigt, die Konfiguration zu ändern."
-
-#: application/exceptions/IOException.php:19
-msgid "Error accessing"
-msgstr "Fehler beim Zugriff"
-
-#: index.php:142
-msgid "Shared links on "
-msgstr "Geteilte Links auf "
-
-#: index.php:164
-msgid "Insufficient permissions:"
-msgstr "Unzureichende Berechtigungen:"
-
-#: index.php:303
-msgid "I said: NO. You are banned for the moment. Go away."
-msgstr "Ich sagte NEIN. Du bist für den Moment gesperrt. Verschwinde."
-
-#: index.php:368
-msgid "Wrong login/password."
-msgstr "Falscher Loging/Passwort."
-
-#: index.php:576 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:42
-msgid "Daily"
-msgstr "Täglich"
-
-#: index.php:681 tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
-#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:71
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:95
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:71
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:95
-msgid "Login"
-msgstr "Einloggen"
-
-#: index.php:722 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:39
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:39
-msgid "Picture wall"
-msgstr "Bildwand"
-
-#: index.php:770 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:36
-#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19
-msgid "Tag cloud"
-msgstr "Tag Cloud"
-
-#: index.php:803 tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19
-msgid "Tag list"
-msgstr "Tag Liste"
-
-#: index.php:1028 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:31
-msgid "Tools"
-msgstr "Tools"
-
-#: index.php:1037
-msgid "You are not supposed to change a password on an Open Shaarli."
-msgstr "Du darfst kein Passwort für ein offenes Shaarli ändern."
-
-#: index.php:1042 index.php:1084 index.php:1160 index.php:1191 index.php:1291
-msgid "Wrong token."
-msgstr "Falsches Zeichen."
-
-#: index.php:1047
-msgid "The old password is not correct."
-msgstr "Das alte Passwort ist nicht korrekt."
-
-#: index.php:1067
-msgid "Your password has been changed"
-msgstr "Dein Passwort wurde geändert"
-
-#: index.php:1072
-#: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29
-msgid "Change password"
-msgstr "Passwort ändern"
-
-#: index.php:1120
-msgid "Configuration was saved."
-msgstr "Konfiguration wurde gespeichert."
-
-#: index.php:1143 tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24
-msgid "Configure"
-msgstr "Konfigurieren"
-
-#: index.php:1154 tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
-msgid "Manage tags"
-msgstr "Tags verwalten"
-
-#: index.php:1172
-#, php-format
-msgid "The tag was removed from %d link."
-msgid_plural "The tag was removed from %d links."
-msgstr[0] "Der Tag wurde aus dem Link %d entfernt."
-msgstr[1] "Der Tag wurde aus den Links %d entfernt."
-
-#: index.php:1173
-#, php-format
-msgid "The tag was renamed in %d link."
-msgid_plural "The tag was renamed in %d links."
-msgstr[0] "Der Tag wurde im Link %d umbenannt."
-msgstr[1] "Der Tag wurde in den Links %d umbenannt."
-
-#: index.php:1181 tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
-msgid "Shaare a new link"
-msgstr "Teile einen neuen Link"
-
-#: index.php:1351 tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
-#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:170
-msgid "Edit"
-msgstr "Bearbeiten"
-
-#: index.php:1351 index.php:1421
-#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:26
-msgid "Shaare"
-msgstr "Teilen"
-
-#: index.php:1390
-msgid "Note: "
-msgstr "Notiz: "
-
-#: index.php:1430 tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:65
-msgid "Export"
-msgstr "Exportieren"
-
-#: index.php:1492 tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:83
-msgid "Import"
-msgstr "Importieren"
-
-#: index.php:1502
-#, php-format
-msgid ""
-"The file you are trying to upload is probably bigger than what this "
-"webserver can accept (%s). Please upload in smaller chunks."
-msgstr ""
-"Die Datei, die du hochladen möchtest, ist wahrscheinlich größer als das, was "
-"dieser Webserver akzeptieren kann (%s). Bitte lade in kleineren Blöcken hoch."
-
-#: index.php:1541 tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22
-msgid "Plugin administration"
-msgstr "Plugin Adminstration"
-
-#: index.php:1706
-msgid "Search: "
-msgstr "Suche: "
-
-#: index.php:1933
-#, php-format
-msgid ""
-"Sessions do not seem to work correctly on your server.
Make sure the "
-"variable \"session.save_path\" is set correctly in your PHP config, and that "
-"you have write access to it.
It currently points to %s.
On some "
-"browsers, accessing your server via a hostname like 'localhost' or any "
-"custom hostname without a dot causes cookie storage to fail. We recommend "
-"accessing your server via it's IP address or Fully Qualified Domain Name.
"
-msgstr ""
-"Sessions scheinen auf deinem Server nicht korrekt zu funktionieren. "
-"
Stelle sicher, dass die Variable \"session.save_path\" in deiner PHP-"
-"Konfiguration richtig eingestellt ist und dass du Schreibzugriff darauf hast."
-"
Es verweist aktuell auf %s.
Bei einigen Browsern führt der Zugriff "
-"auf deinen Server über einen Hostnamen wie \"localhost\" oder einen "
-"beliebigen benutzerdefinierten Hostnamen ohne Punkt dazu, dass der Cookie-"
-"Speicher fehlschlägt. Wir empfehlen den Zugriff auf deinen Server über die "
-"IP-Adresse oder den Fully Qualified Domain Namen.
"
-
-#: index.php:1943
-msgid "Click to try again."
-msgstr "Klicke um es erneut zu versuchen."
-
-#: plugins/addlink_toolbar/addlink_toolbar.php:29
+#: plugins/addlink_toolbar/addlink_toolbar.php:31
msgid "URI"
msgstr "URI"
-#: plugins/addlink_toolbar/addlink_toolbar.php:33
-#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19
+#: plugins/addlink_toolbar/addlink_toolbar.php:35
+#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:20
msgid "Add link"
msgstr "Link hinzufügen"
-#: plugins/addlink_toolbar/addlink_toolbar.php:50
+#: plugins/addlink_toolbar/addlink_toolbar.php:52
msgid "Adds the addlink input on the linklist page."
msgstr "Fügt die Link-hinzufügen-Eingabe auf der Linkliste hinzu."
-#: plugins/archiveorg/archiveorg.php:23
+#: plugins/archiveorg/archiveorg.php:29
msgid "View on archive.org"
msgstr "Auf archive.org ansehen"
-#: plugins/archiveorg/archiveorg.php:36
+#: plugins/archiveorg/archiveorg.php:42
msgid "For each link, add an Archive.org icon."
msgstr "Füge für jeden Link ein Archive.org Symbol hinzu."
-#: plugins/demo_plugin/demo_plugin.php:465
+#: plugins/default_colors/default_colors.php:38
+msgid ""
+"Default colors plugin error: This plugin is active and no custom color is "
+"configured."
+msgstr ""
+"Fehler beim Plugin für Standardfarben: Dieses Plugin ist aktiv und es ist "
+"keine benutzerdefinierte Farbe konfiguriert."
+
+#: plugins/default_colors/default_colors.php:127
+msgid "Override default theme colors. Use any CSS valid color."
+msgstr "Überschreibe Standard-Thema-Farben. Benutze jede gültige CSS Farbe."
+
+#: plugins/default_colors/default_colors.php:128
+msgid "Main color (navbar green)"
+msgstr "Haupt-Farbe (navbar grün)"
+
+#: plugins/default_colors/default_colors.php:129
+msgid "Background color (light grey)"
+msgstr "Hintergrund-Farbe (hellgrau)"
+
+#: plugins/default_colors/default_colors.php:130
+msgid "Dark main color (e.g. visited links)"
+msgstr "Dunkle Haupt-Farbe (z. B. besuchte Links)"
+
+#: plugins/demo_plugin/demo_plugin.php:495
msgid ""
"A demo plugin covering all use cases for template designers and plugin "
"developers."
@@ -459,7 +866,16 @@ msgstr ""
"Ein Demo-Plugin, das alle Anwendungsfälle für Template-Designer und Plugin-"
"Entwickler abdeckt."
-#: plugins/isso/isso.php:20
+#: plugins/demo_plugin/demo_plugin.php:496
+msgid "This is a parameter dedicated to the demo plugin. It'll be suffixed."
+msgstr ""
+"Dies ist ein Parameter, der dem Demo-Plugin gewidmet ist. Es wird angehängt."
+
+#: plugins/demo_plugin/demo_plugin.php:497
+msgid "Other demo parameter"
+msgstr "Andere Demo-Parameter"
+
+#: plugins/isso/isso.php:22
msgid ""
"Isso plugin error: Please define the \"ISSO_SERVER\" setting in the plugin "
"administration page."
@@ -467,47 +883,17 @@ msgstr ""
"Isso Plugin Fehler: Bitte definiere die Einstellung \"ISSO_SERVER\" auf der "
"Plugin-Administrationsseite."
-#: plugins/isso/isso.php:63
+#: plugins/isso/isso.php:92
msgid "Let visitor comment your shaares on permalinks with Isso."
msgstr ""
-"Lassen Sie Besucher ihre geteilten Links auf Permalinks mit Isso "
+"Lassen Sie Besucher Ihre Shaares auf Permalinks mit Isso "
"kommentieren."
-#: plugins/isso/isso.php:64
+#: plugins/isso/isso.php:93
msgid "Isso server URL (without 'http://')"
msgstr "Isso Server URL (ohne 'http://')"
-#: plugins/markdown/markdown.php:158
-msgid "Description will be rendered with"
-msgstr "Die Beschreibung wird dargestellt mit"
-
-#: plugins/markdown/markdown.php:159
-msgid "Markdown syntax documentation"
-msgstr "Markdown Syntax Dokumentation"
-
-#: plugins/markdown/markdown.php:160
-msgid "Markdown syntax"
-msgstr "Markdown Syntax"
-
-#: plugins/markdown/markdown.php:339
-msgid ""
-"Render shaare description with Markdown syntax.
Warning"
-"strong>:\n"
-"If your shaared descriptions contained HTML tags before enabling the "
-"markdown plugin,\n"
-"enabling it might break your page.\n"
-"See the README."
-msgstr ""
-"Übertrage Teilen Beschreibung mit Markdown-Syntax.
Warnung"
-"strong>:\n"
-"Wenn deine Teilen Beschreibungen HTML-Tags enthielten, bevor das Markdown-"
-"Plugin aktiviert wurde,\n"
-"kann es deine Seite beschädigen, solltest du es aktivieren.\n"
-"Weitere Informationen findest du in der README."
-
-#: plugins/piwik/piwik.php:21
+#: plugins/piwik/piwik.php:24
msgid ""
"Piwik plugin error: Please define PIWIK_URL and PIWIK_SITEID in the plugin "
"administration page."
@@ -515,28 +901,28 @@ msgstr ""
"Piwik-Plugin-Fehler: Bitte definiere die PIWIK_URL und PIWIK_SITEID auf der "
"Plugin-Administrationsseite."
-#: plugins/piwik/piwik.php:70
+#: plugins/piwik/piwik.php:73
msgid "A plugin that adds Piwik tracking code to Shaarli pages."
msgstr ""
"Ein Plugin, das einen Piwik-Tracking-Code auf Shaarli-Seiten hinzufügt."
-#: plugins/piwik/piwik.php:71
+#: plugins/piwik/piwik.php:74
msgid "Piwik URL"
msgstr "Piwik URL"
-#: plugins/piwik/piwik.php:72
+#: plugins/piwik/piwik.php:75
msgid "Piwik site ID"
msgstr "Piwik site ID"
-#: plugins/playvideos/playvideos.php:22
+#: plugins/playvideos/playvideos.php:26
msgid "Video player"
msgstr "Videoplayer"
-#: plugins/playvideos/playvideos.php:25
+#: plugins/playvideos/playvideos.php:29
msgid "Play Videos"
msgstr "Videos abspielen"
-#: plugins/playvideos/playvideos.php:56
+#: plugins/playvideos/playvideos.php:60
msgid "Add a button in the toolbar allowing to watch all videos."
msgstr ""
"Fügt eine Schaltfläche in der Symbolleiste hinzu, mit der man alle Videos "
@@ -546,30 +932,30 @@ msgstr ""
msgid "plugins/playvideos/jquery-1.11.2.min.js"
msgstr "plugins/playvideos/jquery-1.11.2.min.js"
-#: plugins/pubsubhubbub/pubsubhubbub.php:69
+#: plugins/pubsubhubbub/pubsubhubbub.php:72
#, php-format
msgid "Could not publish to PubSubHubbub: %s"
msgstr "Veröffentlichung auf PubSubHubbub nicht möglich: %s"
-#: plugins/pubsubhubbub/pubsubhubbub.php:95
+#: plugins/pubsubhubbub/pubsubhubbub.php:99
#, php-format
msgid "Could not post to %s"
msgstr "Kann nicht posten auf %s"
-#: plugins/pubsubhubbub/pubsubhubbub.php:99
+#: plugins/pubsubhubbub/pubsubhubbub.php:103
#, php-format
msgid "Bad response from the hub %s"
msgstr "Ungültige Antwort vom Hub %s"
-#: plugins/pubsubhubbub/pubsubhubbub.php:110
+#: plugins/pubsubhubbub/pubsubhubbub.php:114
msgid "Enable PubSubHubbub feed publishing."
msgstr "Aktiviere PubSubHubbub Feed Veröffentlichung."
-#: plugins/qrcode/qrcode.php:69 plugins/wallabag/wallabag.php:68
+#: plugins/qrcode/qrcode.php:74 plugins/wallabag/wallabag.php:72
msgid "For each link, add a QRCode icon."
msgstr "Für jeden Link, füge eine QRCode Icon hinzu."
-#: plugins/wallabag/wallabag.php:21
+#: plugins/wallabag/wallabag.php:22
msgid ""
"Wallabag plugin error: Please define the \"WALLABAG_URL\" setting in the "
"plugin administration page."
@@ -577,23 +963,28 @@ msgstr ""
"Wallabag Plugin Fehler: Bitte definiere die Einstellung \"WALLABAG_URL\" auf "
"der Plugin Administrationsseite."
-#: plugins/wallabag/wallabag.php:47
+#: plugins/wallabag/wallabag.php:49
msgid "Save to wallabag"
msgstr "Auf Wallabag speichern"
-#: plugins/wallabag/wallabag.php:69
+#: plugins/wallabag/wallabag.php:73
msgid "Wallabag API URL"
msgstr "Wallabag API URL"
-#: plugins/wallabag/wallabag.php:70
+#: plugins/wallabag/wallabag.php:74
msgid "Wallabag API version (1 or 2)"
msgstr "Wallabag API version (1 oder 2)"
#: tests/LanguagesTest.php:214 tests/LanguagesTest.php:227
-#: tests/languages/fr/LanguagesFrTest.php:160
-#: tests/languages/fr/LanguagesFrTest.php:173
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:81
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:81
+#: tests/languages/fr/LanguagesFrTest.php:159
+#: tests/languages/fr/LanguagesFrTest.php:172
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:87
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:87
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:139
+#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46
+#: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:45
msgid "Search"
msgid_plural "Search"
msgstr[0] "Suche"
@@ -607,6 +998,48 @@ msgstr "Entschuldige, hier gibt es nichts zu sehen."
msgid "URL or leave empty to post a note"
msgstr "URL oder leer lassen um eine Notiz hinzuzufügen"
+#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29
+msgid "BULK CREATION"
+msgstr "Mehrfach-Erstellung"
+
+#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:40
+msgid "Metadata asynchronous retrieval is disabled."
+msgstr "Der asynchrone Metadatenabruf ist deaktiviert."
+
+#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42
+msgid ""
+"We recommend that you enable the setting general > "
+"enable_async_metadata in your configuration file to use bulk link "
+"creation."
+msgstr ""
+"Es wird empfohlen, dass Du die Einstellung \"allgemein > "
+"enable_async_metadata in Deiner Konfigurationsdatei aktivierst, um die "
+"Massen-Linkerstellung verwenden zu können."
+
+#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:56
+msgid "Shaare multiple new links"
+msgstr "Shaare mehrere neuen Links"
+
+#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:59
+msgid "Add one URL per line to create multiple bookmarks."
+msgstr "Füge eine URL pro Zeile hinzu, um mehrere Lesezeichen zu erstellen."
+
+#: 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 "Privat"
+
+#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:78
+msgid "Add links"
+msgstr "Links hinzufügen"
+
#: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
msgid "Current password"
msgstr "Aktuelles Passwort"
@@ -633,23 +1066,48 @@ msgid "Case sensitive"
msgstr "Groß- / Kleinschreibung-unterscheidend"
#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34
-msgid "Rename"
-msgstr "Umbenennen"
+#: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:68
+msgid "Rename tag"
+msgstr "Tag umbenennen"
#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35
-#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:79
-#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:172
-msgid "Delete"
-msgstr "Löschen"
+msgid "Delete tag"
+msgstr "Lösche Tag"
-#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:39
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:40
msgid "You can also edit tags in the"
msgstr "Du kannst auch Tags bearbeiten in der"
-#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:39
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:40
msgid "tag list"
msgstr "Tag Liste"
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47
+msgid "Change tags separator"
+msgstr "Tags-Trennzeichen ändern"
+
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:50
+msgid "Your current tag separator is"
+msgstr "Ihr aktuelles Tag-Trennzeichen ist"
+
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:53
+msgid "New separator"
+msgstr "Neues Trennzeichen"
+
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:58
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:355
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:121
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:199
+msgid "Save"
+msgstr "Speichern"
+
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:61
+msgid "Note that hashtags won't fully work with a non-whitespace separator."
+msgstr ""
+"Beachten Sie, dass Hashtags nicht vollständig mit einem Nicht-"
+"Leerraumtrennzeichen funktionieren."
+
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29
msgid "title"
msgstr "Titel"
@@ -666,128 +1124,177 @@ msgstr "Standardwert"
msgid "Theme"
msgstr "Thema"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:87
-#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:78
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:85
+msgid "Description formatter"
+msgstr "Beschreibungsformatierer"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:114
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77
msgid "Language"
msgstr "Sprache"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:116
-#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:143
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:101
msgid "Timezone"
msgstr "Zeitzone"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117
-#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:103
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:144
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102
msgid "Continent"
msgstr "Kontinent"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117
-#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:103
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:144
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102
msgid "City"
msgstr "Stadt"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:164
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:191
msgid "Disable session cookie hijacking protection"
msgstr "Deaktiviere Session Cookie Hijacking Schutz"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:166
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:193
msgid "Check this if you get disconnected or if your IP address changes often"
msgstr ""
"Überprüfe dies, wenn die Verbindung getrennt wird oder wenn sich deine IP-"
"Adresse häufig ändert"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:183
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:210
msgid "Private links by default"
msgstr "Standardmäßig Private Links"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:184
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:211
msgid "All new links are private by default"
msgstr "Alle neuen Links sind standardmäßig privat"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:199
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:226
msgid "RSS direct links"
msgstr "RSS Direkt Links"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:200
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:227
msgid "Check this to use direct URL instead of permalink in feeds"
msgstr ""
"Aktivieren diese Option, um direkte URLs anstelle von Permalinks in Feeds zu "
"verwenden"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:215
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:242
msgid "Hide public links"
msgstr "Verstecke öffentliche Links"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:216
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:243
msgid "Do not show any links if the user is not logged in"
msgstr "Zeige keine Links, wenn der Benutzer nicht angemeldet ist"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:231
-#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:150
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:258
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:149
msgid "Check updates"
msgstr "Auf Updates prüfen"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:232
-#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:152
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:259
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:151
msgid "Notify me when a new release is ready"
msgstr "Benachrichtige mich, wenn eine neue Version zur Verfügung steht"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:247
-#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:274
+msgid "Automatically retrieve description for new bookmarks"
+msgstr "Automatisches Abrufen der Beschreibung für neue Lesezeichen"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:275
+msgid "Shaarli will try to retrieve the description from meta HTML headers"
+msgstr "Shaarli versucht, die Beschreibung aus Meta-HTML-Headern abzurufen"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:290
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:168
msgid "Enable REST API"
msgstr "Aktiviere REST API"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:248
-#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:170
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:291
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169
msgid "Allow third party software to use Shaarli such as mobile application"
msgstr ""
"Erlaube Software von Drittanbietern für Shaarli, wie z.B. die mobile "
"Anwendung"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:263
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:306
msgid "API secret"
-msgstr "API secret"
+msgstr "API-Geheimnis"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:274
-#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74
-#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139
-#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:199
-msgid "Save"
-msgstr "Speichern"
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:320
+msgid "Enable thumbnails"
+msgstr "Aktivierte Thunbnails"
-#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
-msgid "The Daily Shaarli"
-msgstr "Der tägliche Shaarli"
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:324
+msgid "You need to enable the extension php-gd
to use thumbnails."
+msgstr ""
+"Sie müssen die Erweiterung php-gd
aktivieren, um "
+"Miniaturansichten zu verwenden."
-#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:17
-msgid "1 RSS entry per day"
-msgstr "1 RSS Eintrag pro Tag"
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:328
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:122
+msgid "Synchronize thumbnails"
+msgstr "Thumbnails synchronisieren"
-#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:37
-msgid "Previous day"
-msgstr "Vorheriger Tag"
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:339
+#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102
+msgid "All"
+msgstr "Alle"
-#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44
-msgid "All links of one day in a single page."
-msgstr "Alle Links eines Tages auf einer Seite."
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:343
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:106
+msgid "Only common media hosts"
+msgstr "Nur gängige Medienhosts"
-#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:51
-msgid "Next day"
-msgstr "Nächster Tag"
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:347
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:110
+msgid "None"
+msgstr "Keine"
-#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:25
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26
+msgid "1 RSS entry per :type"
+msgid_plural ""
+msgstr[0] "1 RSS Eintrag pro :type"
+msgstr[1] "1 RSS Eintrag pro :type"
+
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:49
+msgid "Previous :type"
+msgid_plural ""
+msgstr[0] "Vorheriger :type"
+msgstr[1] "Vorherige :type"
+
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:56
+#: tmp/dailyrss.b91ef64efc3688266305ea9b42e5017e.rtpl.php:7
+msgid "All links of one :type in a single page."
+msgid_plural ""
+msgstr[0] "Alle Links eines :type auf einer Seite."
+msgstr[1] "Alle Links aller :type auf einer Seite."
+
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:63
+msgid "Next :type"
+msgid_plural ""
+msgstr[0] "Nächster :type"
+msgstr[1] "Nächste :type"
+
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30
+msgid "Edit Shaare"
+msgstr "Bearbeite Shaare"
+
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30
+msgid "New Shaare"
+msgstr "Neue Shaare"
+
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:38
msgid "Created:"
msgstr "Erstellt:"
-#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
msgid "URL"
msgstr "URL"
-#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47
msgid "Title"
msgstr "Titel"
-#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:40
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:58
#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42
#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:75
#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:99
@@ -795,41 +1302,56 @@ msgstr "Titel"
msgid "Description"
msgstr "Beschreibung"
-#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46
-msgid "Tags"
-msgstr "Tags"
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:89
+msgid "Description will be rendered with"
+msgstr "Beschreibung wird dargestellt mit"
-#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:59
-#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
-#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:168
-msgid "Private"
-msgstr "Privat"
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:91
+msgid "Markdown syntax documentation"
+msgstr "Dokumentation der Markdown-Syntax"
-#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:92
+msgid "Markdown syntax"
+msgstr "Markdown-Syntax"
+
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:115
+msgid "Cancel"
+msgstr "Abbruch"
+
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:121
msgid "Apply Changes"
msgstr "Änderungen übernehmen"
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:126
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:173
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:147
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:147
+#: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:67
+msgid "Delete"
+msgstr "Löschen"
+
+#: tmp/editlink.batch.b91ef64efc3688266305ea9b42e5017e.rtpl.php:21
+#: tmp/editlink.batch.b91ef64efc3688266305ea9b42e5017e.rtpl.php:32
+msgid "Save all"
+msgstr "Speichere alles"
+
#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
msgid "Export Database"
msgstr "Exportiere Datenbank"
-#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24
+#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:23
msgid "Selection"
msgstr "Beschreibung"
-#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31
-msgid "All"
-msgstr "Alle"
-
-#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
+#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:40
msgid "Public"
msgstr "Öffentlich"
-#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:52
+#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:51
msgid "Prepend note permalinks with this Shaarli instance's URL"
msgstr "Voranstellen von Notizen-Permalinks mit der URL dieser Shaarli-Instanz"
-#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:53
+#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:52
msgid "Useful to import bookmarks in a web browser"
msgstr "Sinnvoll Lesezeichen im Browser zu importieren"
@@ -869,224 +1391,269 @@ msgstr "Duplikate basierend auf URL"
msgid "Add default tags"
msgstr "Standard-Tag hinzufügen"
-#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22
-msgid "Install Shaarli"
-msgstr "Installiere Shaarli"
-
#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:25
msgid "It looks like it's the first time you run Shaarli. Please configure it."
msgstr ""
"Es sieht so aus, als ob du Shaarli das erste mal verwendest. Bitte "
"konfiguriere es."
-#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:33
-#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:147
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:147
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:32
+#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:167
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:167
msgid "Username"
msgstr "Benutzername"
-#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
-#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:148
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:148
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47
+#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:20
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:168
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:168
msgid "Password"
msgstr "Passwort"
-#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:63
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:62
msgid "Shaarli title"
msgstr "Shaarli Titel"
-#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:69
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:68
msgid "My links"
msgstr "Meine Links"
-#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:182
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:181
msgid "Install"
msgstr "Installiere"
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:190
+msgid "Server requirements"
+msgstr "Server-Anforderungen"
+
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
-#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:80
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:79
msgid "shaare"
msgid_plural "shaares"
-msgstr[0] "Teile"
-msgstr[1] "Teilen"
+msgstr[0] "Shaare"
+msgstr[1] "Shaares"
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:18
-#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:84
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:83
msgid "private link"
msgid_plural "private links"
msgstr[0] "Privater Link"
msgstr[1] "Private Links"
-#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:117
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:123
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:123
msgid "Search text"
msgstr "Text durchsuchen"
-#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:38
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:124
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:124
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:37
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:130
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:130
#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
-#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:64
+#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:65
#: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
#: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74
msgid "Filter by tag"
msgstr "Nach Tag filtern"
-#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:111
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:110
msgid "Nothing found."
msgstr "Nichts gefunden."
-#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:119
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:118
#, php-format
msgid "%s result"
msgid_plural "%s results"
msgstr[0] "%s Ergebnis"
msgstr[1] "%s Ergebnisse"
-#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:123
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:122
msgid "for"
msgstr "für"
-#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:130
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:129
msgid "tagged"
msgstr "markiert"
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:133
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:134
msgid "Remove tag"
msgstr "Tag entfernen"
-#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:143
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:144
msgid "with status"
msgstr "mit Status"
-#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:154
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:155
msgid "without any tag"
msgstr "ohne irgendeinen Tag"
-#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:174
-#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42
-#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:42
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:175
+#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
+#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:41
msgid "Fold"
-msgstr "Ablegen"
+msgstr "Einklappen"
-#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:176
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:177
msgid "Edited: "
msgstr "Bearbeitet: "
-#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:180
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:181
msgid "permalink"
msgstr "Permalink"
-#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:182
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:183
msgid "Add tag"
msgstr "Tag hinzufügen"
-#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:7
-#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:7
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:185
+msgid "Toggle sticky"
+msgstr "Anheften umschalten"
+
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:187
+msgid "Sticky"
+msgstr "Angeheftet"
+
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:189
+msgid "Share a private link"
+msgstr "Teile einen privaten Link"
+
+#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:5
+#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:5
msgid "Filters"
msgstr "Filter"
-#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:12
-#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:12
+#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:10
+#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:10
msgid "Only display private links"
msgstr "Zeige nur private Links"
-#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
-#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:15
+#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
+#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:13
msgid "Only display public links"
msgstr "Zeige nur öffentliche Links"
-#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:20
-#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:20
+#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:18
+#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:18
msgid "Filter untagged links"
msgstr "Unmarkierte Tags filtern"
#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24
-#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:76
#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:24
-#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:76
-#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:43
-#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:43
-msgid "Fold all"
-msgstr "Alles ablegen"
+msgid "Select all"
+msgstr "Alle selektieren"
-#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:69
-#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:69
+#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29
+#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:89
+#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:29
+#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:89
+#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42
+#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:42
+msgid "Fold all"
+msgstr "Alles einklappen"
+
+#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:76
+#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:76
msgid "Links per page"
msgstr "Links pro Seite"
-#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
-msgid ""
-"You have been banned after too many failed login attempts. Try again later."
-msgstr ""
-"Du wurdest nach zu vielen fehlgeschlagenen Anmeldeversuchen gesperrt. "
-"Versuche es später noch einmal."
-
-#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:151
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:151
+#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:25
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:171
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:171
msgid "Remember me"
msgstr "Erinnere dich an mich"
-#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
+#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
-#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:14
+#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:15
#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:48
msgid "by the Shaarli community"
msgstr "von der Shaarli Community"
-#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
-#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:15
+#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
+#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:16
msgid "Documentation"
msgstr "Dokumentation"
-#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44
-#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:44
+#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:43
+#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:43
msgid "Expand"
msgstr "Erweitern"
-#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:45
-#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:45
+#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44
+#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:44
msgid "Expand all"
msgstr "Alles erweitern"
-#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46
-#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:46
+#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:45
+#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:45
msgid "Are you sure you want to delete this link?"
msgstr "Bist du sicher das du diesen Link löschen möchtest?"
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:61
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:86
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:61
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:86
+#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46
+#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:46
+msgid "Are you sure you want to delete this tag?"
+msgstr "Bist du sicher das du diesen Tag löschen möchtest?"
+
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:11
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:11
+msgid "Menu"
+msgstr "Menü"
+
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:38
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:38
+#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19
+msgid "Tag cloud"
+msgstr "Tag-Cloud"
+
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:67
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:92
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:67
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:92
msgid "RSS Feed"
msgstr "RSS Feed"
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:66
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:66
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:102
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:108
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:72
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:108
msgid "Logout"
msgstr "Ausloggen"
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:169
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:152
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:152
+msgid "Set public"
+msgstr "Setze Status auf Öffentlich"
+
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:157
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:157
+msgid "Set private"
+msgstr "Setze Status auf Privat"
+
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:189
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:189
msgid "is available"
msgstr "ist verfügbar"
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:176
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:176
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:196
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:196
msgid "Error"
msgstr "Fehler"
-#: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
+#: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
+msgid "There is no cached thumbnail."
+msgstr "Es gibt keine zwischengespeicherte Miniaturansicht / Thumbnail."
+
+#: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:17
+msgid "Try to synchronize them."
+msgstr "Versuche sie zu synchronisieren."
+
+#: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
msgid "Picture Wall"
msgstr "Bildwand"
-#: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
+#: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
msgid "pics"
msgstr "Bilder"
@@ -1095,6 +1662,11 @@ msgid "You need to enable Javascript to change plugin loading order."
msgstr ""
"Du musst Javascript aktivieren um die Ladereihenfolge der Plugins zu ändern."
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22
+msgid "Plugin administration"
+msgstr "Plugin-Administration"
+
#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29
msgid "Enabled Plugins"
msgstr "Aktivierte Plugins"
@@ -1144,12 +1716,138 @@ msgstr "In der Dokumentation"
#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:150
msgid "Plugin configuration"
-msgstr "Plugin Konfiguration"
+msgstr "Plugin-Konfiguration"
#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:195
msgid "No parameter available."
msgstr "Kein Parameter verfügbar."
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
+msgid "General"
+msgstr "Allgemein"
+
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:20
+msgid "Index URL"
+msgstr "Index-URL"
+
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
+msgid "Base path"
+msgstr "Basispfad"
+
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
+msgid "Client IP"
+msgstr "Client-IP"
+
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44
+msgid "Trusted reverse proxies"
+msgstr "Vertrauenswürdige Reverse-Proxies"
+
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:58
+msgid "N/A"
+msgstr "n. a."
+
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:67
+msgid "Version"
+msgstr "Version"
+
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:71
+msgid "Current version"
+msgstr "Aktuelle Version"
+
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:80
+msgid "Latest release"
+msgstr "Letzte Veröffentlichung"
+
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:84
+msgid "Visit releases page on Github"
+msgstr "Besuche die Releases-/Veröffentlichungs-Seite bei Github"
+
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:92
+msgid "Thumbnails"
+msgstr "Thumbnails / Miniaturbilder"
+
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:96
+msgid "Thumbnails status"
+msgstr "Thumbnails-Status"
+
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:121
+msgid "Synchronize all link thumbnails"
+msgstr "Synchronisiere alle Link-Thumbnails"
+
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:128
+msgid "Cache"
+msgstr "Zwischenspeicher"
+
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:132
+msgid "Clear main cache"
+msgstr "Haupt-Zwischenspeicher leeren"
+
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:138
+msgid "Clear thumbnails cache"
+msgstr "Leere Thumbnail-Zwischenspeicher"
+
+#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:2
+#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:2
+msgid "Permissions"
+msgstr "Berechtigungen"
+
+#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:8
+#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:8
+msgid "There are permissions that need to be fixed."
+msgstr "Es gibt Berechtigungen, die korrigiert werden müssen."
+
+#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:23
+#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:23
+msgid "All read/write permissions are properly set."
+msgstr "Alle Lese-/Schreib-Berechtigungen sind richtig gesetzt."
+
+#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:32
+#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:32
+msgid "Running PHP"
+msgstr "Laufendes PHP"
+
+#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
+#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:36
+msgid "End of life: "
+msgstr "Abgekündigt: "
+
+#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
+#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:48
+msgid "Extension"
+msgstr "Erweiterung"
+
+#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:49
+#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:49
+msgid "Usage"
+msgstr "Benutzung"
+
+#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:50
+#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:50
+msgid "Status"
+msgstr "Status"
+
+#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:51
+#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:66
+#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:51
+#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:66
+msgid "Loaded"
+msgstr "Geladen"
+
+#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:60
+#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:60
+msgid "Required"
+msgstr "Erforderlich"
+
+#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:60
+#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:60
+msgid "Optional"
+msgstr "optional"
+
+#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:70
+#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:70
+msgid "Not loaded"
+msgstr "Nicht geladen"
+
#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19
#: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19
msgid "tags"
@@ -1160,6 +1858,10 @@ msgstr "Tags"
msgid "List all links with those tags"
msgstr "Zeige alle Links mit diesen Tags"
+#: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19
+msgid "Tag list"
+msgstr "Tag Liste"
+
#: tmp/tag.sort.b91ef64efc3688266305ea9b42e5017e.rtpl.php:3
#: tmp/tag.sort.cedf684561d925457130839629000a81.rtpl.php:3
msgid "Sort by:"
@@ -1196,15 +1898,19 @@ msgstr "Shaarli konfigurieren"
msgid "Enable, disable and configure plugins"
msgstr "Plugins aktivieren, deaktivieren und konfigurieren"
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:27
+msgid "Check instance's server configuration"
+msgstr "Überprüfe die Server-Konfiguration dieser Instanz"
+
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34
msgid "Change your password"
msgstr "Ändere dein Passwort"
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
msgid "Rename or delete a tag in all links"
msgstr "Umbenennen oder löschen eines Tags in allen Links"
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47
msgid ""
"Import Netscape HTML bookmarks (as exported from Firefox, Chrome, Opera, "
"delicious...)"
@@ -1212,11 +1918,11 @@ msgstr ""
"Importiere Netscape Lesezeichen (wie aus Firefox exportiert, Chrome, Opera, "
"delicious...)"
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
msgid "Import links"
msgstr "Importiere Links"
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:53
msgid ""
"Export Netscape HTML bookmarks (which can be imported in Firefox, Chrome, "
"Opera, delicious...)"
@@ -1224,11 +1930,11 @@ msgstr ""
"Exportiere Netscape HTML Lesezeichen (welche in Firefox importiert werden "
"können, Chrome, Opera, delicious...)"
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:54
msgid "Export database"
msgstr "Exportiere Datenbank"
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:71
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77
msgid ""
"Drag one of these button to your bookmarks toolbar or right-click it and "
"\"Bookmark This Link\""
@@ -1237,13 +1943,13 @@ msgstr ""
"klicke mit der rechten Maustaste darauf und \"Speichere diesen Link als "
"Lesezeichen\""
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:78
msgid "then click on the bookmarklet in any page you want to share."
msgstr ""
"Klicke dann auf das Bookmarklet auf jeder Seite, welches du teilen möchtest."
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:76
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:100
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:82
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:106
msgid ""
"Drag this link to your bookmarks toolbar or right-click it and Bookmark This "
"Link"
@@ -1251,22 +1957,22 @@ msgstr ""
"Ziehe diese Link in deine Lesezeichen-Symbolleiste oder klicke mit der "
"rechten Maustaste darauf und \"Speichere diesen Link als Lesezeichen\""
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:83
msgid "then click ✚Shaare link button in any page you want to share"
msgstr ""
"klicke dann auf die Schaltfläche ✚Teilen auf jeder Seite, die du teilen "
"möchtest"
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:86
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:108
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:92
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:114
msgid "The selected text is too long, it will be truncated."
msgstr "Der ausgewählte Text ist zu lang, er wird gekürzt."
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:96
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102
msgid "Shaare link"
msgstr "Teile Link"
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:101
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:107
msgid ""
"Then click ✚Add Note button anytime to start composing a private Note (text "
"post) to your Shaarli"
@@ -1274,40 +1980,42 @@ msgstr ""
"Klicke auf ✚Notiz hinzufügen um eine private Notiz (Textnachricht) zu "
"Shaarli hinzuzufügen"
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:123
msgid "Add Note"
msgstr "Notiz hinzufügen"
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:129
-msgid ""
-"You need to browse your Shaarli over HTTPS to use this "
-"functionality."
-msgstr ""
-"Um diese Funktion nutzen zu können, musst du Shaarli über HTTPS"
-"strong> aufrufen."
-
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:134
-msgid "Add to"
-msgstr "Hinzufügen zu"
-
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:145
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:132
msgid "3rd party"
msgstr "Von Dritten"
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:147
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:153
-msgid "Plugin"
-msgstr "Plugin"
-
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:148
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:154
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:135
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:140
msgid "plugin"
msgstr "Plugin"
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:175
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:165
msgid ""
"Drag this link to your bookmarks toolbar, or right-click it and choose "
"Bookmark This Link"
msgstr ""
"Ziehe diesen Link in deine Lesezeichen-Symbolleiste oder klicke mit der "
"rechten Maustaste darauf und wähle \"Speichere diesen Link als Lesezeichen\""
+
+#~ msgid "Rename"
+#~ msgstr "Umbenennen"
+
+#~ msgid "The Daily Shaarli"
+#~ msgstr "Der tägliche Shaarli"
+
+#~ msgid ""
+#~ "You need to browse your Shaarli over HTTPS to use this "
+#~ "functionality."
+#~ msgstr ""
+#~ "Um diese Funktion nutzen zu können, musst du Shaarli über HTTPS"
+#~ "strong> aufrufen."
+
+#~ msgid "Add to"
+#~ msgstr "Hinzufügen zu"
+
+#~ msgid "Plugin"
+#~ msgstr "Plugin"
diff --git a/inc/languages/fr/LC_MESSAGES/shaarli.po b/inc/languages/fr/LC_MESSAGES/shaarli.po
index 026d0101..a54fbcdf 100644
--- a/inc/languages/fr/LC_MESSAGES/shaarli.po
+++ b/inc/languages/fr/LC_MESSAGES/shaarli.po
@@ -1,24 +1,626 @@
msgid ""
msgstr ""
"Project-Id-Version: Shaarli\n"
-"POT-Creation-Date: 2019-07-13 10:45+0200\n"
-"PO-Revision-Date: 2019-07-13 10:49+0200\n"
+"POT-Creation-Date: 2020-11-24 13:13+0100\n"
+"PO-Revision-Date: 2020-11-24 13:14+0100\n"
"Last-Translator: \n"
"Language-Team: Shaarli\n"
"Language: fr_FR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Generator: Poedit 2.2.1\n"
+"X-Generator: Poedit 2.3\n"
"X-Poedit-Basepath: ../../../..\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Poedit-SourceCharset: UTF-8\n"
"X-Poedit-KeywordsList: t:1,2;t\n"
-"X-Poedit-SearchPath-0: .\n"
-"X-Poedit-SearchPathExcluded-0: node_modules\n"
-"X-Poedit-SearchPathExcluded-1: vendor\n"
+"X-Poedit-SearchPath-0: application\n"
+"X-Poedit-SearchPath-1: tmp\n"
+"X-Poedit-SearchPath-2: index.php\n"
+"X-Poedit-SearchPath-3: init.php\n"
+"X-Poedit-SearchPath-4: plugins\n"
-#: application/ApplicationUtils.php:159
+#: application/History.php:181
+msgid "History file isn't readable or writable"
+msgstr "Le fichier d'historique n'est pas accessible en lecture ou en écriture"
+
+#: application/History.php:192
+msgid "Could not parse history file"
+msgstr "Format incorrect pour le fichier d'historique"
+
+#: application/Languages.php:184
+msgid "Automatic"
+msgstr "Automatique"
+
+#: application/Languages.php:185
+msgid "German"
+msgstr "Allemand"
+
+#: application/Languages.php:186
+msgid "English"
+msgstr "Anglais"
+
+#: application/Languages.php:187
+msgid "French"
+msgstr "Français"
+
+#: application/Languages.php:188
+msgid "Japanese"
+msgstr "Japonais"
+
+#: application/Thumbnailer.php:62
+msgid ""
+"php-gd extension must be loaded to use thumbnails. Thumbnails are now "
+"disabled. Please reload the page."
+msgstr ""
+"l'extension php-gd doit être chargée pour utiliser les miniatures. Les "
+"miniatures sont désormais désactivées. Rechargez la page."
+
+#: application/Utils.php:405
+msgid "Setting not set"
+msgstr "Paramètre non défini"
+
+#: application/Utils.php:412
+msgid "Unlimited"
+msgstr "Illimité"
+
+#: application/Utils.php:415
+msgid "B"
+msgstr "o"
+
+#: application/Utils.php:415
+msgid "kiB"
+msgstr "ko"
+
+#: application/Utils.php:415
+msgid "MiB"
+msgstr "Mo"
+
+#: application/Utils.php:415
+msgid "GiB"
+msgstr "Go"
+
+#: application/bookmark/BookmarkFileService.php:185
+#: application/bookmark/BookmarkFileService.php:207
+#: application/bookmark/BookmarkFileService.php:229
+#: application/bookmark/BookmarkFileService.php:243
+msgid "You're not authorized to alter the datastore"
+msgstr "Vous n'êtes pas autorisé à modifier les données"
+
+#: application/bookmark/BookmarkFileService.php:210
+msgid "This bookmarks already exists"
+msgstr "Ce marque-page existe déjà"
+
+#: application/bookmark/BookmarkInitializer.php:42
+msgid "(private bookmark with thumbnail demo)"
+msgstr "(marque page privé avec une miniature)"
+
+#: application/bookmark/BookmarkInitializer.php:45
+msgid ""
+"Shaarli will automatically pick up the thumbnail for links to a variety of "
+"websites.\n"
+"\n"
+"Explore your new Shaarli instance by trying out controls and menus.\n"
+"Visit the project on [Github](https://github.com/shaarli/Shaarli) or [the "
+"documentation](https://shaarli.readthedocs.io/en/master/) to learn more "
+"about Shaarli.\n"
+"\n"
+"Now you can edit or delete the default shaares.\n"
+msgstr ""
+"Shaarli récupérera automatiquement la miniature associée au liens pour de "
+"nombreux sites web.\n"
+"\n"
+"Explorez votre nouvelle instance de Shaarli en essayant les différents "
+"contrôles et menus.\n"
+"Visitez le projet sur [Github](https://github.com/shaarli/Shaarli) ou [la "
+"documentation](https://shaarli.readthedocs.io/en/master/) pour en apprendre "
+"plus sur Shaarli.\n"
+"\n"
+"Maintenant, vous pouvez modifier ou supprimer les shaares créés par défaut.\n"
+
+#: application/bookmark/BookmarkInitializer.php:58
+msgid "Note: Shaare descriptions"
+msgstr "Note : Description des Shaares"
+
+#: application/bookmark/BookmarkInitializer.php:60
+msgid ""
+"Adding a shaare without entering a URL creates a text-only \"note\" post "
+"such as this one.\n"
+"This note is private, so you are the only one able to see it while logged "
+"in.\n"
+"\n"
+"You can use this to keep notes, post articles, code snippets, and much "
+"more.\n"
+"\n"
+"The Markdown formatting setting allows you to format your notes and bookmark "
+"description:\n"
+"\n"
+"### Title headings\n"
+"\n"
+"#### Multiple headings levels\n"
+" * bullet lists\n"
+" * _italic_ text\n"
+" * **bold** text\n"
+" * ~~strike through~~ text\n"
+" * `code` blocks\n"
+" * images\n"
+" * [links](https://en.wikipedia.org/wiki/Markdown)\n"
+"\n"
+"Markdown also supports tables:\n"
+"\n"
+"| Name | Type | Color | Qty |\n"
+"| ------- | --------- | ------ | ----- |\n"
+"| Orange | Fruit | Orange | 126 |\n"
+"| Apple | Fruit | Any | 62 |\n"
+"| Lemon | Fruit | Yellow | 30 |\n"
+"| Carrot | Vegetable | Red | 14 |\n"
+msgstr ""
+"Ajouter un shaare sans préciser d'URL créé une « note » textuelle, telle que "
+"celle-ci.\n"
+"Cette note est privée, donc vous êtes seul à pouvoir la voir lorsque vous "
+"êtes connecté.\n"
+"\n"
+"Vous pouvez utiliser cette fonctionnalité pour prendre des notes, publier "
+"des articles, des extraits de code, et bien plus.\n"
+"\n"
+"L'option du formatage par Markdown vous permet de formater vos description "
+"de notes et marque-pages :\n"
+"\n"
+"### Titre d'en-tête\n"
+"\n"
+"#### Sur plusieurs niveaux\n"
+" * liste à puce\n"
+" * texte en _italique_\n"
+" * texte en **gras**\n"
+" * texte ~~barré~~\n"
+" * blocs de `code`\n"
+" * images\n"
+" * [liens](https://en.wikipedia.org/wiki/Markdown)\n"
+"\n"
+"Markdown supporte aussi les tableaux :\n"
+"\n"
+"| Nom | Type | Couleur | Qte |\n"
+"| ------- | --------- | ------ | ----- |\n"
+"| Orange | Fruit | Orange | 126 |\n"
+"| Pomme | Fruit | Multiple | 62 |\n"
+"| Citron | Fruit | Jaune | 30 |\n"
+"| Carotte | Légume | Orange | 14 |\n"
+
+#: application/bookmark/BookmarkInitializer.php:94
+#: application/legacy/LegacyLinkDB.php:246
+#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
+#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
+#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:15
+#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:48
+msgid ""
+"The personal, minimalist, super-fast, database free, bookmarking service"
+msgstr ""
+"Le gestionnaire de marque-pages personnel, minimaliste, et sans base de "
+"données"
+
+#: application/bookmark/BookmarkInitializer.php:97
+msgid ""
+"Welcome to Shaarli!\n"
+"\n"
+"Shaarli allows you to bookmark your favorite pages, and share them with "
+"others or store them privately.\n"
+"You can add a description to your bookmarks, such as this one, and tag "
+"them.\n"
+"\n"
+"Create a new shaare by clicking the `+Shaare` button, or using any of the "
+"recommended tools (browser extension, mobile app, bookmarklet, REST API, "
+"etc.).\n"
+"\n"
+"You can easily retrieve your links, even with thousands of them, using the "
+"internal search engine, or search through tags (e.g. this Shaare is tagged "
+"with `shaarli` and `help`).\n"
+"Hashtags such as #shaarli #help are also supported.\n"
+"You can also filter the available [RSS feed](/feed/atom) and picture wall by "
+"tag or plaintext search.\n"
+"\n"
+"We hope that you will enjoy using Shaarli, maintained with ❤️ by the "
+"community!\n"
+"Feel free to open [an issue](https://github.com/shaarli/Shaarli/issues) if "
+"you have a suggestion or encounter an issue.\n"
+msgstr ""
+"Bienvenue sur Shaarli !\n"
+"\n"
+"Shaarli vous permet de sauvegarder des marque-pages de vos pages favorites, "
+"et de les partager avec d'autres, ou de les enregistrer en privé.\n"
+"Vous pouvez ajouter une description à vos marque-pages, comme celle-ci, et y "
+"ajouter des tags.\n"
+"\n"
+"Créez un nouveau shaare en cliquant sur le bouton `+Shaare`, ou en utilisant "
+"l'un des outils recommandés (extension de navigateur, application mobile, "
+"bookmarklet, REST API, etc.).\n"
+"\n"
+"Vous pouvez facilement retrouver vos liens, même parmi des milliers, en "
+"utilisant le moteur de recherche interne, ou en filtrant par tags (par "
+"exemple ce Shaare est taggé avec `shaarli` et `help`).\n"
+"Les hashtags comme #shaarli #help sont aussi supportés.\n"
+"Vous pouvez aussi filtrer les [flux RSS](/feed/atom) et [mur d'images]() par "
+"tag ou par texte brut.\n"
+"\n"
+"Nous espérons que vous apprécierez utiliser Shaarli, maintenu avec ❤️ par la "
+"communauté !\n"
+"N'hésitez pas à ouvrir [un ticket (en)](https://github.com/shaarli/Shaarli/"
+"issues) si vous avez une suggestion ou si vous rencontrez un problème.\n"
+" \n"
+
+#: application/bookmark/exception/BookmarkNotFoundException.php:14
+msgid "The link you are trying to reach does not exist or has been deleted."
+msgstr "Le lien que vous essayez de consulter n'existe pas ou a été supprimé."
+
+#: application/config/ConfigJson.php:52 application/config/ConfigPhp.php:131
+msgid ""
+"Shaarli could not create the config file. Please make sure Shaarli has the "
+"right to write in the folder is it installed in."
+msgstr ""
+"Shaarli n'a pas pu créer le fichier de configuration. Merci de vérifier que "
+"Shaarli a les droits d'écriture dans le dossier dans lequel il est installé."
+
+#: application/config/ConfigManager.php:137
+#: application/config/ConfigManager.php:164
+msgid "Invalid setting key parameter. String expected, got: "
+msgstr "Clé de paramétrage invalide. Chaîne de caractères obtenue, attendu : "
+
+#: application/config/exception/MissingFieldConfigException.php:20
+#, php-format
+msgid "Configuration value is required for %s"
+msgstr "Le paramètre %s est obligatoire"
+
+#: application/config/exception/PluginConfigOrderException.php:15
+msgid "An error occurred while trying to save plugins loading order."
+msgstr ""
+"Une erreur s'est produite lors de la sauvegarde de l'ordre des extensions."
+
+#: application/config/exception/UnauthorizedConfigException.php:15
+msgid "You are not authorized to alter config."
+msgstr "Vous n'êtes pas autorisé à modifier la configuration."
+
+#: application/exceptions/IOException.php:23
+msgid "Error accessing"
+msgstr "Une erreur s'est produite en accédant à"
+
+#: application/feed/FeedBuilder.php:180
+msgid "Direct link"
+msgstr "Liens directs"
+
+#: application/feed/FeedBuilder.php:182
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:103
+#: tmp/dailyrss.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:179
+msgid "Permalink"
+msgstr "Permalien"
+
+#: application/front/controller/admin/ConfigureController.php:56
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24
+msgid "Configure"
+msgstr "Configurer"
+
+#: application/front/controller/admin/ConfigureController.php:106
+#: application/legacy/LegacyUpdater.php:539
+msgid "You have enabled or changed thumbnails mode."
+msgstr "Vous avez activé ou changé le mode de miniatures."
+
+#: application/front/controller/admin/ConfigureController.php:108
+#: application/front/controller/admin/ServerController.php:76
+#: application/legacy/LegacyUpdater.php:540
+msgid "Please synchronize them."
+msgstr "Merci de les synchroniser."
+
+#: application/front/controller/admin/ConfigureController.php:119
+#: application/front/controller/visitor/InstallController.php:149
+msgid "Error while writing config file after configuration update."
+msgstr ""
+"Une erreur s'est produite lors de la sauvegarde du fichier de configuration."
+
+#: application/front/controller/admin/ConfigureController.php:128
+msgid "Configuration was saved."
+msgstr "La configuration a été sauvegardée."
+
+#: application/front/controller/admin/ExportController.php:26
+#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:64
+msgid "Export"
+msgstr "Exporter"
+
+#: application/front/controller/admin/ExportController.php:42
+msgid "Please select an export mode."
+msgstr "Merci de choisir un mode d'export."
+
+#: application/front/controller/admin/ImportController.php:41
+#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:83
+msgid "Import"
+msgstr "Importer"
+
+#: application/front/controller/admin/ImportController.php:55
+msgid "No import file provided."
+msgstr "Aucun fichier à importer n'a été fourni."
+
+#: application/front/controller/admin/ImportController.php:66
+#, php-format
+msgid ""
+"The file you are trying to upload is probably bigger than what this "
+"webserver can accept (%s). Please upload in smaller chunks."
+msgstr ""
+"Le fichier que vous essayer d'envoyer est probablement plus lourd que ce que "
+"le serveur web peut accepter (%s). Merci de l'envoyer en parties plus "
+"légères."
+
+#: application/front/controller/admin/ManageTagController.php:30
+msgid "whitespace"
+msgstr "espace"
+
+#: application/front/controller/admin/ManageTagController.php:35
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42
+msgid "Manage tags"
+msgstr "Gérer les tags"
+
+#: application/front/controller/admin/ManageTagController.php:54
+msgid "Invalid tags provided."
+msgstr "Les tags fournis ne sont pas valides."
+
+#: application/front/controller/admin/ManageTagController.php:78
+#, php-format
+msgid "The tag was removed from %d bookmark."
+msgid_plural "The tag was removed from %d bookmarks."
+msgstr[0] "Le tag a été supprimé du %d lien."
+msgstr[1] "Le tag a été supprimé de %d liens."
+
+#: application/front/controller/admin/ManageTagController.php:83
+#, php-format
+msgid "The tag was renamed in %d bookmark."
+msgid_plural "The tag was renamed in %d bookmarks."
+msgstr[0] "Le tag a été renommé dans %d lien."
+msgstr[1] "Le tag a été renommé dans %d liens."
+
+#: application/front/controller/admin/ManageTagController.php:105
+msgid "Tags separator must be a single character."
+msgstr "Un séparateur de tags doit contenir un seul caractère."
+
+#: application/front/controller/admin/ManageTagController.php:111
+msgid "These characters are reserved and can't be used as tags separator: "
+msgstr ""
+"Ces caractères sont réservés et ne peuvent être utilisés comme des "
+"séparateurs de tags : "
+
+#: application/front/controller/admin/PasswordController.php:28
+#: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35
+msgid "Change password"
+msgstr "Modifier le mot de passe"
+
+#: application/front/controller/admin/PasswordController.php:55
+msgid "You must provide the current and new password to change it."
+msgstr ""
+"Vous devez fournir les mots de passe actuel et nouveau pour pouvoir le "
+"modifier."
+
+#: application/front/controller/admin/PasswordController.php:71
+msgid "The old password is not correct."
+msgstr "L'ancien mot de passe est incorrect."
+
+#: application/front/controller/admin/PasswordController.php:97
+msgid "Your password has been changed"
+msgstr "Votre mot de passe a été modifié"
+
+#: application/front/controller/admin/PluginsController.php:45
+msgid "Plugin Administration"
+msgstr "Administration des plugins"
+
+#: application/front/controller/admin/PluginsController.php:76
+msgid "Setting successfully saved."
+msgstr "Les paramètres ont été sauvegardés avec succès."
+
+#: application/front/controller/admin/PluginsController.php:79
+msgid "Error while saving plugin configuration: "
+msgstr ""
+"Une erreur s'est produite lors de la sauvegarde de la configuration des "
+"plugins : "
+
+#: application/front/controller/admin/ServerController.php:35
+msgid "Check disabled"
+msgstr "Vérification désactivée"
+
+#: application/front/controller/admin/ServerController.php:57
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
+msgid "Server administration"
+msgstr "Administration serveur"
+
+#: application/front/controller/admin/ServerController.php:74
+msgid "Thumbnails cache has been cleared."
+msgstr "Le cache des miniatures a été vidé."
+
+#: application/front/controller/admin/ServerController.php:85
+msgid "Shaarli's cache folder has been cleared!"
+msgstr "Le dossier de cache de Shaarli a été vidé !"
+
+#: application/front/controller/admin/ShaareAddController.php:26
+#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
+msgid "Shaare a new link"
+msgstr "Partagez un nouveau lien"
+
+#: application/front/controller/admin/ShaareManageController.php:35
+#: application/front/controller/admin/ShaareManageController.php:93
+msgid "Invalid bookmark ID provided."
+msgstr "L'ID du marque-page fourni n'est pas valide."
+
+#: application/front/controller/admin/ShaareManageController.php:47
+#: application/front/controller/admin/ShaareManageController.php:116
+#: application/front/controller/admin/ShaareManageController.php:156
+#: application/front/controller/admin/ShaarePublishController.php:82
+#, 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:173
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:171
+msgid "Edit"
+msgstr "Modifier"
+
+#: application/front/controller/admin/ShaarePublishController.php:176
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:28
+msgid "Shaare"
+msgstr "Shaare"
+
+#: application/front/controller/admin/ShaarePublishController.php:208
+msgid "Note: "
+msgstr "Note : "
+
+#: application/front/controller/admin/ThumbnailsController.php:37
+#: tmp/thumbnails.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
+msgid "Thumbnails update"
+msgstr "Mise à jour des miniatures"
+
+#: application/front/controller/admin/ToolsController.php:31
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:33
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:33
+msgid "Tools"
+msgstr "Outils"
+
+#: application/front/controller/visitor/BookmarkListController.php:121
+msgid "Search: "
+msgstr "Recherche : "
+
+#: application/front/controller/visitor/DailyController.php:200
+msgid "day"
+msgstr "jour"
+
+#: application/front/controller/visitor/DailyController.php:200
+#: application/front/controller/visitor/DailyController.php:203
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:48
+msgid "Daily"
+msgstr "Quotidien"
+
+#: application/front/controller/visitor/DailyController.php:201
+msgid "week"
+msgstr "semaine"
+
+#: application/front/controller/visitor/DailyController.php:201
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
+msgid "Weekly"
+msgstr "Hebdomadaire"
+
+#: application/front/controller/visitor/DailyController.php:202
+msgid "month"
+msgstr "mois"
+
+#: application/front/controller/visitor/DailyController.php:202
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
+msgid "Monthly"
+msgstr "Mensuel"
+
+#: application/front/controller/visitor/ErrorController.php:30
+msgid "Error: "
+msgstr "Erreur : "
+
+#: application/front/controller/visitor/ErrorController.php:34
+msgid "Please report it on Github."
+msgstr "Merci de la rapporter sur Github."
+
+#: application/front/controller/visitor/ErrorController.php:39
+msgid "An unexpected error occurred."
+msgstr "Une erreur inattendue s'est produite."
+
+#: application/front/controller/visitor/ErrorNotFoundController.php:25
+msgid "Requested page could not be found."
+msgstr "La page demandée n'a pas pu être trouvée."
+
+#: application/front/controller/visitor/InstallController.php:65
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22
+msgid "Install Shaarli"
+msgstr "Installation de Shaarli"
+
+#: application/front/controller/visitor/InstallController.php:85
+#, php-format
+msgid ""
+"Sessions do not seem to work correctly on your server.
Make sure the "
+"variable \"session.save_path\" is set correctly in your PHP config, and that "
+"you have write access to it.
It currently points to %s.
On some "
+"browsers, accessing your server via a hostname like 'localhost' or any "
+"custom hostname without a dot causes cookie storage to fail. We recommend "
+"accessing your server via it's IP address or Fully Qualified Domain Name.
"
+msgstr ""
+"Les sesssions ne semblent pas fonctionner sur ce serveur.
Assurez "
+"vous que la variable « session.save_path » est correctement définie dans "
+"votre fichier de configuration PHP, et que vous avez les droits d'écriture "
+"dessus.
Ce paramètre pointe actuellement sur %s.
Sur certains "
+"navigateurs, accéder à votre serveur depuis un nom d'hôte comme « localhost "
+"» ou autre nom personnalisé sans point '.' entraine l'échec de la sauvegarde "
+"des cookies. Nous vous recommandons d'accéder à votre serveur depuis son "
+"adresse IP ou un Fully Qualified Domain Name.
"
+
+#: application/front/controller/visitor/InstallController.php:157
+msgid ""
+"Shaarli is now configured. Please login and start shaaring your bookmarks!"
+msgstr ""
+"Shaarli est maintenant configuré. Vous pouvez vous connecter et commencez à "
+"shaare vos liens !"
+
+#: application/front/controller/visitor/InstallController.php:171
+msgid "Insufficient permissions:"
+msgstr "Permissions insuffisantes :"
+
+#: application/front/controller/visitor/LoginController.php:46
+#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
+#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:101
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:77
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:101
+msgid "Login"
+msgstr "Connexion"
+
+#: application/front/controller/visitor/LoginController.php:78
+msgid "Wrong login/password."
+msgstr "Nom d'utilisateur ou mot de passe incorrect(s)."
+
+#: application/front/controller/visitor/PictureWallController.php:29
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:43
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:43
+msgid "Picture wall"
+msgstr "Mur d'images"
+
+#: application/front/controller/visitor/TagCloudController.php:90
+msgid "Tag "
+msgstr "Tag "
+
+#: application/front/exceptions/AlreadyInstalledException.php:11
+msgid "Shaarli has already been installed. Login to edit the configuration."
+msgstr ""
+"Shaarli est déjà installé. Connectez-vous pour modifier la configuration."
+
+#: application/front/exceptions/LoginBannedException.php:11
+msgid ""
+"You have been banned after too many failed login attempts. Try again later."
+msgstr ""
+"Vous avez été banni après trop d'échecs d'authentification. Merci de "
+"réessayer plus tard."
+
+#: application/front/exceptions/OpenShaarliPasswordException.php:16
+msgid "You are not supposed to change a password on an Open Shaarli."
+msgstr ""
+"Vous n'êtes pas censé modifier le mot de passe d'un Shaarli en mode ouvert."
+
+#: application/front/exceptions/ThumbnailsDisabledException.php:11
+msgid "Picture wall unavailable (thumbnails are disabled)."
+msgstr ""
+"Le mur d'images n'est pas disponible (les miniatures sont désactivées)."
+
+#: application/front/exceptions/WrongTokenException.php:16
+msgid "Wrong token."
+msgstr "Jeton invalide."
+
+#: application/helper/ApplicationUtils.php:165
#, php-format
msgid ""
"Your PHP version is obsolete! Shaarli requires at least PHP %s, and thus "
@@ -29,109 +631,100 @@ msgstr ""
"peut donc pas fonctionner. Votre version de PHP a des failles de sécurités "
"connues et devrait être mise à jour au plus tôt."
-#: application/ApplicationUtils.php:189 application/ApplicationUtils.php:201
+#: application/helper/ApplicationUtils.php:200
+#: application/helper/ApplicationUtils.php:220
msgid "directory is not readable"
msgstr "le répertoire n'est pas accessible en lecture"
-#: application/ApplicationUtils.php:204
+#: application/helper/ApplicationUtils.php:223
msgid "directory is not writable"
msgstr "le répertoire n'est pas accessible en écriture"
-#: application/ApplicationUtils.php:222
+#: application/helper/ApplicationUtils.php:247
msgid "file is not readable"
msgstr "le fichier n'est pas accessible en lecture"
-#: application/ApplicationUtils.php:225
+#: application/helper/ApplicationUtils.php:250
msgid "file is not writable"
msgstr "le fichier n'est pas accessible en écriture"
-#: application/History.php:178
-msgid "History file isn't readable or writable"
-msgstr "Le fichier d'historique n'est pas accessible en lecture ou en écriture"
-
-#: application/History.php:189
-msgid "Could not parse history file"
-msgstr "Format incorrect pour le fichier d'historique"
-
-#: application/Languages.php:181
-msgid "Automatic"
-msgstr "Automatique"
-
-#: application/Languages.php:182
-msgid "English"
-msgstr "Anglais"
-
-#: application/Languages.php:183
-msgid "French"
-msgstr "Français"
-
-#: application/Languages.php:184
-msgid "German"
-msgstr "Allemand"
-
-#: application/Thumbnailer.php:62
+#: application/helper/ApplicationUtils.php:260
msgid ""
-"php-gd extension must be loaded to use thumbnails. Thumbnails are now "
-"disabled. Please reload the page."
+"Lock can not be acquired on the datastore. You might encounter concurrent "
+"access issues."
msgstr ""
-"l'extension php-gd doit être chargée pour utiliser les miniatures. Les "
-"miniatures sont désormais désactivées. Rechargez la page."
+"Le fichier datastore ne peut pas être verrouillé. Vous pourriez rencontrer "
+"des problèmes d'accès concurrents."
-#: application/Utils.php:379 tests/UtilsTest.php:343
-msgid "Setting not set"
-msgstr "Paramètre non défini"
+#: application/helper/ApplicationUtils.php:293
+msgid "Configuration parsing"
+msgstr "Chargement de la configuration"
-#: application/Utils.php:386 tests/UtilsTest.php:341 tests/UtilsTest.php:342
-msgid "Unlimited"
-msgstr "Illimité"
+#: application/helper/ApplicationUtils.php:294
+msgid "Slim Framework (routing, etc.)"
+msgstr "Slim Framwork (routage, etc.)"
-#: application/Utils.php:389 tests/UtilsTest.php:338 tests/UtilsTest.php:339
-#: tests/UtilsTest.php:353
-msgid "B"
-msgstr "o"
+#: application/helper/ApplicationUtils.php:295
+msgid "Multibyte (Unicode) string support"
+msgstr "Support des chaînes de caractère multibytes (Unicode)"
-#: application/Utils.php:389 tests/UtilsTest.php:332 tests/UtilsTest.php:333
-#: tests/UtilsTest.php:340
-msgid "kiB"
-msgstr "ko"
+#: application/helper/ApplicationUtils.php:296
+msgid "Required to use thumbnails"
+msgstr "Obligatoire pour utiliser les miniatures"
-#: application/Utils.php:389 tests/UtilsTest.php:334 tests/UtilsTest.php:335
-#: tests/UtilsTest.php:351 tests/UtilsTest.php:352
-msgid "MiB"
-msgstr "Mo"
+#: application/helper/ApplicationUtils.php:297
+msgid "Localized text sorting (e.g. e->è->f)"
+msgstr "Tri des textes traduits (ex : e->è->f)"
-#: application/Utils.php:389 tests/UtilsTest.php:336 tests/UtilsTest.php:337
-msgid "GiB"
-msgstr "Go"
+#: application/helper/ApplicationUtils.php:298
+msgid "Better retrieval of bookmark metadata and thumbnail"
+msgstr "Meilleure récupération des meta-données des marque-pages et miniatures"
-#: application/bookmark/LinkDB.php:128
+#: application/helper/ApplicationUtils.php:299
+msgid "Use the translation system in gettext mode"
+msgstr "Utiliser le système de traduction en mode gettext"
+
+#: application/helper/ApplicationUtils.php:300
+msgid "Login using LDAP server"
+msgstr "Authentification via un serveur LDAP"
+
+#: application/helper/DailyPageHelper.php:172
+msgid "Week"
+msgstr "Semaine"
+
+#: application/helper/DailyPageHelper.php:176
+msgid "Today"
+msgstr "Aujourd'hui"
+
+#: application/helper/DailyPageHelper.php:178
+msgid "Yesterday"
+msgstr "Hier"
+
+#: application/helper/FileUtils.php:100
+msgid "Provided path is not a directory."
+msgstr "Le chemin fourni n'est pas un dossier."
+
+#: application/helper/FileUtils.php:104
+msgid "Trying to delete a folder outside of Shaarli path."
+msgstr "Tentative de supprimer un dossier en dehors du chemin de Shaarli."
+
+#: application/legacy/LegacyLinkDB.php:131
msgid "You are not authorized to add a link."
msgstr "Vous n'êtes pas autorisé à ajouter un lien."
-#: application/bookmark/LinkDB.php:131
+#: application/legacy/LegacyLinkDB.php:134
msgid "Internal Error: A link should always have an id and URL."
msgstr "Erreur interne : un lien devrait toujours avoir un ID et une URL."
-#: application/bookmark/LinkDB.php:134
+#: application/legacy/LegacyLinkDB.php:137
msgid "You must specify an integer as a key."
msgstr "Vous devez utiliser un entier comme clé."
-#: application/bookmark/LinkDB.php:137
+#: application/legacy/LegacyLinkDB.php:140
msgid "Array offset and link ID must be equal."
msgstr "La clé du tableau et l'ID du lien doivent être identiques."
-#: application/bookmark/LinkDB.php:243
-#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
-#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:49
-#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:15
-#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:49
-msgid ""
-"The personal, minimalist, super-fast, database free, bookmarking service"
-msgstr ""
-"Le gestionnaire de marque-pages personnel, minimaliste, et sans base de "
-"données"
-
-#: application/bookmark/LinkDB.php:246
+#: application/legacy/LegacyLinkDB.php:249
msgid ""
"Welcome to Shaarli! This is your first public bookmark. To edit or delete "
"me, you must first login.\n"
@@ -151,314 +744,68 @@ msgstr ""
"Vous utilisez la version supportée par la communauté du projet original "
"Shaarli de Sébastien Sauvage."
-#: application/bookmark/LinkDB.php:263
+#: application/legacy/LegacyLinkDB.php:266
msgid "My secret stuff... - Pastebin.com"
msgstr "Mes trucs secrets... - Pastebin.com"
-#: application/bookmark/LinkDB.php:265
+#: application/legacy/LegacyLinkDB.php:268
msgid "Shhhh! I'm a private link only YOU can see. You can delete me too."
msgstr ""
"Pssst ! Je suis un lien privé que VOUS êtes le seul à voir. Vous pouvez me "
"supprimer aussi."
-#: application/bookmark/exception/LinkNotFoundException.php:13
-msgid "The link you are trying to reach does not exist or has been deleted."
-msgstr "Le lien que vous essayez de consulter n'existe pas ou a été supprimé."
+#: application/legacy/LegacyUpdater.php:104
+msgid "Couldn't retrieve updater class methods."
+msgstr "Impossible de récupérer les méthodes de la classe Updater."
-#: application/config/ConfigJson.php:52 application/config/ConfigPhp.php:129
-msgid ""
-"Shaarli could not create the config file. Please make sure Shaarli has the "
-"right to write in the folder is it installed in."
-msgstr ""
-"Shaarli n'a pas pu créer le fichier de configuration. Merci de vérifier que "
-"Shaarli a les droits d'écriture dans le dossier dans lequel il est installé."
+#: application/legacy/LegacyUpdater.php:540
+msgid ""
+msgstr ""
-#: application/config/ConfigManager.php:135
-#: application/config/ConfigManager.php:162
-msgid "Invalid setting key parameter. String expected, got: "
-msgstr "Clé de paramétrage invalide. Chaîne de caractères obtenue, attendu : "
-
-#: application/config/exception/MissingFieldConfigException.php:21
-#, php-format
-msgid "Configuration value is required for %s"
-msgstr "Le paramètre %s est obligatoire"
-
-#: application/config/exception/PluginConfigOrderException.php:15
-msgid "An error occurred while trying to save plugins loading order."
-msgstr ""
-"Une erreur s'est produite lors de la sauvegarde de l'ordre des extensions."
-
-#: application/config/exception/UnauthorizedConfigException.php:16
-msgid "You are not authorized to alter config."
-msgstr "Vous n'êtes pas autorisé à modifier la configuration."
-
-#: application/exceptions/IOException.php:22
-msgid "Error accessing"
-msgstr "Une erreur s'est produite en accédant à"
-
-#: application/feed/Cache.php:16
-#, php-format
-msgid "Cannot purge %s: no directory"
-msgstr "Impossible de purger %s : le répertoire n'existe pas"
-
-#: application/feed/FeedBuilder.php:155
-msgid "Direct link"
-msgstr "Liens directs"
-
-#: application/feed/FeedBuilder.php:157
-#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:96
-#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:177
-msgid "Permalink"
-msgstr "Permalien"
-
-#: application/netscape/NetscapeBookmarkUtils.php:42
+#: application/netscape/NetscapeBookmarkUtils.php:63
msgid "Invalid export selection:"
msgstr "Sélection d'export invalide :"
-#: application/netscape/NetscapeBookmarkUtils.php:87
+#: application/netscape/NetscapeBookmarkUtils.php:215
#, php-format
msgid "File %s (%d bytes) "
msgstr "Le fichier %s (%d octets) "
-#: application/netscape/NetscapeBookmarkUtils.php:89
+#: application/netscape/NetscapeBookmarkUtils.php:217
msgid "has an unknown file format. Nothing was imported."
msgstr "a un format inconnu. Rien n'a été importé."
-#: application/netscape/NetscapeBookmarkUtils.php:93
+#: application/netscape/NetscapeBookmarkUtils.php:221
#, php-format
msgid ""
-"was successfully processed in %d seconds: %d links imported, %d links "
-"overwritten, %d links skipped."
+"was successfully processed in %d seconds: %d bookmarks imported, %d "
+"bookmarks overwritten, %d bookmarks skipped."
msgstr ""
"a été importé avec succès en %d secondes : %d liens importés, %d liens "
"écrasés, %d liens ignorés."
-#: application/plugin/exception/PluginFileNotFoundException.php:21
+#: application/plugin/PluginManager.php:125
+msgid " [plugin incompatibility]: "
+msgstr " [incompatibilité de l'extension] : "
+
+#: application/plugin/exception/PluginFileNotFoundException.php:22
#, php-format
msgid "Plugin \"%s\" files not found."
msgstr "Les fichiers de l'extension \"%s\" sont introuvables."
-#: application/render/PageBuilder.php:209
-msgid "The page you are trying to reach does not exist or has been deleted."
-msgstr "La page que vous essayez de consulter n'existe pas ou a été supprimée."
-
-#: application/render/PageBuilder.php:211
-msgid "404 Not Found"
-msgstr "404 Introuvable"
-
-#: application/updater/Updater.php:99
-#, fuzzy
-#| msgid "Couldn't retrieve Updater class methods."
-msgid "Couldn't retrieve updater class methods."
-msgstr "Impossible de récupérer les méthodes de la classe Updater."
-
-#: application/updater/Updater.php:526 index.php:1034
-msgid ""
-"You have enabled or changed thumbnails mode. Please synchronize them."
-msgstr ""
-"Vous avez activé ou changé le mode de miniatures. Merci de les synchroniser."
-
-#: application/updater/UpdaterUtils.php:32
-msgid "Updates file path is not set, can't write updates."
-msgstr ""
-"Le chemin vers le fichier de mise à jour n'est pas défini, impossible "
-"d'écrire les mises à jour."
-
-#: application/updater/UpdaterUtils.php:37
-msgid "Unable to write updates in "
-msgstr "Impossible d'écrire les mises à jour dans "
+#: application/render/PageCacheManager.php:32
+#, php-format
+msgid "Cannot purge %s: no directory"
+msgstr "Impossible de purger %s : le répertoire n'existe pas"
#: application/updater/exception/UpdaterException.php:51
msgid "An error occurred while running the update "
msgstr "Une erreur s'est produite lors de l'exécution de la mise à jour "
-#: index.php:145
-msgid "Shared links on "
+#: index.php:81
+msgid "Shared bookmarks on "
msgstr "Liens partagés sur "
-#: index.php:167
-msgid "Insufficient permissions:"
-msgstr "Permissions insuffisantes :"
-
-#: index.php:203
-msgid "I said: NO. You are banned for the moment. Go away."
-msgstr "NON. Vous êtes banni pour le moment. Revenez plus tard."
-
-#: index.php:275
-msgid "Wrong login/password."
-msgstr "Nom d'utilisateur ou mot de passe incorrect(s)."
-
-#: index.php:398 index.php:404
-msgid "Today"
-msgstr "Aujourd'hui"
-
-#: index.php:400
-msgid "Yesterday"
-msgstr "Hier"
-
-#: index.php:484 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:46
-msgid "Daily"
-msgstr "Quotidien"
-
-#: index.php:593 tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
-#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:75
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:99
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:75
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:99
-msgid "Login"
-msgstr "Connexion"
-
-#: index.php:608 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:41
-msgid "Picture wall"
-msgstr "Mur d'images"
-
-#: index.php:683 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:36
-#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19
-msgid "Tag cloud"
-msgstr "Nuage de tags"
-
-#: index.php:715 tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19
-msgid "Tag list"
-msgstr "Liste des tags"
-
-#: index.php:944 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:31
-msgid "Tools"
-msgstr "Outils"
-
-#: index.php:952
-msgid "You are not supposed to change a password on an Open Shaarli."
-msgstr ""
-"Vous n'êtes pas censé modifier le mot de passe d'un Shaarli en mode ouvert."
-
-#: index.php:957 index.php:1007 index.php:1094 index.php:1124 index.php:1234
-#: index.php:1281
-msgid "Wrong token."
-msgstr "Jeton invalide."
-
-#: index.php:966
-msgid "The old password is not correct."
-msgstr "L'ancien mot de passe est incorrect."
-
-#: index.php:993
-msgid "Your password has been changed"
-msgstr "Votre mot de passe a été modifié"
-
-#: index.php:997
-#: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29
-msgid "Change password"
-msgstr "Modifier le mot de passe"
-
-#: index.php:1054
-msgid "Configuration was saved."
-msgstr "La configuration a été sauvegardée."
-
-#: index.php:1078 tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24
-msgid "Configure"
-msgstr "Configurer"
-
-#: index.php:1088 tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
-msgid "Manage tags"
-msgstr "Gérer les tags"
-
-#: index.php:1107
-#, php-format
-msgid "The tag was removed from %d link."
-msgid_plural "The tag was removed from %d links."
-msgstr[0] "Le tag a été supprimé de %d lien."
-msgstr[1] "Le tag a été supprimé de %d liens."
-
-#: index.php:1108
-#, php-format
-msgid "The tag was renamed in %d link."
-msgid_plural "The tag was renamed in %d links."
-msgstr[0] "Le tag a été renommé dans %d lien."
-msgstr[1] "Le tag a été renommé dans %d liens."
-
-#: index.php:1115 tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
-msgid "Shaare a new link"
-msgstr "Partager un nouveau lien"
-
-#: index.php:1344 tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169
-msgid "Edit"
-msgstr "Modifier"
-
-#: index.php:1344 index.php:1416
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:26
-msgid "Shaare"
-msgstr "Shaare"
-
-#: index.php:1385
-msgid "Note: "
-msgstr "Note : "
-
-#: index.php:1424
-msgid "Invalid link ID provided"
-msgstr "ID du lien non valide"
-
-#: index.php:1444 tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:65
-msgid "Export"
-msgstr "Exporter"
-
-#: index.php:1506 tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:83
-msgid "Import"
-msgstr "Importer"
-
-#: index.php:1516
-#, php-format
-msgid ""
-"The file you are trying to upload is probably bigger than what this "
-"webserver can accept (%s). Please upload in smaller chunks."
-msgstr ""
-"Le fichier que vous essayer d'envoyer est probablement plus lourd que ce que "
-"le serveur web peut accepter (%s). Merci de l'envoyer en parties plus "
-"légères."
-
-#: index.php:1561 tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22
-msgid "Plugin administration"
-msgstr "Administration des plugins"
-
-#: index.php:1616 tmp/thumbnails.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
-msgid "Thumbnails update"
-msgstr "Mise à jour des miniatures"
-
-#: index.php:1782
-msgid "Search: "
-msgstr "Recherche : "
-
-#: index.php:1825
-#, php-format
-msgid ""
-"Sessions do not seem to work correctly on your server.
Make sure the "
-"variable \"session.save_path\" is set correctly in your PHP config, and that "
-"you have write access to it.
It currently points to %s.
On some "
-"browsers, accessing your server via a hostname like 'localhost' or any "
-"custom hostname without a dot causes cookie storage to fail. We recommend "
-"accessing your server via it's IP address or Fully Qualified Domain Name.
"
-msgstr ""
-"Les sesssions ne semblent pas fonctionner sur ce serveur.
Assurez "
-"vous que la variable « session.save_path » est correctement définie dans "
-"votre fichier de configuration PHP, et que vous avez les droits d'écriture "
-"dessus.
Ce paramètre pointe actuellement sur %s.
Sur certains "
-"navigateurs, accéder à votre serveur depuis un nom d'hôte comme « localhost "
-"» ou autre nom personnalisé sans point '.' entraine l'échec de la sauvegarde "
-"des cookies. Nous vous recommandons d'accéder à votre serveur depuis son "
-"adresse IP ou un Fully Qualified Domain Name.
"
-
-#: index.php:1835
-msgid "Click to try again."
-msgstr "Cliquer ici pour réessayer."
-
#: plugins/addlink_toolbar/addlink_toolbar.php:31
msgid "URI"
msgstr "URI"
@@ -472,15 +819,15 @@ msgstr "Shaare"
msgid "Adds the addlink input on the linklist page."
msgstr "Ajoute le formulaire d'ajout de liens sur la page principale."
-#: plugins/archiveorg/archiveorg.php:25
+#: plugins/archiveorg/archiveorg.php:29
msgid "View on archive.org"
msgstr "Voir sur archive.org"
-#: plugins/archiveorg/archiveorg.php:38
+#: plugins/archiveorg/archiveorg.php:42
msgid "For each link, add an Archive.org icon."
msgstr "Pour chaque lien, ajoute une icône pour Archive.org."
-#: plugins/default_colors/default_colors.php:33
+#: plugins/default_colors/default_colors.php:38
msgid ""
"Default colors plugin error: This plugin is active and no custom color is "
"configured."
@@ -488,25 +835,25 @@ msgstr ""
"Erreur du plugin default colors : ce plugin est actif et aucune couleur "
"n'est configurée."
-#: plugins/default_colors/default_colors.php:107
+#: plugins/default_colors/default_colors.php:113
msgid "Override default theme colors. Use any CSS valid color."
msgstr ""
"Remplacer les couleurs du thème par défaut. Utiliser n'importe quelle "
"couleur CSS valide."
-#: plugins/default_colors/default_colors.php:108
+#: plugins/default_colors/default_colors.php:114
msgid "Main color (navbar green)"
msgstr "Couleur principale (vert de la barre de navigation)"
-#: plugins/default_colors/default_colors.php:109
+#: plugins/default_colors/default_colors.php:115
msgid "Background color (light grey)"
msgstr "Couleur de fond (gris léger)"
-#: plugins/default_colors/default_colors.php:110
+#: plugins/default_colors/default_colors.php:116
msgid "Dark main color (e.g. visited links)"
msgstr "Couleur principale sombre (ex : les liens visités)"
-#: plugins/demo_plugin/demo_plugin.php:482
+#: plugins/demo_plugin/demo_plugin.php:478
msgid ""
"A demo plugin covering all use cases for template designers and plugin "
"developers."
@@ -514,11 +861,11 @@ msgstr ""
"Une extension de démonstration couvrant tous les cas d'utilisation pour les "
"designers de thèmes et les développeurs d'extensions."
-#: plugins/demo_plugin/demo_plugin.php:483
+#: plugins/demo_plugin/demo_plugin.php:479
msgid "This is a parameter dedicated to the demo plugin. It'll be suffixed."
msgstr "Ceci est un paramètre dédié au plugin de démo. Il sera suffixé."
-#: plugins/demo_plugin/demo_plugin.php:484
+#: plugins/demo_plugin/demo_plugin.php:480
msgid "Other demo parameter"
msgstr "Un autre paramètre de démo"
@@ -540,37 +887,7 @@ msgstr ""
msgid "Isso server URL (without 'http://')"
msgstr "URL du serveur Isso (sans 'http://')"
-#: plugins/markdown/markdown.php:163
-msgid "Description will be rendered with"
-msgstr "La description sera générée avec"
-
-#: plugins/markdown/markdown.php:164
-msgid "Markdown syntax documentation"
-msgstr "Documentation sur la syntaxe Markdown"
-
-#: plugins/markdown/markdown.php:165
-msgid "Markdown syntax"
-msgstr "la syntaxe Markdown"
-
-#: plugins/markdown/markdown.php:361
-msgid ""
-"Render shaare description with Markdown syntax.
Warning"
-"strong>:\n"
-"If your shaared descriptions contained HTML tags before enabling the "
-"markdown plugin,\n"
-"enabling it might break your page.\n"
-"See the README."
-msgstr ""
-"Utilise la syntaxe Markdown pour la description des liens."
-"
Attention :\n"
-"Si vous aviez des descriptions contenant du HTML avant d'activer cette "
-"extension,\n"
-"l'activer pourrait déformer vos pages.\n"
-"Voir le README."
-
-#: plugins/piwik/piwik.php:23
+#: plugins/piwik/piwik.php:24
msgid ""
"Piwik plugin error: Please define PIWIK_URL and PIWIK_SITEID in the plugin "
"administration page."
@@ -578,27 +895,27 @@ msgstr ""
"Erreur de l'extension Piwik : Merci de définir les paramètres PIWIK_URL et "
"PIWIK_SITEID dans la page d'administration des extensions."
-#: plugins/piwik/piwik.php:72
+#: plugins/piwik/piwik.php:73
msgid "A plugin that adds Piwik tracking code to Shaarli pages."
msgstr "Ajoute le code de traçage de Piwik sur les pages de Shaarli."
-#: plugins/piwik/piwik.php:73
+#: plugins/piwik/piwik.php:74
msgid "Piwik URL"
msgstr "URL de Piwik"
-#: plugins/piwik/piwik.php:74
+#: plugins/piwik/piwik.php:75
msgid "Piwik site ID"
msgstr "Site ID de Piwik"
-#: plugins/playvideos/playvideos.php:25
+#: plugins/playvideos/playvideos.php:26
msgid "Video player"
msgstr "Lecteur vidéo"
-#: plugins/playvideos/playvideos.php:28
+#: plugins/playvideos/playvideos.php:29
msgid "Play Videos"
msgstr "Jouer les vidéos"
-#: plugins/playvideos/playvideos.php:59
+#: plugins/playvideos/playvideos.php:60
msgid "Add a button in the toolbar allowing to watch all videos."
msgstr ""
"Ajoute un bouton dans la barre de menu pour regarder toutes les vidéos."
@@ -626,11 +943,11 @@ msgstr "Mauvaise réponse du hub %s"
msgid "Enable PubSubHubbub feed publishing."
msgstr "Active la publication de flux vers PubSubHubbub."
-#: plugins/qrcode/qrcode.php:72 plugins/wallabag/wallabag.php:68
+#: plugins/qrcode/qrcode.php:74 plugins/wallabag/wallabag.php:72
msgid "For each link, add a QRCode icon."
msgstr "Pour chaque lien, ajouter une icône de QRCode."
-#: plugins/wallabag/wallabag.php:21
+#: plugins/wallabag/wallabag.php:22
msgid ""
"Wallabag plugin error: Please define the \"WALLABAG_URL\" setting in the "
"plugin administration page."
@@ -638,28 +955,18 @@ msgstr ""
"Erreur de l'extension Wallabag : Merci de définir le paramètre « "
"WALLABAG_URL » dans la page d'administration des extensions."
-#: plugins/wallabag/wallabag.php:47
+#: plugins/wallabag/wallabag.php:49
msgid "Save to wallabag"
msgstr "Sauvegarder dans Wallabag"
-#: plugins/wallabag/wallabag.php:69
+#: plugins/wallabag/wallabag.php:73
msgid "Wallabag API URL"
msgstr "URL de l'API Wallabag"
-#: plugins/wallabag/wallabag.php:70
+#: plugins/wallabag/wallabag.php:74
msgid "Wallabag API version (1 or 2)"
msgstr "Version de l'API Wallabag (1 ou 2)"
-#: tests/LanguagesTest.php:214 tests/LanguagesTest.php:227
-#: tests/languages/fr/LanguagesFrTest.php:159
-#: tests/languages/fr/LanguagesFrTest.php:172
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:85
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:85
-msgid "Search"
-msgid_plural "Search"
-msgstr[0] "Rechercher"
-msgstr[1] "Rechercher"
-
#: tmp/404.b91ef64efc3688266305ea9b42e5017e.rtpl.php:12
msgid "Sorry, nothing to see here."
msgstr "Désolé, il y a rien à voir ici."
@@ -668,6 +975,48 @@ msgstr "Désolé, il y a rien à voir ici."
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 general > "
+"enable_async_metadata in your configuration file to use bulk link "
+"creation."
+msgstr ""
+"Nous recommandons d'activer le paramètre general > "
+"enable_async_metadata 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"
@@ -694,51 +1043,47 @@ msgid "Case sensitive"
msgstr "Sensible à la casse"
#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34
-msgid "Rename"
-msgstr "Renommer"
+#: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:68
+msgid "Rename tag"
+msgstr "Renommer le tag"
#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35
-#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77
-#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:171
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:145
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:145
-msgid "Delete"
-msgstr "Supprimer"
+msgid "Delete tag"
+msgstr "Supprimer le tag"
-#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:39
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:40
msgid "You can also edit tags in the"
msgstr "Vous pouvez aussi modifier les tags dans la"
-#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:39
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:40
msgid "tag list"
msgstr "liste des tags"
-#: tmp/configure.90100d2eaf5d3705e14b9b4f78ecddc9.rtpl.php:143
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:312
-#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31
-msgid "All"
-msgstr "Tous"
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47
+msgid "Change tags separator"
+msgstr "Changer le séparateur de tags"
-#: tmp/configure.90100d2eaf5d3705e14b9b4f78ecddc9.rtpl.php:147
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:316
-msgid "Only common media hosts"
-msgstr "Seulement les hébergeurs de média connus"
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:50
+msgid "Your current tag separator is"
+msgstr "Votre séparateur actuel est"
-#: tmp/configure.90100d2eaf5d3705e14b9b4f78ecddc9.rtpl.php:151
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:320
-msgid "None"
-msgstr "Aucune"
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:53
+msgid "New separator"
+msgstr "Nouveau séparateur"
-#: tmp/configure.90100d2eaf5d3705e14b9b4f78ecddc9.rtpl.php:158
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:297
-msgid "You need to enable the extension php-gd
to use thumbnails."
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:58
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:355
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:121
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:199
+msgid "Save"
+msgstr "Enregistrer"
+
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:61
+msgid "Note that hashtags won't fully work with a non-whitespace separator."
msgstr ""
-"Vous devez activer l'extension php-gd
pour utiliser les "
-"miniatures."
-
-#: tmp/configure.90100d2eaf5d3705e14b9b4f78ecddc9.rtpl.php:162
-msgid "Synchonize thumbnails"
-msgstr "Synchroniser les miniatures"
+"Notez que les hashtags ne sont pas complètement fonctionnels avec un "
+"séparateur qui n'est pas un espace."
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29
msgid "title"
@@ -756,155 +1101,179 @@ msgstr "Valeur par défaut"
msgid "Theme"
msgstr "Thème"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:87
-#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:78
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:85
+msgid "Description formatter"
+msgstr "Format des descriptions"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:114
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77
msgid "Language"
msgstr "Langue"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:116
-#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:143
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:101
msgid "Timezone"
msgstr "Fuseau horaire"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117
-#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:103
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:144
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102
msgid "Continent"
msgstr "Continent"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117
-#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:103
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:144
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102
msgid "City"
msgstr "Ville"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:164
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:191
msgid "Disable session cookie hijacking protection"
msgstr "Désactiver la protection contre le détournement de cookies"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:166
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:193
msgid "Check this if you get disconnected or if your IP address changes often"
msgstr ""
"Cocher cette case si vous êtes souvent déconnecté ou si votre adresse IP "
"change souvent"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:183
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:210
msgid "Private links by default"
msgstr "Liens privés par défaut"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:184
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:211
msgid "All new links are private by default"
msgstr "Tous les nouveaux liens sont privés par défaut"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:199
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:226
msgid "RSS direct links"
msgstr "Liens directs dans le flux RSS"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:200
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:227
msgid "Check this to use direct URL instead of permalink in feeds"
msgstr ""
"Cocher cette case pour utiliser des liens directs au lieu des permaliens "
"dans le flux RSS"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:215
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:242
msgid "Hide public links"
msgstr "Cacher les liens publics"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:216
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:243
msgid "Do not show any links if the user is not logged in"
msgstr "N'afficher aucun lien sans être connecté"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:231
-#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:150
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:258
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:149
msgid "Check updates"
msgstr "Vérifier les mises à jour"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:232
-#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:152
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:259
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:151
msgid "Notify me when a new release is ready"
msgstr "Me notifier lorsqu'une nouvelle version est disponible"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:247
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:274
msgid "Automatically retrieve description for new bookmarks"
msgstr "Récupérer automatiquement la description"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:248
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:275
msgid "Shaarli will try to retrieve the description from meta HTML headers"
msgstr ""
"Shaarli essaiera de récupérer la description depuis les balises HTML meta "
"dans les entêtes"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:263
-#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:290
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:168
msgid "Enable REST API"
msgstr "Activer l'API REST"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:264
-#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:170
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:291
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169
msgid "Allow third party software to use Shaarli such as mobile application"
msgstr ""
"Permet aux applications tierces d'utiliser Shaarli, par exemple les "
"applications mobiles"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:279
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:306
msgid "API secret"
msgstr "Clé d'API secrète"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:293
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:320
msgid "Enable thumbnails"
msgstr "Activer les miniatures"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:301
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:56
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:324
+msgid "You need to enable the extension php-gd
to use thumbnails."
+msgstr ""
+"Vous devez activer l'extension php-gd
pour utiliser les "
+"miniatures."
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:328
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:122
msgid "Synchronize thumbnails"
msgstr "Synchroniser les miniatures"
-#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:328
-#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72
-#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139
-#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:199
-msgid "Save"
-msgstr "Enregistrer"
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:339
+#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102
+msgid "All"
+msgstr "Tous"
-#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
-msgid "The Daily Shaarli"
-msgstr "Le Quotidien Shaarli"
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:343
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:106
+msgid "Only common media hosts"
+msgstr "Seulement les hébergeurs de média connus"
-#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:17
-msgid "1 RSS entry per day"
-msgstr "1 entrée RSS par jour"
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:347
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:110
+msgid "None"
+msgstr "Aucune"
-#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:37
-msgid "Previous day"
-msgstr "Jour précédent"
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26
+msgid "1 RSS entry per :type"
+msgid_plural ""
+msgstr[0] "1 entrée RSS par :type"
+msgstr[1] ""
-#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44
-msgid "All links of one day in a single page."
-msgstr "Tous les liens d'un jour sur une page."
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:49
+msgid "Previous :type"
+msgid_plural ""
+msgstr[0] ":type précédent"
+msgstr[1] "Jour précédent"
-#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:51
-msgid "Next day"
-msgstr "Jour suivant"
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:56
+#: tmp/dailyrss.b91ef64efc3688266305ea9b42e5017e.rtpl.php:7
+msgid "All links of one :type in a single page."
+msgid_plural ""
+msgstr[0] "Tous les liens d'un :type sur une page."
+msgstr[1] "Tous les liens d'un jour sur une page."
-#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:63
+msgid "Next :type"
+msgid_plural ""
+msgstr[0] ":type suivant"
+msgstr[1] ""
+
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30
msgid "Edit Shaare"
msgstr "Modifier le Shaare"
-#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30
msgid "New Shaare"
msgstr "Nouveau Shaare"
-#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:23
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:38
msgid "Created:"
msgstr "Création :"
-#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
msgid "URL"
msgstr "URL"
-#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:32
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47
msgid "Title"
msgstr "Titre"
-#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:38
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:58
#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42
#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:75
#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:99
@@ -912,37 +1281,56 @@ msgstr "Titre"
msgid "Description"
msgstr "Description"
-#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44
-msgid "Tags"
-msgstr "Tags"
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:89
+msgid "Description will be rendered with"
+msgstr "La description sera générée avec"
-#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:57
-#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
-#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:167
-msgid "Private"
-msgstr "Privé"
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:91
+msgid "Markdown syntax documentation"
+msgstr "Documentation sur la syntaxe Markdown"
-#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:92
+msgid "Markdown syntax"
+msgstr "la syntaxe Markdown"
+
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:115
+msgid "Cancel"
+msgstr "Annuler"
+
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:121
msgid "Apply Changes"
msgstr "Appliquer les changements"
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:126
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:173
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:147
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:147
+#: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:67
+msgid "Delete"
+msgstr "Supprimer"
+
+#: tmp/editlink.batch.b91ef64efc3688266305ea9b42e5017e.rtpl.php:21
+#: tmp/editlink.batch.b91ef64efc3688266305ea9b42e5017e.rtpl.php:32
+msgid "Save all"
+msgstr "Tout enregistrer"
+
#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
msgid "Export Database"
msgstr "Exporter les données"
-#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24
+#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:23
msgid "Selection"
msgstr "Choisir"
-#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
+#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:40
msgid "Public"
msgstr "Publics"
-#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:52
+#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:51
msgid "Prepend note permalinks with this Shaarli instance's URL"
msgstr "Préfixer les liens de note avec l'URL de l'instance de Shaarli"
-#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:53
+#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:52
msgid "Useful to import bookmarks in a web browser"
msgstr "Utile pour importer les marques-pages dans un navigateur"
@@ -983,42 +1371,42 @@ msgstr "Les doublons s'appuient sur les URL"
msgid "Add default tags"
msgstr "Ajouter des tags par défaut"
-#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22
-msgid "Install Shaarli"
-msgstr "Installation de Shaarli"
-
#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:25
msgid "It looks like it's the first time you run Shaarli. Please configure it."
msgstr ""
"Il semblerait que ça soit la première fois que vous lancez Shaarli. Merci de "
"le configurer."
-#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:33
-#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:165
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:165
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:32
+#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:167
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:167
msgid "Username"
msgstr "Nom d'utilisateur"
-#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
-#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:166
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:166
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47
+#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:20
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:168
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:168
msgid "Password"
msgstr "Mot de passe"
-#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:63
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:62
msgid "Shaarli title"
msgstr "Titre du Shaarli"
-#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:69
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:68
msgid "My links"
msgstr "Mes liens"
-#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:182
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:181
msgid "Install"
msgstr "Installer"
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:190
+msgid "Server requirements"
+msgstr "Pré-requis serveur"
+
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:79
msgid "shaare"
@@ -1034,21 +1422,31 @@ msgstr[0] "lien privé"
msgstr[1] "liens privés"
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:121
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:121
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:123
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:123
msgid "Search text"
msgstr "Recherche texte"
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:37
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:128
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:128
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:130
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:130
#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
-#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:64
+#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:65
#: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
#: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74
msgid "Filter by tag"
msgstr "Filtrer par tag"
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:87
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:87
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:139
+#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46
+#: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:45
+msgid "Search"
+msgstr "Rechercher"
+
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:110
msgid "Nothing found."
msgstr "Aucun résultat."
@@ -1069,99 +1467,97 @@ msgid "tagged"
msgstr "taggé"
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:133
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:134
msgid "Remove tag"
msgstr "Retirer le tag"
-#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:142
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:144
msgid "with status"
msgstr "avec le statut"
-#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:153
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:155
msgid "without any tag"
msgstr "sans tag"
-#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:173
-#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:43
-#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:43
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:175
+#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
+#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:41
msgid "Fold"
msgstr "Replier"
-#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:175
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:177
msgid "Edited: "
msgstr "Modifié : "
-#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:179
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:181
msgid "permalink"
msgstr "permalien"
-#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:181
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:183
msgid "Add tag"
msgstr "Ajouter un tag"
-#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:183
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:185
msgid "Toggle sticky"
msgstr "Changer statut épinglé"
-#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:185
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:187
msgid "Sticky"
msgstr "Épinglé"
-#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:7
-#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:7
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:189
+msgid "Share a private link"
+msgstr "Partager un lien privé"
+
+#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:5
+#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:5
msgid "Filters"
msgstr "Filtres"
-#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:12
-#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:12
+#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:10
+#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:10
msgid "Only display private links"
msgstr "Afficher uniquement les liens privés"
-#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
-#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:15
+#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
+#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:13
msgid "Only display public links"
msgstr "Afficher uniquement les liens publics"
-#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:20
-#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:20
+#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:18
+#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:18
msgid "Filter untagged links"
-msgstr "Filtrer par liens privés"
+msgstr "Filtrer par liens sans tag"
#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24
#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:24
msgid "Select all"
msgstr "Tout sélectionner"
-#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:27
-#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:79
-#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:27
-#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:79
-#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44
-#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:44
+#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29
+#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:89
+#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:29
+#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:89
+#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42
+#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:42
msgid "Fold all"
msgstr "Replier tout"
-#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72
-#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:72
+#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:76
+#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:76
msgid "Links per page"
msgstr "Liens par page"
-#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
-msgid ""
-"You have been banned after too many failed login attempts. Try again later."
-msgstr ""
-"Vous avez été banni après trop d'échecs d'authentification. Merci de "
-"réessayer plus tard."
-
-#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:169
+#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:25
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:171
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:171
msgid "Remember me"
msgstr "Rester connecté"
#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
-#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:49
+#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:15
-#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:49
+#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:48
msgid "by the Shaarli community"
msgstr "par la communauté Shaarli"
@@ -1170,77 +1566,84 @@ msgstr "par la communauté Shaarli"
msgid "Documentation"
msgstr "Documentation"
-#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:45
-#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:45
+#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:43
+#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:43
msgid "Expand"
msgstr "Déplier"
-#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46
-#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:46
+#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44
+#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:44
msgid "Expand all"
msgstr "Déplier tout"
-#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47
-#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:47
+#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:45
+#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:45
msgid "Are you sure you want to delete this link?"
msgstr "Êtes-vous sûr de vouloir supprimer ce lien ?"
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:65
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:90
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:65
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:90
+#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46
+#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:46
+msgid "Are you sure you want to delete this tag?"
+msgstr "Êtes-vous sûr de vouloir supprimer ce tag ?"
+
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:11
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:11
+msgid "Menu"
+msgstr "Menu"
+
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:38
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:38
+#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19
+msgid "Tag cloud"
+msgstr "Nuage de tags"
+
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:67
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:92
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:67
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:92
msgid "RSS Feed"
msgstr "Flux RSS"
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:70
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:106
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:70
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:106
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:108
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:72
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:108
msgid "Logout"
msgstr "Déconnexion"
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:150
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:150
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:152
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:152
msgid "Set public"
msgstr "Rendre public"
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:155
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:155
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:157
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:157
msgid "Set private"
msgstr "Rendre privé"
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:187
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:187
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:189
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:189
msgid "is available"
msgstr "est disponible"
-#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:194
-#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:194
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:196
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:196
msgid "Error"
msgstr "Erreur"
-#: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
-msgid "Picture wall unavailable (thumbnails are disabled)."
-msgstr ""
-"Le mur d'images n'est pas disponible (les miniatures sont désactivées)."
+#: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
+msgid "There is no cached thumbnail."
+msgstr "Il n'y a aucune miniature dans le cache."
-#: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24
-#, fuzzy
-#| msgid ""
-#| "You don't have any cached thumbnail. Try to synchronize them."
-msgid ""
-"There is no cached thumbnail. Try to synchronize them."
-msgstr ""
-"Il n'y a aucune miniature en cache. Essayer de les synchroniser."
+#: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:17
+msgid "Try to synchronize them."
+msgstr "Essayer de les synchroniser."
-#: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
+#: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
msgid "Picture Wall"
msgstr "Mur d'images"
-#: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
+#: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
msgid "pics"
msgstr "images"
@@ -1249,6 +1652,11 @@ msgid "You need to enable Javascript to change plugin loading order."
msgstr ""
"Vous devez activer Javascript pour pouvoir modifier l'ordre des extensions."
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22
+msgid "Plugin administration"
+msgstr "Administration des plugins"
+
#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29
msgid "Enabled Plugins"
msgstr "Extensions activées"
@@ -1304,6 +1712,100 @@ msgstr "Configuration des extensions"
msgid "No parameter available."
msgstr "Aucun paramètre disponible."
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
+msgid "General"
+msgstr "Général"
+
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:20
+msgid "Index URL"
+msgstr "URL de l'index"
+
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
+msgid "Base path"
+msgstr "Chemin de base"
+
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
+msgid "Client IP"
+msgstr "IP du client"
+
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44
+msgid "Trusted reverse proxies"
+msgstr "Reverse proxies de confiance"
+
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:58
+msgid "N/A"
+msgstr "N/A"
+
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:84
+msgid "Visit releases page on Github"
+msgstr "Visiter la page des releases sur Github"
+
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:121
+msgid "Synchronize all link thumbnails"
+msgstr "Synchroniser toutes les miniatures"
+
+#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:2
+#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:2
+msgid "Permissions"
+msgstr "Permissions"
+
+#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:8
+#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:8
+msgid "There are permissions that need to be fixed."
+msgstr "Il y a des permissions qui doivent être corrigées."
+
+#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:23
+#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:23
+msgid "All read/write permissions are properly set."
+msgstr "Toutes les permissions de lecture/écriture sont définies correctement."
+
+#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:32
+#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:32
+msgid "Running PHP"
+msgstr "Fonctionnant avec PHP"
+
+#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
+#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:36
+msgid "End of life: "
+msgstr "Fin de vie : "
+
+#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
+#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:48
+msgid "Extension"
+msgstr "Extension"
+
+#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:49
+#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:49
+msgid "Usage"
+msgstr "Utilisation"
+
+#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:50
+#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:50
+msgid "Status"
+msgstr "Statut"
+
+#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:51
+#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:66
+#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:51
+#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:66
+msgid "Loaded"
+msgstr "Chargé"
+
+#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:60
+#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:60
+msgid "Required"
+msgstr "Obligatoire"
+
+#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:60
+#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:60
+msgid "Optional"
+msgstr "Optionnel"
+
+#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:70
+#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:70
+msgid "Not loaded"
+msgstr "Non chargé"
+
#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19
#: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19
msgid "tags"
@@ -1314,6 +1816,10 @@ msgstr "tags"
msgid "List all links with those tags"
msgstr "Lister tous les liens avec ces tags"
+#: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19
+msgid "Tag list"
+msgstr "Liste des tags"
+
#: tmp/tag.sort.b91ef64efc3688266305ea9b42e5017e.rtpl.php:3
#: tmp/tag.sort.cedf684561d925457130839629000a81.rtpl.php:3
msgid "Sort by:"
@@ -1350,51 +1856,43 @@ msgstr "Configurer Shaarli"
msgid "Enable, disable and configure plugins"
msgstr "Activer, désactiver et configurer les extensions"
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:27
+msgid "Check instance's server configuration"
+msgstr "Vérifier la configuration serveur de l'instance"
+
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34
msgid "Change your password"
msgstr "Modifier le mot de passe"
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
msgid "Rename or delete a tag in all links"
msgstr "Renommer ou supprimer un tag dans tous les liens"
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
-#, fuzzy
-#| msgid ""
-#| "Import Netscape HTML bookmarks (as exported from Firefox, Chrome, Opera, "
-#| "delicious…)"
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47
msgid ""
"Import Netscape HTML bookmarks (as exported from Firefox, Chrome, Opera, "
"delicious...)"
msgstr ""
"Importer des marques pages au format Netscape HTML (comme exportés depuis "
-"Firefox, Chrome, Opera, delicious…)"
+"Firefox, Chrome, Opera, delicious...)"
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
msgid "Import links"
msgstr "Importer des liens"
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47
-#, fuzzy
-#| msgid ""
-#| "Export Netscape HTML bookmarks (which can be imported in Firefox, Chrome, "
-#| "Opera, delicious…)"
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:53
msgid ""
"Export Netscape HTML bookmarks (which can be imported in Firefox, Chrome, "
"Opera, delicious...)"
msgstr ""
"Exporter les marques pages au format Netscape HTML (comme exportés depuis "
-"Firefox, Chrome, Opera, delicious…)"
+"Firefox, Chrome, Opera, delicious...)"
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:54
msgid "Export database"
msgstr "Exporter les données"
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:55
-msgid "Synchronize all link thumbnails"
-msgstr "Synchroniser toutes les miniatures"
-
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:81
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77
msgid ""
"Drag one of these button to your bookmarks toolbar or right-click it and "
"\"Bookmark This Link\""
@@ -1402,13 +1900,13 @@ msgstr ""
"Glisser un de ces boutons dans votre barre de favoris ou cliquer droit "
"dessus et « Ajouter aux favoris »"
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:82
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:78
msgid "then click on the bookmarklet in any page you want to share."
msgstr ""
"puis cliquer sur le marque-page depuis un site que vous souhaitez partager."
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:86
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:110
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:82
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:106
msgid ""
"Drag this link to your bookmarks toolbar or right-click it and Bookmark This "
"Link"
@@ -1416,40 +1914,40 @@ msgstr ""
"Glisser ce lien dans votre barre de favoris ou cliquer droit dessus et « "
"Ajouter aux favoris »"
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:87
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:83
msgid "then click ✚Shaare link button in any page you want to share"
msgstr "puis cliquer sur ✚Shaare depuis un site que vous souhaitez partager"
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:96
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:118
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:92
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:114
msgid "The selected text is too long, it will be truncated."
msgstr "Le texte sélectionné est trop long, il sera tronqué."
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:106
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102
msgid "Shaare link"
msgstr "Shaare"
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:111
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:107
msgid ""
"Then click ✚Add Note button anytime to start composing a private Note (text "
"post) to your Shaarli"
msgstr ""
"Puis cliquer sur ✚Add Note pour commencer à rédiger une Note sur Shaarli"
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:127
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:123
msgid "Add Note"
msgstr "Ajouter une Note"
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:136
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:132
msgid "3rd party"
msgstr "Applications tierces"
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:144
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:135
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:140
msgid "plugin"
msgstr "extension"
-#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:165
msgid ""
"Drag this link to your bookmarks toolbar, or right-click it and choose "
"Bookmark This Link"
@@ -1457,6 +1955,74 @@ msgstr ""
"Glisser ce lien dans votre barre de favoris ou cliquer droit dessus et « "
"Ajouter aux favoris »"
+#~ msgid "Display:"
+#~ msgstr "Afficher :"
+
+#~ msgid "The Daily Shaarli"
+#~ msgstr "Le Quotidien Shaarli"
+
+#, fuzzy
+#~| msgid "Selection"
+#~ msgid ".ui-selecting"
+#~ msgstr "Choisir"
+
+#, fuzzy
+#~| msgid "Documentation"
+#~ msgid "document"
+#~ msgstr "Documentation"
+
+#~ msgid "The page you are trying to reach does not exist or has been deleted."
+#~ msgstr ""
+#~ "La page que vous essayez de consulter n'existe pas ou a été supprimée."
+
+#~ msgid "404 Not Found"
+#~ msgstr "404 Introuvable"
+
+#~ msgid "Updates file path is not set, can't write updates."
+#~ msgstr ""
+#~ "Le chemin vers le fichier de mise à jour n'est pas défini, impossible "
+#~ "d'écrire les mises à jour."
+
+#~ msgid "Unable to write updates in "
+#~ msgstr "Impossible d'écrire les mises à jour dans "
+
+#~ msgid "I said: NO. You are banned for the moment. Go away."
+#~ msgstr "NON. Vous êtes banni pour le moment. Revenez plus tard."
+
+#~ msgid "Click to try again."
+#~ msgstr "Cliquer ici pour réessayer."
+
+#~ msgid ""
+#~ "Render shaare description with Markdown syntax.
Warning"
+#~ "strong>:\n"
+#~ "If your shaared descriptions contained HTML tags before enabling the "
+#~ "markdown plugin,\n"
+#~ "enabling it might break your page.\n"
+#~ "See the README."
+#~ msgstr ""
+#~ "Utilise la syntaxe Markdown pour la description des liens."
+#~ "
Attention :\n"
+#~ "Si vous aviez des descriptions contenant du HTML avant d'activer cette "
+#~ "extension,\n"
+#~ "l'activer pourrait déformer vos pages.\n"
+#~ "Voir le README."
+
+#~ msgid "Synchonize thumbnails"
+#~ msgstr "Synchroniser les miniatures"
+
+#, fuzzy
+#~| msgid ""
+#~| "You don't have any cached thumbnail. Try to synchronize them."
+#~ msgid ""
+#~ "There is no cached thumbnail. Try to synchronize them."
+#~ msgstr ""
+#~ "Il n'y a aucune miniature en cache. Essayer de les synchroniser."
+
#~ msgid ""
#~ "You need to browse your Shaarli over HTTPS to use this "
#~ "functionality."
diff --git a/inc/languages/jp/LC_MESSAGES/shaarli.po b/inc/languages/jp/LC_MESSAGES/shaarli.po
new file mode 100644
index 00000000..36164433
--- /dev/null
+++ b/inc/languages/jp/LC_MESSAGES/shaarli.po
@@ -0,0 +1,1441 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: Shaarli\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-04-04 11:29+0900\n"
+"PO-Revision-Date: 2021-04-04 13:20+0900\n"
+"Last-Translator: yude \n"
+"Language-Team: Shaarli\n"
+"Language: ja\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 2.4.2\n"
+"X-Poedit-Basepath: ../../../..\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Poedit-SourceCharset: UTF-8\n"
+"X-Poedit-KeywordsList: t:1,2;t\n"
+"X-Poedit-SearchPath-0: .\n"
+"X-Poedit-SearchPathExcluded-0: node_modules\n"
+"X-Poedit-SearchPathExcluded-1: vendor\n"
+
+#: application/History.php:181
+msgid "History file isn't readable or writable"
+msgstr "履歴ファイルを読み込む、または書き込むための権限がありません"
+
+#: application/History.php:192
+msgid "Could not parse history file"
+msgstr "履歴ファイルを正常に復元できませんでした"
+
+#: application/Languages.php:184
+msgid "Automatic"
+msgstr "自動"
+
+#: application/Languages.php:185
+msgid "German"
+msgstr "ドイツ語"
+
+#: application/Languages.php:186
+msgid "English"
+msgstr "英語"
+
+#: application/Languages.php:187
+msgid "French"
+msgstr "フランス語"
+
+#: application/Languages.php:188
+msgid "Japanese"
+msgstr "日本語"
+
+#: application/Languages.php:189
+msgid "Russian"
+msgstr "ロシア語"
+
+#: application/Thumbnailer.php:62
+msgid ""
+"php-gd extension must be loaded to use thumbnails. Thumbnails are now "
+"disabled. Please reload the page."
+msgstr ""
+"サムネイルを使用するには、php-gd 拡張機能が読み込まれている必要があります。サ"
+"ムネイルは無効化されました。ページを再読込してください。"
+
+#: application/Utils.php:406 tests/UtilsTest.php:327
+msgid "Setting not set"
+msgstr "未設定"
+
+#: application/Utils.php:413 tests/UtilsTest.php:325 tests/UtilsTest.php:326
+msgid "Unlimited"
+msgstr "無制限"
+
+#: application/Utils.php:416 tests/UtilsTest.php:322 tests/UtilsTest.php:323
+#: tests/UtilsTest.php:337
+msgid "B"
+msgstr "B"
+
+#: application/Utils.php:416 tests/UtilsTest.php:316 tests/UtilsTest.php:317
+#: tests/UtilsTest.php:324
+msgid "kiB"
+msgstr "kiB"
+
+#: application/Utils.php:416 tests/UtilsTest.php:318 tests/UtilsTest.php:319
+#: tests/UtilsTest.php:335 tests/UtilsTest.php:336
+msgid "MiB"
+msgstr "MiB"
+
+#: application/Utils.php:416 tests/UtilsTest.php:320 tests/UtilsTest.php:321
+msgid "GiB"
+msgstr "GiB"
+
+#: application/bookmark/BookmarkFileService.php:203
+#: application/bookmark/BookmarkFileService.php:225
+#: application/bookmark/BookmarkFileService.php:247
+#: application/bookmark/BookmarkFileService.php:261
+msgid "You're not authorized to alter the datastore"
+msgstr "設定を変更する権限がありません"
+
+#: application/bookmark/BookmarkFileService.php:228
+msgid "This bookmarks already exists"
+msgstr "このブックマークは既に存在します"
+
+#: application/bookmark/BookmarkInitializer.php:42
+msgid "(private bookmark with thumbnail demo)"
+msgstr "(サムネイルデモが付属しているプライベートブックマーク)"
+
+#: application/bookmark/BookmarkInitializer.php:45
+msgid ""
+"Shaarli will automatically pick up the thumbnail for links to a variety of "
+"websites.\n"
+"\n"
+"Explore your new Shaarli instance by trying out controls and menus.\n"
+"Visit the project on [Github](https://github.com/shaarli/Shaarli) or [the "
+"documentation](https://shaarli.readthedocs.io/en/master/) to learn more "
+"about Shaarli.\n"
+"\n"
+"Now you can edit or delete the default shaares.\n"
+msgstr ""
+"Shaarli は自動的に多様なウェブサイトのサムネイルを取得します。\n"
+"\n"
+"あなたの新しい Shaarli インスタンスをコントロールやメニューを試したりして、探"
+"検してください。\n"
+" [Github](https://github.com/shaarli/Shaarli) または [the documentation]"
+"(https://shaarli.readthedocs.io/en/master/) でプロジェクトを訪問して、"
+"Shaarli をもっとよく知ることができます。\n"
+"\n"
+"今から、既定の shaares を編集したり、削除したりすることができます。\n"
+
+#: application/bookmark/BookmarkInitializer.php:58
+msgid "Note: Shaare descriptions"
+msgstr "説明: Shaare の概要"
+
+#: application/bookmark/BookmarkInitializer.php:60
+msgid ""
+"Adding a shaare without entering a URL creates a text-only \"note\" post "
+"such as this one.\n"
+"This note is private, so you are the only one able to see it while logged "
+"in.\n"
+"\n"
+"You can use this to keep notes, post articles, code snippets, and much "
+"more.\n"
+"\n"
+"The Markdown formatting setting allows you to format your notes and bookmark "
+"description:\n"
+"\n"
+"### Title headings\n"
+"\n"
+"#### Multiple headings levels\n"
+" * bullet lists\n"
+" * _italic_ text\n"
+" * **bold** text\n"
+" * ~~strike through~~ text\n"
+" * `code` blocks\n"
+" * images\n"
+" * [links](https://en.wikipedia.org/wiki/Markdown)\n"
+"\n"
+"Markdown also supports tables:\n"
+"\n"
+"| Name | Type | Color | Qty |\n"
+"| ------- | --------- | ------ | ----- |\n"
+"| Orange | Fruit | Orange | 126 |\n"
+"| Apple | Fruit | Any | 62 |\n"
+"| Lemon | Fruit | Yellow | 30 |\n"
+"| Carrot | Vegetable | Red | 14 |\n"
+msgstr ""
+"URL を追加せずに shaare を作成すると、テキストのみのこのような \"ノート\" が"
+"作成されます。\n"
+"このノートはプライベートなので、ログイン中のあなたしか見ることはできませ"
+"ん。\n"
+"\n"
+"あなたはこれをメモ帳として使ったり、記事を投稿したり、コード スニペットとした"
+"りするなどといったことに使えます。\n"
+"\n"
+"Markdown フォーマットの設定により、ノートやブックマークの概要を以下のように"
+"フォーマットできます:\n"
+"\n"
+"### タイトル ヘッダー\n"
+"\n"
+"#### 複数の見出し\n"
+" * 箇条書きリスト\n"
+" * _イタリック_ 文字\n"
+" * **ボールド** 文字\n"
+" * ~~打ち消し~~ 文字\n"
+" * `コード` ブロック\n"
+" * 画像\n"
+" * [リンク](https://en.wikipedia.org/wiki/Markdown)\n"
+"\n"
+"Markdown は表もサポートします:\n"
+"\n"
+"| 名前 | 種類 | 色 | 数量 |\n"
+"| ------- | --------- | ------ | ----- |\n"
+"| オレンジ | 果物 | 橙 | 126 |\n"
+"| リンゴ | 果物 | 任意 | 62 |\n"
+"| レモン | 果物 | 黄 | 30 |\n"
+"| 人参 | 野菜 | 赤 | 14 |\n"
+
+#: application/bookmark/BookmarkInitializer.php:94
+#: application/legacy/LegacyLinkDB.php:246
+msgid ""
+"The personal, minimalist, super-fast, database free, bookmarking service"
+msgstr ""
+"個人向けの、ミニマムで高速でかつデータベースのいらないブックマークサービス"
+
+#: application/bookmark/BookmarkInitializer.php:97
+msgid ""
+"Welcome to Shaarli!\n"
+"\n"
+"Shaarli allows you to bookmark your favorite pages, and share them with "
+"others or store them privately.\n"
+"You can add a description to your bookmarks, such as this one, and tag "
+"them.\n"
+"\n"
+"Create a new shaare by clicking the `+Shaare` button, or using any of the "
+"recommended tools (browser extension, mobile app, bookmarklet, REST API, "
+"etc.).\n"
+"\n"
+"You can easily retrieve your links, even with thousands of them, using the "
+"internal search engine, or search through tags (e.g. this Shaare is tagged "
+"with `shaarli` and `help`).\n"
+"Hashtags such as #shaarli #help are also supported.\n"
+"You can also filter the available [RSS feed](/feed/atom) and picture wall by "
+"tag or plaintext search.\n"
+"\n"
+"We hope that you will enjoy using Shaarli, maintained with ❤️ by the "
+"community!\n"
+"Feel free to open [an issue](https://github.com/shaarli/Shaarli/issues) if "
+"you have a suggestion or encounter an issue.\n"
+msgstr ""
+"Shaarli へようこそ!\n"
+"\n"
+"Shaarli では、あなたのお気に入りのページをブックマークしたり、それを他の人と"
+"共有するか、またはプライベートなものとして保管することができます。\n"
+"加えて、あなたのブックマークにこの項目のように概要を追加したり、タグ付けした"
+"りすることができます。\n"
+"\n"
+"`+Shaare` ボタンをクリックすることで新しい shaare を作成できます。また、推奨"
+"されたツールを使うこともできます (ブラウザー 拡張機能、モバイル アプリ、ブッ"
+"クマークレット、REST API など...)。\n"
+"\n"
+"また、簡単にあなたのリンクを取得できます。それが何千と登る数であっても、内部"
+"の検索エンジンや、タグを使って検索できます (例えば、この Shaare は `shaarli` "
+"と `help` というタグが付いています)。\n"
+"#shaarli や #help といったハッシュタグもサポートされています。\n"
+"タグやテキスト検索による [RSS フィード](/feed/atom) や ピクチャー ウォール で"
+"項目を絞ることもできます。\n"
+"\n"
+"私たちはあなたが Shaarli を楽しんでくれることを願っています。Shaarli はコミュ"
+"ニティーによって ♡ と共にメンテナンスされています!\n"
+"何か問題に遭遇したり、提案があれば、気軽に [Issue](https://github.com/"
+"shaarli/Shaarli/issues) を開いてください。\n"
+
+#: application/bookmark/exception/BookmarkNotFoundException.php:14
+msgid "The link you are trying to reach does not exist or has been deleted."
+msgstr "開こうとしたリンクは存在しないか、削除されています。"
+
+#: application/config/ConfigJson.php:52 application/config/ConfigPhp.php:131
+msgid ""
+"Shaarli could not create the config file. Please make sure Shaarli has the "
+"right to write in the folder is it installed in."
+msgstr ""
+"Shaarli は設定ファイルを作成できませんでした。Shaarli が正しい権限下に置かれ"
+"ていて、インストールされているディレクトリに書き込みできることを確認してくだ"
+"さい。"
+
+#: application/config/ConfigManager.php:137
+#: application/config/ConfigManager.php:164
+msgid "Invalid setting key parameter. String expected, got: "
+msgstr ""
+"不正なキーの値です。文字列が想定されていますが、次のように入力されました: "
+
+#: application/config/exception/MissingFieldConfigException.php:20
+#, php-format
+msgid "Configuration value is required for %s"
+msgstr "%s には設定が必要です"
+
+#: application/config/exception/PluginConfigOrderException.php:15
+msgid "An error occurred while trying to save plugins loading order."
+msgstr "プラグインの読込順を変更する際にエラーが発生しました。"
+
+#: application/config/exception/UnauthorizedConfigException.php:15
+msgid "You are not authorized to alter config."
+msgstr "設定を変更する権限がありません。"
+
+#: application/exceptions/IOException.php:23
+msgid "Error accessing"
+msgstr "読込中にエラーが発生しました"
+
+#: application/feed/FeedBuilder.php:174
+msgid "Direct link"
+msgstr "ダイレクトリンク"
+
+#: application/feed/FeedBuilder.php:176
+msgid "Permalink"
+msgstr "パーマリンク"
+
+#: application/front/controller/admin/ConfigureController.php:56
+msgid "Configure"
+msgstr "設定"
+
+#: application/front/controller/admin/ConfigureController.php:106
+#: application/legacy/LegacyUpdater.php:539
+msgid "You have enabled or changed thumbnails mode."
+msgstr "サムネイルのモードを有効化、または変更しました。"
+
+#: application/front/controller/admin/ConfigureController.php:108
+#: application/front/controller/admin/ServerController.php:81
+#: application/legacy/LegacyUpdater.php:540
+msgid "Please synchronize them."
+msgstr "それらを同期してください。"
+
+#: application/front/controller/admin/ConfigureController.php:119
+#: application/front/controller/visitor/InstallController.php:154
+msgid "Error while writing config file after configuration update."
+msgstr "設定ファイルを更新した後の書き込みに失敗しました。"
+
+#: application/front/controller/admin/ConfigureController.php:128
+msgid "Configuration was saved."
+msgstr "設定は保存されました。"
+
+#: application/front/controller/admin/ExportController.php:26
+msgid "Export"
+msgstr "エクスポート"
+
+#: application/front/controller/admin/ExportController.php:42
+msgid "Please select an export mode."
+msgstr "エクスポート モードを指定してください。"
+
+#: application/front/controller/admin/ImportController.php:41
+msgid "Import"
+msgstr "インポート"
+
+#: application/front/controller/admin/ImportController.php:55
+msgid "No import file provided."
+msgstr "何のインポート元ファイルも指定されませんでした。"
+
+#: application/front/controller/admin/ImportController.php:66
+#, php-format
+msgid ""
+"The file you are trying to upload is probably bigger than what this "
+"webserver can accept (%s). Please upload in smaller chunks."
+msgstr ""
+"あなたがアップロードしようとしているファイルは、サーバーが許可しているファイ"
+"ルサイズ (%s) よりも大きいです。もう少し小さいものをアップロードしてくださ"
+"い。"
+
+#: application/front/controller/admin/ManageTagController.php:30
+msgid "whitespace"
+msgstr "空白"
+
+#: application/front/controller/admin/ManageTagController.php:35
+msgid "Manage tags"
+msgstr "タグを設定"
+
+#: application/front/controller/admin/ManageTagController.php:54
+msgid "Invalid tags provided."
+msgstr "不正なタグが入力されました。"
+
+#: application/front/controller/admin/ManageTagController.php:81
+#, php-format
+msgid "The tag was removed from %d bookmark."
+msgid_plural "The tag was removed from %d bookmarks."
+msgstr[0] "%d 件のリンクからタグが削除されました。"
+msgstr[1] "%d 件のリンクからタグが削除されました。"
+
+#: application/front/controller/admin/ManageTagController.php:86
+#, php-format
+msgid "The tag was renamed in %d bookmark."
+msgid_plural "The tag was renamed in %d bookmarks."
+msgstr[0] "このタグを持つ %d 件のリンクにおいて、名前が変更されました。"
+msgstr[1] "このタグを持つ %d 件のリンクにおいて、名前が変更されました。"
+
+#: application/front/controller/admin/ManageTagController.php:108
+msgid "Tags separator must be a single character."
+msgstr "タグを分離する文字は1つでなければいけません。"
+
+#: application/front/controller/admin/ManageTagController.php:114
+msgid "These characters are reserved and can't be used as tags separator: "
+msgstr ""
+"それらの文字は予約文字であり、タグを分離するための文字として使うことはできま"
+"せん: "
+
+#: application/front/controller/admin/PasswordController.php:28
+msgid "Change password"
+msgstr "パスワードを変更"
+
+#: application/front/controller/admin/PasswordController.php:55
+msgid "You must provide the current and new password to change it."
+msgstr ""
+"パスワードを変更するには、現在のパスワードと、新しいパスワードを入力する必要"
+"があります。"
+
+#: application/front/controller/admin/PasswordController.php:71
+msgid "The old password is not correct."
+msgstr "元のパスワードが正しくありません。"
+
+#: application/front/controller/admin/PasswordController.php:97
+msgid "Your password has been changed"
+msgstr "あなたのパスワードは変更されました"
+
+#: application/front/controller/admin/PluginsController.php:45
+msgid "Plugin Administration"
+msgstr "プラグイン管理"
+
+#: application/front/controller/admin/PluginsController.php:76
+msgid "Setting successfully saved."
+msgstr "設定が正常に保存されました。"
+
+#: application/front/controller/admin/PluginsController.php:79
+msgid "Error while saving plugin configuration: "
+msgstr "プラグインの設定ファイルを保存するときにエラーが発生しました: "
+
+#: application/front/controller/admin/ServerController.php:35
+msgid "Check disabled"
+msgstr "無効になっている項目をチェック"
+
+#: application/front/controller/admin/ServerController.php:62
+msgid "Server administration"
+msgstr "サーバー管理"
+
+#: application/front/controller/admin/ServerController.php:79
+msgid "Thumbnails cache has been cleared."
+msgstr "サムネイルのキャッシュがクリアされました。"
+
+#: application/front/controller/admin/ServerController.php:90
+msgid "Shaarli's cache folder has been cleared!"
+msgstr "Shaarli のキャッシュのフォルダーがクリアされました!"
+
+#: application/front/controller/admin/ShaareAddController.php:26
+msgid "Shaare a new link"
+msgstr "新しいリンクを追加"
+
+#: application/front/controller/admin/ShaareManageController.php:35
+#: application/front/controller/admin/ShaareManageController.php:97
+msgid "Invalid bookmark ID provided."
+msgstr "不正なブックマーク ID が入力されました。"
+
+#: application/front/controller/admin/ShaareManageController.php:47
+#: application/front/controller/admin/ShaareManageController.php:120
+#: application/front/controller/admin/ShaareManageController.php:160
+#: application/front/controller/admin/ShaarePublishController.php:82
+#, php-format
+msgid "Bookmark with identifier %s could not be found."
+msgstr "%s という識別子を持ったブックマークは見つかりませんでした。"
+
+#: application/front/controller/admin/ShaareManageController.php:105
+msgid "Invalid visibility provided."
+msgstr "不正な公開設定が入力されました。"
+
+#: application/front/controller/admin/ShaarePublishController.php:173
+msgid "Edit"
+msgstr "共有"
+
+#: application/front/controller/admin/ShaarePublishController.php:176
+msgid "Shaare"
+msgstr "Shaare"
+
+#: application/front/controller/admin/ShaarePublishController.php:208
+msgid "Note: "
+msgstr "注: "
+
+#: application/front/controller/admin/ThumbnailsController.php:37
+msgid "Thumbnails update"
+msgstr "サムネイルの更新"
+
+#: application/front/controller/admin/ToolsController.php:31
+msgid "Tools"
+msgstr "ツール"
+
+#: application/front/controller/visitor/BookmarkListController.php:103
+msgid "Search: "
+msgstr "検索: "
+
+#: application/front/controller/visitor/DailyController.php:201
+msgid "day"
+msgstr "日"
+
+#: application/front/controller/visitor/DailyController.php:201
+#: application/front/controller/visitor/DailyController.php:204
+msgid "Daily"
+msgstr "デイリー"
+
+#: application/front/controller/visitor/DailyController.php:202
+msgid "week"
+msgstr "週"
+
+#: application/front/controller/visitor/DailyController.php:202
+msgid "Weekly"
+msgstr "週間"
+
+#: application/front/controller/visitor/DailyController.php:203
+msgid "month"
+msgstr "月"
+
+#: application/front/controller/visitor/DailyController.php:203
+msgid "Monthly"
+msgstr "月間"
+
+#: application/front/controller/visitor/ErrorController.php:30
+msgid "Error: "
+msgstr "エラー: "
+
+#: application/front/controller/visitor/ErrorController.php:34
+msgid "Please report it on Github."
+msgstr "GitHub で報告してください。"
+
+#: application/front/controller/visitor/ErrorController.php:39
+msgid "An unexpected error occurred."
+msgstr "予期しないエラーが発生しました。"
+
+#: application/front/controller/visitor/ErrorNotFoundController.php:25
+msgid "Requested page could not be found."
+msgstr "リクエストされたページは存在しません。"
+
+#: application/front/controller/visitor/InstallController.php:70
+msgid "Install Shaarli"
+msgstr "Shaarli をインストール"
+
+#: application/front/controller/visitor/InstallController.php:90
+#, php-format
+msgid ""
+"Sessions do not seem to work correctly on your server.
Make sure the "
+"variable \"session.save_path\" is set correctly in your PHP config, and that "
+"you have write access to it.
It currently points to %s.
On some "
+"browsers, accessing your server via a hostname like 'localhost' or any "
+"custom hostname without a dot causes cookie storage to fail. We recommend "
+"accessing your server via it's IP address or Fully Qualified Domain Name.
"
+msgstr ""
+"セッションが正常にあなたのサーバー上で稼働していないようです。
PHP の"
+"設定ファイル内にて、正しく \"session.save_path\" の値が設定されていることと、"
+"権限が間違っていないことを確認してください。
現在 %s からPHPの設定ファイル"
+"を読み込んでいます。
一部のブラウザーにおいて、localhost や他のドットを含"
+"まないホスト名にてサーバーにアクセスする際に、クッキーを保存できないことがあ"
+"ります。IP アドレスや完全なドメイン名でサーバーにアクセスすることをおすすめし"
+"ます。
"
+
+#: application/front/controller/visitor/InstallController.php:162
+msgid ""
+"Shaarli is now configured. Please login and start shaaring your bookmarks!"
+msgstr ""
+"Shaarli の設定が完了しました。ログインして、あなたのブックマークを登録しま"
+"しょう!"
+
+#: application/front/controller/visitor/InstallController.php:176
+msgid "Insufficient permissions:"
+msgstr "権限がありません:"
+
+#: application/front/controller/visitor/LoginController.php:46
+msgid "Login"
+msgstr "ログイン"
+
+#: application/front/controller/visitor/LoginController.php:78
+msgid "Wrong login/password."
+msgstr "不正なユーザー名、またはパスワードです。"
+
+#: application/front/controller/visitor/PictureWallController.php:29
+msgid "Picture wall"
+msgstr "ピクチャウォール"
+
+#: application/front/controller/visitor/TagCloudController.php:90
+msgid "Tag "
+msgstr "タグ "
+
+#: application/front/exceptions/AlreadyInstalledException.php:11
+msgid "Shaarli has already been installed. Login to edit the configuration."
+msgstr "Shaarli がインストールされました。ログインして設定を変更できます。"
+
+#: application/front/exceptions/LoginBannedException.php:11
+msgid ""
+"You have been banned after too many failed login attempts. Try again later."
+msgstr "複数回に渡るログインへの失敗を検出しました。後でまた試してください。"
+
+#: application/front/exceptions/OpenShaarliPasswordException.php:16
+msgid "You are not supposed to change a password on an Open Shaarli."
+msgstr ""
+"公開されている Shaarli において、パスワードを変更することは想定されていませ"
+"ん。"
+
+#: application/front/exceptions/ThumbnailsDisabledException.php:11
+msgid "Picture wall unavailable (thumbnails are disabled)."
+msgstr "ピクチャ ウォールは利用できません (サムネイルが無効化されています)。"
+
+#: application/front/exceptions/WrongTokenException.php:16
+msgid "Wrong token."
+msgstr "不正なトークンです。"
+
+#: application/helper/ApplicationUtils.php:165
+#, php-format
+msgid ""
+"Your PHP version is obsolete! Shaarli requires at least PHP %s, and thus "
+"cannot run. Your PHP version has known security vulnerabilities and should "
+"be updated as soon as possible."
+msgstr ""
+"使用している PHP のバージョンが古すぎます! Shaarli の実行には最低でも PHP %s "
+"が必要です。 現在使用している PHP のバージョンには脆弱性があり、できるだけ速"
+"やかにアップデートするべきです。"
+
+#: application/helper/ApplicationUtils.php:200
+#: application/helper/ApplicationUtils.php:220
+msgid "directory is not readable"
+msgstr "ディレクトリを読み込めません"
+
+#: application/helper/ApplicationUtils.php:223
+msgid "directory is not writable"
+msgstr "ディレクトリに書き込めません"
+
+#: application/helper/ApplicationUtils.php:247
+msgid "file is not readable"
+msgstr "ファイルを読み取る権限がありません"
+
+#: application/helper/ApplicationUtils.php:250
+msgid "file is not writable"
+msgstr "ファイルを書き込む権限がありません"
+
+#: application/helper/ApplicationUtils.php:265
+msgid ""
+"Lock can not be acquired on the datastore. You might encounter concurrent "
+"access issues."
+msgstr ""
+"ロックはデータストアでは取得できません。同時にアクセスしたことによってエラー"
+"が発生した可能性があります。"
+
+#: application/helper/ApplicationUtils.php:298
+msgid "Configuration parsing"
+msgstr "設定ファイルのパース"
+
+#: application/helper/ApplicationUtils.php:299
+msgid "Slim Framework (routing, etc.)"
+msgstr "軽量なフレームワーク (ルーティングなど)"
+
+#: application/helper/ApplicationUtils.php:300
+msgid "Multibyte (Unicode) string support"
+msgstr "マルチバイト (Unicode) の文字のサポート"
+
+#: application/helper/ApplicationUtils.php:301
+msgid "Required to use thumbnails"
+msgstr "サムネイルを使うことは必須です"
+
+#: application/helper/ApplicationUtils.php:302
+msgid "Localized text sorting (e.g. e->è->f)"
+msgstr "ローカライズされた文字のソート (例: e->è->f)"
+
+#: application/helper/ApplicationUtils.php:303
+msgid "Better retrieval of bookmark metadata and thumbnail"
+msgstr "ブックマークのメタデータとサムネイルの改善された検索"
+
+#: application/helper/ApplicationUtils.php:304
+msgid "Use the translation system in gettext mode"
+msgstr "gettext モードで翻訳システムを使用"
+
+#: application/helper/ApplicationUtils.php:305
+msgid "Login using LDAP server"
+msgstr "LDAP サーバーを使用してログイン"
+
+#: application/helper/DailyPageHelper.php:179
+msgid "Week"
+msgstr "週"
+
+#: application/helper/DailyPageHelper.php:183
+msgid "Today"
+msgstr "今日"
+
+#: application/helper/DailyPageHelper.php:185
+msgid "Yesterday"
+msgstr "昨日"
+
+#: application/helper/FileUtils.php:100
+msgid "Provided path is not a directory."
+msgstr "入力されたパスはディレクトリではありません。"
+
+#: application/helper/FileUtils.php:104
+msgid "Trying to delete a folder outside of Shaarli path."
+msgstr "Shaarli のパスの外にあるフォルダを削除しようとしています。"
+
+#: application/legacy/LegacyLinkDB.php:131
+msgid "You are not authorized to add a link."
+msgstr "リンクを追加するには、ログインする必要があります。"
+
+#: application/legacy/LegacyLinkDB.php:134
+msgid "Internal Error: A link should always have an id and URL."
+msgstr "エラー: リンクにはIDとURLを登録しなければなりません。"
+
+#: application/legacy/LegacyLinkDB.php:137
+msgid "You must specify an integer as a key."
+msgstr "正常なキーの値ではありません。"
+
+#: application/legacy/LegacyLinkDB.php:140
+msgid "Array offset and link ID must be equal."
+msgstr "Array オフセットとリンクのIDは同じでなければなりません。"
+
+#: application/legacy/LegacyLinkDB.php:249
+msgid ""
+"Welcome to Shaarli! This is your first public bookmark. To edit or delete "
+"me, you must first login.\n"
+"\n"
+"To learn how to use Shaarli, consult the link \"Documentation\" at the "
+"bottom of this page.\n"
+"\n"
+"You use the community supported version of the original Shaarli project, by "
+"Sebastien Sauvage."
+msgstr ""
+"Shaarli へようこそ! これはあなたの最初の公開ブックマークです。これを編集した"
+"り削除したりするには、ログインする必要があります。\n"
+"\n"
+"Shaarli の使い方を知るには、このページの下にある「ドキュメント」のリンクを開"
+"いてください。\n"
+"\n"
+"あなたは Sebastien Sauvage による、コミュニティーサポートのあるバージョンのオ"
+"リジナルのShaarli プロジェクトを使用しています。"
+
+#: application/legacy/LegacyLinkDB.php:266
+msgid "My secret stuff... - Pastebin.com"
+msgstr "わたしのひ💗み💗つ💗 - Pastebin.com"
+
+#: application/legacy/LegacyLinkDB.php:268
+msgid "Shhhh! I'm a private link only YOU can see. You can delete me too."
+msgstr ""
+"シーッ! これはあなたしか見られないプライベートリンクです。消すこともできま"
+"す。"
+
+#: application/legacy/LegacyUpdater.php:104
+msgid "Couldn't retrieve updater class methods."
+msgstr "アップデーターのクラスメゾットを受信できませんでした。"
+
+#: application/legacy/LegacyUpdater.php:540
+msgid ""
+msgstr ""
+
+#: application/netscape/NetscapeBookmarkUtils.php:63
+msgid "Invalid export selection:"
+msgstr "不正なエクスポートの選択:"
+
+#: application/netscape/NetscapeBookmarkUtils.php:215
+#, php-format
+msgid "File %s (%d bytes) "
+msgstr "ファイル %s (%d バイト) "
+
+#: application/netscape/NetscapeBookmarkUtils.php:217
+msgid "has an unknown file format. Nothing was imported."
+msgstr "は不明なファイル形式です。インポートは中止されました。"
+
+#: application/netscape/NetscapeBookmarkUtils.php:221
+#, php-format
+msgid ""
+"was successfully processed in %d seconds: %d bookmarks imported, %d "
+"bookmarks overwritten, %d bookmarks skipped."
+msgstr ""
+"が %d 秒で処理され、%d 件のリンクがインポートされ、%d 件のリンクが上書きさ"
+"れ、%d 件のリンクがスキップされました。"
+
+#: application/plugin/PluginManager.php:103
+#: application/plugin/PluginManager.php:141
+msgid " [plugin incompatibility]: "
+msgstr " [非対応のプラグイン]: "
+
+#: application/plugin/exception/PluginFileNotFoundException.php:22
+#, php-format
+msgid "Plugin \"%s\" files not found."
+msgstr "プラグイン「%s」のファイルが存在しません。"
+
+#: application/render/PageCacheManager.php:33
+#, php-format
+msgid "Cannot purge %s: no directory"
+msgstr "%s を削除できません: ディレクトリが存在しません"
+
+#: application/updater/exception/UpdaterException.php:51
+msgid "An error occurred while running the update "
+msgstr "更新中に問題が発生しました "
+
+#: index.php:82
+msgid "Shared bookmarks on "
+msgstr "次において共有されたリンク "
+
+#: plugins/addlink_toolbar/addlink_toolbar.php:31
+msgid "URI"
+msgstr "URI"
+
+#: plugins/addlink_toolbar/addlink_toolbar.php:35
+msgid "Add link"
+msgstr "リンクを追加"
+
+#: plugins/addlink_toolbar/addlink_toolbar.php:52
+msgid "Adds the addlink input on the linklist page."
+msgstr "リンク一覧のページに、リンクを追加するためのフォームを表示する。"
+
+#: plugins/archiveorg/archiveorg.php:29
+msgid "View on archive.org"
+msgstr "archive.org 上で表示する"
+
+#: plugins/archiveorg/archiveorg.php:42
+msgid "For each link, add an Archive.org icon."
+msgstr "それぞれのリンクに、Archive.org のアイコンを追加する。"
+
+#: plugins/default_colors/default_colors.php:38
+msgid ""
+"Default colors plugin error: This plugin is active and no custom color is "
+"configured."
+msgstr ""
+"既定の色のプラグインにおけるエラー: このプラグインは有効なので、カスタム カ"
+"ラーは適用されません。"
+
+#: plugins/default_colors/default_colors.php:127
+msgid "Override default theme colors. Use any CSS valid color."
+msgstr ""
+"既定のテーマの色を上書きします。どのような CSS カラーコードでも使えます。"
+
+#: plugins/default_colors/default_colors.php:128
+msgid "Main color (navbar green)"
+msgstr "メイン カラー (ナビバーの緑)"
+
+#: plugins/default_colors/default_colors.php:129
+msgid "Background color (light grey)"
+msgstr "背景色 (灰色)"
+
+#: plugins/default_colors/default_colors.php:130
+msgid "Dark main color (e.g. visited links)"
+msgstr "暗い方の メイン カラー (例: 閲覧済みリンク)"
+
+#: plugins/demo_plugin/demo_plugin.php:528
+msgid ""
+"A demo plugin covering all use cases for template designers and plugin "
+"developers."
+msgstr ""
+"テンプレートのデザイナーや、プラグインの開発者のためのすべての状況に対応でき"
+"るデモプラグインです。"
+
+#: plugins/demo_plugin/demo_plugin.php:529
+msgid "This is a parameter dedicated to the demo plugin. It'll be suffixed."
+msgstr "これはデモプラグイン専用のパラメーターです。末尾に追加されます。"
+
+#: plugins/demo_plugin/demo_plugin.php:530
+msgid "Other demo parameter"
+msgstr "他のデモ パラメーター"
+
+#: plugins/isso/isso.php:22
+msgid ""
+"Isso plugin error: Please define the \"ISSO_SERVER\" setting in the plugin "
+"administration page."
+msgstr ""
+"Isso プラグインエラー: \"ISSO_SERVER\" の値をプラグイン管理ページにて指定して"
+"ください。"
+
+#: plugins/isso/isso.php:92
+msgid "Let visitor comment your shaares on permalinks with Isso."
+msgstr ""
+"Isso を使って、あなたのパーマリンク上のリンクに第三者がコメントを残すことがで"
+"きます。"
+
+#: plugins/isso/isso.php:93
+msgid "Isso server URL (without 'http://')"
+msgstr "Isso server URL ('http://' 抜き)"
+
+#: plugins/piwik/piwik.php:24
+msgid ""
+"Piwik plugin error: Please define PIWIK_URL and PIWIK_SITEID in the plugin "
+"administration page."
+msgstr ""
+"Piwik プラグインエラー: PIWIK_URL と PIWIK_SITEID の値をプラグイン管理ページ"
+"で指定してください。"
+
+#: plugins/piwik/piwik.php:73
+msgid "A plugin that adds Piwik tracking code to Shaarli pages."
+msgstr "Piwik のトラッキングコードをShaarliに追加するプラグインです。"
+
+#: plugins/piwik/piwik.php:74
+msgid "Piwik URL"
+msgstr "Piwik URL"
+
+#: plugins/piwik/piwik.php:75
+msgid "Piwik site ID"
+msgstr "Piwik サイトID"
+
+#: plugins/playvideos/playvideos.php:26
+msgid "Video player"
+msgstr "動画プレイヤー"
+
+#: plugins/playvideos/playvideos.php:29
+msgid "Play Videos"
+msgstr "動画を再生"
+
+#: plugins/playvideos/playvideos.php:60
+msgid "Add a button in the toolbar allowing to watch all videos."
+msgstr "すべての動画を閲覧するボタンをツールバーに追加します。"
+
+#: plugins/playvideos/youtube_playlist.js:214
+msgid "plugins/playvideos/jquery-1.11.2.min.js"
+msgstr "plugins/playvideos/jquery-1.11.2.min.js"
+
+#: plugins/pubsubhubbub/pubsubhubbub.php:72
+#, php-format
+msgid "Could not publish to PubSubHubbub: %s"
+msgstr "PubSubHubbub に登録できませんでした: %s"
+
+#: plugins/pubsubhubbub/pubsubhubbub.php:99
+#, php-format
+msgid "Could not post to %s"
+msgstr "%s に登録できませんでした"
+
+#: plugins/pubsubhubbub/pubsubhubbub.php:103
+#, php-format
+msgid "Bad response from the hub %s"
+msgstr "ハブ %s からの不正なレスポンス"
+
+#: plugins/pubsubhubbub/pubsubhubbub.php:114
+msgid "Enable PubSubHubbub feed publishing."
+msgstr "PubSubHubbub へのフィードを公開する。"
+
+#: plugins/qrcode/qrcode.php:74 plugins/wallabag/wallabag.php:72
+msgid "For each link, add a QRCode icon."
+msgstr "それぞれのリンクについて、QRコードのアイコンを追加する。"
+
+#: plugins/wallabag/wallabag.php:22
+msgid ""
+"Wallabag plugin error: Please define the \"WALLABAG_URL\" setting in the "
+"plugin administration page."
+msgstr ""
+"Wallabag プラグインエラー: \"WALLABAG_URL\" の値をプラグイン管理ページにおい"
+"て指定してください。"
+
+#: plugins/wallabag/wallabag.php:49
+msgid "Save to wallabag"
+msgstr "Wallabag に保存"
+
+#: plugins/wallabag/wallabag.php:73
+msgid "Wallabag API URL"
+msgstr "Wallabag のAPIのURL"
+
+#: plugins/wallabag/wallabag.php:74
+msgid "Wallabag API version (1 or 2)"
+msgstr "Wallabag のAPIのバージョン (1 または 2)"
+
+#: tests/LanguagesTest.php:214 tests/LanguagesTest.php:227
+#: tests/languages/fr/LanguagesFrTest.php:159
+#: tests/languages/fr/LanguagesFrTest.php:172
+msgid "Search"
+msgid_plural "Search"
+msgstr[0] "検索"
+msgstr[1] "検索"
+
+#~ msgid "The page you are trying to reach does not exist or has been deleted."
+#~ msgstr "あなたが開こうとしたページは存在しないか、削除されています。"
+
+#~ msgid "404 Not Found"
+#~ msgstr "404 ページが存在しません"
+
+#~ msgid "Updates file path is not set, can't write updates."
+#~ msgstr ""
+#~ "更新するファイルのパスが指定されていないため、更新を書き込めません。"
+
+#~ msgid "Unable to write updates in "
+#~ msgstr "更新を次の項目に書き込めませんでした: "
+
+#~ msgid "I said: NO. You are banned for the moment. Go away."
+#~ msgstr "あなたはこのサーバーからBANされています。"
+
+#~ msgid "Tag cloud"
+#~ msgstr "タグクラウド"
+
+#~ msgid "Click to try again."
+#~ msgstr "クリックして再度試します。"
+
+#~ msgid "Description will be rendered with"
+#~ msgstr "説明は次の方法で描画されます:"
+
+#~ msgid "Markdown syntax documentation"
+#~ msgstr "マークダウン形式のドキュメント"
+
+#~ msgid "Markdown syntax"
+#~ msgstr "マークダウン形式"
+
+#~ msgid ""
+#~ "Render shaare description with Markdown syntax.
Warning"
+#~ "strong>:\n"
+#~ "If your shaared descriptions contained HTML tags before enabling the "
+#~ "markdown plugin,\n"
+#~ "enabling it might break your page.\n"
+#~ "See the README."
+#~ msgstr ""
+#~ "リンクの説明をマークダウン形式で表示します。
警告:\n"
+#~ "リンクの説明にHTMLタグがこのプラグインを有効にする前に含まれていた場合、\n"
+#~ "正常にページを表示できなくなるかもしれません。\n"
+#~ "詳しくは README をご覧ください。"
+
+#~ msgid "Sorry, nothing to see here."
+#~ msgstr "すみませんが、ここには何もありません。"
+
+#~ msgid "URL or leave empty to post a note"
+#~ msgstr "URL を入力するか、空欄にするとノートを投稿します"
+
+#~ msgid "Current password"
+#~ msgstr "現在のパスワード"
+
+#~ msgid "New password"
+#~ msgstr "新しいパスワード"
+
+#~ msgid "Change"
+#~ msgstr "変更"
+
+#~ msgid "Tag"
+#~ msgstr "タグ"
+
+#~ msgid "New name"
+#~ msgstr "変更先の名前"
+
+#~ msgid "Case sensitive"
+#~ msgstr "大文字と小文字を区別"
+
+#~ msgid "Rename"
+#~ msgstr "名前を変更"
+
+#~ msgid "Delete"
+#~ msgstr "削除"
+
+#~ msgid "You can also edit tags in the"
+#~ msgstr "次に含まれるタグを編集することもできます:"
+
+#~ msgid "tag list"
+#~ msgstr "タグ一覧"
+
+#~ msgid "title"
+#~ msgstr "タイトル"
+
+#~ msgid "Home link"
+#~ msgstr "ホームのリンク先"
+
+#~ msgid "Default value"
+#~ msgstr "既定の値"
+
+#~ msgid "Theme"
+#~ msgstr "テーマ"
+
+#~ msgid "Language"
+#~ msgstr "言語"
+
+#~ msgid "Timezone"
+#~ msgstr "タイムゾーン"
+
+#~ msgid "Continent"
+#~ msgstr "大陸"
+
+#~ msgid "City"
+#~ msgstr "町"
+
+#~ msgid "Disable session cookie hijacking protection"
+#~ msgstr "不正ログイン防止のためのセッションクッキーを無効化"
+
+#~ msgid ""
+#~ "Check this if you get disconnected or if your IP address changes often"
+#~ msgstr ""
+#~ "あなたが切断されたり、IPアドレスが頻繁に変わる環境下であるならチェックを入"
+#~ "れてください"
+
+#~ msgid "Private links by default"
+#~ msgstr "既定でプライベートリンク"
+
+#~ msgid "All new links are private by default"
+#~ msgstr "すべての新規リンクをプライベートで作成"
+
+#~ msgid "RSS direct links"
+#~ msgstr "RSS 直リンク"
+
+#~ msgid "Check this to use direct URL instead of permalink in feeds"
+#~ msgstr "フィードでパーマリンクの代わりに直リンクを使う"
+
+#~ msgid "Hide public links"
+#~ msgstr "公開リンクを隠す"
+
+#~ msgid "Do not show any links if the user is not logged in"
+#~ msgstr "ログインしていないユーザーには何のリンクも表示しない"
+
+#~ msgid "Notify me when a new release is ready"
+#~ msgstr "新しいバージョンがリリースされたときに通知"
+
+#~ msgid "Enable REST API"
+#~ msgstr "REST API を有効化"
+
+#~ msgid "Allow third party software to use Shaarli such as mobile application"
+#~ msgstr ""
+#~ "モバイルアプリといったサードパーティーのソフトウェアにShaarliを使用するこ"
+#~ "とを許可"
+
+#~ msgid "API secret"
+#~ msgstr "API シークレット"
+
+#~ msgid "Save"
+#~ msgstr "保存"
+
+#~ msgid "The Daily Shaarli"
+#~ msgstr "デイリーSharli"
+
+#~ msgid "1 RSS entry per day"
+#~ msgstr "各日1つずつのRSS項目"
+
+#~ msgid "Previous day"
+#~ msgstr "前日"
+
+#~ msgid "All links of one day in a single page."
+#~ msgstr "1日に作成されたすべてのリンクです。"
+
+#~ msgid "Next day"
+#~ msgstr "翌日"
+
+#~ msgid "Created:"
+#~ msgstr "作成:"
+
+#~ msgid "URL"
+#~ msgstr "URL"
+
+#~ msgid "Title"
+#~ msgstr "タイトル"
+
+#~ msgid "Description"
+#~ msgstr "説明"
+
+#~ msgid "Tags"
+#~ msgstr "タグ"
+
+#~ msgid "Private"
+#~ msgstr "プライベート"
+
+#~ msgid "Apply Changes"
+#~ msgstr "変更を適用"
+
+#~ msgid "Export Database"
+#~ msgstr "データベースをエクスポート"
+
+#~ msgid "Selection"
+#~ msgstr "選択済み"
+
+#~ msgid "All"
+#~ msgstr "すべて"
+
+#~ msgid "Public"
+#~ msgstr "公開"
+
+#~ msgid "Prepend note permalinks with this Shaarli instance's URL"
+#~ msgstr ""
+#~ "この Shaarli のインスタンスのURL にノートへのパーマリンクを付け加える"
+
+#~ msgid "Useful to import bookmarks in a web browser"
+#~ msgstr "ウェブブラウザーのリンクをインポートするのに有効です"
+
+#~ msgid "Import Database"
+#~ msgstr "データベースをインポート"
+
+#~ msgid "Maximum size allowed:"
+#~ msgstr "最大サイズ:"
+
+#~ msgid "Visibility"
+#~ msgstr "可視性"
+
+#~ msgid "Use values from the imported file, default to public"
+#~ msgstr "インポート元のファイルの値を使用 (既定は公開リンクとなります)"
+
+#~ msgid "Import all bookmarks as public"
+#~ msgstr "すべてのブックマーク項目を公開リンクとしてインポート"
+
+#~ msgid "Overwrite existing bookmarks"
+#~ msgstr "既に存在しているブックマークを上書き"
+
+#~ msgid "Duplicates based on URL"
+#~ msgstr "URL による重複"
+
+#~ msgid "Add default tags"
+#~ msgstr "既定のタグを追加"
+
+#~ msgid ""
+#~ "It looks like it's the first time you run Shaarli. Please configure it."
+#~ msgstr "どうやら Shaarli を初めて起動しているようです。設定してください。"
+
+#~ msgid "Username"
+#~ msgstr "ユーザー名"
+
+#~ msgid "Password"
+#~ msgstr "パスワード"
+
+#~ msgid "Shaarli title"
+#~ msgstr "Shaarli のタイトル"
+
+#~ msgid "My links"
+#~ msgstr "自分のリンク"
+
+#~ msgid "Install"
+#~ msgstr "インストール"
+
+#~ msgid "shaare"
+#~ msgid_plural "shaares"
+#~ msgstr[0] "共有"
+#~ msgstr[1] "共有"
+
+#~ msgid "private link"
+#~ msgid_plural "private links"
+#~ msgstr[0] "プライベートリンク"
+#~ msgstr[1] "プライベートリンク"
+
+#~ msgid "Search text"
+#~ msgstr "文字列で検索"
+
+#~ msgid "Filter by tag"
+#~ msgstr "タグによって分類"
+
+#~ msgid "Nothing found."
+#~ msgstr "何も見つかりませんでした。"
+
+#~ msgid "%s result"
+#~ msgid_plural "%s results"
+#~ msgstr[0] "%s 件の結果"
+#~ msgstr[1] "%s 件の結果"
+
+#~ msgid "for"
+#~ msgstr "for"
+
+#~ msgid "tagged"
+#~ msgstr "タグ付けされた"
+
+#~ msgid "Remove tag"
+#~ msgstr "タグを削除"
+
+#~ msgid "with status"
+#~ msgstr "with status"
+
+#~ msgid "without any tag"
+#~ msgstr "タグなし"
+
+#~ msgid "Fold"
+#~ msgstr "畳む"
+
+#~ msgid "Edited: "
+#~ msgstr "編集済み: "
+
+#~ msgid "permalink"
+#~ msgstr "パーマリンク"
+
+#~ msgid "Add tag"
+#~ msgstr "タグを追加"
+
+#~ msgid "Filters"
+#~ msgstr "分類"
+
+#~ msgid "Only display private links"
+#~ msgstr "プライベートリンクのみを表示"
+
+#~ msgid "Only display public links"
+#~ msgstr "公開リンクのみを表示"
+
+#~ msgid "Filter untagged links"
+#~ msgstr "タグ付けされていないリンクで分類"
+
+#~ msgid "Fold all"
+#~ msgstr "すべて畳む"
+
+#~ msgid "Links per page"
+#~ msgstr "各ページをリンク"
+
+#~ msgid "Remember me"
+#~ msgstr "パスワードを保存"
+
+#~ msgid "by the Shaarli community"
+#~ msgstr "by Shaarli コミュニティ"
+
+#~ msgid "Documentation"
+#~ msgstr "ドキュメント"
+
+#~ msgid "Expand"
+#~ msgstr "展開する"
+
+#~ msgid "Expand all"
+#~ msgstr "すべて展開する"
+
+#~ msgid "Are you sure you want to delete this link?"
+#~ msgstr "本当にこのリンクを削除しますか?"
+
+#~ msgid "RSS Feed"
+#~ msgstr "RSS フィード"
+
+#~ msgid "Logout"
+#~ msgstr "ログアウト"
+
+#~ msgid "is available"
+#~ msgstr "が利用可能"
+
+#~ msgid "Picture Wall"
+#~ msgstr "ピクチャーウォール"
+
+#~ msgid "pics"
+#~ msgstr "画像"
+
+#~ msgid "You need to enable Javascript to change plugin loading order."
+#~ msgstr ""
+#~ "プラグインを読み込む順番を変更するには、Javascriptを有効にする必要がありま"
+#~ "す。"
+
+#~ msgid "Enabled Plugins"
+#~ msgstr "有効なプラグイン"
+
+#~ msgid "No plugin enabled."
+#~ msgstr "有効なプラグインはありません。"
+
+#~ msgid "Disable"
+#~ msgstr "無効化"
+
+#~ msgid "Name"
+#~ msgstr "名前"
+
+#~ msgid "Order"
+#~ msgstr "順序"
+
+#~ msgid "Disabled Plugins"
+#~ msgstr "無効なプラグイン"
+
+#~ msgid "No plugin disabled."
+#~ msgstr "無効なプラグインはありません。"
+
+#~ msgid "Enable"
+#~ msgstr "有効化"
+
+#~ msgid "More plugins available"
+#~ msgstr "さらに利用できるプラグインがあります"
+
+#~ msgid "in the documentation"
+#~ msgstr "ドキュメント内"
+
+#~ msgid "No parameter available."
+#~ msgstr "利用可能な設定項目はありません。"
+
+#~ msgid "tags"
+#~ msgstr "タグ"
+
+#~ msgid "List all links with those tags"
+#~ msgstr "このタグが付いているリンクをリスト化する"
+
+#~ msgid "Sort by:"
+#~ msgstr "分類:"
+
+#~ msgid "Cloud"
+#~ msgstr "クラウド"
+
+#~ msgid "Most used"
+#~ msgstr "もっとも使われた"
+
+#~ msgid "Alphabetical"
+#~ msgstr "アルファベット順"
+
+#~ msgid "Settings"
+#~ msgstr "設定"
+
+#~ msgid "Change Shaarli settings: title, timezone, etc."
+#~ msgstr "Shaarli の設定を変更: タイトル、タイムゾーンなど。"
+
+#~ msgid "Configure your Shaarli"
+#~ msgstr "あなたの Shaarli を設定"
+
+#~ msgid "Enable, disable and configure plugins"
+#~ msgstr "プラグインを有効化、無効化、設定する"
+
+#~ msgid "Change your password"
+#~ msgstr "パスワードを変更"
+
+#~ msgid "Rename or delete a tag in all links"
+#~ msgstr "すべてのリンクのタグの名前を変更する、または削除する"
+
+#~ msgid ""
+#~ "Import Netscape HTML bookmarks (as exported from Firefox, Chrome, Opera, "
+#~ "delicious...)"
+#~ msgstr ""
+#~ "Netscape HTML 形式のブックマークをインポートする (Firefox、Chrome、Operaと"
+#~ "いったブラウザーが含まれます)"
+
+#~ msgid "Import links"
+#~ msgstr "リンクをインポート"
+
+#~ msgid ""
+#~ "Export Netscape HTML bookmarks (which can be imported in Firefox, Chrome, "
+#~ "Opera, delicious...)"
+#~ msgstr ""
+#~ "Netscape HTML 形式のブックマークをエクスポートする (Firefox、Chrome、Opera"
+#~ "といったブラウザーが含まれます)"
+
+#~ msgid "Export database"
+#~ msgstr "リンクをエクスポート"
+
+#~ msgid ""
+#~ "Drag one of these button to your bookmarks toolbar or right-click it and "
+#~ "\"Bookmark This Link\""
+#~ msgstr ""
+#~ "これらのボタンのうち1つををブックマークバーにドラッグするか、右クリックし"
+#~ "て「このリンクをブックマークに追加」してください"
+
+#~ msgid "then click on the bookmarklet in any page you want to share."
+#~ msgstr "共有したいページでブックマークレットをクリックしてください。"
+
+#~ msgid ""
+#~ "Drag this link to your bookmarks toolbar or right-click it and Bookmark "
+#~ "This Link"
+#~ msgstr ""
+#~ "このリンクをブックマークバーにドラッグするか、右クリックして「このリンクを"
+#~ "ブックマークに追加」してください"
+
+#~ msgid "then click ✚Shaare link button in any page you want to share"
+#~ msgstr ""
+#~ "✚リンクを共有 ボタンをクリックすることで、どこでもリンクを共有できます"
+
+#~ msgid "The selected text is too long, it will be truncated."
+#~ msgstr "選択された文字列は長すぎるので、一部が切り捨てられます。"
+
+#~ msgid "Shaare link"
+#~ msgstr "共有リンク"
+
+#~ msgid ""
+#~ "Then click ✚Add Note button anytime to start composing a private Note "
+#~ "(text post) to your Shaarli"
+#~ msgstr ""
+#~ "✚ノートを追加 ボタンをクリックすることで、いつでもプライベートノート(テキ"
+#~ "スト形式)をShaarli上に作成できます"
+
+#~ msgid "Add Note"
+#~ msgstr "ノートを追加"
+
+#~ msgid ""
+#~ "You need to browse your Shaarli over HTTPS to use this "
+#~ "functionality."
+#~ msgstr ""
+#~ "この機能を使用するには、HTTPS 経由でShaarliに接続してくだ"
+#~ "さい。"
+
+#~ msgid "Add to"
+#~ msgstr "次に追加:"
+
+#~ msgid "3rd party"
+#~ msgstr "サードパーティー"
+
+#~ msgid "Plugin"
+#~ msgstr "プラグイン"
+
+#~ msgid "plugin"
+#~ msgstr "プラグイン"
+
+#~ msgid ""
+#~ "Drag this link to your bookmarks toolbar, or right-click it and choose "
+#~ "Bookmark This Link"
+#~ msgstr ""
+#~ "このリンクをブックマークバーにドラッグするか、右クリックして「このリンクを"
+#~ "ブックマークに追加」してください"
diff --git a/inc/languages/ru/LC_MESSAGES/shaarli.po b/inc/languages/ru/LC_MESSAGES/shaarli.po
new file mode 100644
index 00000000..98e70425
--- /dev/null
+++ b/inc/languages/ru/LC_MESSAGES/shaarli.po
@@ -0,0 +1,1944 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: Shaarli\n"
+"POT-Creation-Date: 2020-11-14 07:47+0500\n"
+"PO-Revision-Date: 2020-11-15 06:16+0500\n"
+"Last-Translator: progit \n"
+"Language-Team: Shaarli\n"
+"Language: ru_RU\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 2.0.1\n"
+"X-Poedit-Basepath: ../../../..\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
+"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Poedit-SourceCharset: UTF-8\n"
+"X-Poedit-KeywordsList: t:1,2;t\n"
+"X-Poedit-SearchPath-0: application\n"
+"X-Poedit-SearchPath-1: tmp\n"
+"X-Poedit-SearchPath-2: index.php\n"
+"X-Poedit-SearchPath-3: init.php\n"
+"X-Poedit-SearchPath-4: plugins\n"
+
+#: application/History.php:181
+msgid "History file isn't readable or writable"
+msgstr "Файл истории не доступен для чтения или записи"
+
+#: application/History.php:192
+msgid "Could not parse history file"
+msgstr "Не удалось разобрать файл истории"
+
+#: application/Languages.php:184
+msgid "Automatic"
+msgstr "Автоматический"
+
+#: application/Languages.php:185
+msgid "German"
+msgstr "Немецкий"
+
+#: application/Languages.php:186
+msgid "English"
+msgstr "Английский"
+
+#: application/Languages.php:187
+msgid "French"
+msgstr "Французский"
+
+#: application/Languages.php:188
+msgid "Japanese"
+msgstr "Японский"
+
+#: application/Languages.php:189
+msgid "Russian"
+msgstr "Русский"
+
+#: application/Thumbnailer.php:62
+msgid ""
+"php-gd extension must be loaded to use thumbnails. Thumbnails are now "
+"disabled. Please reload the page."
+msgstr ""
+"для использования миниатюр необходимо загрузить расширение php-gd. Миниатюры "
+"сейчас отключены. Перезагрузите страницу."
+
+#: application/Utils.php:405
+msgid "Setting not set"
+msgstr "Настройка не задана"
+
+#: application/Utils.php:412
+msgid "Unlimited"
+msgstr "Неограниченно"
+
+#: application/Utils.php:415
+msgid "B"
+msgstr "Б"
+
+#: application/Utils.php:415
+msgid "kiB"
+msgstr "КБ"
+
+#: application/Utils.php:415
+msgid "MiB"
+msgstr "МБ"
+
+#: application/Utils.php:415
+msgid "GiB"
+msgstr "ГБ"
+
+#: application/bookmark/BookmarkFileService.php:185
+#: application/bookmark/BookmarkFileService.php:207
+#: application/bookmark/BookmarkFileService.php:229
+#: application/bookmark/BookmarkFileService.php:243
+msgid "You're not authorized to alter the datastore"
+msgstr "У вас нет прав на изменение хранилища данных"
+
+#: application/bookmark/BookmarkFileService.php:210
+msgid "This bookmarks already exists"
+msgstr "Эта закладка уже существует"
+
+#: application/bookmark/BookmarkInitializer.php:42
+msgid "(private bookmark with thumbnail demo)"
+msgstr "(личная закладка с показом миниатюр)"
+
+#: application/bookmark/BookmarkInitializer.php:45
+msgid ""
+"Shaarli will automatically pick up the thumbnail for links to a variety of "
+"websites.\n"
+"\n"
+"Explore your new Shaarli instance by trying out controls and menus.\n"
+"Visit the project on [Github](https://github.com/shaarli/Shaarli) or [the "
+"documentation](https://shaarli.readthedocs.io/en/master/) to learn more "
+"about Shaarli.\n"
+"\n"
+"Now you can edit or delete the default shaares.\n"
+msgstr ""
+"Shaarli автоматически подберет миниатюру для ссылок на различные сайты.\n"
+"\n"
+"Изучите Shaarli, попробовав элементы управления и меню.\n"
+"Посетите проект [Github](https://github.com/shaarli/Shaarli) или "
+"[документацию](https://shaarli.readthedocs.io/en/master/),чтобы узнать "
+"больше о Shaarli.\n"
+"\n"
+"Теперь вы можете редактировать или удалять шаары по умолчанию.\n"
+
+#: application/bookmark/BookmarkInitializer.php:58
+msgid "Note: Shaare descriptions"
+msgstr "Примечание: описания Шаар"
+
+#: application/bookmark/BookmarkInitializer.php:60
+msgid ""
+"Adding a shaare without entering a URL creates a text-only \"note\" post "
+"such as this one.\n"
+"This note is private, so you are the only one able to see it while logged "
+"in.\n"
+"\n"
+"You can use this to keep notes, post articles, code snippets, and much "
+"more.\n"
+"\n"
+"The Markdown formatting setting allows you to format your notes and bookmark "
+"description:\n"
+"\n"
+"### Title headings\n"
+"\n"
+"#### Multiple headings levels\n"
+" * bullet lists\n"
+" * _italic_ text\n"
+" * **bold** text\n"
+" * ~~strike through~~ text\n"
+" * `code` blocks\n"
+" * images\n"
+" * [links](https://en.wikipedia.org/wiki/Markdown)\n"
+"\n"
+"Markdown also supports tables:\n"
+"\n"
+"| Name | Type | Color | Qty |\n"
+"| ------- | --------- | ------ | ----- |\n"
+"| Orange | Fruit | Orange | 126 |\n"
+"| Apple | Fruit | Any | 62 |\n"
+"| Lemon | Fruit | Yellow | 30 |\n"
+"| Carrot | Vegetable | Red | 14 |\n"
+msgstr ""
+"При добавлении закладки без ввода URL адреса создается текстовая \"заметка"
+"\", такая как эта.\n"
+"Эта заметка является личной, поэтому вы единственный, кто может ее увидеть, "
+"находясь в системе.\n"
+"\n"
+"Вы можете использовать это для хранения заметок, публикации статей, "
+"фрагментов кода и многого другого.\n"
+"\n"
+"Параметр форматирования Markdown позволяет форматировать заметки и описание "
+"закладок:\n"
+"\n"
+"### Заголовок заголовков\n"
+"\n"
+"#### Multiple headings levels\n"
+" * маркированные списки\n"
+" * _наклонный_ текст\n"
+" * **жирный** текст\n"
+" * ~~зачеркнутый~~ текст\n"
+" * блоки `кода`\n"
+" * изображения\n"
+" * [ссылки](https://en.wikipedia.org/wiki/Markdown)\n"
+"\n"
+"Markdown также поддерживает таблицы:\n"
+"\n"
+"| Имя | Тип | Цвет | Количество |\n"
+"| ------- | --------- | ------ | ----- |\n"
+"| Апельсин | Фрукт | Оранжевый | 126 |\n"
+"| Яблоко | Фрукт | Любой | 62 |\n"
+"| Лимон | Фрукт | Желтый | 30 |\n"
+"| Морковь | Овощ | Красный | 14 |\n"
+
+#: application/bookmark/BookmarkInitializer.php:94
+#: application/legacy/LegacyLinkDB.php:246
+#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
+#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
+#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:15
+#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:48
+msgid ""
+"The personal, minimalist, super-fast, database free, bookmarking service"
+msgstr "Личный, минималистичный, сверхбыстрый сервис закладок без баз данных"
+
+#: application/bookmark/BookmarkInitializer.php:97
+msgid ""
+"Welcome to Shaarli!\n"
+"\n"
+"Shaarli allows you to bookmark your favorite pages, and share them with "
+"others or store them privately.\n"
+"You can add a description to your bookmarks, such as this one, and tag "
+"them.\n"
+"\n"
+"Create a new shaare by clicking the `+Shaare` button, or using any of the "
+"recommended tools (browser extension, mobile app, bookmarklet, REST API, "
+"etc.).\n"
+"\n"
+"You can easily retrieve your links, even with thousands of them, using the "
+"internal search engine, or search through tags (e.g. this Shaare is tagged "
+"with `shaarli` and `help`).\n"
+"Hashtags such as #shaarli #help are also supported.\n"
+"You can also filter the available [RSS feed](/feed/atom) and picture wall by "
+"tag or plaintext search.\n"
+"\n"
+"We hope that you will enjoy using Shaarli, maintained with ❤️ by the "
+"community!\n"
+"Feel free to open [an issue](https://github.com/shaarli/Shaarli/issues) if "
+"you have a suggestion or encounter an issue.\n"
+msgstr ""
+"Добро пожаловать в Shaarli!\n"
+"\n"
+"Shaarli позволяет добавлять в закладки свои любимые страницы и делиться ими "
+"с другими или хранить их в частном порядке.\n"
+"Вы можете добавить описание к своим закладкам, например этой, и пометить "
+"их.\n"
+"\n"
+"Создайте новую закладку, нажав кнопку `+Поделиться`, или используя любой из "
+"рекомендуемых инструментов (расширение для браузера, мобильное приложение, "
+"букмарклет, REST API и т.д.).\n"
+"\n"
+"Вы можете легко получить свои ссылки, даже если их тысячи, с помощью "
+"внутренней поисковой системы или поиска по тегам (например, эта заметка "
+"помечена тегами `shaarli` and `help`).\n"
+"Также поддерживаются хэштеги, такие как #shaarli #help.\n"
+"Вы можете также фильтровать доступный [RSS канал](/feed/atom) и галерею по "
+"тегу или по поиску текста.\n"
+"\n"
+"Мы надеемся, что вам понравится использовать Shaarli, с ❤️ поддерживаемый "
+"сообществом!\n"
+"Не стесняйтесь открывать [запрос](https://github.com/shaarli/Shaarli/"
+"issues), если у вас есть предложение или возникла проблема.\n"
+
+#: application/bookmark/exception/BookmarkNotFoundException.php:14
+msgid "The link you are trying to reach does not exist or has been deleted."
+msgstr ""
+"Ссылка, по которой вы пытаетесь перейти, не существует или была удалена."
+
+#: application/config/ConfigJson.php:52 application/config/ConfigPhp.php:131
+msgid ""
+"Shaarli could not create the config file. Please make sure Shaarli has the "
+"right to write in the folder is it installed in."
+msgstr ""
+"Shaarli не удалось создать файл конфигурации. Убедитесь, что у Shaarli есть "
+"право на запись в папку, в которой он установлен."
+
+#: application/config/ConfigManager.php:137
+#: application/config/ConfigManager.php:164
+msgid "Invalid setting key parameter. String expected, got: "
+msgstr "Неверная настройка ключевого параметра. Ожидалась строка, получено: "
+
+#: application/config/exception/MissingFieldConfigException.php:20
+#, php-format
+msgid "Configuration value is required for %s"
+msgstr "Значение конфигурации требуется для %s"
+
+#: application/config/exception/PluginConfigOrderException.php:15
+msgid "An error occurred while trying to save plugins loading order."
+msgstr "Произошла ошибка при попытке сохранить порядок загрузки плагинов."
+
+#: application/config/exception/UnauthorizedConfigException.php:15
+msgid "You are not authorized to alter config."
+msgstr "Вы не авторизованы для изменения конфигурации."
+
+#: application/exceptions/IOException.php:23
+msgid "Error accessing"
+msgstr "Ошибка доступа"
+
+#: application/feed/FeedBuilder.php:180
+msgid "Direct link"
+msgstr "Прямая ссылка"
+
+#: application/feed/FeedBuilder.php:182
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:103
+#: tmp/dailyrss.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:179
+msgid "Permalink"
+msgstr "Постоянная ссылка"
+
+#: application/front/controller/admin/ConfigureController.php:56
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24
+msgid "Configure"
+msgstr "Настройка"
+
+#: application/front/controller/admin/ConfigureController.php:106
+#: application/legacy/LegacyUpdater.php:539
+msgid "You have enabled or changed thumbnails mode."
+msgstr "Вы включили или изменили режим миниатюр."
+
+#: application/front/controller/admin/ConfigureController.php:108
+#: application/front/controller/admin/ServerController.php:76
+#: application/legacy/LegacyUpdater.php:540
+msgid "Please synchronize them."
+msgstr "Пожалуйста, синхронизируйте их."
+
+#: application/front/controller/admin/ConfigureController.php:119
+#: application/front/controller/visitor/InstallController.php:149
+msgid "Error while writing config file after configuration update."
+msgstr "Ошибка при записи файла конфигурации после обновления конфигурации."
+
+#: application/front/controller/admin/ConfigureController.php:128
+msgid "Configuration was saved."
+msgstr "Конфигурация сохранена."
+
+#: application/front/controller/admin/ExportController.php:26
+#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:64
+msgid "Export"
+msgstr "Экспорт"
+
+#: application/front/controller/admin/ExportController.php:42
+msgid "Please select an export mode."
+msgstr "Выберите режим экспорта."
+
+#: application/front/controller/admin/ImportController.php:41
+#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:83
+msgid "Import"
+msgstr "Импорт"
+
+#: application/front/controller/admin/ImportController.php:55
+msgid "No import file provided."
+msgstr "Файл импорта не предоставлен."
+
+#: application/front/controller/admin/ImportController.php:66
+#, php-format
+msgid ""
+"The file you are trying to upload is probably bigger than what this "
+"webserver can accept (%s). Please upload in smaller chunks."
+msgstr ""
+"Файл, который вы пытаетесь загрузить, вероятно, больше, чем может принять "
+"этот сервер (%s). Пожалуйста, загружайте небольшими частями."
+
+#: application/front/controller/admin/ManageTagController.php:30
+msgid "whitespace"
+msgstr "пробел"
+
+#: application/front/controller/admin/ManageTagController.php:35
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42
+msgid "Manage tags"
+msgstr "Управление тегами"
+
+#: application/front/controller/admin/ManageTagController.php:54
+msgid "Invalid tags provided."
+msgstr "Предоставлены недействительные теги."
+
+#: application/front/controller/admin/ManageTagController.php:78
+#, php-format
+msgid "The tag was removed from %d bookmark."
+msgid_plural "The tag was removed from %d bookmarks."
+msgstr[0] "Тег был удален из %d закладки."
+msgstr[1] "Тег был удален из %d закладок."
+msgstr[2] "Тег был удален из %d закладок."
+
+#: application/front/controller/admin/ManageTagController.php:83
+#, php-format
+msgid "The tag was renamed in %d bookmark."
+msgid_plural "The tag was renamed in %d bookmarks."
+msgstr[0] "Тег был переименован в %d закладке."
+msgstr[1] "Тег был переименован в %d закладках."
+msgstr[2] "Тег был переименован в %d закладках."
+
+#: application/front/controller/admin/ManageTagController.php:105
+msgid "Tags separator must be a single character."
+msgstr "Разделитель тегов должен состоять из одного символа."
+
+#: application/front/controller/admin/ManageTagController.php:111
+msgid "These characters are reserved and can't be used as tags separator: "
+msgstr ""
+"Эти символы зарезервированы и не могут использоваться в качестве разделителя "
+"тегов: "
+
+#: application/front/controller/admin/PasswordController.php:28
+#: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35
+msgid "Change password"
+msgstr "Изменить пароль"
+
+#: application/front/controller/admin/PasswordController.php:55
+msgid "You must provide the current and new password to change it."
+msgstr "Вы должны предоставить текущий и новый пароль, чтобы изменить его."
+
+#: application/front/controller/admin/PasswordController.php:71
+msgid "The old password is not correct."
+msgstr "Старый пароль неверен."
+
+#: application/front/controller/admin/PasswordController.php:97
+msgid "Your password has been changed"
+msgstr "Пароль изменен"
+
+#: application/front/controller/admin/PluginsController.php:45
+msgid "Plugin Administration"
+msgstr "Управление плагинами"
+
+#: application/front/controller/admin/PluginsController.php:76
+msgid "Setting successfully saved."
+msgstr "Настройка успешно сохранена."
+
+#: application/front/controller/admin/PluginsController.php:79
+msgid "Error while saving plugin configuration: "
+msgstr "Ошибка при сохранении конфигурации плагина: "
+
+#: application/front/controller/admin/ServerController.php:35
+msgid "Check disabled"
+msgstr "Проверка отключена"
+
+#: application/front/controller/admin/ServerController.php:57
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
+msgid "Server administration"
+msgstr "Администрирование сервера"
+
+#: application/front/controller/admin/ServerController.php:74
+msgid "Thumbnails cache has been cleared."
+msgstr "Кэш миниатюр очищен."
+
+#: application/front/controller/admin/ServerController.php:85
+msgid "Shaarli's cache folder has been cleared!"
+msgstr "Папка с кэшем Shaarli очищена!"
+
+#: application/front/controller/admin/ShaareAddController.php:26
+#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
+msgid "Shaare a new link"
+msgstr "Поделиться новой ссылкой"
+
+#: application/front/controller/admin/ShaareManageController.php:35
+#: application/front/controller/admin/ShaareManageController.php:93
+msgid "Invalid bookmark ID provided."
+msgstr "Указан неверный идентификатор закладки."
+
+#: application/front/controller/admin/ShaareManageController.php:47
+#: application/front/controller/admin/ShaareManageController.php:116
+#: application/front/controller/admin/ShaareManageController.php:156
+#: application/front/controller/admin/ShaarePublishController.php:82
+#, php-format
+msgid "Bookmark with identifier %s could not be found."
+msgstr "Закладка с идентификатором %s не найдена."
+
+#: application/front/controller/admin/ShaareManageController.php:101
+msgid "Invalid visibility provided."
+msgstr "Предоставлена недопустимая видимость."
+
+#: application/front/controller/admin/ShaarePublishController.php:173
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:171
+msgid "Edit"
+msgstr "Редактировать"
+
+#: application/front/controller/admin/ShaarePublishController.php:176
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:28
+msgid "Shaare"
+msgstr "Поделиться"
+
+#: application/front/controller/admin/ShaarePublishController.php:208
+msgid "Note: "
+msgstr "Заметка: "
+
+#: application/front/controller/admin/ThumbnailsController.php:37
+#: tmp/thumbnails.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
+msgid "Thumbnails update"
+msgstr "Обновление миниатюр"
+
+#: application/front/controller/admin/ToolsController.php:31
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:33
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:33
+msgid "Tools"
+msgstr "Инструменты"
+
+#: application/front/controller/visitor/BookmarkListController.php:121
+msgid "Search: "
+msgstr "Поиск: "
+
+#: application/front/controller/visitor/DailyController.php:200
+msgid "day"
+msgstr "день"
+
+#: application/front/controller/visitor/DailyController.php:200
+#: application/front/controller/visitor/DailyController.php:203
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:48
+msgid "Daily"
+msgstr "За день"
+
+#: application/front/controller/visitor/DailyController.php:201
+msgid "week"
+msgstr "неделя"
+
+#: application/front/controller/visitor/DailyController.php:201
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
+msgid "Weekly"
+msgstr "За неделю"
+
+#: application/front/controller/visitor/DailyController.php:202
+msgid "month"
+msgstr "месяц"
+
+#: application/front/controller/visitor/DailyController.php:202
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
+msgid "Monthly"
+msgstr "За месяц"
+
+#: application/front/controller/visitor/ErrorController.php:30
+msgid "Error: "
+msgstr "Ошибка: "
+
+#: application/front/controller/visitor/ErrorController.php:34
+msgid "Please report it on Github."
+msgstr "Пожалуйста, сообщите об этом на Github."
+
+#: application/front/controller/visitor/ErrorController.php:39
+msgid "An unexpected error occurred."
+msgstr "Произошла непредвиденная ошибка."
+
+#: application/front/controller/visitor/ErrorNotFoundController.php:25
+msgid "Requested page could not be found."
+msgstr "Запрошенная страница не может быть найдена."
+
+#: application/front/controller/visitor/InstallController.php:65
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22
+msgid "Install Shaarli"
+msgstr "Установить Shaarli"
+
+#: application/front/controller/visitor/InstallController.php:85
+#, php-format
+msgid ""
+"Sessions do not seem to work correctly on your server.
Make sure the "
+"variable \"session.save_path\" is set correctly in your PHP config, and that "
+"you have write access to it.
It currently points to %s.
On some "
+"browsers, accessing your server via a hostname like 'localhost' or any "
+"custom hostname without a dot causes cookie storage to fail. We recommend "
+"accessing your server via it's IP address or Fully Qualified Domain Name.
"
+msgstr ""
+"Сессии на вашем сервере работают некорректно.
Убедитесь, что "
+"переменная \"session.save_path\" правильно установлена в вашей конфигурации "
+"PHP и что у вас есть доступ к ней на запись.
В настоящее время она "
+"указывает на %s.
В некоторых браузерах доступ к вашему серверу через имя "
+"хоста, например localhost или любое другое имя хоста без точки, приводит к "
+"сбою хранилища файлов cookie. Мы рекомендуем получить доступ к вашему "
+"серверу через его IP адрес или полное доменное имя.
"
+
+#: application/front/controller/visitor/InstallController.php:157
+msgid ""
+"Shaarli is now configured. Please login and start shaaring your bookmarks!"
+msgstr "Shaarli настроен. Войдите и начните делиться своими закладками!"
+
+#: application/front/controller/visitor/InstallController.php:171
+msgid "Insufficient permissions:"
+msgstr "Недостаточно разрешений:"
+
+#: application/front/controller/visitor/LoginController.php:46
+#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
+#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:101
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:77
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:101
+msgid "Login"
+msgstr "Вход"
+
+#: application/front/controller/visitor/LoginController.php:78
+msgid "Wrong login/password."
+msgstr "Неверный логин или пароль."
+
+#: application/front/controller/visitor/PictureWallController.php:29
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:43
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:43
+msgid "Picture wall"
+msgstr "Галерея"
+
+#: application/front/controller/visitor/TagCloudController.php:90
+msgid "Tag "
+msgstr "Тег "
+
+#: application/front/exceptions/AlreadyInstalledException.php:11
+msgid "Shaarli has already been installed. Login to edit the configuration."
+msgstr "Shaarli уже установлен. Войдите, чтобы изменить конфигурацию."
+
+#: application/front/exceptions/LoginBannedException.php:11
+msgid ""
+"You have been banned after too many failed login attempts. Try again later."
+msgstr ""
+"Вы были заблокированы из-за большого количества неудачных попыток входа в "
+"систему. Попробуйте позже."
+
+#: application/front/exceptions/OpenShaarliPasswordException.php:16
+msgid "You are not supposed to change a password on an Open Shaarli."
+msgstr "Вы не должны менять пароль на Open Shaarli."
+
+#: application/front/exceptions/ThumbnailsDisabledException.php:11
+msgid "Picture wall unavailable (thumbnails are disabled)."
+msgstr "Галерея недоступна (миниатюры отключены)."
+
+#: application/front/exceptions/WrongTokenException.php:16
+msgid "Wrong token."
+msgstr "Неправильный токен."
+
+#: application/helper/ApplicationUtils.php:163
+#, php-format
+msgid ""
+"Your PHP version is obsolete! Shaarli requires at least PHP %s, and thus "
+"cannot run. Your PHP version has known security vulnerabilities and should "
+"be updated as soon as possible."
+msgstr ""
+"Ваша версия PHP устарела! Shaarli требует как минимум PHP %s, и поэтому не "
+"может работать. В вашей версии PHP есть известные уязвимости в системе "
+"безопасности, и ее следует обновить как можно скорее."
+
+#: application/helper/ApplicationUtils.php:198
+#: application/helper/ApplicationUtils.php:218
+msgid "directory is not readable"
+msgstr "папка не доступна для чтения"
+
+#: application/helper/ApplicationUtils.php:221
+msgid "directory is not writable"
+msgstr "папка не доступна для записи"
+
+#: application/helper/ApplicationUtils.php:245
+msgid "file is not readable"
+msgstr "файл не доступен для чтения"
+
+#: application/helper/ApplicationUtils.php:248
+msgid "file is not writable"
+msgstr "файл не доступен для записи"
+
+#: application/helper/ApplicationUtils.php:282
+msgid "Configuration parsing"
+msgstr "Разбор конфигурации"
+
+#: application/helper/ApplicationUtils.php:283
+msgid "Slim Framework (routing, etc.)"
+msgstr "Slim Framework (маршрутизация и т. д.)"
+
+#: application/helper/ApplicationUtils.php:284
+msgid "Multibyte (Unicode) string support"
+msgstr "Поддержка многобайтовых (Unicode) строк"
+
+#: application/helper/ApplicationUtils.php:285
+msgid "Required to use thumbnails"
+msgstr "Обязательно использование миниатюр"
+
+#: application/helper/ApplicationUtils.php:286
+msgid "Localized text sorting (e.g. e->è->f)"
+msgstr "Локализованная сортировка текста (например, e->è->f)"
+
+#: application/helper/ApplicationUtils.php:287
+msgid "Better retrieval of bookmark metadata and thumbnail"
+msgstr "Лучшее получение метаданных закладок и миниатюр"
+
+#: application/helper/ApplicationUtils.php:288
+msgid "Use the translation system in gettext mode"
+msgstr "Используйте систему перевода в режиме gettext"
+
+#: application/helper/ApplicationUtils.php:289
+msgid "Login using LDAP server"
+msgstr "Вход через LDAP сервер"
+
+#: application/helper/DailyPageHelper.php:172
+msgid "Week"
+msgstr "Неделя"
+
+#: application/helper/DailyPageHelper.php:176
+msgid "Today"
+msgstr "Сегодня"
+
+#: application/helper/DailyPageHelper.php:178
+msgid "Yesterday"
+msgstr "Вчера"
+
+#: application/helper/FileUtils.php:100
+msgid "Provided path is not a directory."
+msgstr "Указанный путь не является папкой."
+
+#: application/helper/FileUtils.php:104
+msgid "Trying to delete a folder outside of Shaarli path."
+msgstr "Попытка удалить папку за пределами пути Shaarli."
+
+#: application/legacy/LegacyLinkDB.php:131
+msgid "You are not authorized to add a link."
+msgstr "Вы не авторизованы для изменения ссылки."
+
+#: application/legacy/LegacyLinkDB.php:134
+msgid "Internal Error: A link should always have an id and URL."
+msgstr "Внутренняя ошибка: ссылка всегда должна иметь идентификатор и URL."
+
+#: application/legacy/LegacyLinkDB.php:137
+msgid "You must specify an integer as a key."
+msgstr "В качестве ключа необходимо указать целое число."
+
+#: application/legacy/LegacyLinkDB.php:140
+msgid "Array offset and link ID must be equal."
+msgstr "Смещение массива и идентификатор ссылки должны быть одинаковыми."
+
+#: application/legacy/LegacyLinkDB.php:249
+msgid ""
+"Welcome to Shaarli! This is your first public bookmark. To edit or delete "
+"me, you must first login.\n"
+"\n"
+"To learn how to use Shaarli, consult the link \"Documentation\" at the "
+"bottom of this page.\n"
+"\n"
+"You use the community supported version of the original Shaarli project, by "
+"Sebastien Sauvage."
+msgstr ""
+"Добро пожаловать в Shaarli! Это ваша первая общедоступная закладка. Чтобы "
+"отредактировать или удалить меня, вы должны сначала авторизоваться.\n"
+"\n"
+"Чтобы узнать, как использовать Shaarli, перейдите по ссылке \"Документация\" "
+"внизу этой страницы.\n"
+"\n"
+"Вы используете поддерживаемую сообществом версию оригинального проекта "
+"Shaarli от Себастьяна Соваж."
+
+#: application/legacy/LegacyLinkDB.php:266
+msgid "My secret stuff... - Pastebin.com"
+msgstr "Мой секрет... - Pastebin.com"
+
+#: application/legacy/LegacyLinkDB.php:268
+msgid "Shhhh! I'm a private link only YOU can see. You can delete me too."
+msgstr ""
+"Тссс! Это личная ссылка, которую видите только ВЫ. Вы тоже можете удалить "
+"меня."
+
+#: application/legacy/LegacyUpdater.php:104
+msgid "Couldn't retrieve updater class methods."
+msgstr "Не удалось получить методы класса средства обновления."
+
+#: application/legacy/LegacyUpdater.php:540
+msgid ""
+msgstr ""
+
+#: application/netscape/NetscapeBookmarkUtils.php:63
+msgid "Invalid export selection:"
+msgstr "Неверный выбор экспорта:"
+
+#: application/netscape/NetscapeBookmarkUtils.php:215
+#, php-format
+msgid "File %s (%d bytes) "
+msgstr "Файл %s (%d байт) "
+
+#: application/netscape/NetscapeBookmarkUtils.php:217
+msgid "has an unknown file format. Nothing was imported."
+msgstr "имеет неизвестный формат файла. Ничего не импортировано."
+
+#: application/netscape/NetscapeBookmarkUtils.php:221
+#, php-format
+msgid ""
+"was successfully processed in %d seconds: %d bookmarks imported, %d "
+"bookmarks overwritten, %d bookmarks skipped."
+msgstr ""
+"успешно обработано за %d секунд: %d закладок импортировано, %d закладок "
+"перезаписаны, %d закладок пропущено."
+
+#: application/plugin/PluginManager.php:125
+msgid " [plugin incompatibility]: "
+msgstr " [несовместимость плагинов]: "
+
+#: application/plugin/exception/PluginFileNotFoundException.php:22
+#, php-format
+msgid "Plugin \"%s\" files not found."
+msgstr "Файл плагина \"%s\" не найден."
+
+#: application/render/PageCacheManager.php:32
+#, php-format
+msgid "Cannot purge %s: no directory"
+msgstr "Невозможно очистить%s: нет папки"
+
+#: application/updater/exception/UpdaterException.php:51
+msgid "An error occurred while running the update "
+msgstr "Произошла ошибка при запуске обновления "
+
+#: index.php:81
+msgid "Shared bookmarks on "
+msgstr "Общие закладки на "
+
+#: plugins/addlink_toolbar/addlink_toolbar.php:31
+msgid "URI"
+msgstr "URI"
+
+#: plugins/addlink_toolbar/addlink_toolbar.php:35
+#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:20
+msgid "Add link"
+msgstr "Добавить ссылку"
+
+#: plugins/addlink_toolbar/addlink_toolbar.php:52
+msgid "Adds the addlink input on the linklist page."
+msgstr ""
+"Добавляет на страницу списка ссылок поле для добавления новой закладки."
+
+#: plugins/archiveorg/archiveorg.php:29
+msgid "View on archive.org"
+msgstr "Посмотреть на archive.org"
+
+#: plugins/archiveorg/archiveorg.php:42
+msgid "For each link, add an Archive.org icon."
+msgstr "Для каждой ссылки добавить значок с Archive.org."
+
+#: plugins/default_colors/default_colors.php:38
+msgid ""
+"Default colors plugin error: This plugin is active and no custom color is "
+"configured."
+msgstr ""
+"Ошибка плагина цветов по умолчанию: этот плагин активен, и пользовательский "
+"цвет не настроен."
+
+#: plugins/default_colors/default_colors.php:113
+msgid "Override default theme colors. Use any CSS valid color."
+msgstr ""
+"Переопределить цвета темы по умолчанию. Используйте любой допустимый цвет "
+"CSS."
+
+#: plugins/default_colors/default_colors.php:114
+msgid "Main color (navbar green)"
+msgstr "Основной цвет (зеленый на панели навигации)"
+
+#: plugins/default_colors/default_colors.php:115
+msgid "Background color (light grey)"
+msgstr "Цвет фона (светло-серый)"
+
+#: plugins/default_colors/default_colors.php:116
+msgid "Dark main color (e.g. visited links)"
+msgstr "Темный основной цвет (например, посещенные ссылки)"
+
+#: plugins/demo_plugin/demo_plugin.php:478
+msgid ""
+"A demo plugin covering all use cases for template designers and plugin "
+"developers."
+msgstr ""
+"Демо плагин, охватывающий все варианты использования для дизайнеров шаблонов "
+"и разработчиков плагинов."
+
+#: plugins/demo_plugin/demo_plugin.php:479
+msgid "This is a parameter dedicated to the demo plugin. It'll be suffixed."
+msgstr ""
+"Это параметр предназначен для демонстрационного плагина. Это будет суффикс."
+
+#: plugins/demo_plugin/demo_plugin.php:480
+msgid "Other demo parameter"
+msgstr "Другой демонстрационный параметр"
+
+#: plugins/isso/isso.php:22
+msgid ""
+"Isso plugin error: Please define the \"ISSO_SERVER\" setting in the plugin "
+"administration page."
+msgstr ""
+"Ошибка плагина Isso: определите параметр \"ISSO_SERVER\" на странице "
+"настройки плагина."
+
+#: plugins/isso/isso.php:92
+msgid "Let visitor comment your shaares on permalinks with Isso."
+msgstr ""
+"Позволить посетителю комментировать ваши закладки по постоянным ссылкам с "
+"Isso."
+
+#: plugins/isso/isso.php:93
+msgid "Isso server URL (without 'http://')"
+msgstr "URL сервера Isso (без 'http: //')"
+
+#: plugins/piwik/piwik.php:24
+msgid ""
+"Piwik plugin error: Please define PIWIK_URL and PIWIK_SITEID in the plugin "
+"administration page."
+msgstr ""
+"Ошибка плагина Piwik: укажите PIWIK_URL и PIWIK_SITEID на странице настройки "
+"плагина."
+
+#: plugins/piwik/piwik.php:73
+msgid "A plugin that adds Piwik tracking code to Shaarli pages."
+msgstr "Плагин, который добавляет код отслеживания Piwik на страницы Shaarli."
+
+#: plugins/piwik/piwik.php:74
+msgid "Piwik URL"
+msgstr "Piwik URL"
+
+#: plugins/piwik/piwik.php:75
+msgid "Piwik site ID"
+msgstr "Piwik site ID"
+
+#: plugins/playvideos/playvideos.php:26
+msgid "Video player"
+msgstr "Видео плеер"
+
+#: plugins/playvideos/playvideos.php:29
+msgid "Play Videos"
+msgstr "Воспроизвести видео"
+
+#: plugins/playvideos/playvideos.php:60
+msgid "Add a button in the toolbar allowing to watch all videos."
+msgstr ""
+"Добавьте кнопку на панель инструментов, позволяющую смотреть все видео."
+
+#: plugins/playvideos/youtube_playlist.js:214
+msgid "plugins/playvideos/jquery-1.11.2.min.js"
+msgstr "plugins/playvideos/jquery-1.11.2.min.js"
+
+#: plugins/pubsubhubbub/pubsubhubbub.php:72
+#, php-format
+msgid "Could not publish to PubSubHubbub: %s"
+msgstr "Не удалось опубликовать в PubSubHubbub: %s"
+
+#: plugins/pubsubhubbub/pubsubhubbub.php:99
+#, php-format
+msgid "Could not post to %s"
+msgstr "Не удалось отправить сообщение в %s"
+
+#: plugins/pubsubhubbub/pubsubhubbub.php:103
+#, php-format
+msgid "Bad response from the hub %s"
+msgstr "Плохой ответ от хаба %s"
+
+#: plugins/pubsubhubbub/pubsubhubbub.php:114
+msgid "Enable PubSubHubbub feed publishing."
+msgstr "Включить публикацию канала PubSubHubbub."
+
+#: plugins/qrcode/qrcode.php:74 plugins/wallabag/wallabag.php:72
+msgid "For each link, add a QRCode icon."
+msgstr "Для каждой ссылки добавить значок QR кода."
+
+#: plugins/wallabag/wallabag.php:22
+msgid ""
+"Wallabag plugin error: Please define the \"WALLABAG_URL\" setting in the "
+"plugin administration page."
+msgstr ""
+"Ошибка плагина Wallabag: определите параметр \"WALLABAG_URL\" на странице "
+"настройки плагина."
+
+#: plugins/wallabag/wallabag.php:49
+msgid "Save to wallabag"
+msgstr "Сохранить в wallabag"
+
+#: plugins/wallabag/wallabag.php:73
+msgid "Wallabag API URL"
+msgstr "Wallabag API URL"
+
+#: plugins/wallabag/wallabag.php:74
+msgid "Wallabag API version (1 or 2)"
+msgstr "Wallabag версия API (1 или 2)"
+
+#: tmp/404.b91ef64efc3688266305ea9b42e5017e.rtpl.php:12
+msgid "Sorry, nothing to see here."
+msgstr "Извините, тут ничего нет."
+
+#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
+msgid "URL or leave empty to post a note"
+msgstr "URL или оставьте пустым, чтобы опубликовать заметку"
+
+#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29
+msgid "BULK CREATION"
+msgstr "МАССОВОЕ СОЗДАНИЕ"
+
+#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:40
+msgid "Metadata asynchronous retrieval is disabled."
+msgstr "Асинхронное получение метаданных отключено."
+
+#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42
+msgid ""
+"We recommend that you enable the setting general > "
+"enable_async_metadata in your configuration file to use bulk link "
+"creation."
+msgstr ""
+"Мы рекомендуем включить параметр general > enable_async_metadata в "
+"вашем файле конфигурации, чтобы использовать массовое создание ссылок."
+
+#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:56
+msgid "Shaare multiple new links"
+msgstr "Поделиться несколькими новыми ссылками"
+
+#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:59
+msgid "Add one URL per line to create multiple bookmarks."
+msgstr "Добавьте по одному URL в строке, чтобы создать несколько закладок."
+
+#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:63
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:67
+msgid "Tags"
+msgstr "Теги"
+
+#: 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 "Личный"
+
+#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:78
+msgid "Add links"
+msgstr "Добавить ссылки"
+
+#: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
+msgid "Current password"
+msgstr "Текущий пароль"
+
+#: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19
+msgid "New password"
+msgstr "Новый пароль"
+
+#: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:23
+msgid "Change"
+msgstr "Изменить"
+
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
+#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77
+msgid "Tag"
+msgstr "Тег"
+
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24
+msgid "New name"
+msgstr "Новое имя"
+
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31
+msgid "Case sensitive"
+msgstr "С учетом регистра"
+
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34
+#: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:68
+msgid "Rename tag"
+msgstr "Переименовать тег"
+
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35
+msgid "Delete tag"
+msgstr "Удалить тег"
+
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:40
+msgid "You can also edit tags in the"
+msgstr "Вы также можете редактировать теги в"
+
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:40
+msgid "tag list"
+msgstr "список тегов"
+
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47
+msgid "Change tags separator"
+msgstr "Изменить разделитель тегов"
+
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:50
+msgid "Your current tag separator is"
+msgstr "Текущий разделитель тегов"
+
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:53
+msgid "New separator"
+msgstr "Новый разделитель"
+
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:58
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:355
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:121
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:199
+msgid "Save"
+msgstr "Сохранить"
+
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:61
+msgid "Note that hashtags won't fully work with a non-whitespace separator."
+msgstr ""
+"Обратите внимание, что хэштеги не будут полностью работать с разделителем, "
+"отличным от пробелов."
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29
+msgid "title"
+msgstr "заголовок"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:43
+msgid "Home link"
+msgstr "Домашняя ссылка"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44
+msgid "Default value"
+msgstr "Значение по умолчанию"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:58
+msgid "Theme"
+msgstr "Тема"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:85
+msgid "Description formatter"
+msgstr "Средство форматирования описания"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:114
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77
+msgid "Language"
+msgstr "Язык"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:143
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:101
+msgid "Timezone"
+msgstr "Часовой пояс"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:144
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102
+msgid "Continent"
+msgstr "Континент"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:144
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102
+msgid "City"
+msgstr "Город"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:191
+msgid "Disable session cookie hijacking protection"
+msgstr "Отключить защиту от перехвата файлов сеанса cookie"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:193
+msgid "Check this if you get disconnected or if your IP address changes often"
+msgstr "Проверьте это, если вы отключаетесь или ваш IP адрес часто меняется"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:210
+msgid "Private links by default"
+msgstr "Приватные ссылки по умолчанию"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:211
+msgid "All new links are private by default"
+msgstr "Все новые ссылки по умолчанию являются приватными"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:226
+msgid "RSS direct links"
+msgstr "RSS прямые ссылки"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:227
+msgid "Check this to use direct URL instead of permalink in feeds"
+msgstr ""
+"Установите этот флажок, чтобы использовать прямой URL вместо постоянной "
+"ссылки в фидах"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:242
+msgid "Hide public links"
+msgstr "Скрыть общедоступные ссылки"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:243
+msgid "Do not show any links if the user is not logged in"
+msgstr "Не показывать ссылки, если пользователь не авторизован"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:258
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:149
+msgid "Check updates"
+msgstr "Проверить обновления"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:259
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:151
+msgid "Notify me when a new release is ready"
+msgstr "Оповестить, когда будет готов новый выпуск"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:274
+msgid "Automatically retrieve description for new bookmarks"
+msgstr "Автоматически получать описание для новых закладок"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:275
+msgid "Shaarli will try to retrieve the description from meta HTML headers"
+msgstr "Shaarli попытается получить описание из мета заголовков HTML"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:290
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:168
+msgid "Enable REST API"
+msgstr "Включить REST API"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:291
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169
+msgid "Allow third party software to use Shaarli such as mobile application"
+msgstr ""
+"Разрешить стороннему программному обеспечению использовать Shaarli, например "
+"мобильное приложение"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:306
+msgid "API secret"
+msgstr "API ключ"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:320
+msgid "Enable thumbnails"
+msgstr "Включить миниатюры"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:324
+msgid "You need to enable the extension php-gd
to use thumbnails."
+msgstr ""
+"Вам необходимо включить расширение php-gd
для использования "
+"миниатюр."
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:328
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:122
+msgid "Synchronize thumbnails"
+msgstr "Синхронизировать миниатюры"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:339
+#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102
+msgid "All"
+msgstr "Все"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:343
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:106
+msgid "Only common media hosts"
+msgstr "Только обычные медиа хосты"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:347
+#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:110
+msgid "None"
+msgstr "Ничего"
+
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26
+msgid "1 RSS entry per :type"
+msgid_plural ""
+msgstr[0] "1 RSS запись для каждого :type"
+msgstr[1] "1 RSS запись для каждого :type"
+msgstr[2] "1 RSS запись для каждого :type"
+
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:49
+msgid "Previous :type"
+msgid_plural ""
+msgstr[0] "Предыдущий :type"
+msgstr[1] "Предыдущих :type"
+msgstr[2] "Предыдущих :type"
+
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:56
+#: tmp/dailyrss.b91ef64efc3688266305ea9b42e5017e.rtpl.php:7
+msgid "All links of one :type in a single page."
+msgid_plural ""
+msgstr[0] "Все ссылки одного :type на одной странице."
+msgstr[1] "Все ссылки одного :type на одной странице."
+msgstr[2] "Все ссылки одного :type на одной странице."
+
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:63
+msgid "Next :type"
+msgid_plural ""
+msgstr[0] "Следующий :type"
+msgstr[1] "Следующие :type"
+msgstr[2] "Следующие :type"
+
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30
+msgid "Edit Shaare"
+msgstr "Изменить закладку"
+
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30
+msgid "New Shaare"
+msgstr "Новая закладка"
+
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:38
+msgid "Created:"
+msgstr "Создано:"
+
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
+msgid "URL"
+msgstr "URL"
+
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47
+msgid "Title"
+msgstr "Заголовок"
+
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:58
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:75
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:99
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:124
+msgid "Description"
+msgstr "Описание"
+
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:89
+msgid "Description will be rendered with"
+msgstr "Описание будет отображаться с"
+
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:91
+msgid "Markdown syntax documentation"
+msgstr "Документация по синтаксису Markdown"
+
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:92
+msgid "Markdown syntax"
+msgstr "Синтаксис Markdown"
+
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:115
+msgid "Cancel"
+msgstr "Отменить"
+
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:121
+msgid "Apply Changes"
+msgstr "Применить изменения"
+
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:126
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:173
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:147
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:147
+#: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:67
+msgid "Delete"
+msgstr "Удалить"
+
+#: tmp/editlink.batch.b91ef64efc3688266305ea9b42e5017e.rtpl.php:21
+#: tmp/editlink.batch.b91ef64efc3688266305ea9b42e5017e.rtpl.php:32
+msgid "Save all"
+msgstr "Сохранить все"
+
+#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
+msgid "Export Database"
+msgstr "Экспорт базы данных"
+
+#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:23
+msgid "Selection"
+msgstr "Выбор"
+
+#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:40
+msgid "Public"
+msgstr "Общедоступно"
+
+#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:51
+msgid "Prepend note permalinks with this Shaarli instance's URL"
+msgstr ""
+"Добавить постоянные ссылки на заметку с URL адресом этого экземпляра Shaarli"
+
+#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:52
+msgid "Useful to import bookmarks in a web browser"
+msgstr "Useful to import bookmarks in a web browser"
+
+#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
+msgid "Import Database"
+msgstr "Импорт базы данных"
+
+#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:23
+msgid "Maximum size allowed:"
+msgstr "Максимально допустимый размер:"
+
+#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29
+msgid "Visibility"
+msgstr "Видимость"
+
+#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
+msgid "Use values from the imported file, default to public"
+msgstr ""
+"Использовать значения из импортированного файла, по умолчанию общедоступные"
+
+#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
+msgid "Import all bookmarks as private"
+msgstr "Импортировать все закладки как личные"
+
+#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46
+msgid "Import all bookmarks as public"
+msgstr "Импортировать все закладки как общедоступные"
+
+#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:57
+msgid "Overwrite existing bookmarks"
+msgstr "Заменить существующие закладки"
+
+#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:58
+msgid "Duplicates based on URL"
+msgstr "Дубликаты на основе URL"
+
+#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72
+msgid "Add default tags"
+msgstr "Добавить теги по умолчанию"
+
+#: tmp/install.b91ef64efc3