RichEditorExtension::renderElements()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 7
c 2
b 0
f 0
dl 0
loc 12
rs 10
cc 3
nc 3
nop 2
1
<?php
2
3
/*
4
 * This file is part of Monsieur Biz' Rich Editor plugin for Sylius.
5
 *
6
 * (c) Monsieur Biz <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace MonsieurBiz\SyliusRichEditorPlugin\Twig;
15
16
use MonsieurBiz\SyliusRichEditorPlugin\Exception\UiElementNotFoundException;
17
use MonsieurBiz\SyliusRichEditorPlugin\UiElement\RegistryInterface;
18
use MonsieurBiz\SyliusRichEditorPlugin\Validator\Constraints\YoutubeUrlValidator;
19
use Symfony\Component\HttpFoundation\File\UploadedFile;
20
use Twig\Environment;
21
use Twig\Error\LoaderError;
22
use Twig\Error\RuntimeError;
23
use Twig\Error\SyntaxError;
24
use Twig\Extension\AbstractExtension;
25
use Twig\TwigFilter;
26
use Twig\TwigFunction;
27
28
final class RichEditorExtension extends AbstractExtension
29
{
30
    private RegistryInterface $uiElementRegistry;
31
32
    private Environment $twig;
33
    private string $defaultElement;
34
35
    private string $defaultElementDataField;
36
37
    /**
38
     * RichEditorExtension constructor.
39
     *
40
     * @param RegistryInterface $uiElementRegistry
41
     * @param Environment $twig
42
     * @param string $monsieurbizRicheditorDefaultElement
43
     * @param string $monsieurbizRicheditorDefaultElementDataField
44
     */
45
    public function __construct(
46
        RegistryInterface $uiElementRegistry,
47
        Environment $twig,
48
        string $monsieurbizRicheditorDefaultElement,
49
        string $monsieurbizRicheditorDefaultElementDataField
50
    ) {
51
        $this->uiElementRegistry = $uiElementRegistry;
52
        $this->twig = $twig;
53
        $this->defaultElement = $monsieurbizRicheditorDefaultElement;
54
        $this->defaultElementDataField = $monsieurbizRicheditorDefaultElementDataField;
55
    }
56
57
    /**
58
     * @return TwigFilter[]
59
     */
60
    public function getFilters(): array
61
    {
62
        return [
63
            new TwigFilter('monsieurbiz_richeditor_render_field', [$this, 'renderField'], ['is_safe' => ['html'], 'needs_context' => true]),
64
            new TwigFilter('monsieurbiz_richeditor_render_elements', [$this, 'renderElements'], ['is_safe' => ['html'], 'needs_context' => true]),
65
            new TwigFilter('monsieurbiz_richeditor_render_element', [$this, 'renderElement'], ['is_safe' => ['html'], 'needs_context' => true]),
66
        ];
67
    }
68
69
    /**
70
     * @return array|TwigFunction[]
71
     */
72
    public function getFunctions(): array
73
    {
74
        return [
75
            new TwigFunction('monsieurbiz_richeditor_list_elements', [$this, 'listUiElements'], ['is_safe' => ['html', 'js']]),
76
            new TwigFunction('monsieurbiz_richeditor_youtube_link', [$this, 'convertYoutubeEmbeddedLink'], ['is_safe' => ['html', 'js']]),
77
            new TwigFunction('monsieurbiz_richeditor_youtube_id', [$this, 'getYoutubeIdFromLink'], ['is_safe' => ['html', 'js']]),
78
            new TwigFunction('monsieurbiz_richeditor_get_elements', [$this, 'getElements'], ['is_safe' => ['html']]),
79
            new TwigFunction('monsieurbiz_richeditor_get_default_element', [$this, 'getDefaultElement'], ['is_safe' => ['html']]),
80
            new TwigFunction('monsieurbiz_richeditor_get_default_element_data_field', [$this, 'getDefaultElementDataField'], ['is_safe' => ['html']]),
81
            new TwigFunction('monsieurbiz_richeditor_get_current_file_path', [$this, 'getCurrentFilePath'], ['needs_context' => true, 'is_safe' => ['html']]),
82
        ];
83
    }
84
85
    /**
86
     * @param array $context
87
     * @param string|null $content
88
     *
89
     * @throws LoaderError
90
     * @throws RuntimeError
91
     * @throws SyntaxError
92
     *
93
     * @return string
94
     */
95
    public function renderField(array $context, ?string $content): string
96
    {
97
        if (null === $content) {
98
            return '';
99
        }
100
101
        $elements = json_decode($content, true);
102
        if (!\is_array($elements)) {
103
            return $content;
104
        }
105
106
        return $this->renderElements($context, $elements);
107
    }
