├── CHANGELOG-1.x.md
├── README.md
├── composer.json
└── src
├── Command
└── InstallCommand.php
├── Controller
└── MediaController.php
├── DependencyInjection
├── Configuration.php
└── LeaptFroalaEditorExtension.php
├── Form
└── Type
│ └── FroalaEditorType.php
├── LeaptFroalaEditorBundle.php
├── Resources
├── config
│ ├── routing.php
│ ├── routing.yml
│ └── services.php
├── public
│ └── misc.js
└── views
│ └── Form
│ ├── froala_widget.html.twig
│ └── value.html.twig
├── Service
├── MediaManager.php
├── OptionManager.php
└── PluginProvider.php
├── Twig
└── FroalaExtension.php
└── Utility
└── UConfiguration.php
/CHANGELOG-1.x.md:
--------------------------------------------------------------------------------
1 | 1.7.1
2 | -----
3 |
4 | * Fix DI deprecation
5 | * Test against PHP 8.4 & Symfony 7.1
6 |
7 | 1.7.0
8 | -----
9 |
10 | * Add Word Counter plugin options
11 |
12 | 1.6.0
13 | -----
14 |
15 | * Add Track changes plugin & options
16 |
17 | 1.5.0
18 | -----
19 |
20 | * Drop support for PHP < 8.2
21 | * Drop support for Symfony < 6.4
22 | * Add support for Symfony 7
23 |
24 | 1.4.0
25 | -----
26 |
27 | * File uploads: improve message when the request may be truncated
28 | * Error events: add response parameter to JS function call
29 | * Error events: allow to call different JS functions for each event
30 |
31 | 1.3.2
32 | -----
33 |
34 | * File uploads: set status code & make it fail if file upload fails for some reason
35 |
36 | 1.3.1
37 | -----
38 |
39 | * Run CI against PHP 8.2
40 | * Don't register JS function if already loaded (may happen using symfony/ux-turbo)
41 |
42 | 1.3.0
43 | -----
44 |
45 | * Test Symfony 6.1 in CI
46 | * Improve .gitattributes
47 | * Add missing options & add a test to compare options from docs with the ones in the bundle
48 |
49 | 1.2.0
50 | -----
51 |
52 | * Add PHP routing config
53 | * Rewrite services config with PHP
54 |
55 | 1.1.0
56 | -----
57 |
58 | * Drop support for Symfony < 5.4
59 | * Fix variable if customJS is defined
60 |
61 | 1.0.0
62 | -----
63 |
64 | Initial release.
65 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Leapt FroalaEditor bundle
2 |
3 | [](https://packagist.org/packages/leapt/froala-editor-bundle)
4 | [](https://github.com/leapt/froala-editor-bundle/actions?query=workflow%3A%22Continuous+Integration%22)
5 | [](https://packagist.org/packages/leapt/froala-editor-bundle)
6 | [](https://packagist.org/packages/leapt/froala-editor-bundle)
7 | [](https://packagist.org/packages/leapt/froala-editor-bundle)
8 |
9 | ## Introduction
10 |
11 | This bundle aims to easily integrate & use the Froala editor in Symfony 6.4+/7.0+.
12 |
13 | This bundle is a maintained fork of the [KMSFroalaEditorBundle](https://github.com/froala/KMSFroalaEditorBundle).
14 |
15 | The changelog is available here:
16 |
17 | * [CHANGELOG-1.x.md](CHANGELOG-1.x.md)
18 |
19 | ## Table of Contents
20 |
21 | 1. [Migration to Leapt Froala Editor bundle from KMS](#migration-to-leapt-froala-editor-bundle-from-kms)
22 | 1. [Installation](#installation)
23 | 1. [Step 1: Install the bundle using composer](#step-1-install-the-bundle-using-composer)
24 | 1. [Step 2: Add the bundle to your bundles.php](#step-2-add-the-bundle-to-your-bundlesphp)
25 | 1. [Step 3: Import routes](#step-3-import-routes)
26 | 1. [Step 4: Load Twig form widget](#step-4-load-twig-form-widget)
27 | 1. [Step 5: Configure the bundle](#step-5-configure-the-bundle)
28 | 1. [Required](#required)
29 | 1. [Other options](#other-options)
30 | 1. [Step 6: Add Froala to your form](#step-6-add-froala-to-your-form)
31 | 1. [Step 7: Install asset files](#step-7-install-asset-files)
32 | 1. [Step 8: Display editor content](#step-8-display-editor-content)
33 | 1. [Manually](#manually)
34 | 1. [Using the Twig extension](#using-the-twig-extension)
35 | 1. [Step 9: Profiles (custom configurations)](#step-9-profiles-custom-configurations)
36 | 1. [More configuration](#more-configuration)
37 | 1. [Plugins](#plugins)
38 | 1. [Concept: Image upload/manager](#concept-image-uploadmanager)
39 | 1. [Concept: File upload](#concept-file-upload)
40 | 1. [Concept: Autosave](#concept-auto-save)
41 | 1. [Webpack Encore configuration](#webpack-encore-configuration)
42 | 1. [TODO](#todo)
43 | 1. [Licence](#licence)
44 | 1. [Contributing](#contributing)
45 |
46 | ## Migration to Leapt Froala Editor bundle from KMS
47 |
48 | It now supports only Symfony 6.4+ & 7.0+, and PHP >= 8.2.
49 | Symfony 5.3 is supported in v1.0.0, but its support has been dropped in v1.1.0.
50 | Symfony < 6.4 is supported in v1.4.0, but its support has been dropped in v1.5.0.
51 |
52 | Replace occurrences of "kms" by "leapt" everywhere (matching case: `KMS` becomes `Leapt` & `kms` becomes `leapt`).
53 |
54 | ## Available demo
55 |
56 | If you want to try the bundle before installing it in your own projects, you can run this demo project locally:
57 | https://github.com/leapt/demo
58 |
59 | ## Installation
60 |
61 | ### Step 1: Install the bundle using composer
62 |
63 | ```bash
64 | composer require leapt/froala-editor-bundle
65 | ```
66 |
67 | Note: if you install the bundle using Symfony Flex & accepted the recipe, you can skip steps 2 to 4.
68 |
69 | ### Step 2: Add the bundle to your bundles.php
70 |
71 | ```php
72 | // config/bundles.php
73 | return [
74 | //..
75 | Leapt\FroalaEditorBundle\LeaptFroalaEditorBundle::class => ['all' => true],
76 | ];
77 | ```
78 |
79 | ### Step 3: Import routes
80 |
81 | ```yaml
82 | # config/routes.yaml
83 | leapt_froala_editor:
84 | resource: '@LeaptFroalaEditorBundle/Resources/config/routing.php'
85 | prefix: /froalaeditor
86 | ```
87 |
88 | ### Step 4: Load Twig form widget
89 |
90 | ```yaml
91 | # In config/packages/twig.yaml
92 | twig:
93 | form_themes:
94 | - '@LeaptFroalaEditor/Form/froala_widget.html.twig'
95 | ```
96 |
97 | ### Step 5: Configure the bundle
98 |
99 | #### Required
100 |
101 | First, you have to select your language, other settings are optional (see below).
102 |
103 | ```yaml
104 | # config/packages/leapt_froala_editor.yaml
105 | leapt_froala_editor:
106 | language: 'nl'
107 | ```
108 |
109 | #### Other options
110 |
111 | All Froala options ([list provided here](https://www.froala.com/wysiwyg-editor/docs/options)) are supported.
112 | Just add the option name (prefixed with `froala_` if it's in your form type) with your value.
113 | If you want to keep Froala default value, don't provide anything in your config file.
114 | For options which require an array, provide a value array.
115 | For options which require an object, provide a key/value array.
116 |
117 | Note that some options need some plugins (all information provided in the [Froala documentation](https://www.froala.com/wysiwyg-editor/docs/options)).
118 |
119 | Example for each option type below:
120 |
121 | ```yaml
122 | # config/packages/leapt_froala_editor.yaml
123 | leapt_froala_editor:
124 | toolbarInline: true
125 | tableColors: [ '#FFFFFF', '#FF0000' ]
126 | saveParams: { "id" : "myEditorField" }
127 | ```
128 |
129 | To provide a better integration with Symfony, some custom options are added, see the full list below:
130 |
131 | ```yaml
132 | # config/packages/leapt_froala_editor.yaml
133 | leapt_froala_editor:
134 | # Froala licence number if you want to use a purchased licence.
135 | serialNumber: 'XXXX-XXXX-XXXX'
136 |
137 | # Disable CodeMirror inclusion.
138 | includeCodeMirror: false
139 |
140 | # Disable Font Awesome inclusion.
141 | includeFontAwesome: false
142 |
143 | # Disable all bundle JavaScript inclusion (not concerning CodeMirror).
144 | # Usage: if you are using Grunt or Webpack or other, and you want to include yourself all scripts.
145 | includeJS: false
146 |
147 | # Disable all bundle CSS inclusion (not concerning Font Awesome nor CodeMirror).
148 | # Usage: if you are using Grunt or Webpack or other, and you want to include yourself all stylesheets.
149 | includeCSS: false
150 |
151 | # Change the froala base path.
152 | # Useful eg. when you load it from your own public directory.
153 | # Defaults to "/bundles/leaptfroalaeditor/froala_editor"
154 | basePath: '/my/custom/path'
155 |
156 | # Custom JS file.
157 | # Usage: add custom plugins/buttons...
158 | customJS: '/custom/js/path'
159 | ```
160 |
161 | ### Step 6: Add Froala to your form
162 |
163 | Just add a Froala type in your form:
164 |
165 | ```php
166 | use Leapt\FroalaEditorBundle\Form\Type\FroalaEditorType;
167 |
168 | $builder->add('field', FroalaEditorType::class);
169 | ```
170 |
171 | All configuration items can be overridden:
172 |
173 | ```php
174 | $builder->add('field', FroalaEditorType::class, [
175 | 'froala_language' => 'fr',
176 | 'froala_toolbarInline' => true,
177 | 'froala_tableColors' => ['#FFFFFF', '#FF0000'],
178 | 'froala_saveParams' => ['id' => 'myEditorField'],
179 | ]);
180 | ```
181 |
182 | ### Step 7: Install asset files
183 |
184 | To install the asset files, there is `froala:install` command that downloads the last version available of Froala Editor
185 | and puts it by default in the `vendor/leapt/froala-editor-bundle/src/Resources/public/froala_editor/` directory:
186 |
187 | ```bash
188 | bin/console froala:install
189 | ```
190 |
191 | There are a few arguments/options available:
192 |
193 | * First (and only) argument (optional): the absolute path where the files will be put after download.
194 | Defaults to `vendor/leapt/froala-editor-bundle/src/Resources/public/froala_editor/`.
195 | * Option `tag`: the version of Froala that will be installed (eg. `v4.0.1`). Defaults to `master`.
196 | * Option `clear` (no value expected, disabled by default): Allow the command to clear a previous install if the path already exists.
197 |
198 | After you launched the install command, you have to link assets, eg.:
199 |
200 | ```bash
201 | bin/console assets:install --symlink public
202 | ```
203 |
204 | ### Step 8: Display editor content
205 |
206 | #### Manually
207 |
208 | To preserve the look of the edited HTML outside of the editor you have to include the following CSS files:
209 |
210 | ```twig
211 |
212 |
213 | ```
214 |
215 | Also, you should make sure that you put the edited content inside an element that has the class fr-view:
216 |
217 | ```twig
218 |
219 | {{ myContentHtml|raw }}
220 |
221 | ```
222 |
223 | #### Using the Twig extension
224 |
225 | To use the Twig extension, simply call the display function (note that the front CSS file is not included
226 | if the parameter includeCSS is false):
227 |
228 | ```twig
229 | {{ froala_display(myContentHtml) }}
230 | ```
231 |
232 | ### Step 9: Profiles (custom configurations)
233 |
234 | You can define several configuration profiles that will be reused in your forms, without repeating these configurations.
235 |
236 | When using a profile, the root configuration options will be used & overridden:
237 |
238 | ```yaml
239 | # config/packages/leapt_froala_editor.yaml
240 | leapt_froala_editor:
241 | heightMax: 400
242 | attribution: false
243 | profiles:
244 | profile_1:
245 | heightMax: 500
246 | ```
247 |
248 | ```php
249 | use Leapt\FroalaEditorBundle\Form\Type\FroalaEditorType;
250 |
251 | $builder->add('field', FroalaEditorType::class, [
252 | 'froala_profile' => 'profile_1',
253 | ]);
254 | ```
255 |
256 | In this example, `profile_1` profile will have these configuration options set:
257 |
258 | * `heightMax`: 500
259 | * `attribution`: false
260 |
261 | ## More configuration
262 |
263 | ### Plugins
264 |
265 | All [Froala plugins](https://www.froala.com/wysiwyg-editor/docs/plugins) are enabled, but if you don't need one of them, you can disable some plugins...
266 |
267 | ```yaml
268 | # config/packages/leapt_froala_editor.yaml
269 | leapt_froala_editor:
270 | # Disable some plugins.
271 | pluginsDisabled: [ 'save', 'fullscreen' ]
272 | ```
273 | ... or chose only plugins to enable:
274 |
275 | ```yaml
276 | # config/packages/leapt_froala_editor.yaml
277 | leapt_froala_editor:
278 | # Enable only some plugins.
279 | pluginsEnabled: [ 'image', 'file' ]
280 | ```
281 |
282 | Plugins can be enabled/disabled for each Froala instance by passing the same array in the form builder.
283 |
284 | ### Concept: Image upload/manager
285 |
286 | This bundle provides an integration of the [Froala image upload concept](https://www.froala.com/wysiwyg-editor/docs/concepts/image/upload) to store your images on your own web server (see custom options for configuration like upload folder).
287 |
288 | If you want to use your own uploader, you can override the configuration (if you need to do that, please explain me why to improve the provided uploader).
289 |
290 | To provide a better integration with Symfony, some custom options are added, see the full list below:
291 |
292 | ```yaml
293 | # config/packages/leapt_froala_editor.yaml
294 | leapt_froala_editor:
295 | # The image upload folder in your /web directory.
296 | # Default: "/upload".
297 | imageUploadFolder: '/my/upload/folder'
298 |
299 | # The image upload URL base.
300 | # Usage: if you are using URL rewriting for your assets.
301 | # Default: same value as provided as folder.
302 | imageUploadPath: '/my/upload/path'
303 | ```
304 |
305 | ### Concept: File upload
306 |
307 | This bundle provides an integration of the [Froala file upload concept](https://www.froala.com/wysiwyg-editor/docs/concepts/file/upload) to store your files on your own web server (see custom options for configuration like upload folder).
308 |
309 | If you want to use your own uploader, you can override the configuration (if you need to do that, please explain me why to improve the provided uploader).
310 |
311 | To provide a better integration with Symfony, some custom options are added, see the full list below:
312 |
313 | ```yaml
314 | # config/packages/leapt_froala_editor.yaml
315 | leapt_froala_editor:
316 | # The file upload folder in your /web directory.
317 | # Default: "/upload".
318 | fileUploadFolder: '/my/upload/folder'
319 |
320 | # The file upload URL base.
321 | # Usage: if you are using URL rewritting for your assets.
322 | # Default: same value as provided as folder.
323 | fileUploadPath: '/my/upload/path'
324 |
325 | # Your public directory, from the root directory.
326 | # Default: "/public"
327 | publicDir: '/home'
328 | ```
329 |
330 | ### Concept: Auto-save
331 |
332 | The [Froala auto-save concept](https://www.froala.com/wysiwyg-editor/docs/concepts/save/autosave) to automatically request a save action on your server is working, just enter the correct options in your configuration file:
333 |
334 | ```yaml
335 | # config/packages/leapt_froala_editor.yaml
336 | leapt_froala_editor:
337 | saveURL: 'my_save_route'
338 | saveInterval: 2500
339 | saveParam: "content"
340 | ```
341 |
342 | To provide a better integration with Symfony, some custom options are added, see the full list below:
343 |
344 | ```yaml
345 | # config/packages/leapt_froala_editor.yaml
346 | leapt_froala_editor:
347 | # Add some parameters to your save URL.
348 | # Usage: if you need parameters to generate your save action route (see save explaination below).
349 | # Default: null.
350 | saveURLParams: { "id" : "myId" }
351 | ```
352 |
353 | You can add some parameters in your save route (see custom options).
354 |
355 | ### Webpack Encore configuration
356 |
357 | If you want to load Froala asset files using npm/yarn and Webpack Encore, here's how to do it:
358 |
359 | ```javascript
360 | import FroalaEditor from 'froala-editor';
361 | import 'froala-editor/css/froala_editor.pkgd.min.css';
362 | import 'froala-editor/css/froala_style.min.css';
363 |
364 | // Load your languages
365 | import 'froala-editor/js/languages/fr.js';
366 |
367 | // Load all plugins, or specific ones
368 | import 'froala-editor/js/plugins.pkgd.min.js';
369 | import 'froala-editor/css/plugins.pkgd.min.css';
370 |
371 | window.FroalaEditor = FroalaEditor;
372 |
373 | /**
374 | * @param editor Editor instance
375 | * @param error Error object generated by Froala
376 | * @param response Response object coming from the server
377 | */
378 | function froalaDisplayError(editor, error, response) {
379 | alert(`Error ${error.code}: ${error.message}`);
380 | }
381 |
382 | window.froalaDisplayError = froalaDisplayError;
383 | ```
384 |
385 | Instead of defining a generic `froalaDisplayError` function, you can also define one per error event, using these
386 | function names:
387 |
388 | * `froalaImageErrorEventHandler`
389 | * `froalaImageManagerErrorEventHandler`
390 | * `froalaSaveErrorEventHandler`
391 | * `froalaFileErrorEventHandler`
392 | * `froalaVideoErrorEventHandler`
393 |
394 | Now you can disable Froala bundle CSS/JS inclusion:
395 |
396 | ```yaml
397 | # config/packages/leapt_froala_editor.yaml
398 | leapt_froala_editor:
399 | includeJS: false
400 | includeCSS: false
401 | ```
402 |
403 | Don't forget to import the generated Encore CSS/JS files in your HTML if needed.
404 |
405 | ## TODO
406 |
407 | - Add some tests
408 |
409 | ## Licence
410 |
411 | This bundle provides an integration of the WYSIWYG [Froala Editor](https://www.froala.com/wysiwyg-editor) commercial version.
412 | Please read the [Froala licence agreement](https://www.froala.com/wysiwyg-editor/terms) and go to the [pricing page](https://www.froala.com/wysiwyg-editor/pricing)
413 | if you don't have a licence.
414 |
415 | ## Contributing
416 |
417 | Feel free to contribute, like sending [pull requests](https://github.com/leapt/froala-editor-bundle/pulls) to add features/tests.
418 |
419 | Note there are a few helpers to maintain code quality, that you can run using these commands:
420 |
421 | ```bash
422 | composer cs:dry # Code style check
423 | composer phpstan # Static analysis
424 | vendor/bin/phpunit # Run tests
425 | ```
426 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "leapt/froala-editor-bundle",
3 | "type": "symfony-bundle",
4 | "description": "Provides a Froala editor integration for Symfony 6 & 7.",
5 | "keywords": [ "froala", "editor", "wysiwyg" ],
6 | "license": "MIT",
7 | "authors": [
8 | {
9 | "name": "Simon Guionniere",
10 | "email": "simon.guionniere@gmail.com"
11 | },
12 | {
13 | "name": "Stefan Neculai",
14 | "email": "stefan.neculai@gmail.com"
15 | },
16 | {
17 | "name": "Jonathan Scheiber",
18 | "homepage": "https://github.com/jmsche"
19 | }
20 | ],
21 | "require": {
22 | "php": "^8.2",
23 | "ext-zip": "*",
24 | "symfony/asset": "^6.4 || ^7.0",
25 | "symfony/console": "^6.4 || ^7.0",
26 | "symfony/framework-bundle": "^6.4 || ^7.0",
27 | "symfony/form": "^6.4 || ^7.0",
28 | "symfony/http-client": "^6.4 || ^7.0",
29 | "symfony/string": "^6.4 || ^7.0",
30 | "symfony/twig-bundle": "^6.4 || ^7.0",
31 | "twig/twig": "^3.0"
32 | },
33 | "require-dev": {
34 | "friendsofphp/php-cs-fixer": "^3.64.0",
35 | "phpstan/phpstan": "^1.12.5",
36 | "phpstan/phpstan-deprecation-rules": "^1.2.1",
37 | "phpunit/phpunit": "^9.6.16",
38 | "symfony/css-selector": "^6.4 || ^7.0",
39 | "symfony/dom-crawler": "^6.4 || ^7.0",
40 | "symfony/yaml": "^6.4 || ^7.0"
41 | },
42 | "scripts": {
43 | "ci": [
44 | "@cs:dry",
45 | "@phpstan",
46 | "vendor/bin/phpunit --colors=auto"
47 | ],
48 | "cs:dry": "php-cs-fixer fix --diff --dry-run --no-interaction --ansi",
49 | "cs:fix": "php-cs-fixer fix --ansi",
50 | "phpstan": "vendor/bin/phpstan analyse --ansi"
51 | },
52 | "autoload": {
53 | "psr-4": {
54 | "Leapt\\FroalaEditorBundle\\": "src/"
55 | }
56 | },
57 | "autoload-dev": {
58 | "psr-4": {
59 | "Leapt\\FroalaEditorBundle\\Tests\\": "tests/"
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Command/InstallCommand.php:
--------------------------------------------------------------------------------
1 | setName('froala:install')
21 | ->addArgument('path', InputArgument::OPTIONAL, 'Absolute path where to install Froala editor', \dirname(__DIR__) . '/Resources/public/froala_editor')
22 | ->addOption('tag', null, InputOption::VALUE_REQUIRED, 'Froala editor tag to install (eg. "v4.0.1")', 'master')
23 | ->addOption('clear', null, InputOption::VALUE_NONE, 'Allow the command to clear a previous install if the path already exists')
24 | ;
25 | }
26 |
27 | protected function execute(InputInterface $input, OutputInterface $output): int
28 | {
29 | $zipPath = $this->downloadZip($output, $input->getOption('tag'));
30 | $this->extractZip($output, $zipPath, $input->getArgument('path'), $input->getOption('clear'));
31 |
32 | return 0;
33 | }
34 |
35 | private function downloadZip(OutputInterface $output, string $tag): string
36 | {
37 | $output->write('Downloading Froala Editor...');
38 | $httpClient = HttpClient::create();
39 | $zip = $httpClient->request('GET', 'https://github.com/froala/wysiwyg-editor/archive/' . $tag . '.zip')->getContent();
40 |
41 | $path = (string) tempnam(sys_get_temp_dir(), 'froala-' . $tag . '.zip');
42 | if (!@file_put_contents($path, $zip)) {
43 | throw new \RuntimeException(\sprintf('Unable to write Froala ZIP archive to "%s".', $path));
44 | }
45 |
46 | $output->writeln(' Ok.');
47 |
48 | return $path;
49 | }
50 |
51 | private function extractZip(OutputInterface $output, string $zipPath, string $outputPath, bool $clear): void
52 | {
53 | $output->write('Extracting zip file...');
54 |
55 | $fileSystem = new Filesystem();
56 | $fileSystem->exists($outputPath);
57 |
58 | if ($fileSystem->exists($outputPath) && !$clear) {
59 | $output->writeln(\sprintf("\nThe directory \"%s\" already exists and the clear option is not enabled, aborting.", $outputPath));
60 | } else {
61 | if (is_dir($outputPath)) {
62 | $fileSystem->remove($outputPath);
63 | }
64 | $fileSystem->mkdir($outputPath);
65 |
66 | $zip = new \ZipArchive();
67 | if (true !== $zip->open($zipPath)) {
68 | throw new \RuntimeException(\sprintf('Cannot open zip file "%s".', $zipPath));
69 | }
70 | for ($i = 0; $i < $zip->numFiles; ++$i) {
71 | $filename = $zip->getNameIndex($i);
72 | $zipFile = \sprintf('zip://%s#%s', $zipPath, $filename);
73 | // Remove the first directory (eg. "wysiwyg-editor-master") from the file path
74 | $explodedPath = explode('/', $filename, 2);
75 | $realFilePath = $explodedPath[1];
76 |
77 | if (str_ends_with($filename, '/')) {
78 | $fileSystem->mkdir($outputPath . '/' . $realFilePath);
79 | } else {
80 | copy($zipFile, $outputPath . '/' . $realFilePath);
81 | }
82 | }
83 | $zip->close();
84 |
85 | $output->writeln(' Ok.');
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/Controller/MediaController.php:
--------------------------------------------------------------------------------
1 | request->get('path', '');
22 | $folder = $request->request->get('folder', '');
23 | $rootDir = $this->kernel->getProjectDir();
24 | $publicDir = $request->request->get('public_dir', '');
25 | $basePath = $request->getBasePath();
26 |
27 | return $this->mediaManager->uploadImage($request->files, $rootDir, $publicDir, $basePath, $folder, $path);
28 | }
29 |
30 | public function deleteImage(Request $request): Response
31 | {
32 | $imageSrc = urldecode($request->request->get('src'));
33 | $folder = urldecode($request->request->get('folder'));
34 | $rootDir = $this->kernel->getProjectDir();
35 | $publicDir = urldecode($request->request->get('public_dir'));
36 |
37 | $this->mediaManager->deleteImage($imageSrc, $rootDir, $publicDir, $folder);
38 |
39 | return new Response();
40 | }
41 |
42 | public function loadImages(Request $request): JsonResponse
43 | {
44 | $path = $request->query->get('path');
45 | $folder = $request->query->get('folder');
46 | $rootDir = $this->kernel->getProjectDir();
47 | $publicDir = $request->query->get('public_dir');
48 | $basePath = $request->getBasePath();
49 |
50 | return $this->mediaManager->loadImages($rootDir, $publicDir, $basePath, $folder, $path);
51 | }
52 |
53 | public function uploadFile(Request $request): JsonResponse
54 | {
55 | $path = $request->request->get('path', '');
56 | $folder = $request->request->get('folder', '');
57 | $rootDir = $this->kernel->getProjectDir();
58 | $publicDir = $request->request->get('public_dir', '');
59 | $basePath = $request->getBasePath();
60 |
61 | return $this->mediaManager->uploadFile($request->files, $rootDir, $publicDir, $basePath, $folder, $path);
62 | }
63 |
64 | public function uploadVideo(Request $request): JsonResponse
65 | {
66 | $path = $request->request->get('path', '');
67 | $folder = $request->request->get('folder', '');
68 | $rootDir = $this->kernel->getProjectDir();
69 | $publicDir = $request->request->get('public_dir', '');
70 | $basePath = $request->getBasePath();
71 |
72 | return $this->mediaManager->uploadVideo($request->files, $rootDir, $publicDir, $basePath, $folder, $path);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/DependencyInjection/Configuration.php:
--------------------------------------------------------------------------------
1 | getRootNode();
22 |
23 | // Add all available configuration nodes.
24 | $nodeBuilder = $rootNode->addDefaultsIfNotSet()->children();
25 | $this->addFroalaConfigTree($nodeBuilder);
26 |
27 | // "profiles" are treated separately as they repeat main option structures
28 | /** @var ArrayNodeDefinition $profileSubTreeBuilder */
29 | $profileSubTreeBuilder = $nodeBuilder
30 | ->arrayNode('profiles')
31 | ->useAttributeAsKey('name')
32 | ->prototype('array');
33 | $childrenProfileSubTreeBuilder = $profileSubTreeBuilder->children();
34 | $this->addFroalaConfigTree($childrenProfileSubTreeBuilder, false);
35 | // End profiles' children node
36 | $profileSubTreeBuilder->end();
37 | // End profiles node
38 | $profileSubTreeBuilder->end();
39 | $nodeBuilder->end();
40 |
41 | return $treeBuilder;
42 | }
43 |
44 | /**
45 | * Add all options to configuration subtree.
46 | */
47 | private function addFroalaConfigTree(NodeBuilder $nodeBuilder, bool $addDefaultValue = true): void
48 | {
49 | UConfiguration::addArrOptionBoolean($nodeBuilder, $addDefaultValue);
50 | UConfiguration::addArrOptionInteger($nodeBuilder, $addDefaultValue);
51 | UConfiguration::addArrOptionString($nodeBuilder, $addDefaultValue);
52 | UConfiguration::addArrOptionArray($nodeBuilder, $addDefaultValue);
53 | UConfiguration::addArrOptionObject($nodeBuilder, $addDefaultValue);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/DependencyInjection/LeaptFroalaEditorExtension.php:
--------------------------------------------------------------------------------
1 | processConfiguration($configuration, $configs);
19 |
20 | $loader = new Loader\PhpFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
21 | $loader->load('services.php');
22 |
23 | $this->loadConfig($container, $arrConfig);
24 | }
25 |
26 | private function loadConfig(ContainerBuilder $container, array $arrConfig): void
27 | {
28 | // Load defined options in config file.
29 | foreach (UConfiguration::getArrOptionAll() as $option) {
30 | if (false === empty($arrConfig[$option])
31 | || false === $arrConfig[$option]
32 | || 0 === $arrConfig[$option]
33 | ) {
34 | $container->setParameter(Configuration::NODE_ROOT . '.' . $option, $arrConfig[$option]);
35 | }
36 | }
37 |
38 | $parameterProfiles = [];
39 |
40 | foreach ($arrConfig['profiles'] as $key => $profile) {
41 | $parameterProfiles[$key] = [];
42 | foreach ($profile as $optionKey => $optionValue) {
43 | if (false === empty($optionValue)
44 | || false === $optionValue
45 | || 0 === $optionValue
46 | ) {
47 | $parameterProfiles[$key][$optionKey] = $optionValue;
48 | }
49 | }
50 | }
51 |
52 | $container->setParameter(Configuration::NODE_ROOT . '.profiles', $parameterProfiles);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Form/Type/FroalaEditorType.php:
--------------------------------------------------------------------------------
1 | $option) {
31 | if (!str_starts_with($key, 'froala_')) {
32 | continue;
33 | }
34 | $builder->setAttribute($key, $option);
35 | }
36 | }
37 |
38 | public function buildView(FormView $view, FormInterface $form, array $options): void
39 | {
40 | $options = array_filter($options, static function ($key) {
41 | return str_starts_with($key, 'froala_');
42 | }, \ARRAY_FILTER_USE_KEY);
43 |
44 | $arrKey = UConfiguration::getArrOption();
45 | $arrKeyCustom = UConfiguration::getArrOptionCustom();
46 | $arrOption = [];
47 | $arrPluginEnabled = $options['froala_pluginsEnabled'] ?? [];
48 | $arrPluginDisabled = $options['froala_pluginsDisabled'] ?? [];
49 | $arrEvent = $options['froala_events'] ?? [];
50 | $profile = $options['froala_profile'] ?? null;
51 |
52 | if ($profile && $this->parameterBag->has(Configuration::NODE_ROOT . '.profiles')) {
53 | $profiles = $this->parameterBag->get(Configuration::NODE_ROOT . '.profiles');
54 |
55 | if (\array_key_exists($profile, $profiles)) {
56 | $profileConfig = $profiles[$profile];
57 | foreach ($profileConfig as $profileKey => $profileOption) {
58 | $options['froala_' . $profileKey] = $profileOption;
59 | }
60 | } else {
61 | throw new \InvalidArgumentException(\sprintf('Unrecognized profile "%s". Available profiles are "%s".', $profile, implode('"", "', array_keys($profiles))));
62 | }
63 | }
64 |
65 | $finalOptions = [];
66 | foreach ($options as $key => $value) {
67 | $finalOptions[substr($key, \strlen('froala_'))] = $value;
68 | }
69 |
70 | $this->optionManager->prepareOptions($finalOptions);
71 |
72 | // Separate Froala options from custom, to iterate properly in twig widget.
73 | foreach ($finalOptions as $key => $option) {
74 | if (\in_array($key, $arrKey, true)) {
75 | $arrOption[$key] = $option;
76 | } elseif (\in_array($key, $arrKeyCustom, true)) {
77 | $view->vars['froala_' . $key] = $option;
78 | }
79 | }
80 |
81 | $view->vars['froala_arrOption'] = $arrOption;
82 |
83 | $arrPlugin = $this->pluginProvider->obtainArrPluginToInclude($arrPluginEnabled, $arrPluginDisabled);
84 |
85 | $view->vars['froala_arrOption']['pluginsEnabled'] = array_map(fn (string $plugin): string => 'trackChanges' === $plugin ? 'track_changes' : $plugin, $this->pluginProvider->obtainArrPluginCamelized($arrPlugin));
86 | $view->vars['froala_arrPluginJS'] = $this->pluginProvider->obtainArrPluginJS($arrPlugin);
87 | $view->vars['froala_arrPluginCSS'] = $this->pluginProvider->obtainArrPluginCSS($arrPlugin);
88 | $view->vars['froala_events'] = $arrEvent;
89 | }
90 |
91 | public function configureOptions(OptionsResolver $resolver): void
92 | {
93 | $arrDefault = [];
94 | $arrDefined = [];
95 |
96 | foreach (UConfiguration::getArrOptionAll() as $option) {
97 | $optionName = 'froala_' . $option;
98 | // If defined in config file, set default value to form, else set option as available.
99 | if ($this->parameterBag->has(Configuration::NODE_ROOT . '.' . $option)) {
100 | $arrDefault[$optionName] = $this->parameterBag->get(Configuration::NODE_ROOT . '.' . $option);
101 | } else {
102 | $arrDefined[] = $optionName;
103 | }
104 | }
105 |
106 | $arrDefined[] = 'froala_profile';
107 | $resolver->setDefined($arrDefined);
108 | $resolver->setDefaults($arrDefault);
109 | }
110 |
111 | public function getParent(): string
112 | {
113 | return TextareaType::class;
114 | }
115 |
116 | public function getBlockPrefix(): string
117 | {
118 | return 'froala';
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/LeaptFroalaEditorBundle.php:
--------------------------------------------------------------------------------
1 | add('leapt_froala_editor_upload_image', '/upload_image')
11 | ->controller([MediaController::class, 'uploadImage']);
12 |
13 | $routes->add('leapt_froala_editor_delete_image', '/delete_image')
14 | ->controller([MediaController::class, 'deleteImage']);
15 |
16 | $routes->add('leapt_froala_editor_load_images', '/load_images')
17 | ->controller([MediaController::class, 'loadImages']);
18 |
19 | // File upload
20 | $routes->add('leapt_froala_editor_upload_file', '/upload_file')
21 | ->controller([MediaController::class, 'uploadFile']);
22 |
23 | // Video upload
24 | $routes->add('leapt_froala_editor_upload_video', '/upload_video')
25 | ->controller([MediaController::class, 'uploadVideo']);
26 | };
27 |
--------------------------------------------------------------------------------
/src/Resources/config/routing.yml:
--------------------------------------------------------------------------------
1 | # Image upload
2 | leapt_froala_editor_upload_image:
3 | path: /upload_image
4 | controller: Leapt\FroalaEditorBundle\Controller\MediaController::uploadImage
5 |
6 | leapt_froala_editor_delete_image:
7 | path: /delete_image
8 | controller: Leapt\FroalaEditorBundle\Controller\MediaController::deleteImage
9 |
10 | leapt_froala_editor_load_images:
11 | path: /load_images
12 | controller: Leapt\FroalaEditorBundle\Controller\MediaController::loadImages
13 |
14 | # File upload
15 | leapt_froala_editor_upload_file:
16 | path: /upload_file
17 | controller: Leapt\FroalaEditorBundle\Controller\MediaController::uploadFile
18 |
19 | # Video upload
20 | leapt_froala_editor_upload_video:
21 | path: /upload_video
22 | controller: Leapt\FroalaEditorBundle\Controller\MediaController::uploadVideo
23 |
--------------------------------------------------------------------------------
/src/Resources/config/services.php:
--------------------------------------------------------------------------------
1 | services()
18 | // Commands
19 | ->set(InstallCommand::class)
20 | ->tag('console.command', ['command' => 'froala:install'])
21 |
22 | // Controllers
23 | ->set(MediaController::class)
24 | ->arg('$mediaManager', service('leapt_froala_editor.media_manager'))
25 | ->arg('$kernel', service('kernel'))
26 | ->public()
27 |
28 | // Form types
29 | ->set('leapt_froala_editor.form.type')
30 | ->class(FroalaEditorType::class)
31 | ->arg('$parameterBag', service('parameter_bag'))
32 | ->arg('$optionManager', service('leapt_froala_editor.option_manager'))
33 | ->arg('$pluginProvider', service('leapt_froala_editor.plugin_provider'))
34 | ->tag('form.type')
35 |
36 | // Managers/providers
37 | ->set('leapt_froala_editor.option_manager')
38 | ->class(OptionManager::class)
39 | ->arg('$router', service('router'))
40 |
41 | ->set('leapt_froala_editor.plugin_provider')
42 | ->class(PluginProvider::class)
43 |
44 | ->set('leapt_froala_editor.media_manager')
45 | ->class(MediaManager::class)
46 | ->public()
47 |
48 | // Twig extensions
49 | ->set('leapt_froala_editor.froala_extension')
50 | ->class(FroalaExtension::class)
51 | ->arg('$parameterBag', service('parameter_bag'))
52 | ->arg('$packages', service('assets.packages'))
53 | ->tag('twig.extension')
54 | ;
55 | };
56 |
--------------------------------------------------------------------------------
/src/Resources/public/misc.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @param editor Editor instance
3 | * @param error Error object generated by Froala
4 | * @param response Response object coming from the server
5 | */
6 | function froalaDisplayError(editor, error, response) {
7 | alert(`Error ${error.code}: ${error.message}`);
8 | }
9 |
--------------------------------------------------------------------------------
/src/Resources/views/Form/froala_widget.html.twig:
--------------------------------------------------------------------------------
1 |
2 | {% block froala_widget %}
3 |
4 | {# CSS. #}
5 | {% if froala_includeFontAwesome %}
6 |
7 | {% endif %}
8 | {% if froala_includeCodeMirror %}
9 |
10 | {% endif %}
11 |
12 | {% if froala_includeCSS %}
13 |
14 |
15 |
16 | {% if froala_arrOption[ "theme" ] is defined %}
17 |
18 | {% endif %}
19 |
20 | {% for plugin in froala_arrPluginCSS %}
21 |
22 | {% endfor %}
23 |
24 | {% endif %}
25 |
26 | {# Editor textarea. #}
27 |
28 |
29 | {# JS. #}
30 |
31 | {% if froala_includeCodeMirror %}
32 |
33 |
34 | {% endif %}
35 |
36 | {% if froala_includeJS %}
37 |
38 |
39 |
40 |
43 |
44 |
45 | {% for plugin in froala_arrPluginJS %}
46 |
47 | {% endfor %}
48 | {% endif %}
49 |
50 | {% if froala_customJS is defined %}
51 |
52 | {% endif %}
53 |
54 | {# Load the editor. #}
55 |
117 |
118 | {% endblock %}
119 |
--------------------------------------------------------------------------------
/src/Resources/views/Form/value.html.twig:
--------------------------------------------------------------------------------
1 | {% if value is iterable %}JSON.parse( "{{ value | json_encode | raw | escape('js') }}" ),
2 | {% elseif value is same as(true) %}true,
3 | {% elseif value is same as(false) %}false,
4 | {% elseif value matches '/^\\d+$/' %}{{ value }},
5 | {% elseif option == 'fontAwesomeTemplate' %}"{{ value|escape('js')|raw }}",
6 | {% else %}"{{ value }}",
7 | {% endif %}
--------------------------------------------------------------------------------
/src/Service/MediaManager.php:
--------------------------------------------------------------------------------
1 | handleFileUpload($fileBag, $rootDir, $publicDir, $basePath, $configuredFolder, $requestPath, 'image');
20 | }
21 |
22 | public function deleteImage(string $imageSrc, string $rootDir, string $publicDir, string $configuredFolder): void
23 | {
24 | $folder = $this->obtainFolder($rootDir, $publicDir, $configuredFolder);
25 | $arrExploded = explode('/', $imageSrc);
26 |
27 | $fileName = $arrExploded[\count($arrExploded) - 1];
28 | unlink($folder . '/' . $fileName);
29 | }
30 |
31 | public function loadImages(string $rootDir, string $publicDir, string $basePath, string $configuredFolder, string $requestPath): JsonResponse
32 | {
33 | $response = new JsonResponse();
34 | $arrImage = [];
35 | $folder = $this->obtainFolder($rootDir, $publicDir, $configuredFolder);
36 | $path = $this->obtainPath($basePath, $requestPath);
37 | $finder = new Finder();
38 |
39 | $finder->files()->in($folder);
40 |
41 | foreach ($finder as $file) {
42 | if (!\in_array($file->getExtension(), self::ALLOWED_IMAGE_FILE_EXTENSIONS, true)) {
43 | continue;
44 | }
45 | $arrImage[] = ['url' => $path . $file->getFilename(), 'thumb' => $path . $file->getFilename()];
46 | }
47 |
48 | $response->setData($arrImage);
49 |
50 | return $response;
51 | }
52 |
53 | public function uploadFile(FileBag $fileBag, string $rootDir, string $publicDir, string $basePath, string $configuredFolder, string $requestPath): JsonResponse
54 | {
55 | return $this->handleFileUpload($fileBag, $rootDir, $publicDir, $basePath, $configuredFolder, $requestPath);
56 | }
57 |
58 | public function uploadVideo(FileBag $fileBag, string $rootDir, string $publicDir, string $basePath, string $configuredFolder, string $requestPath): JsonResponse
59 | {
60 | return $this->handleFileUpload($fileBag, $rootDir, $publicDir, $basePath, $configuredFolder, $requestPath);
61 | }
62 |
63 | /**
64 | * Obtain the physical folder.
65 | */
66 | private function obtainFolder(string $rootDir, string $publicDir, string $folder): string
67 | {
68 | return \sprintf('%s%s/%s', $rootDir, $publicDir, $folder);
69 | }
70 |
71 | private function obtainPath(string $basePath, string $path): string
72 | {
73 | return $basePath . '/' . $path;
74 | }
75 |
76 | private function handleFileUpload(FileBag $fileBag, string $rootDir, string $publicDir, string $basePath, string $configuredFolder, string $requestPath, ?string $specificType = null): JsonResponse
77 | {
78 | $folder = $this->obtainFolder($rootDir, $publicDir, $configuredFolder);
79 | $path = $this->obtainPath($basePath, $requestPath);
80 | $response = new JsonResponse();
81 |
82 | $file = $fileBag->get('file');
83 | if (null === $file) {
84 | $response->setData([
85 | 'error' => 'No file received. Maybe file size exceeded maximum file size allowed?',
86 | ]);
87 | $response->setStatusCode(400);
88 |
89 | return $response;
90 | }
91 |
92 | if (0 !== $file->getError()) {
93 | $response->setData([
94 | 'error' => $file->getErrorMessage(),
95 | ]);
96 | $response->setStatusCode(400);
97 |
98 | return $response;
99 | }
100 |
101 | if ($file->getSize() > UploadedFile::getMaxFilesize()) {
102 | $response->setData([
103 | 'error' => 'File too big.',
104 | ]);
105 | $response->setStatusCode(400);
106 |
107 | return $response;
108 | }
109 |
110 | // Check image type.
111 | if ('image' === $specificType && (!\in_array($file->guessExtension(), self::ALLOWED_IMAGE_FILE_EXTENSIONS, true) || !\in_array($file->getMimeType(), self::ALLOWED_IMAGE_FILE_MIME_TYPES, true))) {
112 | $response->setData([
113 | 'error' => 'File not supported.',
114 | ]);
115 | $response->setStatusCode(400);
116 |
117 | return $response;
118 | }
119 |
120 | // Generates random name.
121 | $name = sha1(uniqid((string) mt_rand(), true)) . '.' . $file->guessExtension();
122 |
123 | // Save file in the folder.
124 | $file->move($folder, $name);
125 |
126 | $response->setData([
127 | 'link' => $path . $name,
128 | ]);
129 |
130 | return $response;
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/Service/OptionManager.php:
--------------------------------------------------------------------------------
1 | formatOptions($options);
21 | $this->generateRoutes($options);
22 | $this->addImageCustomParams($options);
23 | $this->addFileCustomParams($options);
24 | $this->addVideoCustomParams($options);
25 | }
26 |
27 | /**
28 | * Format some options.
29 | */
30 | private function formatOptions(array &$options): void
31 | {
32 | $basePath = $options['basePath'];
33 | $imageUploadFolder = $options['imageUploadFolder'];
34 | $fileUploadFolder = $options['fileUploadFolder'];
35 | $videoUploadFolder = $options['videoUploadFolder'];
36 |
37 | $options['basePath'] = trim($basePath, '/') . '/';
38 | $options['imageUploadFolder'] = trim($imageUploadFolder, '/') . '/';
39 | $options['fileUploadFolder'] = trim($fileUploadFolder, '/') . '/';
40 | $options['videoUploadFolder'] = trim($videoUploadFolder, '/') . '/';
41 |
42 | // Image folder and path.
43 | if (false === isset($options['imageUploadPath'])) {
44 | $options['imageUploadPath'] = $options['imageUploadFolder'];
45 | } else {
46 | $options['imageUploadPath'] = trim($options['imageUploadPath'], '/') . '/';
47 | }
48 |
49 | // File folder and path.
50 | if (false === isset($options['fileUploadPath'])) {
51 | $options['fileUploadPath'] = $options['fileUploadFolder'];
52 | } else {
53 | $options['fileUploadPath'] = trim($options['fileUploadPath'], '/') . '/';
54 | }
55 |
56 | // Video folder and path.
57 | if (false === isset($options['videoUploadPath'])) {
58 | $options['videoUploadPath'] = $options['videoUploadFolder'];
59 | } else {
60 | $options['videoUploadPath'] = trim($options['videoUploadPath'], '/') . '/';
61 | }
62 |
63 | // Custom JS.
64 | if (isset($options['customJS'])) {
65 | $options['customJS'] = trim($options['customJS'], '/');
66 | }
67 | }
68 |
69 | /**
70 | * Convert some route to URL.
71 | */
72 | private function generateRoutes(array &$options): void
73 | {
74 | // Manage user entries, image has default values (can be set to null by user), but save and parameters has no default values.
75 | $imageManagerDeleteURL = $options['imageManagerDeleteURL'] ?? null;
76 | $imageManagerLoadURL = $options['imageManagerLoadURL'] ?? null;
77 | $imageUploadURL = $options['imageUploadURL'] ?? null;
78 | $fileUploadURL = $options['fileUploadURL'] ?? null;
79 | $videoUploadURL = $options['videoUploadURL'] ?? null;
80 |
81 | $saveURL = $options['saveURL'] ?? null;
82 | $imageManagerDeleteURLParams = $options['imageManagerDeleteURLParams'] ?? [];
83 | $imageManagerLoadURLParams = $options['imageManagerLoadURLParams'] ?? [];
84 | $imageUploadURLParams = $options['imageUploadURLParams'] ?? [];
85 | $saveURLParams = $options['saveURLParams'] ?? [];
86 | $fileUploadURLParams = $options['fileUploadURLParams'] ?? [];
87 | $videoUploadURLParams = $options['videoUploadURLParams'] ?? [];
88 |
89 | if (null !== $imageManagerDeleteURL) {
90 | $options['imageManagerDeleteURL'] = $this->router->generate($imageManagerDeleteURL, $imageManagerDeleteURLParams);
91 | }
92 |
93 | if (null !== $imageManagerLoadURL) {
94 | $options['imageManagerLoadURL'] = $this->router->generate($imageManagerLoadURL, $imageManagerLoadURLParams);
95 | }
96 |
97 | if (null !== $imageUploadURL) {
98 | $options['imageUploadURL'] = $this->router->generate($imageUploadURL, $imageUploadURLParams);
99 | }
100 |
101 | if (null !== $saveURL) {
102 | $options['saveURL'] = $this->router->generate($saveURL, $saveURLParams);
103 | }
104 |
105 | if (null !== $fileUploadURL) {
106 | $options['fileUploadURL'] = $this->router->generate($fileUploadURL, $fileUploadURLParams);
107 | }
108 |
109 | if (null !== $videoUploadURL) {
110 | $options['videoUploadURL'] = $this->router->generate($videoUploadURL, $videoUploadURLParams);
111 | }
112 | }
113 |
114 | private function addImageCustomParams(array &$options): void
115 | {
116 | $imageUploadParams = $options['imageUploadParams'] ?? [];
117 | $imageManagerLoadParams = $options['imageManagerLoadParams'] ?? [];
118 | $imageManagerDeleteParams = $options['imageManagerDeleteParams'] ?? [];
119 | $arrCustomParams = ['folder' => $options['imageUploadFolder'], 'path' => $options['imageUploadPath'], 'public_dir' => $options['publicDir']];
120 |
121 | // Always adding these params breaks s3 signing in some cases
122 | if (!\array_key_exists('imageUploadToS3', $options)) {
123 | $options['imageUploadParams'] = array_merge($imageUploadParams, $arrCustomParams);
124 | }
125 | $options['imageManagerLoadParams'] = array_merge($imageManagerLoadParams, $arrCustomParams);
126 | $options['imageManagerDeleteParams'] = array_merge($imageManagerDeleteParams, $arrCustomParams);
127 | }
128 |
129 | private function addFileCustomParams(array &$options): void
130 | {
131 | $fileUploadParams = $options['fileUploadParams'] ?? [];
132 | $arrCustomParams = ['folder' => $options['fileUploadFolder'], 'path' => $options['fileUploadPath'], 'public_dir' => $options['publicDir']];
133 |
134 | $options['fileUploadParams'] = array_merge($fileUploadParams, $arrCustomParams);
135 | }
136 |
137 | private function addVideoCustomParams(array &$options): void
138 | {
139 | $videoUploadParams = $options['videoUploadParams'] ?? [];
140 | $arrCustomParams = ['folder' => $options['videoUploadFolder'], 'path' => $options['videoUploadPath'], 'public_dir' => $options['publicDir']];
141 |
142 | $options['videoUploadParams'] = array_merge($videoUploadParams, $arrCustomParams);
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/src/Service/PluginProvider.php:
--------------------------------------------------------------------------------
1 | [self::KEY_CSS => 0, self::KEY_FOLDER => self::VALUE_PLUGINS],
22 | 'char_counter' => [self::KEY_CSS => 1, self::KEY_FOLDER => self::VALUE_PLUGINS],
23 | 'code_beautifier' => [self::KEY_CSS => 0, self::KEY_FOLDER => self::VALUE_PLUGINS],
24 | 'code_view' => [self::KEY_CSS => 1, self::KEY_FOLDER => self::VALUE_PLUGINS],
25 | 'colors' => [self::KEY_CSS => 1, self::KEY_FOLDER => self::VALUE_PLUGINS],
26 | 'draggable' => [self::KEY_CSS => 1, self::KEY_FOLDER => self::VALUE_PLUGINS],
27 | 'emoticons' => [self::KEY_CSS => 1, self::KEY_FOLDER => self::VALUE_PLUGINS],
28 | 'entities' => [self::KEY_CSS => 0, self::KEY_FOLDER => self::VALUE_PLUGINS],
29 | 'file' => [self::KEY_CSS => 1, self::KEY_FOLDER => self::VALUE_PLUGINS],
30 | 'files_manager' => [self::KEY_CSS => 1, self::KEY_FOLDER => self::VALUE_PLUGINS],
31 | 'font_family' => [self::KEY_CSS => 0, self::KEY_FOLDER => self::VALUE_PLUGINS],
32 | 'font_size' => [self::KEY_CSS => 0, self::KEY_FOLDER => self::VALUE_PLUGINS],
33 | 'fullscreen' => [self::KEY_CSS => 1, self::KEY_FOLDER => self::VALUE_PLUGINS],
34 | 'help' => [self::KEY_CSS => 1, self::KEY_FOLDER => self::VALUE_PLUGINS],
35 | 'image' => [self::KEY_CSS => 1, self::KEY_FOLDER => self::VALUE_PLUGINS],
36 | 'image_manager' => [self::KEY_CSS => 1, self::KEY_FOLDER => self::VALUE_PLUGINS],
37 | 'inline_class' => [self::KEY_CSS => 0, self::KEY_FOLDER => self::VALUE_PLUGINS],
38 | 'inline_style' => [self::KEY_CSS => 0, self::KEY_FOLDER => self::VALUE_PLUGINS],
39 | 'line_breaker' => [self::KEY_CSS => 1, self::KEY_FOLDER => self::VALUE_PLUGINS],
40 | 'line_height' => [self::KEY_CSS => 0, self::KEY_FOLDER => self::VALUE_PLUGINS],
41 | 'link' => [self::KEY_CSS => 0, self::KEY_FOLDER => self::VALUE_PLUGINS],
42 | 'lists' => [self::KEY_CSS => 0, self::KEY_FOLDER => self::VALUE_PLUGINS],
43 | 'paragraph_format' => [self::KEY_CSS => 0, self::KEY_FOLDER => self::VALUE_PLUGINS],
44 | 'paragraph_style' => [self::KEY_CSS => 0, self::KEY_FOLDER => self::VALUE_PLUGINS],
45 | 'print' => [self::KEY_CSS => 0, self::KEY_FOLDER => self::VALUE_PLUGINS],
46 | 'quick_insert' => [self::KEY_CSS => 1, self::KEY_FOLDER => self::VALUE_PLUGINS],
47 | 'quote' => [self::KEY_CSS => 0, self::KEY_FOLDER => self::VALUE_PLUGINS],
48 | 'save' => [self::KEY_CSS => 0, self::KEY_FOLDER => self::VALUE_PLUGINS],
49 | 'special_characters' => [self::KEY_CSS => 1, self::KEY_FOLDER => self::VALUE_PLUGINS],
50 | 'table' => [self::KEY_CSS => 1, self::KEY_FOLDER => self::VALUE_PLUGINS],
51 | 'track_changes' => [self::KEY_CSS => 0, self::KEY_FOLDER => self::VALUE_PLUGINS],
52 | 'url' => [self::KEY_CSS => 0, self::KEY_FOLDER => self::VALUE_PLUGINS],
53 | 'video' => [self::KEY_CSS => 1, self::KEY_FOLDER => self::VALUE_PLUGINS],
54 | 'word_paste' => [self::KEY_CSS => 0, self::KEY_FOLDER => self::VALUE_PLUGINS],
55 |
56 | // Third party.
57 | 'embedly' => [self::KEY_CSS => 1, self::KEY_FOLDER => self::VALUE_THIRD_PARTY],
58 | 'spell_checker' => [self::KEY_CSS => 1, self::KEY_FOLDER => self::VALUE_THIRD_PARTY],
59 | 'font_awesome' => [self::KEY_CSS => 1, self::KEY_FOLDER => self::VALUE_THIRD_PARTY],
60 | 'image_tui' => [self::KEY_CSS => 1, self::KEY_FOLDER => self::VALUE_THIRD_PARTY],
61 | ];
62 |
63 | public function obtainArrPluginToInclude(array $enabledPlugins, array $disabledPlugins): array
64 | {
65 | $arrPluginName = array_keys(self::ARR_PLUGIN_CONFIG);
66 |
67 | if (!empty($disabledPlugins)) {
68 | return array_diff($arrPluginName, $disabledPlugins);
69 | }
70 |
71 | if (!empty($enabledPlugins)) {
72 | return array_intersect($arrPluginName, $enabledPlugins);
73 | }
74 |
75 | return $arrPluginName;
76 | }
77 |
78 | /**
79 | * Obtains array of JS files to include (all have one).
80 | */
81 | public function obtainArrPluginJS(array $plugins): array
82 | {
83 | $arrPlugin = [];
84 |
85 | foreach ($plugins as $plugin) {
86 | $arrPlugin[] = $this->obtainConfiguration($plugin, self::KEY_FOLDER) . '/' . $plugin;
87 | }
88 |
89 | return $arrPlugin;
90 | }
91 |
92 | /**
93 | * Obtains array of CSS files to include (check in const).
94 | */
95 | public function obtainArrPluginCSS($plugins): array
96 | {
97 | $arrPlugin = [];
98 |
99 | foreach ($plugins as $plugin) {
100 | if ($this->obtainConfiguration($plugin, self::KEY_CSS) === 1) {
101 | $arrPlugin[] = $this->obtainConfiguration($plugin, self::KEY_FOLDER) . '/' . $plugin;
102 | }
103 | }
104 |
105 | return $arrPlugin;
106 | }
107 |
108 | /**
109 | * Obtains array of plugin to include (camelized).
110 | */
111 | public function obtainArrPluginCamelized(array $plugins): array
112 | {
113 | $arrPlugin = [];
114 |
115 | foreach ($plugins as $plugin) {
116 | $arrPlugin[] = (new UnicodeString($plugin))->camel()->toString();
117 | }
118 |
119 | return $arrPlugin;
120 | }
121 |
122 | private function obtainConfiguration(string $plugin, string $key)
123 | {
124 | return self::ARR_PLUGIN_CONFIG[$plugin][$key];
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/Twig/FroalaExtension.php:
--------------------------------------------------------------------------------
1 | ['html']]),
23 | ];
24 | }
25 |
26 | public function froalaDisplay(?string $html): string
27 | {
28 | $str = '';
29 | $includeCSS = $this->parameterBag->get(Configuration::NODE_ROOT . '.includeCSS');
30 |
31 | if ($includeCSS) {
32 | $basePath = $this->parameterBag->get(Configuration::NODE_ROOT . '.basePath');
33 | $url = $this->packages->getUrl(trim($basePath, '/') . '/css/froala_style.min.css');
34 | $str .= '';
35 | }
36 |
37 | $str .= '' . $html . '
';
38 |
39 | return $str;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Utility/UConfiguration.php:
--------------------------------------------------------------------------------
1 | null,
13 | 'autofocus' => null,
14 | 'autoStart' => null,
15 | 'charCounterCount' => null,
16 | 'codeBeautifier' => null,
17 | 'codeMirror' => null,
18 | 'colorsHEXInput' => null,
19 | 'disableRightClick' => null,
20 | 'documentReady' => null,
21 | 'dragInline' => null,
22 | 'editInPopup' => null,
23 | 'editorClass' => null,
24 | 'emoticonsUseImage' => null,
25 | 'fileUpload' => null,
26 | 'fileUseSelectedText' => null,
27 | 'fontFamilySelection' => null,
28 | 'fontSizeSelection' => null,
29 | 'formMultipleStyles' => null,
30 | 'fullPage' => null,
31 | 'htmlAllowComments' => null,
32 | 'htmlExecuteScripts' => null,
33 | 'htmlSimpleAmpersand' => null,
34 | 'htmlUntouched' => null,
35 | 'iframe' => null,
36 | 'imageAddNewLine' => null,
37 | 'imageManagerToggleTags' => null,
38 | 'imageMove' => null,
39 | 'imageMultipleStyles' => null,
40 | 'imagePaste' => null,
41 | 'imageResize' => null,
42 | 'imageResizeWithPercent' => null,
43 | 'imageRoundPercent' => null,
44 | 'imageSplitHTML' => null,
45 | 'imageTextNear' => null,
46 | 'imageUpload' => null,
47 | 'imageUploadRemoteUrls' => null,
48 | 'imageOutputSize' => null,
49 | 'imagePasteProcess' => null,
50 | 'initOnClick' => null,
51 | 'keepFormatOnDelete' => null,
52 | 'linkAlwaysBlank' => null,
53 | 'linkAlwaysNoFollow' => null,
54 | 'linkConvertEmailAddress' => null,
55 | 'linkMultipleStyles' => null,
56 | 'linkNoOpener' => null,
57 | 'linkNoReferrer' => null,
58 | 'linkText' => null,
59 | 'listAdvancedTypes' => null,
60 | 'multiLine' => null,
61 | 'paragraphDefaultSelection' => null,
62 | 'paragraphFormatSelection' => null,
63 | 'paragraphMultipleStyles' => null,
64 | 'pasteAllowLocalImages' => null,
65 | 'pastePlain' => null,
66 | 'quickInsertEnabled' => null,
67 | 'requestWithCredentials' => null,
68 | 'requestWithCORS' => null,
69 | 'shortcutsHint' => null,
70 | 'showChangesEnabled' => null,
71 | 'spellcheck' => null,
72 | 'tableCellMultipleStyles' => null,
73 | 'tableInsertHelper' => null,
74 | 'tableMultipleStyles' => null,
75 | 'tableResizer' => null,
76 | 'toolbarBottom' => null,
77 | 'toolbarContainer' => null,
78 | 'toolbarInline' => null,
79 | 'toolbarSticky' => null,
80 | 'toolbarVisibleWithoutSelection' => null,
81 | 'tooltips' => null,
82 | 'trackChangesEnabled' => null,
83 | 'useClasses' => null,
84 | 'videoMove' => null,
85 | 'videoResize' => null,
86 | 'videoResponsive' => null,
87 | 'videoSplitHTML' => null,
88 | 'videoTextNear' => null,
89 | 'videoUpload' => null,
90 | 'wordCounterCount' => null,
91 | 'wordPasteKeepFormatting' => null,
92 | 'wordPasteModal' => null,
93 | ];
94 |
95 | public const OPTIONS_BOOLEAN_CUSTOM = [
96 | 'includeJS' => true,
97 | 'includeCSS' => true,
98 | 'includeFontAwesome' => true,
99 | 'includeCodeMirror' => true,
100 | ];
101 |
102 | public const OPTIONS_INTEGER = [
103 | 'charCounterMax' => null,
104 | 'colorsStep' => null,
105 | 'emoticonsStep' => null,
106 | 'fileMaxSize' => null,
107 | 'filesManagerMaxSize' => null,
108 | 'height' => null,
109 | 'heightMax' => null,
110 | 'heightMin' => null,
111 | 'imageDefaultMargin' => null,
112 | 'imageDefaultWidth' => null,
113 | 'imageManagerPageSize' => null,
114 | 'imageManagerScrollOffset' => null,
115 | 'imageMaxSize' => null,
116 | 'imageMinWidth' => null,
117 | 'indentMargin' => null,
118 | 'lineBreakerHorizontalOffset' => null,
119 | 'lineBreakerOffset' => null,
120 | 'saveInterval' => null,
121 | 'tabIndex' => null,
122 | 'tabSpaces' => null,
123 | 'tableColorsStep' => null,
124 | 'tableInsertHelperOffset' => null,
125 | 'tableInsertMaxSize' => null,
126 | 'tableResizerOffset' => null,
127 | 'tableResizingLimit' => null,
128 | 'toolbarStickyOffset' => null,
129 | 'typingTimer' => null,
130 | 'videoDefaultWidth' => null,
131 | 'videoMaxSize' => null,
132 | 'wordCounterMax' => null,
133 | 'zIndex' => null,
134 | ];
135 |
136 | public const OPTIONS_STRING = [
137 | 'apiKey' => null,
138 | 'app' => null,
139 | 'aviaryKey' => null,
140 | 'colorsDefaultTab' => null,
141 | 'direction' => null,
142 | 'docId' => null,
143 | 'embedlyKey' => null,
144 | 'embedlyScriptPath' => null,
145 | 'enter' => null,
146 | 'entities' => null,
147 | 'fileUploadMethod' => null,
148 | 'fileUploadParam' => null,
149 | 'fileUploadURL' => 'leapt_froala_editor_upload_file',
150 | 'filesManagerUploadURL' => null,
151 | 'fontAwesomeTemplate' => null,
152 | 'fontFamilyDefaultSelection' => null,
153 | 'fontSizeDefaultSelection' => null,
154 | 'fontSizeUnit' => null,
155 | 'iconsTemplate' => null,
156 | 'iframeDefaultStyle' => null,
157 | 'iframeStyle' => null,
158 | 'imageCORSProxy' => null,
159 | 'imageDefaultAlign' => null,
160 | 'imageDefaultDisplay' => null,
161 | 'imageManagerDeleteMethod' => null,
162 | 'imageManagerDeleteURL' => 'leapt_froala_editor_delete_image',
163 | 'imageManagerLoadMethod' => null,
164 | 'imageManagerLoadURL' => 'leapt_froala_editor_load_images',
165 | 'imageManagerPreloader' => null,
166 | 'imageUploadMethod' => null,
167 | 'imageUploadParam' => null,
168 | 'imageUploadURL' => 'leapt_froala_editor_upload_image',
169 | 'language' => null,
170 | 'linkAutoPrefix' => null,
171 | 'placeholderText' => null,
172 | 'saveMethod' => null,
173 | 'saveParam' => null,
174 | 'saveURL' => null,
175 | 'scaytCustomerId' => null,
176 | 'scrollableContainer' => null,
177 | 'tableDefaultWidth' => null,
178 | 'theme' => null,
179 | 'username' => null,
180 | 'videoDefaultAlign' => null,
181 | 'videoDefaultDisplay' => null,
182 | 'videoUploadMethod' => null,
183 | 'videoUploadParam' => null,
184 | 'videoUploadURL' => 'leapt_froala_editor_upload_video',
185 | 'width' => null,
186 | ];
187 |
188 | public const OPTIONS_STRING_CUSTOM = [
189 | 'customJS' => null,
190 | 'basePath' => '/bundles/leaptfroalaeditor/froala_editor',
191 | 'imageUploadFolder' => '/upload',
192 | 'imageUploadPath' => null,
193 | 'fileUploadFolder' => '/upload',
194 | 'fileUploadPath' => null,
195 | 'serialNumber' => null,
196 | 'videoUploadFolder' => '/upload',
197 | 'videoUploadPath' => null,
198 | 'publicDir' => '/public',
199 | ];
200 |
201 | public const OPTIONS_ARRAY = [
202 | 'codeViewKeepActiveButtons' => [],
203 | 'colorsBackground' => [],
204 | 'colorsButtons' => [],
205 | 'colorsText' => [],
206 | 'embedlyEditButtons' => [],
207 | 'embedlyInsertButtons' => [],
208 | 'emoticonsSet' => [],
209 | 'emoticonsButtons' => [],
210 | 'faButtons' => [],
211 | 'fileAllowedTypes' => [],
212 | 'fileInsertButtons' => [],
213 | 'filesManagerAllowedTypes' => [],
214 | 'fontAwesomeSets' => [],
215 | 'fontAwesome5Sets' => [],
216 | 'fontSize' => [],
217 | 'formEditButtons' => [],
218 | 'formUpdateButtons' => [],
219 | 'htmlAllowedAttrs' => [],
220 | 'htmlAllowedEmptyTags' => [],
221 | 'htmlAllowedStyleProps' => [],
222 | 'htmlAllowedTags' => [],
223 | 'htmlIgnoreCSSProperties' => [],
224 | 'htmlDoNotWrapTags' => [],
225 | 'htmlRemoveTags' => [],
226 | 'iframeStyleFiles' => [],
227 | 'imageAllowedTypes' => [],
228 | 'imageAltButtons' => [],
229 | 'imageEditButtons' => [],
230 | 'imageInsertButtons' => [],
231 | 'imageSizeButtons' => [],
232 | 'lineBreakerTags' => [],
233 | 'linkEditButtons' => [],
234 | 'linkInsertButtons' => [],
235 | 'linkList' => [],
236 | 'pasteAllowedStyleProps' => [],
237 | 'pasteDeniedAttrs' => [],
238 | 'pasteDeniedTags' => [],
239 | 'pluginsEnabled' => [],
240 | 'quickInsertButtons' => [],
241 | 'quickInsertTags' => [],
242 | 'shortcutsEnabled' => [],
243 | 'specialCharButtons' => [],
244 | 'tableColors' => [],
245 | 'tableColorsButtons' => [],
246 | 'tableEditButtons' => [],
247 | 'tableInsertButtons' => [],
248 | 'toolbarButtons' => [],
249 | 'toolbarButtonsMD' => [],
250 | 'toolbarButtonsSM' => [],
251 | 'toolbarButtonsXS' => [],
252 | 'videoAllowedProviders' => [],
253 | 'videoAllowedTypes' => [],
254 | 'videoEditButtons' => [],
255 | 'videoInsertButtons' => [],
256 | 'videoSizeButtons' => [],
257 | 'wordAllowedStyleProps' => [],
258 | 'wordDeniedAttrs' => [],
259 | 'wordDeniedTags' => [],
260 | ];
261 |
262 | public const OPTIONS_ARRAY_CUSTOM = [
263 | 'pluginsDisabled' => [],
264 | 'events' => [],
265 | ];
266 |
267 | public const OPTIONS_OBJECT = [
268 | 'aviaryOptions' => [],
269 | 'codeMirrorOptions' => [],
270 | 'codeBeautifierOptions' => [],
271 | 'codoxOptions' => [],
272 | 'fileUploadParams' => [],
273 | 'filesManagerUploadParams' => [],
274 | 'fileUploadToS3' => [],
275 | 'filesManagerUploadToS3' => [],
276 | 'fontFamily' => [],
277 | 'formStyles' => [],
278 | 'googleOptions' => [],
279 | 'helpSets' => [],
280 | 'imageManagerDeleteParams' => [],
281 | 'imageManagerLoadParams' => [],
282 | 'imageStyles' => [],
283 | 'imageTUIOptions' => [],
284 | 'imageUploadParams' => [],
285 | 'imageUploadToS3' => [],
286 | 'inlineClasses' => [],
287 | 'inlineStyles' => [],
288 | 'lineHeights' => [],
289 | 'linkAttributes' => [],
290 | 'linkStyles' => [],
291 | 'paragraphFormat' => [],
292 | 'paragraphStyles' => [],
293 | 'requestHeaders' => [],
294 | 'saveParams' => [],
295 | 'scaytOptions' => [],
296 | 'specialCharactersSets' => [],
297 | 'tableStyles' => [],
298 | 'tableCellStyles' => [],
299 | 'videoUploadParams' => [],
300 | 'videoUploadToS3' => [],
301 | ];
302 |
303 | public const OPTIONS_OBJECT_CUSTOM = [
304 | 'imageManagerDeleteURLParams' => [],
305 | 'imageManagerLoadURLParams' => [],
306 | 'imageUploadURLParams' => [],
307 | 'saveURLParams' => [],
308 | 'fileUploadURLParams' => [],
309 | 'videoUploadURLParams' => [],
310 | ];
311 |
312 | public static function getArrOptionAll(): array
313 | {
314 | return array_merge(self::getArrOption(), self::getArrOptionCustom());
315 | }
316 |
317 | public static function getArrOption(): array
318 | {
319 | return array_merge(array_merge(
320 | array_merge(
321 | array_merge(
322 | array_keys(self::OPTIONS_BOOLEAN),
323 | array_keys(self::OPTIONS_INTEGER)),
324 | array_keys(self::OPTIONS_STRING)),
325 | array_keys(self::OPTIONS_ARRAY)),
326 | array_keys(self::OPTIONS_OBJECT));
327 | }
328 |
329 | public static function getArrOptionCustom(): array
330 | {
331 | return array_merge(array_merge(
332 | array_merge(
333 | array_keys(self::OPTIONS_BOOLEAN_CUSTOM),
334 | array_keys(self::OPTIONS_STRING_CUSTOM)),
335 | array_keys(self::OPTIONS_ARRAY_CUSTOM)),
336 | array_keys(self::OPTIONS_OBJECT_CUSTOM));
337 | }
338 |
339 | public static function addArrOptionBoolean(NodeBuilder $nodeBuilder, bool $addDefaultValue = true): void
340 | {
341 | $array = array_merge(self::OPTIONS_BOOLEAN, self::OPTIONS_BOOLEAN_CUSTOM);
342 |
343 | foreach ($array as $option => $defaultValue) {
344 | $nodeBuilder = $nodeBuilder->booleanNode($option);
345 | if ($addDefaultValue) {
346 | $nodeBuilder->defaultValue($defaultValue);
347 | }
348 |
349 | $nodeBuilder = $nodeBuilder->end();
350 | }
351 | }
352 |
353 | public static function addArrOptionInteger(NodeBuilder $nodeBuilder, bool $addDefaultValue = true): void
354 | {
355 | foreach (self::OPTIONS_INTEGER as $option => $defaultValue) {
356 | $nodeBuilder = $nodeBuilder->integerNode($option);
357 |
358 | if ($addDefaultValue) {
359 | $nodeBuilder = $nodeBuilder->defaultValue($defaultValue);
360 | }
361 |
362 | $nodeBuilder = $nodeBuilder->end();
363 | }
364 | }
365 |
366 | public static function addArrOptionString(NodeBuilder $nodeBuilder, bool $addDefaultValue = true): void
367 | {
368 | $array = array_merge(self::OPTIONS_STRING, self::OPTIONS_STRING_CUSTOM);
369 |
370 | foreach ($array as $option => $defaultValue) {
371 | $nodeBuilder = $nodeBuilder->scalarNode($option);
372 | if ($addDefaultValue) {
373 | $nodeBuilder = $nodeBuilder->defaultValue($defaultValue);
374 | }
375 | $nodeBuilder = $nodeBuilder->end();
376 | }
377 | }
378 |
379 | public static function addArrOptionArray(NodeBuilder $nodeBuilder, bool $addDefaultValue = true): void
380 | {
381 | $array = array_merge(self::OPTIONS_ARRAY, self::OPTIONS_ARRAY_CUSTOM);
382 |
383 | foreach ($array as $option => $defaultValue) {
384 | $nodeBuilder =
385 | $nodeBuilder->arrayNode($option)->prototype('variable')->end();
386 |
387 | if ($addDefaultValue) {
388 | $nodeBuilder = $nodeBuilder->defaultValue($defaultValue);
389 | }
390 |
391 | $nodeBuilder = $nodeBuilder->end();
392 | }
393 | }
394 |
395 | public static function addArrOptionObject(NodeBuilder $nodeBuilder, bool $addDefaultValue = true): void
396 | {
397 | $array = array_merge(self::OPTIONS_OBJECT, self::OPTIONS_OBJECT_CUSTOM);
398 |
399 | foreach ($array as $option => $defaultValue) {
400 | $nodeBuilder =
401 | $nodeBuilder->arrayNode($option)->prototype('variable')->end();
402 |
403 | if ($addDefaultValue) {
404 | $nodeBuilder = $nodeBuilder->defaultValue($defaultValue);
405 | }
406 |
407 | $nodeBuilder = $nodeBuilder->end();
408 | }
409 | }
410 | }
411 |
--------------------------------------------------------------------------------