]> 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 077e5312b75b620fb2e2ace73cf353edb7cea99a..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;
@@ -42,7 +43,7 @@ class BookmarkMarkdownFormatter extends BookmarkDefaultFormatter
     {
         parent::__construct($conf, $isLoggedIn);
 
-        $this->parsedown = new \Parsedown();
+        $this->parsedown = new ShaarliParsedown();
         $this->escape = $conf->get('security.markdown_escape', true);
         $this->allowedProtocols = $conf->get('security.allowed_protocols', []);
     }
@@ -56,7 +57,10 @@ 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);
@@ -65,9 +69,10 @@ class BookmarkMarkdownFormatter extends BookmarkDefaultFormatter
             ->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;
@@ -106,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
         );
@@ -114,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
@@ -124,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
@@ -132,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 = '';
@@ -152,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;
@@ -174,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]);
                 },