108
109
    /**
110
     * @param string|null $content
111
     *
112
     * @return array
113
     */
114
    public function getElements(?string $content): array
115
    {
116
        if (null === $content) {
117
            return [];
118
        }
119
120
        $elements = json_decode($content, true);
121
        if (!\is_array($elements)) {
122
            // If the JSON decode failed, return a new UIElement with default configuration
123
            return [
124
                'type' => $this->getDefaultElement(),
125
                'data' => [$this->getDefaultElementDataField() => $content],
126
            ];
127
        }
128
129
        return $elements;
130
    }
131
132
    /**
133
     * @param array $context
134
     * @param array $elements
135
     *
136
     * @throws LoaderError
137
     * @throws RuntimeError
138
     * @throws SyntaxError
139
     *
140
     * @return string
141
     */
142
    public function renderElements(array $context, array $elements): string
143
    {
144
        $html = '';
145
        foreach ($elements as $element) {
146
            try {
147
                $html .= $this->renderElement($context, $element);
148
            } catch (UiElementNotFoundException $e) {
149
                continue;
150
            }
151
        }
152
153
        return $html;
154
    }
155
156
    /**
157
     * @param array $context
158
     * @param array $element
159
     *
160
     * @throws UiElementNotFoundException
161
     * @throws LoaderError [twig.render] When the template cannot be found
162
     * @throws SyntaxError [twig.render] When an error occurred during compilation
163
     * @throws RuntimeError [twig.render] When an error occurred during rendering
164
     *
165
     * @return string
166
     */
167
    public function renderElement(array $context, array $element): string
168
    {
169
        if (!isset($element['code'])) {
170
            if (!isset($element['type'], $element['fields'])) {
171
                throw new UiElementNotFoundException('unknown');
172
            }
173
            $element = [
174
                'code' => $element['type'],
175
                'data' => $element['fields'],
176
            ];
177
        }
178
179
        $uiElement = $this->uiElementRegistry->getUiElement($element['code']);
180
181
        if (!$uiElement->isEnabled()) {
182
            return '';
183
        }
184
185
        $template = $uiElement->getFrontRenderTemplate();
186
187
        $context = array_merge($context, [
188
            'ui_element' => $uiElement,
189
            'element' => $element['data'],
190
        ]);
191
192
        return $this->twig->render($template, $context);
193
    }
194
195
    /**
196
     * List available Ui Elements in JSON.
197
     *
198
     * @return string
199
     */
200
    public function listUiElements(): string
201
    {
202
        return (string) json_encode($this->uiElementRegistry);
203
    }
204
205
    /**
206
     * Convert Youtube link to embed URL.
207
     *
208
     * @param string $url
209
     *
210
     * @return string|null
211
     */
212
    public function convertYoutubeEmbeddedLink(string $url): ?string
213
    {
214
        if (null === $id = $this->getYoutubeIdFromLink($url)) {
215
            return null;
216
        }
217
218
        return sprintf('https://www.youtube.com/embed/%s', $id);
219
    }
220
221
    /**
222
     * Retrieve the Youtube ID from a Youtube link.
223
     *
224
     * @param string $url
225
     *
226
     * @return string|null
227
     */
228
    public function getYoutubeIdFromLink(string $url): ?string
229
    {
230
        $isValid = (bool) preg_match(YoutubeUrlValidator::YOUTUBE_REGEX_VALIDATOR, $url, $matches);
231
232
        if (!$isValid || !isset($matches[1])) {
233
            return null;
234
        }
235
236
        return $matches[1];
237
    }
238
239
    public function getDefaultElement(): string
240
    {
241
        return $this->defaultElement;
242
    }
243
244
    public function getDefaultElementDataField(): string
245
    {
246
        return $this->defaultElementDataField;
247
    }
248
249
    /**
250
     * @param array $context
251
     *
252
     * @return string|null
253
     */
254
    public function getCurrentFilePath(array $context, string $varName = 'full_name'): ?string
255
    {
256
        $form = $context['form'];
257
        $app = $context['app'];
258
        if (empty($form) || empty($app)) {
259
            return null;
260
        }
261
262
        $path = $form->vars['data'];
263
        if (!empty($app->getRequest()->get('rich_editor_uploaded_files'))) {
264
            $uploadedFile = $app->getRequest()->get('rich_editor_uploaded_files');
265
            if (null !== ($fullName = $uploadedFile[$varName] ?? null)) {
266
                if ($fullName instanceof UploadedFile) {
267
                    return null;
268
                }
269
270
                return $fullName;
271
            }
272
        }
273
274
        return $path;
275
    }
276
}
277