From e8ed43056c1665522a586e3485da67f2bdf073aa Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Mon, 2 Oct 2023 15:40:50 +0200 Subject: [PATCH] enable markdown syntax in custom_notes field Signed-off-by: Michael Kaufmann --- composer.json | 4 +- composer.lock | 455 +++++++++++++++++++++++-- lib/Froxlor/System/Markdown.php | 58 ++++ lib/Froxlor/UI/Callbacks/Customer.php | 8 +- lib/Froxlor/UI/Callbacks/Text.php | 3 +- lib/Froxlor/UI/Panel/FroxlorTwig.php | 10 +- lng/de.lng.php | 2 +- lng/en.lng.php | 2 +- templates/Froxlor/user/index.html.twig | 8 +- 9 files changed, 511 insertions(+), 39 deletions(-) create mode 100644 lib/Froxlor/System/Markdown.php diff --git a/composer.json b/composer.json index 5ec331e0..f3d884fb 100644 --- a/composer.json +++ b/composer.json @@ -53,10 +53,10 @@ "froxlor/idna-convert-legacy": "^2.1", "voku/anti-xss": "^4.1", "twig/twig": "^3.3", - "erusev/parsedown": "^1.7", "symfony/console": "^5.4", "pear/net_dns2": "^1.5", - "amnuts/opcache-gui": "^3.4" + "amnuts/opcache-gui": "^3.4", + "league/commonmark": "^2.4" }, "require-dev": { "phpunit/phpunit": "^9", diff --git a/composer.lock b/composer.lock index a24fe7bd..c63fbb7a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1f9acc318c920b38ee8141e74298655a", + "content-hash": "cf7a156f58ab3f86204a4b4d263621e2", "packages": [ { "name": "amnuts/opcache-gui", @@ -69,30 +69,38 @@ "time": "2023-08-25T18:02:59+00:00" }, { - "name": "erusev/parsedown", - "version": "1.7.4", + "name": "dflydev/dot-access-data", + "version": "v3.0.2", "source": { "type": "git", - "url": "https://github.com/erusev/parsedown.git", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "f41715465d65213d644d3141a6a93081be5d3549" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/f41715465d65213d644d3141a6a93081be5d3549", + "reference": "f41715465d65213d644d3141a6a93081be5d3549", "shasum": "" }, "require": { - "ext-mbstring": "*", - "php": ">=5.3.0" + "php": "^7.1 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35" + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.0.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, "autoload": { - "psr-0": { - "Parsedown": "" + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -101,22 +109,39 @@ ], "authors": [ { - "name": "Emanuil Rusev", - "email": "hello@erusev.com", - "homepage": "http://erusev.com" + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" } ], - "description": "Parser for Markdown.", - "homepage": "http://parsedown.org", + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", "keywords": [ - "markdown", - "parser" + "access", + "data", + "dot", + "notation" ], "support": { - "issues": "https://github.com/erusev/parsedown/issues", - "source": "https://github.com/erusev/parsedown/tree/1.7.x" + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.2" }, - "time": "2019-12-30T22:54:17+00:00" + "time": "2022-10-27T11:44:00+00:00" }, { "name": "froxlor/idna-convert-legacy", @@ -174,6 +199,194 @@ }, "time": "2019-12-31T12:16:30+00:00" }, + { + "name": "league/commonmark", + "version": "2.4.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "3669d6d5f7a47a93c08ddff335e6d945481a1dd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/3669d6d5f7a47a93c08ddff335e6d945481a1dd5", + "reference": "3669d6d5f7a47a93c08ddff335e6d945481a1dd5", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "cebe/markdown": "^1.0", + "commonmark/cmark": "0.30.0", + "commonmark/commonmark.js": "0.30.0", + "composer/package-versions-deprecated": "^1.8", + "embed/embed": "^4.4", + "erusev/parsedown": "^1.0", + "ext-json": "*", + "github/gfm": "0.29.0", + "michelf/php-markdown": "^1.4 || ^2.0", + "nyholm/psr7": "^1.5", + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.21", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3 | ^6.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", + "unleashedtech/php-coding-standard": "^3.1.1", + "vimeo/psalm": "^4.24.0 || ^5.0.0" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + } + }, + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], + "support": { + "docs": "https://commonmark.thephpleague.com/", + "forum": "https://github.com/thephpleague/commonmark/discussions", + "issues": "https://github.com/thephpleague/commonmark/issues", + "rss": "https://github.com/thephpleague/commonmark/releases.atom", + "source": "https://github.com/thephpleague/commonmark" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "time": "2023-08-30T16:55:00+00:00" + }, + { + "name": "league/config", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/config.git", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "support": { + "docs": "https://config.thephpleague.com/", + "issues": "https://github.com/thephpleague/config/issues", + "rss": "https://github.com/thephpleague/config/releases.atom", + "source": "https://github.com/thephpleague/config" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "time": "2022-12-11T20:36:23+00:00" + }, { "name": "monolog/monolog", "version": "1.27.1", @@ -260,6 +473,154 @@ ], "time": "2022-06-09T08:53:42+00:00" }, + { + "name": "nette/schema", + "version": "v1.2.4", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "c9ff517a53903b3d4e29ec547fb20feecb05b8ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/c9ff517a53903b3d4e29ec547fb20feecb05b8ab", + "reference": "c9ff517a53903b3d4e29ec547fb20feecb05b8ab", + "shasum": "" + }, + "require": { + "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", + "php": "7.1 - 8.3" + }, + "require-dev": { + "nette/tester": "^2.3 || ^2.4", + "phpstan/phpstan-nette": "^1.0", + "tracy/tracy": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "support": { + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.2.4" + }, + "time": "2023-08-05T18:56:25+00:00" + }, + { + "name": "nette/utils", + "version": "v3.2.10", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "a4175c62652f2300c8017fb7e640f9ccb11648d2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/a4175c62652f2300c8017fb7e640f9ccb11648d2", + "reference": "a4175c62652f2300c8017fb7e640f9ccb11648d2", + "shasum": "" + }, + "require": { + "php": ">=7.2 <8.4" + }, + "conflict": { + "nette/di": "<3.0.6" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "dev-master", + "nette/tester": "~2.0", + "phpstan/phpstan": "^1.0", + "tracy/tracy": "^2.3" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", + "ext-xml": "to use Strings::length() etc. when mbstring is not available" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v3.2.10" + }, + "time": "2023-07-30T15:38:18+00:00" + }, { "name": "pear/net_dns2", "version": "v1.5.3", @@ -439,6 +800,56 @@ }, "time": "2021-11-05T16:50:12+00:00" }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, { "name": "psr/log", "version": "1.1.4", diff --git a/lib/Froxlor/System/Markdown.php b/lib/Froxlor/System/Markdown.php new file mode 100644 index 00000000..a7a6e506 --- /dev/null +++ b/lib/Froxlor/System/Markdown.php @@ -0,0 +1,58 @@ + + * @license https://files.froxlor.org/misc/COPYING.txt GPLv2 + */ + +namespace Froxlor\System; + +use League\CommonMark\Exception\CommonMarkException; +use League\CommonMark\GithubFlavoredMarkdownConverter; + +class Markdown +{ + + private static $converter = null; + + public static function converter(): ?GithubFlavoredMarkdownConverter + { + if (is_null(self::$converter)) { + self::$converter = new GithubFlavoredMarkdownConverter([ + 'html_input' => 'strip', + 'allow_unsafe_links' => false, + ]); + } + return self::$converter; + } + + public static function cleanCustomNotes(string $note = ""): string + { + if (!empty($note)) { + try { + $note = self::converter()->convert($note)->getContent(); + } catch (CommonMarkException $e) { + $note = ""; + } + } + return $note; + } +} diff --git a/lib/Froxlor/UI/Callbacks/Customer.php b/lib/Froxlor/UI/Callbacks/Customer.php index b1c1016f..aa2cb061 100644 --- a/lib/Froxlor/UI/Callbacks/Customer.php +++ b/lib/Froxlor/UI/Callbacks/Customer.php @@ -26,17 +26,19 @@ namespace Froxlor\UI\Callbacks; use Froxlor\Settings; +use Froxlor\System\Markdown; class Customer { - public static function isLocked(array $attributes) + public static function isLocked(array $attributes): bool { return $attributes['fields']['loginfail_count'] >= Settings::Get('login.maxloginattempts') && $attributes['fields']['lastlogin_fail'] > (time() - Settings::Get('login.deactivatetime')); } - public static function hasNote(array $attributes) + public static function hasNote(array $attributes): bool { - return !empty($attributes['fields']['custom_notes']); + $cleanNote = Markdown::cleanCustomNotes($attributes['fields']['custom_notes'] ?? ""); + return !empty($cleanNote); } } diff --git a/lib/Froxlor/UI/Callbacks/Text.php b/lib/Froxlor/UI/Callbacks/Text.php index 22bbf91f..9e63e3c3 100644 --- a/lib/Froxlor/UI/Callbacks/Text.php +++ b/lib/Froxlor/UI/Callbacks/Text.php @@ -29,6 +29,7 @@ use Froxlor\CurrentUser; use Froxlor\Database\Database; use Froxlor\Froxlor; use Froxlor\PhpHelper; +use Froxlor\System\Markdown; use Froxlor\UI\Panel\UI; use Froxlor\User; use PDO; @@ -93,7 +94,7 @@ class Text 'entry' => $attributes['fields']['id'], 'id' => 'cnModal' . $attributes['fields']['id'], 'title' => lng('usersettings.custom_notes.title') . ': ' . ($attributes['fields']['loginname'] ?? $attributes['fields']['adminname']), - 'body' => nl2br($note) + 'body' => nl2br(Markdown::cleanCustomNotes($note)) ]; } diff --git a/lib/Froxlor/UI/Panel/FroxlorTwig.php b/lib/Froxlor/UI/Panel/FroxlorTwig.php index d2f49175..3cc32c12 100644 --- a/lib/Froxlor/UI/Panel/FroxlorTwig.php +++ b/lib/Froxlor/UI/Panel/FroxlorTwig.php @@ -29,6 +29,7 @@ namespace Froxlor\UI\Panel; use Froxlor\Idna\IdnaWrapper; use Froxlor\Settings; +use Froxlor\System\Markdown; use Parsedown; use Twig\Extension\AbstractExtension; use Twig\TwigFilter; @@ -53,9 +54,9 @@ class FroxlorTwig extends AbstractExtension $this, 'idnDecodeFilter' ]), - new TwigFilter('parsedown', [ + new TwigFilter('markdown', [ $this, - 'callParsedown' + 'callMarkdown' ]) ]; } @@ -148,10 +149,9 @@ class FroxlorTwig extends AbstractExtension return UI::getLinker()->getLink($linkopts); } - public function callParsedown($string) + public function callMarkdown($string): string { - $pd = new Parsedown(); - return $pd->line($string); + return Markdown::cleanCustomNotes($string ?? ""); } /** diff --git a/lng/de.lng.php b/lng/de.lng.php index ab66b97f..a5ac3368 100644 --- a/lng/de.lng.php +++ b/lng/de.lng.php @@ -2213,7 +2213,7 @@ Vielen Dank, Ihr Administrator', 'usersettings' => [ 'custom_notes' => [ 'title' => 'Eigene Notizen', - 'description' => 'Hier können Notizen je nach Lust und Laune eingetragen werden. Diese werden in der Administrator/Kunden-Übersicht bei dem jeweiligen Benutzer angezeigt.', + 'description' => 'Hier können Notizen je nach Lust und Laune eingetragen werden. Diese werden in der Administrator/Kunden-Übersicht bei dem jeweiligen Benutzer angezeigt.
Markdown ist unterstützt, HTML wird entfernt.', 'show' => 'Zeige die Notizen auf dem Dashboard des Benutzers', ], 'api_allowed' => [ diff --git a/lng/en.lng.php b/lng/en.lng.php index 010c2dfb..24cd510b 100644 --- a/lng/en.lng.php +++ b/lng/en.lng.php @@ -2346,7 +2346,7 @@ Yours sincerely, your administrator', 'usersettings' => [ 'custom_notes' => [ 'title' => 'Custom notes', - 'description' => 'Feel free to put any notes you want/need in here. They will show up in the admin/customer overview for the corresponding user.', + 'description' => 'Feel free to put any notes you want/need in here. They will show up in the admin/customer overview for the corresponding user.
Markdown is supported, HTML will be removed.', 'show' => 'Show your notes on the dashboard of the user', ], 'api_allowed' => [ diff --git a/templates/Froxlor/user/index.html.twig b/templates/Froxlor/user/index.html.twig index 8c3ef479..7d40f263 100644 --- a/templates/Froxlor/user/index.html.twig +++ b/templates/Froxlor/user/index.html.twig @@ -136,12 +136,12 @@ - {% if userinfo.custom_notes is not empty and userinfo.custom_notes_show == 1 %} + {% if userinfo.custom_notes|markdown is not empty and userinfo.custom_notes_show == 1 %}
  • - {{ userinfo.custom_notes|nl2br|raw }} + {{ userinfo.custom_notes|markdown|raw }}
@@ -263,10 +263,10 @@
{% endif %} - {% if userinfo.custom_notes is not empty and userinfo.custom_notes_show == 1 %} + {% if userinfo.custom_notes|markdown is not empty and userinfo.custom_notes_show == 1 %}
  • - {{ userinfo.custom_notes|nl2br|raw }} + {{ userinfo.custom_notes|markdown|raw }}
  • {% endif %}