diff --git a/src/Extend/Formatter.php b/src/Extend/Formatter.php index b185a12cd..98041514c 100644 --- a/src/Extend/Formatter.php +++ b/src/Extend/Formatter.php @@ -10,38 +10,104 @@ namespace Flarum\Extend; use Flarum\Extension\Extension; -use Flarum\Formatter\Event\Configuring; use Flarum\Formatter\Formatter as ActualFormatter; use Illuminate\Contracts\Container\Container; -use Illuminate\Events\Dispatcher; class Formatter implements ExtenderInterface, LifecycleInterface { - private $callback; + private $configurationCallbacks = []; + private $parsingCallbacks = []; + private $renderingCallbacks = []; + /** + * Configure the formatter. This can be used to add support for custom markdown/bbcode/etc tags, + * or otherwise change the formatter. Please see documentation for the s9e text formatter library for more + * information on how to use this. + * + * @param callable|string $callback + * + * The callback can be a closure or invokable class, and should accept: + * - \s9e\TextFormatter\Configurator $configurator + */ public function configure($callback) { - $this->callback = $callback; + $this->configurationCallbacks[] = $callback; + + return $this; + } + + /** + * Prepare the system for parsing. This can be used to modify the text that will be parsed, or to modify the parser. + * Please note that the text to be parsed must be returned, regardless of whether it's changed. + * + * @param callable|string $callback + * + * The callback can be a closure or invokable class, and should accept: + * - \s9e\TextFormatter\Parser $parser + * - mixed $context + * - string $text: The text to be parsed. + * + * The callback should return: + * - string $text: The text to be parsed. + */ + public function parse($callback) + { + $this->parsingCallbacks[] = $callback; + + return $this; + } + + /** + * Prepare the system for rendering. This can be used to modify the xml that will be rendered, or to modify the renderer. + * Please note that the xml to be rendered must be returned, regardless of whether it's changed. + * + * @param callable|string $callback + * + * The callback can be a closure or invokable class, and should accept: + * - \s9e\TextFormatter\Rendered $renderer + * - mixed $context + * - string $xml: The xml to be rendered. + * - ServerRequestInterface $request + * + * The callback should return: + * - string $xml: The xml to be rendered. + */ + public function render($callback) + { + $this->renderingCallbacks[] = $callback; return $this; } public function extend(Container $container, Extension $extension = null) { - $events = $container->make(Dispatcher::class); - - $events->listen( - Configuring::class, - function (Configuring $event) use ($container) { - if (is_string($this->callback)) { - $callback = $container->make($this->callback); - } else { - $callback = $this->callback; + $container->extend('flarum.formatter', function ($formatter, $container) { + foreach ($this->configurationCallbacks as $callback) { + if (is_string($callback)) { + $callback = $container->make($callback); } - $callback($event->configurator); + $formatter->addConfigurationCallback($callback); } - ); + + foreach ($this->parsingCallbacks as $callback) { + if (is_string($callback)) { + $callback = $container->make($callback); + } + + $formatter->addParsingCallback($callback); + } + + foreach ($this->renderingCallbacks as $callback) { + if (is_string($callback)) { + $callback = $container->make($callback); + } + + $formatter->addRenderingCallback($callback); + } + + return $formatter; + }); } public function onEnable(Container $container, Extension $extension) diff --git a/src/Formatter/Event/Configuring.php b/src/Formatter/Event/Configuring.php index a09c558e7..9f5993ce4 100644 --- a/src/Formatter/Event/Configuring.php +++ b/src/Formatter/Event/Configuring.php @@ -11,6 +11,9 @@ namespace Flarum\Formatter\Event; use s9e\TextFormatter\Configurator; +/** + * @deprecated beta 15, removed beta 16. Use the Formatter extender instead. + */ class Configuring { /** diff --git a/src/Formatter/Event/Parsing.php b/src/Formatter/Event/Parsing.php index de72035cf..b0fcb3fd1 100644 --- a/src/Formatter/Event/Parsing.php +++ b/src/Formatter/Event/Parsing.php @@ -11,6 +11,9 @@ namespace Flarum\Formatter\Event; use s9e\TextFormatter\Parser; +/** + * @deprecated beta 15, removed beta 16. Use the Formatter extender instead. + */ class Parsing { /** diff --git a/src/Formatter/Event/Rendering.php b/src/Formatter/Event/Rendering.php index 4ec2360f9..16c17d80c 100644 --- a/src/Formatter/Event/Rendering.php +++ b/src/Formatter/Event/Rendering.php @@ -12,6 +12,9 @@ namespace Flarum\Formatter\Event; use Psr\Http\Message\ServerRequestInterface; use s9e\TextFormatter\Renderer; +/** + * @deprecated beta 15, removed beta 16. Use the Formatter extender instead. + */ class Rendering { /** diff --git a/src/Formatter/Formatter.php b/src/Formatter/Formatter.php index d8cfe62da..f2d6b7c5b 100644 --- a/src/Formatter/Formatter.php +++ b/src/Formatter/Formatter.php @@ -20,6 +20,12 @@ use s9e\TextFormatter\Unparser; class Formatter { + protected $configurationCallbacks = []; + + protected $parsingCallbacks = []; + + protected $renderingCallbacks = []; + /** * @var Repository */ @@ -47,6 +53,21 @@ class Formatter $this->cacheDir = $cacheDir; } + public function addConfigurationCallback($callback) + { + $this->configurationCallbacks[] = $callback; + } + + public function addParsingCallback($callback) + { + $this->parsingCallbacks[] = $callback; + } + + public function addRenderingCallback($callback) + { + $this->renderingCallbacks[] = $callback; + } + /** * Parse text. * @@ -58,8 +79,13 @@ class Formatter { $parser = $this->getParser($context); + // Deprecated in beta 15, remove in beta 16 $this->events->dispatch(new Parsing($parser, $context, $text)); + foreach ($this->parsingCallbacks as $callback) { + $text = $callback($parser, $context, $text); + } + return $parser->parse($text); } @@ -75,8 +101,13 @@ class Formatter { $renderer = $this->getRenderer(); + // Deprecated in beta 15, remove in beta 16 $this->events->dispatch(new Rendering($renderer, $context, $xml, $request)); + foreach ($this->renderingCallbacks as $callback) { + $xml = $callback($renderer, $context, $xml, $request); + } + return $renderer->render($xml); } @@ -122,8 +153,13 @@ class Formatter $configurator->Autolink; $configurator->tags->onDuplicate('replace'); + // Deprecated in beta 15, remove in beta 16 $this->events->dispatch(new Configuring($configurator)); + foreach ($this->configurationCallbacks as $callback) { + $callback($configurator); + } + $this->configureExternalLinks($configurator); return $configurator; diff --git a/tests/integration/extenders/FormatterTest.php b/tests/integration/extenders/FormatterTest.php new file mode 100644 index 000000000..722f83783 --- /dev/null +++ b/tests/integration/extenders/FormatterTest.php @@ -0,0 +1,145 @@ +app()->getContainer()->make(Formatter::class); + $formatter->flush(); + + return $formatter; + } + + /** + * @test + */ + public function custom_formatter_config_doesnt_work_by_default() + { + $formatter = $this->getFormatter(); + + $this->assertEquals('[B]something[/B]', $formatter->parse('[B]something[/B]')); + } + + /** + * @test + */ + public function custom_formatter_config_works_if_added_with_closure() + { + $this->extend((new Extend\Formatter)->configure(function ($config) { + $config->BBCodes->addFromRepository('B'); + })); + + $formatter = $this->getFormatter(); + + $this->assertEquals('something', $formatter->render($formatter->parse('[B]something[/B]'))); + } + + /** + * @test + */ + public function custom_formatter_config_works_if_added_with_invokable_class() + { + $this->extend((new Extend\Formatter)->configure(InvokableConfig::class)); + + $formatter = $this->getFormatter(); + + $this->assertEquals('something', $formatter->render($formatter->parse('[B]something[/B]'))); + } + + /** + * @test + */ + public function custom_formatter_parsing_doesnt_work_by_default() + { + $this->assertEquals('Text<a>', $this->getFormatter()->parse('Text')); + } + + /** + * @test + */ + public function custom_formatter_parsing_works_if_added_with_closure() + { + $this->extend((new Extend\Formatter)->parse(function ($parser, $context, $text) { + return 'ReplacedText'; + })); + + $this->assertEquals('ReplacedText<a>', $this->getFormatter()->parse('Text')); + } + + /** + * @test + */ + public function custom_formatter_parsing_works_if_added_with_invokable_class() + { + $this->extend((new Extend\Formatter)->parse(InvokableParsing::class)); + + $this->assertEquals('ReplacedText<a>', $this->getFormatter()->parse('Text')); + } + + /** + * @test + */ + public function custom_formatter_rendering_doesnt_work_by_default() + { + $this->assertEquals('Text', $this->getFormatter()->render('

Text

')); + } + + /** + * @test + */ + public function custom_formatter_rendering_works_if_added_with_closure() + { + $this->extend((new Extend\Formatter)->render(function ($renderer, $context, $xml, $request) { + return 'ReplacedText'; + })); + + $this->assertEquals('ReplacedText', $this->getFormatter()->render('Text')); + } + + /** + * @test + */ + public function custom_formatter_rendering_works_if_added_with_invokable_class() + { + $this->extend((new Extend\Formatter)->render(InvokableRendering::class)); + + $this->assertEquals('ReplacedText', $this->getFormatter()->render('Text')); + } +} + +class InvokableConfig +{ + public function __invoke($config) + { + $config->BBCodes->addFromRepository('B'); + } +} + +class InvokableParsing +{ + public function __invoke($parser, $context, $text) + { + return 'ReplacedText
'; + } +} + +class InvokableRendering +{ + public function __invoke($renderer, $context, $xml, $request) + { + return 'ReplacedText'; + } +}