]> git.immae.eu Git - github/shaarli/Shaarli.git/blobdiff - application/formatter/BookmarkMarkdownFormatter.php
Support search highlights when matching URL content
[github/shaarli/Shaarli.git] / application / formatter / BookmarkMarkdownFormatter.php
index f60c61f46dcdc811815f68406b98929e43d03483..d4dccee6231bbcaded94d215ca343b8be9ba6d0f 100644 (file)
@@ -3,6 +3,7 @@
 namespace Shaarli\Formatter;
 
 use Shaarli\Config\ConfigManager;
+use Shaarli\Formatter\Parsedown\ShaarliParsedown;
 
 /**
  * Class BookmarkMarkdownFormatter
@@ -16,7 +17,7 @@ class BookmarkMarkdownFormatter extends BookmarkDefaultFormatter
     /**
      * When this tag is present in a bookmark, its description should not be processed with Markdown
      */
-    const NO_MD_TAG = 'nomarkdown';
+    public const NO_MD_TAG = 'nomarkdown';
 
     /** @var \Parsedown instance */
     protected $parsedown;
@@ -36,11 +37,13 @@ class BookmarkMarkdownFormatter extends BookmarkDefaultFormatter
      * LinkMarkdownFormatter constructor.
      *
      * @param ConfigManager $conf instance
+     * @param bool          $isLoggedIn
      */
-    public function __construct(ConfigManager $conf)
+    public function __construct(ConfigManager $conf, bool $isLoggedIn)
     {
-        parent::__construct($conf);
-        $this->parsedown = new \Parsedown();
+        parent::__construct($conf, $isLoggedIn);
+
+        $this->parsedown = new ShaarliParsedown();
         $this->escape = $conf->get('security.markdown_escape', true);
         $this->allowedProtocols = $conf->get('security.allowed_protocols', []);
     }
@@ -54,17 +57,22 @@ class BookmarkMarkdownFormatter extends BookmarkDefaultFormatter
             return parent::formatDescription($bookmark);
         }
 
-        $processedDescription = $bookmark->getDescription();
+        $processedDescription = $this->tokenizeSearchHighlightField(
+            $bookmark->getDescription() ?? '',
+            $bookmark->getAdditionalContentEntry('search_highlight')['description'] ?? []
+        );
         $processedDescription = $this->filterProtocols($processedDescription);
         $processedDescription = $this->formatHashTags($processedDescription);
+        $processedDescription = $this->reverseEscapedHtml($processedDescription);
         $processedDescription = $this->parsedown
             ->setMarkupEscaped($this->escape)
             ->setBreaksEnabled(true)
             ->text($processedDescription);
         $processedDescription = $this->sanitizeHtml($processedDescription);
+        $processedDescription = $this->replaceTokens($processedDescription);
 
         if (!empty($processedDescription)) {
-            $processedDescription = '<div class="markdown">'. $processedDescription . '</div>';
+            $processedDescription = '<div class="markdown">' . $processedDescription . '</div>';
         }
 
         return $processedDescription;
@@ -78,7 +86,7 @@ class BookmarkMarkdownFormatter extends BookmarkDefaultFormatter
     protected function formatTagList($bookmark)
     {
         $out = parent::formatTagList($bookmark);
-        if (($pos = array_search(self::NO_MD_TAG, $out)) !== false) {
+        if ($this->isLoggedIn === false && ($pos = array_search(self::NO_MD_TAG, $out)) !== false) {
             unset($out[$pos]);
             return array_values($out);
         }
@@ -103,7 +111,7 @@ class BookmarkMarkdownFormatter extends BookmarkDefaultFormatter
             function ($match) use ($allowedProtocols, $indexUrl) {
                 $link = startsWith($match[1], '?') || startsWith($match[1], '/') ? $indexUrl : '';
                 $link .= whitelist_protocols($match[1], $allowedProtocols);
-                return ']('. $link.')';
+                return '](' . $link . ')';
             },
             $description
         );
@@ -111,7 +119,7 @@ class BookmarkMarkdownFormatter extends BookmarkDefaultFormatter
 
     /**
      * Replace hashtag in Markdown links format
-     * E.g. `#hashtag` becomes `[#hashtag](?addtag=hashtag)`
+     * E.g. `#hashtag` becomes `[#hashtag](./add-tag/hashtag)`
      * It includes the index URL if specified.
      *
      * @param string $description
@@ -121,6 +129,9 @@ class BookmarkMarkdownFormatter extends BookmarkDefaultFormatter
     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
@@ -129,8 +140,15 @@ class BookmarkMarkdownFormatter extends BookmarkDefaultFormatter
          * \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}]+)/mui';
-        $replacement = '$1[#$2]('. $indexUrl .'?addtag=$2)';
+        $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 = '';
@@ -149,7 +167,7 @@ class BookmarkMarkdownFormatter extends BookmarkDefaultFormatter
             }
 
             if (!$codeBlockOn && !$codeLineOn) {
-                $descriptionLine = preg_replace($regex, $replacement, $descriptionLine);
+                $descriptionLine = preg_replace_callback($regex, $replacement, $descriptionLine);
             }
 
             $descriptionOut .= $descriptionLine;
@@ -171,17 +189,17 @@ class BookmarkMarkdownFormatter extends BookmarkDefaultFormatter
      */
     protected function sanitizeHtml($description)
     {
-        $escapeTags = array(
+        $escapeTags = [
             'script',
             'style',
             'link',
             'iframe',
             'frameset',
             'frame',
-        );
+        ];
         foreach ($escapeTags as $tag) {
             $description = preg_replace_callback(
-                '#<\s*'. $tag .'[^>]*>(.*</\s*'. $tag .'[^>]*>)?#is',
+                '#<\s*' . $tag . '[^>]*>(.*</\s*' . $tag . '[^>]*>)?#is',
                 function ($match) {
                     return escape($match[0]);
                 },
@@ -195,4 +213,9 @@ class BookmarkMarkdownFormatter extends BookmarkDefaultFormatter
         );
         return $description;
     }
+
+    protected function reverseEscapedHtml($description)
+    {
+        return unescape($description);
+    }
 }