├── 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 | [![Package version](https://img.shields.io/packagist/v/leapt/froala-editor-bundle.svg)](https://packagist.org/packages/leapt/froala-editor-bundle) 4 | [![Build Status](https://img.shields.io/github/actions/workflow/status/leapt/froala-editor-bundle/continuous-integration.yml?branch=1.x)](https://github.com/leapt/froala-editor-bundle/actions?query=workflow%3A%22Continuous+Integration%22) 5 | [![Downloads](https://img.shields.io/packagist/dt/leapt/froala-editor-bundle.svg)](https://packagist.org/packages/leapt/froala-editor-bundle) 6 | [![PHP Version](https://img.shields.io/packagist/php-v/leapt/froala-editor-bundle.svg)](https://packagist.org/packages/leapt/froala-editor-bundle) 7 | [![Licence](https://img.shields.io/packagist/l/leapt/froala-editor-bundle.svg)](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 | --------------------------------------------------------------------------------