Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide a semantic bundle configuration #21

Merged
merged 12 commits into from
May 31, 2022
1 change: 0 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ jobs:
matrix:
include:
- { php-version: 7.2, symfony-locked-version: none, dependency-version: prefer-lowest }
- { php-version: 7.2, symfony-locked-version: 3.4.*, dependency-version: prefer-stable }
- { php-version: 7.4, symfony-locked-version: 4.4.*, dependency-version: prefer-stable }
- { php-version: 8.1, symfony-locked-version: none, dependency-version: prefer-stable }
name: PHPUnit (PHP ${{matrix.php-version}}, Symfony Version Lock ${{ matrix.symfony-locked-version }}, ${{ matrix.dependency-version }})
Expand Down
1 change: 1 addition & 0 deletions .php_cs.dist
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ return PhpCsFixer\Config::create()
->notPath('node_modules/')
->notPath('var/cache')
->notPath('vendor/')
->notPath('tests/Fixtures/cache')
)
;
211 changes: 61 additions & 150 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,44 +1,22 @@
# WebfactoryShortcodeBundle

WebfactoryShortcodeBundle is a Symfony bundle that integrates [thunderer/Shortcode](https://github.com/thunderer/Shortcode).
A Symfony bundle to resolve `[shortcode]` markup in Twig templates, using the [thunderer/Shortcode](https://github.com/thunderer/Shortcode) library.

It allows you to define shortcodes and their replacements in a jiffy. Shortcodes are special text fragments that can be
used by users in user generated content to embed some other content or markup. E.g. a user could use the following in a
comment:
It allows you to define shortcodes and their replacements in a jiffy. Shortcodes are special text fragments that can be replaced with other content or markup. E.g. a user could use the following in a comment:

```
[image url="https://upload.wikimedia.org/wikipedia/en/f/f7/RickRoll.png"]
[text color="red"]This is red text.[/text]
```

In analogy to living style guides, this bundle also provides an optional shortcode guide. This guide can be used for
automated testing of your shortcodes as well.
In analogy to living style guides, this bundle provides a shortcode guide that lists all registered shortcodes with an optional description and example.

## Installation

As usual, install via [composer](https://getcomposer.org/) and register the bundle in your application:
As usual, install via [Composer](https://getcomposer.org/) and register the bundle in your application:

composer require webfactory/shortcode-bundle

For Symfony < 4:

```php
<?php
// app/AppKernel.php

public function registerBundles()
{
$bundles = array(
// ...
new Webfactory\ShortcodeBundle\WebfactoryShortcodeBundle(),
// ...
);
// ...
}
```

For Symfony >= 4:

```php
<?php
// config/bundles.php
Expand All @@ -56,131 +34,78 @@ public function registerBundles()

## Usage

### Defining your own shortcodes
### Twig Filter

The easiest way is to add one service for each shortcode in your services definition:
The bundle will set up a `shortcodes` Twig filter. What you pass through this filter will be processed by the `Processor` class (see [docs](https://github.com/thunderer/Shortcode#processing)).

```xml
<service id="webfactory.shortcode.your-shortcode-name" parent="Webfactory\ShortcodeBundle\Handler\EmbeddedShortcodeHandler.inline" class="Webfactory\ShortcodeBundle\Handler\EmbeddedShortcodeHandler">
<argument index="1">reference-to-your-replacement-controller</argument>
<tag name="webfactory.shortcode" shortcode="your-shortcode-name"/>
</service>
```twig
{% apply shortcodes %}
[image url="https://upload.wikimedia.org/wikipedia/en/f/f7/RickRoll.png"]
[text color="red"]This is red text.[/text]
{% endapply %}
{{ some_content |shortcodes }}
```

The parent ```Webfactory\ShortcodeBundle\Handler\EmbeddedShortcodeHandler.inline``` will use
inline rendering while the parent ```Webfactory\ShortcodeBundle\Handler\EmbeddedShortcodeHandler.esi``` will use [ESI rendering](https://symfony.com/doc/current/http_cache/esi.html).

ESI may be nice for caching but comes with a problem: ESI embeds controller actions by calling a special internal `_fragment`-URL and needs to somehow serialize all parameters for an action in this URL. This works well for scalar values but neither for objects nor arrays of scalar values. But for context sensitive shortcodes, we pass the request attributes to the embedded controller action. And these request attributes might contain objects, e.g. the result object of a ParamConverter. This can lead to hard to debug errors, especially when recursion comes into play.

Also, logging needs more configuration (explained in the Logging section) with ESI.
### Using Controllers as Shortcode Handlers

The ```reference-to-your-replacement-controller``` could be a string like ```AppBundle\Controller\EmbeddedImageController::showAction```
or if use controllers as services, something like ```AppBundle\Controller\EmbeddedImageController:showAction```. We recommend
using several controllers grouped by feature with only a few actions to keep things simple and unit testable, instead of
one huge ShortcodeController for all shortcodes. But of course, that's up to you.
This bundle comes with a helper class that allows to use Symfony's [Fragment Sub-Framework](https://symfony.com/blog/new-in-symfony-2-2-the-new-fragment-sub-framework) and the technique of [embedding controllers](https://symfony.com/doc/current/templates.html#embedding-controllers) to have controllers generate the replacement output for shortcodes.

Finally ```your-shortcode-name``` is the name the users can use in their text inside the squared bracktes. Anything
after the name in the suqared brackets wll be considered as parameters that will be passed onto the controller.
To give an example, assume the following configuration:

### Full example

To allow a user input of ```[image url="https://upload.wikimedia.org/wikipedia/en/f/f7/RickRoll.png"]``` to be replaced
with HTML markup for this image, use the twig filter "shortcodes" on the user input:

```twig
{# user-generated-comment.html.twig #}
<div class="comment">
{{ comment |shortcodes }}
</div>
```yaml
# config.yml
webfactory_shortcodes:
shortcodes:
image: AppBundle\Controller\EmbeddedImageController::show
```

Then, write a service definition like this:
Then, when doing something like this in Twig:

```xml
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>

<!-- ... -->

<service id="webfactory.shortcode.image" parent="Webfactory\ShortcodeBundle\Handler\EmbeddedShortcodeHandler.inline" class="Webfactory\ShortcodeBundle\Handler\EmbeddedShortcodeHandler">
<argument index="1">AppBundle\Controller\EmbeddedImageController:showAction</argument>
<tag name="webfactory.shortcode" shortcode="image"/>
</service>

<service id="AppBundle\Controller\EmbeddedImageController">
<argument type="service" id="templating" />
</service>

<!-- ... -->

</services>
</container>
```twig
{{ '[image url="https://upload.wikimedia.org/wikipedia/en/f/f7/RickRoll.png"]' |shortcodes }}
```

A controller like this:
... the `AppBundle\Controller\EmbeddedImageController::show()` controller method will be called. Additional shortcode attributes, like `url` in the above example, will be passed as parameters to the controller. The response returned by the controller will be used to replace the shortcode in the given content. The controller can generate the response directly, or use Twig to render a template to create it.

```php
<?php
// src/AppBundle/Controller/EmbeddedImageController.php
#### Rendering with Edge Side Includes

namespace AppBundle\Controller;
You can also use [ESI rendering](https://symfony.com/doc/current/http_cache/esi.html) for particular shortcodes. The advantage of ESI is that single shortcode replacements can be stored in edge caches and/or reverse proxies like Varnish and possibly be reused on multiple pages.
MalteWunsch marked this conversation as resolved.
Show resolved Hide resolved

use Symfony\Bundle\TwigBundle\TwigEngine;
use Symfony\Component\HttpFoundation\Response;
⚠️ Take care: Due to the way ESI works, the (master) `Request` visible to controllers is no longer the one where the shortcode was used. Keep that in mind wehn you, for example, want to log the URLs where shortcodes are beiung used.

final class EmbeddedImageController
{
/** @var TwigEngine */
private $twigEngine;
To use ESI-based embedding for a particular shortcode, use the following configuration:

public function __construct(TwigEngine $twigEngine)
{
$this->twigEngine = $twigEngine;
}

public function showAction(string $url): Response
{
if (!$url) {
throw new \RuntimeException('No url provided');
}

return $this->twigEngine->renderResponse('@App/EmbeddedImage/show.html.twig', ['url' => $url]);
}
}
```yaml
# config.yml
webfactory_shortcodes:
shortcodes:
image:
controller: AppBundle\Controller\EmbeddedImageController::showAction
method: esi
```

And finally a twig template like this:
### Registering Handlers as Services

```twig
{# src/Ressources/views/EmbeddedImage/show.html.twig #}
<div class="shortcode-container">
<img src="{{ url }}" />
</div>
In the [thunderer/Shortcode](https://github.com/thunderer/Shortcode) package, _handlers_ transform shortcodes into desired replacements. You can register services from the Symfony Dependency Injection Container to be used as shortcode handlers by tagging them with `webfactory.shortcode` and adding a `shortcode` attribute to the tag indicating the shortcode name.

```yaml
services:
My\Shortcode\Handler\Service:
tags:
- { name: 'webfactory.shortcode', shortcode: 'my-shortcode-name' }
```

### Activating the Shortcode Guide
### Removing `<p>` Tags around Shortcodes

The optional shortcode guide is a controller providing an overview page of the configured shortcodes and a detail page
for each shortcode including a rendered example. Activate it in three simple steps:
By default, the `RemoveWrappingParagraphElementsEventHandler` contained in this bundle will be used to remove `<p>...</p>` tags around shortcodes, if the shortcode is the only text content in that paragraph.

At first, include the controller service definition. It is located at ```webfactory/shortcode-bundle/Resources/config/guide.xml```.
You can easily import it from your own configurations, just have a think about the correct environment. E.g.:
## Activating the Shortcode Guide

```xml
<!-- src/AppBundle/Resources/config/shortcodes.xml -->
<?xml version="1.0" ?>
<container xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<imports>
<import resource="../../../../vendor/webfactory/shortcode-bundle/Resources/config/guide.xml"/>
</imports>
The optional Shortcode Guide is a controller providing an overview page of all configured shortcodes. For every shortcode, there is also a detail page including a rendered example.

<!-- your shortcode services -->
</container>
```
To use the Shortcode Guide, include the routing configuration from `@WebfactoryShortcodeBundle/Resources/config/guide-routing.xml`.

Secondly, include the routes located at ```@WebfactoryShortcodeBundle/Resources/config/guide-routing.xml```, again
considering the environment. Maybe you want to restrict access in your security configuration.
⚠️ You probably want to do this only for your Symfony `dev` and/or `test` environment, and possibly restrict access in your security configuration in addition to that.

```yaml
# src/routing.yml
Expand All @@ -189,41 +114,27 @@ _shortcode-guide:
resource: "@WebfactoryShortcodeBundle/Resources/config/guide-routing.xml"
```

Finally, enrich your shortcode tags with description and example attributes for the guide:

```xml
<!-- src/AppBundle/Resources/config/shortcodes.xml -->
<?xml version="1.0" ?>
<container xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

<!-- import guide.xml -->
With the route prefix defined as above, visit `/shortcodes/` to see a list of all defined shortcodes. If you want to add descriptions to shortcodes and/or provide the example shortcode that shall be rendered on the detail page, you can add this information when configuring shortcodes:

<services>
<service id="webfactory.shortcode.image" parent="Webfactory\ShortcodeBundle\Handler\EmbeddedShortcodeHandler.inline" class="Webfactory\ShortcodeBundle\Handler\EmbeddedShortcodeHandler">
<argument index="1">AppBundle\Controller\EmbeddedImageController:showAction</argument>
<tag
name="webfactory.shortcode"
shortcode="image"
description="Renders an image tag with the {url} as it's source."
example="image url=https://upload.wikimedia.org/wikipedia/en/f/f7/RickRoll.png"
/>
</service>
</services>
</container>
```yaml
# config.yml
webfactory_shortcodes:
shortcodes:
image:
controller: AppBundle\Controller\EmbeddedImageController::showAction
description: "Renders an image tag with the {url} as it's source."
example: "image url=https://upload.wikimedia.org/wikipedia/en/f/f7/RickRoll.png"
```

With the route prefix defined as above, call ```/shortcodes/``` to get the list of shortcodes and follow the links to the
detail pages.

### Configuration
### Other Configuration Parameters

In most cases, the default values should work fine. But you might want to configure something else, e.g. if the default
parser needs too much memory for a large snippet. See thunderer's documentation on [parsing](https://github.com/thunderer/Shortcode#parsing)
and [configuration](https://github.com/thunderer/Shortcode#configuration) so you understand the advantages,
disadvantages and limitations:

```yaml
// config.yml
# config.yml

webfactory_shortcode:
parser: 'regex' # default: regular
Expand Down Expand Up @@ -323,4 +234,4 @@ This bundle was started at webfactory GmbH, Bonn.
- <https://www.webfactory.de>
- <https://twitter.com/webfactory>

Copyright 2018-2021 webfactory GmbH, Bonn. Code released under [the MIT license](LICENSE).
Copyright 2018-2022 webfactory GmbH, Bonn. Code released under [the MIT license](LICENSE).
20 changes: 10 additions & 10 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,23 @@
"ext-json": "*",
"ext-mbstring": "*",
"psr/log": "^1.0.2",
"symfony/config": "^3.4.11|^4.0|^5.0",
"symfony/dependency-injection": "^3.4.11|^4.0|^5.0",
"symfony/http-foundation": "^3.4.11|^4.0|^5.0",
"symfony/http-kernel": "^3.4.11|^4.0|^5.0",
"symfony/config": "^4.4|^5.0",
"symfony/dependency-injection": "^4.4|^5.0",
"symfony/http-foundation": "^4.4|^5.0",
"symfony/http-kernel": "^4.4|^5.0",
"thunderer/shortcode": "^0.6.5|^0.7",
"twig/twig": "^1.34|^2.0|^3.0"
},

"require-dev": {
"phpunit/phpunit": "^8.5|^9.5",
"symfony/expression-language": "^3.4.11|^4.0|^5.0",
"symfony/framework-bundle": "^3.4.23|^4.2.4|^5.0",
"symfony/browser-kit": "^4.4|^5.4",
"symfony/expression-language": "^4.4|^5.0",
"symfony/framework-bundle": "^4.4|^5.0",
"symfony/phpunit-bridge": ">= 6.0",
"symfony/routing": "^3.4.11|^4.0|^5.0",
"symfony/templating": "^3.4.11|^4.0|^5.0",
"symfony/twig-bundle": "^3.4.11|^4.0|^5.0",
"symfony/yaml": "^3.4.11|^4.0|^5.0"
"symfony/routing": "^4.4|^5.0",
"symfony/twig-bundle": "^4.4|^5.0",
"symfony/yaml": "^4.4|^5.0"
},

"autoload": {
Expand Down
Loading