aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--application/formatter/BookmarkMarkdownExtraFormatter.php24
-rw-r--r--application/front/controller/admin/ConfigureController.php2
-rw-r--r--composer.json10
-rw-r--r--composer.lock67
-rw-r--r--tests/formatter/BookmarkMarkdownExtraFormatterTest.php162
-rw-r--r--tests/front/controller/admin/ConfigureControllerTest.php2
-rw-r--r--tpl/default/includes.html2
7 files changed, 256 insertions, 13 deletions
diff --git a/application/formatter/BookmarkMarkdownExtraFormatter.php b/application/formatter/BookmarkMarkdownExtraFormatter.php
new file mode 100644
index 00000000..0694b23f
--- /dev/null
+++ b/application/formatter/BookmarkMarkdownExtraFormatter.php
@@ -0,0 +1,24 @@
1<?php
2
3namespace Shaarli\Formatter;
4
5use Shaarli\Config\ConfigManager;
6
7/**
8 * Class BookmarkMarkdownExtraFormatter
9 *
10 * Format bookmark description into MarkdownExtra format.
11 *
12 * @see https://michelf.ca/projects/php-markdown/extra/
13 *
14 * @package Shaarli\Formatter
15 */
16class BookmarkMarkdownExtraFormatter extends BookmarkMarkdownFormatter
17{
18 public function __construct(ConfigManager $conf, bool $isLoggedIn)
19 {
20 parent::__construct($conf, $isLoggedIn);
21
22 $this->parsedown = new \ParsedownExtra();
23 }
24}
diff --git a/application/front/controller/admin/ConfigureController.php b/application/front/controller/admin/ConfigureController.php
index e675fcca..0ed7ad81 100644
--- a/application/front/controller/admin/ConfigureController.php
+++ b/application/front/controller/admin/ConfigureController.php
@@ -30,7 +30,7 @@ class ConfigureController extends ShaarliAdminController
30 'theme_available', 30 'theme_available',
31 ThemeUtils::getThemes($this->container->conf->get('resource.raintpl_tpl')) 31 ThemeUtils::getThemes($this->container->conf->get('resource.raintpl_tpl'))
32 ); 32 );
33 $this->assignView('formatter_available', ['default', 'markdown']); 33 $this->assignView('formatter_available', ['default', 'markdown', 'markdownExtra']);
34 list($continents, $cities) = generateTimeZoneData( 34 list($continents, $cities) = generateTimeZoneData(
35 timezone_identifiers_list(), 35 timezone_identifiers_list(),
36 $this->container->conf->get('general.timezone') 36 $this->container->conf->get('general.timezone')
diff --git a/composer.json b/composer.json
index cd9fcf5b..7e675623 100644
--- a/composer.json
+++ b/composer.json
@@ -10,6 +10,7 @@
10 }, 10 },
11 "keywords": ["bookmark", "link", "share", "web"], 11 "keywords": ["bookmark", "link", "share", "web"],
12 "config": { 12 "config": {
13 "sort-packages": true,
13 "platform": { 14 "platform": {
14 "php": "7.1.29" 15 "php": "7.1.29"
15 } 16 }
@@ -18,12 +19,13 @@
18 "php": ">=7.1", 19 "php": ">=7.1",
19 "ext-json": "*", 20 "ext-json": "*",
20 "ext-zlib": "*", 21 "ext-zlib": "*",
21 "shaarli/netscape-bookmark-parser": "^2.1",
22 "erusev/parsedown": "^1.6",
23 "slim/slim": "^3.0",
24 "arthurhoaro/web-thumbnailer": "^2.0", 22 "arthurhoaro/web-thumbnailer": "^2.0",
23 "erusev/parsedown": "^1.6",
24 "erusev/parsedown-extra": "^0.8.1",
25 "gettext/gettext": "^4.4",
25 "pubsubhubbub/publisher": "dev-master", 26 "pubsubhubbub/publisher": "dev-master",
26 "gettext/gettext": "^4.4" 27 "shaarli/netscape-bookmark-parser": "^2.1",
28 "slim/slim": "^3.0"
27 }, 29 },
28 "require-dev": { 30 "require-dev": {
29 "roave/security-advisories": "dev-master", 31 "roave/security-advisories": "dev-master",
diff --git a/composer.lock b/composer.lock
index 2c8b0ea7..e02491ff 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
4 "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 4 "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5 "This file is @generated automatically" 5 "This file is @generated automatically"
6 ], 6 ],
7 "content-hash": "98520a05a7185503ee13d05ffaa535f6", 7 "content-hash": "f84918821b0dceb0cd569875c0418bb8",
8 "packages": [ 8 "packages": [
9 { 9 {
10 "name": "arthurhoaro/web-thumbnailer", 10 "name": "arthurhoaro/web-thumbnailer",
@@ -108,6 +108,57 @@
108 "time": "2019-12-30T22:54:17+00:00" 108 "time": "2019-12-30T22:54:17+00:00"
109 }, 109 },
110 { 110 {
111 "name": "erusev/parsedown-extra",
112 "version": "0.8.1",
113 "source": {
114 "type": "git",
115 "url": "https://github.com/erusev/parsedown-extra.git",
116 "reference": "91ac3ff98f0cea243bdccc688df43810f044dcef"
117 },
118 "dist": {
119 "type": "zip",
120 "url": "https://api.github.com/repos/erusev/parsedown-extra/zipball/91ac3ff98f0cea243bdccc688df43810f044dcef",
121 "reference": "91ac3ff98f0cea243bdccc688df43810f044dcef",
122 "shasum": ""
123 },
124 "require": {
125 "erusev/parsedown": "^1.7.4"
126 },
127 "require-dev": {
128 "phpunit/phpunit": "^4.8.35"
129 },
130 "type": "library",
131 "autoload": {
132 "psr-0": {
133 "ParsedownExtra": ""
134 }
135 },
136 "notification-url": "https://packagist.org/downloads/",
137 "license": [
138 "MIT"
139 ],
140 "authors": [
141 {
142 "name": "Emanuil Rusev",
143 "email": "hello@erusev.com",
144 "homepage": "http://erusev.com"
145 }
146 ],
147 "description": "An extension of Parsedown that adds support for Markdown Extra.",
148 "homepage": "https://github.com/erusev/parsedown-extra",
149 "keywords": [
150 "markdown",
151 "markdown extra",
152 "parsedown",
153 "parser"
154 ],
155 "support": {
156 "issues": "https://github.com/erusev/parsedown-extra/issues",
157 "source": "https://github.com/erusev/parsedown-extra/tree/0.8.x"
158 },
159 "time": "2019-12-30T23:20:37+00:00"
160 },
161 {
111 "name": "gettext/gettext", 162 "name": "gettext/gettext",
112 "version": "v4.8.2", 163 "version": "v4.8.2",
113 "source": { 164 "source": {
@@ -1577,12 +1628,12 @@
1577 "source": { 1628 "source": {
1578 "type": "git", 1629 "type": "git",
1579 "url": "https://github.com/Roave/SecurityAdvisories.git", 1630 "url": "https://github.com/Roave/SecurityAdvisories.git",
1580 "reference": "0749ceaf15c136d085b722a5bb88141398a54142" 1631 "reference": "ba5d234b3a1559321b816b64aafc2ce6728799ff"
1581 }, 1632 },
1582 "dist": { 1633 "dist": {
1583 "type": "zip", 1634 "type": "zip",
1584 "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/0749ceaf15c136d085b722a5bb88141398a54142", 1635 "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/ba5d234b3a1559321b816b64aafc2ce6728799ff",
1585 "reference": "0749ceaf15c136d085b722a5bb88141398a54142", 1636 "reference": "ba5d234b3a1559321b816b64aafc2ce6728799ff",
1586 "shasum": "" 1637 "shasum": ""
1587 }, 1638 },
1588 "conflict": { 1639 "conflict": {
@@ -1642,7 +1693,7 @@
1642 "ezsystems/ezplatform-kernel": ">=1,<1.0.2.1", 1693 "ezsystems/ezplatform-kernel": ">=1,<1.0.2.1",
1643 "ezsystems/ezplatform-user": ">=1,<1.0.1", 1694 "ezsystems/ezplatform-user": ">=1,<1.0.1",
1644 "ezsystems/ezpublish-kernel": ">=5.3,<5.3.12.1|>=5.4,<5.4.14.2|>=6,<6.7.9.1|>=6.8,<6.13.6.3|>=7,<7.2.4.1|>=7.3,<7.3.2.1|>=7.5,<7.5.7.1", 1695 "ezsystems/ezpublish-kernel": ">=5.3,<5.3.12.1|>=5.4,<5.4.14.2|>=6,<6.7.9.1|>=6.8,<6.13.6.3|>=7,<7.2.4.1|>=7.3,<7.3.2.1|>=7.5,<7.5.7.1",
1645 "ezsystems/ezpublish-legacy": ">=5.3,<5.3.12.6|>=5.4,<5.4.14.1|>=2011,<2017.12.7.2|>=2018.6,<2018.6.1.4|>=2018.9,<2018.9.1.3|>=2019.3,<2019.3.4.2", 1696 "ezsystems/ezpublish-legacy": ">=5.3,<5.3.12.6|>=5.4,<5.4.14.2|>=2011,<2017.12.7.3|>=2018.6,<2018.6.1.4|>=2018.9,<2018.9.1.3|>=2019.3,<2019.3.5.1",
1646 "ezsystems/platform-ui-assets-bundle": ">=4.2,<4.2.3", 1697 "ezsystems/platform-ui-assets-bundle": ">=4.2,<4.2.3",
1647 "ezsystems/repository-forms": ">=2.3,<2.3.2.1", 1698 "ezsystems/repository-forms": ">=2.3,<2.3.2.1",
1648 "ezyang/htmlpurifier": "<4.1.1", 1699 "ezyang/htmlpurifier": "<4.1.1",
@@ -1685,6 +1736,8 @@
1685 "mittwald/typo3_forum": "<1.2.1", 1736 "mittwald/typo3_forum": "<1.2.1",
1686 "monolog/monolog": ">=1.8,<1.12", 1737 "monolog/monolog": ">=1.8,<1.12",
1687 "namshi/jose": "<2.2", 1738 "namshi/jose": "<2.2",
1739 "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",
1740 "nette/nette": ">=2,<2.0.19|>=2.1,<2.1.13",
1688 "nystudio107/craft-seomatic": "<3.3", 1741 "nystudio107/craft-seomatic": "<3.3",
1689 "nzo/url-encryptor-bundle": ">=4,<4.3.2|>=5,<5.0.1", 1742 "nzo/url-encryptor-bundle": ">=4,<4.3.2|>=5,<5.0.1",
1690 "october/backend": ">=1.0.319,<1.0.467", 1743 "october/backend": ">=1.0.319,<1.0.467",
@@ -1720,6 +1773,7 @@
1720 "privatebin/privatebin": "<1.2.2|>=1.3,<1.3.2", 1773 "privatebin/privatebin": "<1.2.2|>=1.3,<1.3.2",
1721 "propel/propel": ">=2-alpha.1,<=2-alpha.7", 1774 "propel/propel": ">=2-alpha.1,<=2-alpha.7",
1722 "propel/propel1": ">=1,<=1.7.1", 1775 "propel/propel1": ">=1,<=1.7.1",
1776 "pterodactyl/panel": "<0.7.19|>=1-rc.0,<=1-rc.6",
1723 "pusher/pusher-php-server": "<2.2.1", 1777 "pusher/pusher-php-server": "<2.2.1",
1724 "rainlab/debugbar-plugin": "<3.1", 1778 "rainlab/debugbar-plugin": "<3.1",
1725 "robrichards/xmlseclibs": "<3.0.4", 1779 "robrichards/xmlseclibs": "<3.0.4",
@@ -1805,6 +1859,7 @@
1805 "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", 1859 "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",
1806 "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4", 1860 "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4",
1807 "typo3/phar-stream-wrapper": ">=1,<2.1.1|>=3,<3.1.1", 1861 "typo3/phar-stream-wrapper": ">=1,<2.1.1|>=3,<3.1.1",
1862 "typo3fluid/fluid": ">=2,<2.0.5|>=2.1,<2.1.4|>=2.2,<2.2.1|>=2.3,<2.3.5|>=2.4,<2.4.1|>=2.5,<2.5.5|>=2.6,<2.6.1",
1808 "ua-parser/uap-php": "<3.8", 1863 "ua-parser/uap-php": "<3.8",
1809 "usmanhalalit/pixie": "<1.0.3|>=2,<2.0.2", 1864 "usmanhalalit/pixie": "<1.0.3|>=2,<2.0.2",
1810 "verot/class.upload.php": "<=1.0.3|>=2,<=2.0.4", 1865 "verot/class.upload.php": "<=1.0.3|>=2,<=2.0.4",
@@ -1878,7 +1933,7 @@
1878 "type": "tidelift" 1933 "type": "tidelift"
1879 } 1934 }
1880 ], 1935 ],
1881 "time": "2020-09-24T17:02:11+00:00" 1936 "time": "2020-10-08T21:02:27+00:00"
1882 }, 1937 },
1883 { 1938 {
1884 "name": "sebastian/code-unit-reverse-lookup", 1939 "name": "sebastian/code-unit-reverse-lookup",
diff --git a/tests/formatter/BookmarkMarkdownExtraFormatterTest.php b/tests/formatter/BookmarkMarkdownExtraFormatterTest.php
new file mode 100644
index 00000000..d4941ef3
--- /dev/null
+++ b/tests/formatter/BookmarkMarkdownExtraFormatterTest.php
@@ -0,0 +1,162 @@
1<?php
2
3namespace Shaarli\Formatter;
4
5use DateTime;
6use PHPUnit\Framework\TestCase;
7use Shaarli\Bookmark\Bookmark;
8use Shaarli\Config\ConfigManager;
9
10/**
11 * Class BookmarkMarkdownExtraFormatterTest
12 * @package Shaarli\Formatter
13 */
14class BookmarkMarkdownExtraFormatterTest extends TestCase
15{
16 /** @var string Path of test config file */
17 protected static $testConf = 'sandbox/config';
18
19 /** @var BookmarkFormatter */
20 protected $formatter;
21
22 /** @var ConfigManager instance */
23 protected $conf;
24
25 /**
26 * Initialize formatter instance.
27 */
28 public function setUp(): void
29 {
30 copy('tests/utils/config/configJson.json.php', self::$testConf .'.json.php');
31 $this->conf = new ConfigManager(self::$testConf);
32 $this->formatter = new BookmarkMarkdownExtraFormatter($this->conf, true);
33 }
34
35 /**
36 * Test formatting a bookmark with all its attribute filled.
37 */
38 public function testFormatExtra(): void
39 {
40 $bookmark = new Bookmark();
41 $bookmark->setId($id = 11);
42 $bookmark->setShortUrl($short = 'abcdef');
43 $bookmark->setUrl('https://sub.domain.tld?query=here&for=real#hash');
44 $bookmark->setTitle($title = 'This is a <strong>bookmark</strong>');
45 $bookmark->setDescription('<h2>Content</h2><p>`Here is some content</p>');
46 $bookmark->setTags($tags = ['tag1', 'bookmark', 'other', '<script>alert("xss");</script>']);
47 $bookmark->setThumbnail('http://domain2.tdl2/?type=img&name=file.png');
48 $bookmark->setSticky(true);
49 $bookmark->setCreated($created = DateTime::createFromFormat('Ymd_His', '20190521_190412'));
50 $bookmark->setUpdated($updated = DateTime::createFromFormat('Ymd_His', '20190521_191213'));
51 $bookmark->setPrivate(true);
52
53 $link = $this->formatter->format($bookmark);
54 $this->assertEquals($id, $link['id']);
55 $this->assertEquals($short, $link['shorturl']);
56 $this->assertEquals('https://sub.domain.tld?query=here&amp;for=real#hash', $link['url']);
57 $this->assertEquals(
58 'https://sub.domain.tld?query=here&amp;for=real#hash',
59 $link['real_url']
60 );
61 $this->assertEquals('This is a &lt;strong&gt;bookmark&lt;/strong&gt;', $link['title']);
62 $this->assertEquals(
63 '<div class="markdown"><p>'.
64 '&lt;h2&gt;Content&lt;/h2&gt;&lt;p&gt;`Here is some content&lt;/p&gt;'.
65 '</p></div>',
66 $link['description']
67 );
68 $tags[3] = '&lt;script&gt;alert(&quot;xss&quot;);&lt;/script&gt;';
69 $this->assertEquals($tags, $link['taglist']);
70 $this->assertEquals(implode(' ', $tags), $link['tags']);
71 $this->assertEquals(
72 'http://domain2.tdl2/?type=img&amp;name=file.png',
73 $link['thumbnail']
74 );
75 $this->assertEquals($created, $link['created']);
76 $this->assertEquals($created->getTimestamp(), $link['timestamp']);
77 $this->assertEquals($updated, $link['updated']);
78 $this->assertEquals($updated->getTimestamp(), $link['updated_timestamp']);
79 $this->assertTrue($link['private']);
80 $this->assertTrue($link['sticky']);
81 $this->assertEquals('private', $link['class']);
82 }
83
84 /**
85 * Test formatting a bookmark with all its attribute filled.
86 */
87 public function testFormatExtraMinimal(): void
88 {
89 $bookmark = new Bookmark();
90
91 $link = $this->formatter->format($bookmark);
92 $this->assertEmpty($link['id']);
93 $this->assertEmpty($link['shorturl']);
94 $this->assertEmpty($link['url']);
95 $this->assertEmpty($link['real_url']);
96 $this->assertEmpty($link['title']);
97 $this->assertEmpty($link['description']);
98 $this->assertEmpty($link['taglist']);
99 $this->assertEmpty($link['tags']);
100 $this->assertEmpty($link['thumbnail']);
101 $this->assertEmpty($link['created']);
102 $this->assertEmpty($link['timestamp']);
103 $this->assertEmpty($link['updated']);
104 $this->assertEmpty($link['updated_timestamp']);
105 $this->assertFalse($link['private']);
106 $this->assertFalse($link['sticky']);
107 $this->assertEmpty($link['class']);
108 }
109
110 /**
111 * Make sure that the description is properly formatted by the default formatter.
112 */
113 public function testFormatExtrraDescription(): void
114 {
115 $description = 'This a <strong>description</strong>'. PHP_EOL;
116 $description .= 'text https://sub.domain.tld?query=here&for=real#hash more text'. PHP_EOL;
117 $description .= 'Also, there is an #hashtag added'. PHP_EOL;
118 $description .= ' A N D KEEP SPACES ! '. PHP_EOL;
119 $description .= '# Header {.class}'. PHP_EOL;
120
121 $bookmark = new Bookmark();
122 $bookmark->setDescription($description);
123 $link = $this->formatter->format($bookmark);
124
125 $description = '<div class="markdown"><p>';
126 $description .= 'This a &lt;strong&gt;description&lt;/strong&gt;<br />'. PHP_EOL;
127 $url = 'https://sub.domain.tld?query=here&amp;for=real#hash';
128 $description .= 'text <a href="'. $url .'">'. $url .'</a> more text<br />'. PHP_EOL;
129 $description .= 'Also, there is an <a href="./add-tag/hashtag">#hashtag</a> added<br />'. PHP_EOL;
130 $description .= 'A N D KEEP SPACES ! </p>' . PHP_EOL;
131 $description .= '<h1 class="class">Header</h1>';
132 $description .= '</div>';
133
134 $this->assertEquals($description, $link['description']);
135 }
136
137 /**
138 * Test formatting URL with an index_url set
139 * It should prepend relative links.
140 */
141 public function testFormatExtraNoteWithIndexUrl(): void
142 {
143 $bookmark = new Bookmark();
144 $bookmark->setUrl($short = '?abcdef');
145 $description = 'Text #hashtag more text';
146 $bookmark->setDescription($description);
147
148 $this->formatter->addContextData('index_url', $root = 'https://domain.tld/hithere/');
149
150 $description = '<div class="markdown"><p>';
151 $description .= 'Text <a href="'. $root .'./add-tag/hashtag">#hashtag</a> more text';
152 $description .= '</p></div>';
153
154 $link = $this->formatter->format($bookmark);
155 $this->assertEquals($root . $short, $link['url']);
156 $this->assertEquals($root . $short, $link['real_url']);
157 $this->assertEquals(
158 $description,
159 $link['description']
160 );
161 }
162}
diff --git a/tests/front/controller/admin/ConfigureControllerTest.php b/tests/front/controller/admin/ConfigureControllerTest.php
index aca6cff3..d82db0a7 100644
--- a/tests/front/controller/admin/ConfigureControllerTest.php
+++ b/tests/front/controller/admin/ConfigureControllerTest.php
@@ -51,7 +51,7 @@ class ConfigureControllerTest extends TestCase
51 static::assertSame('general.title', $assignedVariables['title']); 51 static::assertSame('general.title', $assignedVariables['title']);
52 static::assertSame('resource.theme', $assignedVariables['theme']); 52 static::assertSame('resource.theme', $assignedVariables['theme']);
53 static::assertEmpty($assignedVariables['theme_available']); 53 static::assertEmpty($assignedVariables['theme_available']);
54 static::assertSame(['default', 'markdown'], $assignedVariables['formatter_available']); 54 static::assertSame(['default', 'markdown', 'markdownExtra'], $assignedVariables['formatter_available']);
55 static::assertNotEmpty($assignedVariables['continents']); 55 static::assertNotEmpty($assignedVariables['continents']);
56 static::assertNotEmpty($assignedVariables['cities']); 56 static::assertNotEmpty($assignedVariables['cities']);
57 static::assertSame('general.retrieve_description', $assignedVariables['retrieve_description']); 57 static::assertSame('general.retrieve_description', $assignedVariables['retrieve_description']);
diff --git a/tpl/default/includes.html b/tpl/default/includes.html
index 227f9b52..09768ac4 100644
--- a/tpl/default/includes.html
+++ b/tpl/default/includes.html
@@ -8,7 +8,7 @@
8<link href="{$asset_path}/img/favicon.png#" rel="shortcut icon" type="image/png" /> 8<link href="{$asset_path}/img/favicon.png#" rel="shortcut icon" type="image/png" />
9<link href="{$asset_path}/img/apple-touch-icon.png#" rel="apple-touch-icon" sizes="180x180" /> 9<link href="{$asset_path}/img/apple-touch-icon.png#" rel="apple-touch-icon" sizes="180x180" />
10<link type="text/css" rel="stylesheet" href="{$asset_path}/css/shaarli.min.css?v={$version_hash}#" /> 10<link type="text/css" rel="stylesheet" href="{$asset_path}/css/shaarli.min.css?v={$version_hash}#" />
11{if="$formatter==='markdown'"} 11{if="strpos($formatter, 'markdown') !== false"}
12 <link type="text/css" rel="stylesheet" href="{$asset_path}/css/markdown.min.css?v={$version_hash}#" /> 12 <link type="text/css" rel="stylesheet" href="{$asset_path}/css/markdown.min.css?v={$version_hash}#" />
13{/if} 13{/if}
14{loop="$plugins_includes.css_files"} 14{loop="$plugins_includes.css_files"}