Added an console command to convert the old BBCode comments to markdown

This commit is contained in:
Jan Böhmer 2019-10-13 00:32:09 +02:00
parent c814bae3af
commit 8cfaee5c62
9 changed files with 427 additions and 95 deletions

View file

@ -11,9 +11,9 @@
"ext-json": "*",
"ext-mbstring": "*",
"doctrine/annotations": "^1.6",
"erusev/parsedown": "^1.8-dev",
"florianv/swap": "^4.0",
"friendsofsymfony/ckeditor-bundle": "^2.0",
"league/html-to-markdown": "^4.8",
"liip/imagine-bundle": "^2.2",
"nyholm/psr7": "^1.1",
"ocramius/proxy-manager": "2.1.*",
@ -21,7 +21,7 @@
"php-http/curl-client": "^2.0",
"php-http/guzzle6-adapter": "^2.0",
"php-http/message": "^1.8",
"s9e/text-formatter": "^2.0",
"s9e/text-formatter": "^2.1",
"sensio/framework-extra-bundle": "^5.1",
"shivas/versioning-bundle": "^3.1",
"symfony/apache-pack": "^1.0",

113
composer.lock generated
View file

@ -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": "d81e2f85d8e6d5ead84ec2272a7f0618",
"content-hash": "f4b935dcb469ba97b409fd529352d145",
"packages": [
{
"name": "clue/stream-filter",
@ -1316,52 +1316,6 @@
],
"time": "2019-08-13T17:33:27+00:00"
},
{
"name": "erusev/parsedown",
"version": "1.8.0-beta-7",
"source": {
"type": "git",
"url": "https://github.com/erusev/parsedown.git",
"reference": "fe7a50eceb4a3c867cc9fa9c0aa906b1067d1955"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/erusev/parsedown/zipball/fe7a50eceb4a3c867cc9fa9c0aa906b1067d1955",
"reference": "fe7a50eceb4a3c867cc9fa9c0aa906b1067d1955",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35"
},
"type": "library",
"autoload": {
"psr-0": {
"Parsedown": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Emanuil Rusev",
"email": "hello@erusev.com",
"homepage": "http://erusev.com"
}
],
"description": "Parser for Markdown.",
"homepage": "http://parsedown.org",
"keywords": [
"markdown",
"parser"
],
"time": "2019-03-17T18:47:21+00:00"
},
{
"name": "fig/link-util",
"version": "1.0.0",
@ -1910,6 +1864,70 @@
],
"time": "2014-01-12T16:20:24+00:00"
},
{
"name": "league/html-to-markdown",
"version": "4.8.2",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/html-to-markdown.git",
"reference": "e747489191f8e9144a7270eb61f8b9516e99e413"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/html-to-markdown/zipball/e747489191f8e9144a7270eb61f8b9516e99e413",
"reference": "e747489191f8e9144a7270eb61f8b9516e99e413",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-xml": "*",
"php": ">=5.3.3"
},
"require-dev": {
"mikehaertl/php-shellcommand": "~1.1.0",
"phpunit/phpunit": "4.*",
"scrutinizer/ocular": "~1.1"
},
"bin": [
"bin/html-to-markdown"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.9-dev"
}
},
"autoload": {
"psr-4": {
"League\\HTMLToMarkdown\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Colin O'Dell",
"email": "colinodell@gmail.com",
"homepage": "https://www.colinodell.com",
"role": "Lead Developer"
},
{
"name": "Nick Cernis",
"email": "nick@cern.is",
"homepage": "http://modernnerd.net",
"role": "Original Author"
}
],
"description": "An HTML-to-markdown conversion helper for PHP",
"homepage": "https://github.com/thephpleague/html-to-markdown",
"keywords": [
"html",
"markdown"
],
"time": "2019-08-02T11:57:39+00:00"
},
{
"name": "liip/imagine-bundle",
"version": "2.2.0",
@ -9275,7 +9293,6 @@
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"erusev/parsedown": 20,
"twig/extra-bundle": 20,
"twig/intl-extra": 20,
"roave/security-advisories": 20

View file

@ -0,0 +1,180 @@
<?php
/**
*
* part-db version 0.1
* Copyright (C) 2005 Christoph Lechner
* http://www.cl-projects.de/
*
* part-db version 0.2+
* Copyright (C) 2009 K. Jacobs and others (see authors.php)
* http://code.google.com/p/part-db/
*
* Part-DB Version 0.4+
* Copyright (C) 2016 - 2019 Jan Böhmer
* https://github.com/jbtronics
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
*/
namespace App\Command;
use App\Entity\Attachments\AttachmentType;
use App\Entity\Base\NamedDBElement;
use App\Entity\Devices\Device;
use App\Entity\Parts\Category;
use App\Entity\Parts\Manufacturer;
use App\Entity\Parts\MeasurementUnit;
use App\Entity\Parts\Part;
use App\Entity\Parts\Storelocation;
use App\Entity\Parts\Supplier;
use App\Entity\PriceInformations\Currency;
use App\Entity\UserSystem\Group;
use App\Helpers\BBCodeToMarkdownConverter;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
/**
* This command converts the BBCode used by old Part-DB versions (<1.0), to the current used markdown format.
* @package App\Command
*/
class ConvertBBCodeCommand extends Command
{
/** @var string The LIKE criteria used to detect on SQL server if a entry contains BBCode */
protected const BBCODE_CRITERIA = "%[%]%[/%]%";
/** @var string The regex (performed in PHP) used to check if a property really contains BBCODE */
protected const BBCODE_REGEX = '/\\[.+\\].*\\[\\/.+\\]/';
protected static $defaultName = 'app:convert-bbcode';
protected $em;
protected $propertyAccessor;
protected $converter;
public function __construct(EntityManagerInterface $entityManager, PropertyAccessorInterface $propertyAccessor)
{
$this->em = $entityManager;
$this->propertyAccessor = $propertyAccessor;
$this->converter = new BBCodeToMarkdownConverter();
parent::__construct();
}
protected function configure()
{
$this
->setDescription('Converts BBCode used in old Part-DB versions to newly used Markdown')
->setHelp('Older versions of Part-DB (<1.0) used BBCode for rich text formatting.
Part-DB now uses Markdown which offers more features but is incompatible with BBCode.
When you upgrade from an pre 1.0 version you have to run this command to convert your comment fields');
$this->addOption('dry-run', null, null, 'Do not save changes to DB. In combination with -v or -vv you can check what will be changed!');
}
/**
* Returns a list which entities and which properties need to be checked.
* @return array
*/
protected function getTargetsLists() : array
{
return [
Part::class => ['description', 'comment'],
AttachmentType::class => ['comment'],
Storelocation::class => ['comment'],
Device::class => ['comment'],
Category::class => ['comment'],
Manufacturer::class => ['comment'],
MeasurementUnit::class => ['comment'],
Supplier::class => ['comment'],
Currency::class => ['comment'],
Group::class => ['comment'],
];
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$targets = $this->getTargetsLists();
//Convert for every class target
foreach ($targets as $class => $properties) {
$io->section(sprintf('Convert entities of class %s', $class));
$io->note(sprintf(
'Search for entities of type %s that need conversion',
$class
));
//Determine which entities of this type we need to modify
/** @var EntityRepository $repo */
$repo = $this->em->getRepository($class);
$qb = $repo->createQueryBuilder('e')
->select('e');
//Add fields criteria
foreach ($properties as $key => $property) {
$qb->orWhere('e.' . $property . ' LIKE ?' . $key);
$qb->setParameter($key, static::BBCODE_CRITERIA);
}
//Fetch resulting classes
$results = $qb->getQuery()->getResult();
$io->note(sprintf('Found %d entities, that need to be converted!', count($results)));
//In verbose mode print the names of the entities
foreach ($results as $result) {
/** @var NamedDBElement $result */
$io->writeln(
'Convert entity: ' . $result->getName() . ' (' . $result->getIDString() . ')',
OutputInterface::VERBOSITY_VERBOSE
);
foreach ($properties as $property) {
//Retrieve bbcode from entity
$bbcode = $this->propertyAccessor->getValue($result, $property);
//Check if the current property really contains BBCode
if (!preg_match(static::BBCODE_REGEX, $bbcode)) {
continue;
}
$io->writeln(
'BBCode (old): '
. str_replace('\n', ' ', substr($bbcode, 0, 255)),
OutputInterface::VERBOSITY_VERY_VERBOSE
);
$markdown = $this->converter->convert($bbcode);
$io->writeln(
'Markdown (new): '
. str_replace('\n', ' ', substr($markdown, 0, 255)),
OutputInterface::VERBOSITY_VERY_VERBOSE
);
$io->writeln('', OutputInterface::VERBOSITY_VERY_VERBOSE);
$this->propertyAccessor->setValue($result, $property, $markdown);
}
}
}
//If we are not in dry run, save changes to DB
if (!$input->getOption('dry-run')) {
$this->em->flush();
$io->success('Changes saved to DB successfully!');
}
}
}

View file

@ -0,0 +1,63 @@
<?php
/**
*
* part-db version 0.1
* Copyright (C) 2005 Christoph Lechner
* http://www.cl-projects.de/
*
* part-db version 0.2+
* Copyright (C) 2009 K. Jacobs and others (see authors.php)
* http://code.google.com/p/part-db/
*
* Part-DB Version 0.4+
* Copyright (C) 2016 - 2019 Jan Böhmer
* https://github.com/jbtronics
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
*/
namespace App\Helpers;
use League\HTMLToMarkdown\HtmlConverter;
use s9e\TextFormatter\Bundles\Forum as TextFormatter;
use SebastianBergmann\CodeCoverage\Report\Text;
class BBCodeToMarkdownConverter
{
protected $html_to_markdown;
public function __construct()
{
$this->html_to_markdown = new HtmlConverter();
}
/**
* Converts the given BBCode to markdown.
* BBCode tags that does not have a markdown aequivalent are outputed as HTML tags.
* @param $bbcode string The Markdown that should be converted.
* @return string The markdown version of the text.
*/
public function convert(string $bbcode) : string
{
//Convert BBCode to html
$xml = TextFormatter::parse($bbcode);
$html = TextFormatter::render($xml);
//Now convert the HTML to markdown
return $this->html_to_markdown->convert($html);
}
}

View file

@ -42,11 +42,14 @@ use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\PostLoad;
use Doctrine\ORM\Mapping\PreUpdate;
use ReflectionClass;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Security\Core\Security;
/**
* The purpose of this class is to hook into the doctrine entity lifecycle and restrict access to entity informations
* configured by ColoumnSecurity Annotation.
* If the current programm is running from CLI (like a CLI command), the security checks are disabled.
* (Commands should be able to do everything they like)
*
* If a user does not have access to an coloumn, it will be filled, with a placeholder, after doctrine loading is finished.
* The edit process is also catched, so that these placeholders, does not get saved to database.
@ -56,12 +59,29 @@ class ElementPermissionListener
protected $security;
protected $reader;
protected $em;
protected $disabled;
public function __construct(Security $security, Reader $reader, EntityManagerInterface $em)
public function __construct(Security $security, Reader $reader, EntityManagerInterface $em, KernelInterface $kernel)
{
$this->security = $security;
$this->reader = $reader;
$this->em = $em;
//Disable security when the current program is running from CLI
$this->disabled = $this->isRunningFromCLI();
}
/**
* This function checks if the current script is run from web or from a terminal.
* @return bool Returns true if the current programm is running from CLI (terminal)
*/
protected function isRunningFromCLI()
{
if (empty($_SERVER['REMOTE_ADDR']) && !isset($_SERVER['HTTP_USER_AGENT']) && count($_SERVER['argv']) > 0) {
return true;
}
return false;
}
/**
@ -76,6 +96,11 @@ class ElementPermissionListener
*/
public function postLoadHandler(DBElement $element, LifecycleEventArgs $event)
{
//Do nothing if security is disabled
if ($this->disabled) {
return;
}
//Read Annotations and properties.
$reflectionClass = new ReflectionClass($element);
$properties = $reflectionClass->getProperties();
@ -112,6 +137,11 @@ class ElementPermissionListener
*/
public function preFlushHandler(DBElement $element, PreFlushEventArgs $eventArgs)
{
//Do nothing if security is disabled
if ($this->disabled) {
return;
}
$em = $eventArgs->getEntityManager();
$unitOfWork = $eventArgs->getEntityManager()->getUnitOfWork();

View file

@ -31,9 +31,7 @@
namespace App\Services;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* This class allows you to convert markdown text to HTML.
@ -41,52 +39,26 @@ use Symfony\Contracts\Cache\ItemInterface;
*/
class MarkdownParser
{
protected $cache;
/** @var \Parsedown */
protected $parsedown;
protected $translator;
public function __construct(CacheInterface $cache)
public function __construct(TranslatorInterface $translator)
{
$this->cache = $cache;
$this->initParsedown();
}
protected function initParsedown()
{
$this->parsedown = new \Parsedown();
$this->parsedown->setSafeMode(true);
$this->translator = $translator;
}
/**
* Converts the given markdown text to HTML.
* The result is cached.
* Mark the markdown for rendering.
* The rendering of markdown is done on client side
* @param string $markdown The markdown text that should be parsed to html.
* @param bool $inline_mode Only allow inline markdown codes like (*bold* or **italic**), not something like tables
* @return string The HTML version of the given text.
* @throws \Psr\Cache\InvalidArgumentException
* @return string The markdown in a version that can be parsed on client side.
*/
public function parse(string $markdown, bool $inline_mode = false) : string
public function markForRendering(string $markdown, bool $inline_mode = false) : string
{
return sprintf(
'<div class="markdown" data-markdown="%s">Markdown loading...</div>',
htmlspecialchars($markdown)
'<div class="markdown" data-markdown="%s">%s</div>',
htmlspecialchars($markdown),
$this->translator->trans('markdown.loading')
);
//Generate key
/*if ($inline_mode) {
$key = 'markdown_i_' . md5($markdown);
} else {
$key = 'markdown_' . md5($markdown);
}
return $this->cache->get($key, function (ItemInterface $item) use ($markdown, $inline_mode) {
//Expire text after 2 months
$item->expiresAfter(311040000);
if ($inline_mode) {
return $this->parsedown->line($markdown);
}
return '<div class="markdown">' . $this->parsedown->text($markdown) . '</div>';
});*/
}
}

View file

@ -80,7 +80,7 @@ class AppExtension extends AbstractExtension
{
return [
new TwigFilter('entityURL', [$this, 'generateEntityURL']),
new TwigFilter('markdown', [$this->markdownParser, 'parse'], ['pre_escape' => 'html', 'is_safe' => ['html']]),
new TwigFilter('markdown', [$this->markdownParser, 'markForRendering'], ['pre_escape' => 'html', 'is_safe' => ['html']]),
new TwigFilter('moneyFormat', [$this, 'formatCurrency']),
new TwigFilter('siFormat', [$this, 'siFormat']),
new TwigFilter('amountFormat', [$this, 'amountFormat']),

View file

@ -105,9 +105,6 @@
"egulias/email-validator": {
"version": "2.1.7"
},
"erusev/parsedown": {
"version": "1.7.3"
},
"felixfbecker/advanced-json-rpc": {
"version": "v3.0.4"
},
@ -150,6 +147,9 @@
"jdorn/sql-formatter": {
"version": "v1.2.17"
},
"league/html-to-markdown": {
"version": "4.8.2"
},
"liip/imagine-bundle": {
"version": "1.8",
"recipe": {
@ -269,7 +269,7 @@
"version": "1.4.3"
},
"s9e/text-formatter": {
"version": "1.4.1"
"version": "2.1.2"
},
"sebastian/diff": {
"version": "3.0.2"

View file

@ -0,0 +1,70 @@
<?php
/**
*
* part-db version 0.1
* Copyright (C) 2005 Christoph Lechner
* http://www.cl-projects.de/
*
* part-db version 0.2+
* Copyright (C) 2009 K. Jacobs and others (see authors.php)
* http://code.google.com/p/part-db/
*
* Part-DB Version 0.4+
* Copyright (C) 2016 - 2019 Jan Böhmer
* https://github.com/jbtronics
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
*/
namespace App\Tests\Helpers;
use App\Helpers\BBCodeToMarkdownConverter;
use PHPUnit\Framework\TestCase;
class BBCodeToMarkdownConverterTest extends TestCase
{
protected $converter;
public function setUp()
{
$this->converter = new BBCodeToMarkdownConverter();
}
public function dataProvider()
{
return [
['[b]Bold[/b]', '**Bold**'],
['[i]Italic[/i]', '*Italic*'],
['[s]Strike[/s]', '<s>Strike</s>'],
['[url]https://foo.bar[/url]', '<https://foo.bar>'],
['[url=https://foo.bar]test[/url]', '[test](https://foo.bar)'],
['[center]Centered[/center]', '<div style="text-align:center">Centered</div>'],
['test no change', 'test no change'],
['**Test**', '**Test**'],
];
}
/**
* @dataProvider dataProvider
* @param $bbcode
* @param $expected
*/
public function testConvert($bbcode, $expected)
{
$this->assertEquals($expected, $this->converter->convert($bbcode));
}
}