├── .symfony.bundle.yaml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── UPGRADE-2.0.md ├── composer.json ├── docs ├── conf.py ├── index.rst ├── installation.rst ├── migration.rst ├── requirements.txt └── usage │ ├── append-javascript.rst │ ├── autoinline.rst │ ├── ckeditor.rst │ ├── config.rst │ ├── file-browse-upload.rst │ ├── index.rst │ ├── index.rst.inc │ ├── inline.rst │ ├── jquery.rst │ ├── json-builder.rst │ ├── language.rst │ ├── loading.rst │ ├── plugin.rst │ ├── require-js.rst │ ├── skin.rst │ ├── style.rst │ ├── template.rst │ ├── textarea-fallback.rst │ ├── textarea-sync.rst │ ├── toolbar.rst │ └── version.rst └── src ├── Builder └── JsonBuilder.php ├── Command └── CKEditorInstallerCommand.php ├── Config ├── CKEditorConfiguration.php └── CKEditorConfigurationInterface.php ├── DependencyInjection ├── Configuration.php └── FOSCKEditorExtension.php ├── Exception ├── BadProxyUrlException.php ├── ConfigException.php └── FOSCKEditorException.php ├── FOSCKEditorBundle.php ├── Form └── Type │ └── CKEditorType.php ├── Installer └── CKEditorInstaller.php ├── Renderer ├── CKEditorRenderer.php └── CKEditorRendererInterface.php ├── Resources ├── config │ ├── builder.xml │ ├── command.xml │ ├── config.xml │ ├── form.xml │ ├── installer.xml │ ├── renderer.xml │ └── twig.xml └── views │ └── Form │ └── ckeditor_widget.html.twig └── Twig └── CKEditorExtension.php /.symfony.bundle.yaml: -------------------------------------------------------------------------------- 1 | # This file must be called '.symfony.bundle.yaml' and 2 | # must be stored at the root directory of the default GitHub branch 3 | # Here's a real example in action: 4 | # https://github.com/EasyCorp/EasyAdminBundle/blob/master/.symfony.bundle.yaml 5 | 6 | # MANDATORY 7 | # An array with the name of the branches for which you want to publish docs 8 | # It doesn't have to be an exhaustive list of your repository branches, but 9 | # only the main repository branches (past or present) 10 | branches: ["1.x", "2.x"] 11 | 12 | # MANDATORY 13 | # A subset of the 'branches' array which lists only the branch(es) that 14 | # you currently maintain. We build the docs of these branch(es) frequently, 15 | # whereas we build the docs of the other branch(es) much less frequently 16 | maintained_branches: ["2.x"] 17 | 18 | # MANDATORY 19 | # The location of your RST doc files, defined as a relative directory 20 | # If all the branches defined in the 'branches' option use the same 21 | # directory, then you can define it as a string 22 | doc_dir: "docs/" 23 | 24 | # OPTIONAL 25 | # This is the branch used to read the docs by default (and it's mapped as 26 | # 'current' in the public URL to generate stable URLs for your docs). 27 | # It should match the branch you use to publish the stable releases. 28 | # This value is optional because we assign it automatically to the 29 | # "default branch" of your repository as returned by the GitHub API 30 | current_branch: "2.x" 31 | 32 | # OPTIONAL 33 | # This is the branch used to publish the next versions of your bundle. 34 | # For bundles it's common that 'current_branch' and 'dev_branch' are the same. 35 | # That's why we automatically assign to it the same value as 'current_branch' 36 | dev_branch: "2.x" 37 | 38 | # OPTIONAL 39 | # This is the version that future releases of your bundle will use. 40 | # It's used in some symfony.com labels to avoid using the generic 41 | # "master" or "main" labels. 42 | dev_branch_alias: "2.x" 43 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## [1.1.0](https://github.com/FriendsOfSymfony/FOSCKEditorBundle/compare/1.0.0...1.1.0) - 2018-05-25 4 | ### Added 5 | - Deprecation message for IvoryCKEditorBundle 6 | 7 | ### Changed 8 | - The command is now lazy loaded in Symfony 3.4+ 9 | 10 | ### Fixed 11 | - `ckeditor:install` command not working with `--no-interaction` 12 | 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | =============== 3 | 4 | Copyright (c) 2018-2018 Marko Kunic 5 | Maximilian Berghoff 6 | Copyright (c) 2011-2017 Eric GELOEN 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is furnished 13 | to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FOSCKEditorBundle 2 | ================== 3 | General: 4 | [![Build Status](https://github.com/FriendsOfSymfony/FOSCKEditorBundle/workflows/Test/badge.svg?branch=2.x)](https://github.com/FriendsOfSymfony/FOSCKEditorBundle/actions?query=workflow%3ATest+branch%3A2.x) 5 | [![Latest Stable Version](https://poser.pugx.org/friendsofsymfony/ckeditor-bundle/v/stable.svg)](https://packagist.org/packages/friendsofsymfony/ckeditor-bundle) 6 | 7 | Quality: 8 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/FriendsOfSymfony/FOSCKEditorBundle/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/FriendsOfSymfony/FOSCKEditorBundle/?branch=master) 9 | [![Scrutinizer Build](https://scrutinizer-ci.com/g/FriendsOfSymfony/FOSCKEditorBundle/badges/build.png?b=master)](https://scrutinizer-ci.com/g/FriendsOfSymfony/FOSCKEditorBundle/badges/quality-score.png?b=master) 10 | [![Coverage Status](https://coveralls.io/repos/github/FriendsOfSymfony/FOSCKEditorBundle/badge.svg)](https://coveralls.io/github/FriendsOfSymfony/FOSCKEditorBundle) 11 | [![Scrutinizer Code Intelligence](https://scrutinizer-ci.com/g/FriendsOfSymfony/FOSCKEditorBundle/badges/code-intelligence.svg?b=master)](https://scrutinizer-ci.com/g/FriendsOfSymfony/FOSCKEditorBundle/?branch=master) 12 | 13 | Numbers: 14 | [![Total Downloads](https://poser.pugx.org/friendsofsymfony/ckeditor-bundle/downloads)](https://packagist.org/packages/friendsofsymfony/ckeditor-bundle) 15 | [![Monthly Downloads](https://poser.pugx.org/friendsofsymfony/ckeditor-bundle/d/monthly)](https://packagist.org/packages/friendsofsymfony/ckeditor-bundle) 16 | [![Daily Downloads](https://poser.pugx.org/friendsofsymfony/ckeditor-bundle/d/daily)](https://packagist.org/packages/friendsofsymfony/ckeditor-bundle) 17 | 18 | Introduction 19 | ------------ 20 | 21 | The bundle provides a [CKEditor 4](https://ckeditor.com/ckeditor-4/) integration for your Symfony Project. It automatically registers 22 | the new `ckeditor` form type which can be easily as well as highly configured. 23 | 24 | Migration from IvoryCKEditorBundle to FOSCKEditorBundle 25 | ------------------------------------------------------- 26 | 27 | As IvoryCKEditorBundle was abandoned, FriendsOfSymfony took this bundle over, to help 28 | you easily migrate [just follow our guide](/docs/migration.rst). 29 | 30 | Documentation 31 | ------------- 32 | 33 | Please, read the [official documentation](https://symfony.com/bundles/FOSCKEditorBundle/current/index.html). 34 | 35 | License 36 | ------- 37 | 38 | This bundle is released under the MIT license. See the included 39 | [LICENSE](LICENSE) file for more information. 40 | 41 | ## Contribute 42 | ------------- 43 | 44 | We love contributors! Ivory is an open source project. If you'd like to contribute, feel free to propose a PR! You 45 | can follow the [CONTRIBUTING](/CONTRIBUTING.md) file which will explain you how to set up the project. 46 | -------------------------------------------------------------------------------- /UPGRADE-2.0.md: -------------------------------------------------------------------------------- 1 | # UPGRADE 2 | 3 | UPGRADE FROM 1.x to 2.0 4 | ======================= 5 | 6 | Added typehints and return types. 7 | 8 | Removed nullable constructor arguments on most services. 9 | 10 | Classes are now final. 11 | 12 | Marker exception is now an interface that implements throwable. 13 | 14 | All Model Managers have been removed. 15 | Not used exceptions have been removed. 16 | 17 | CKEditorType Form Type now accepts only 1 argument 18 | of type `FOS\CKEditorBundle\Config\CKEditorConfigurationInterface`. 19 | 20 | All getters and setters have been removed from the CKEditorType Form Type. 21 | 22 | Minimum Symfony version is 3.4 and minimum php version is 7.1. 23 | 24 | symfony/templating has been dropped along with php templates. 25 | 26 | Twig is now a required dependency and only templating engine this library supports. 27 | 28 | Composer Script has been removed. 29 | 30 | To make Twig render the editors, you must add some configuration under the `twig.form_themes` config key: 31 | 32 | ```yaml 33 | # Symfony 2/3: app/config/config.yml 34 | # Symfony 4: config/packages/twig.yaml 35 | 36 | twig: 37 | form_themes: 38 | - '@FOSCKEditor/Form/ckeditor_widget.html.twig' 39 | ``` 40 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "friendsofsymfony/ckeditor-bundle", 3 | "type": "symfony-bundle", 4 | "description": "Provides a CKEditor integration for your Symfony project.", 5 | "keywords": [ 6 | "ckeditor" 7 | ], 8 | "license": "MIT", 9 | "authors": [ 10 | { 11 | "name": "Eric GELOEN", 12 | "email": "geloen.eric@gmail.com" 13 | }, 14 | { 15 | "name": "FriendsOfSymfony Community", 16 | "homepage": "https://github.com/FriendsOfSymfony/FOSCKEditorBundle/graphs/contributors" 17 | } 18 | ], 19 | "require": { 20 | "php": "^8.0", 21 | "ext-zip": "*", 22 | "ext-json": "*", 23 | "symfony/asset": "^5.4 || ^6.0 || ^7.0", 24 | "symfony/config": "^5.4 || ^6.0 || ^7.0", 25 | "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", 26 | "symfony/expression-language": "^5.4 || ^6.0 || ^7.0", 27 | "symfony/form": "^5.4 || ^6.0 || ^7.0", 28 | "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0", 29 | "symfony/http-foundation": "^5.4 || ^6.0 || ^7.0", 30 | "symfony/http-kernel": "^5.4 || ^6.0 || ^7.0", 31 | "symfony/options-resolver": "^5.4 || ^6.0 || ^7.0", 32 | "symfony/property-access": "^5.4 || ^6.0 || ^7.0", 33 | "symfony/routing": "^5.4 || ^6.0 || ^7.0", 34 | "symfony/twig-bundle": "^5.4 || ^6.0 || ^7.0", 35 | "twig/twig": "^2.4 || ^3.0" 36 | }, 37 | "conflict": { 38 | "sebastian/environment": "<1.3.4", 39 | "sebastian/exporter": "<2.0.0" 40 | }, 41 | "require-dev": { 42 | "friendsofphp/php-cs-fixer": "^3.41", 43 | "matthiasnoback/symfony-dependency-injection-test": "^4.0 || ^5.0", 44 | "phpunit/phpunit": "^9.6", 45 | "symfony/console": "^5.4 || ^6.0 || ^7.0", 46 | "symfony/phpunit-bridge": "^5.4 || ^6.0 || ^7.0", 47 | "symfony/yaml": "^5.4 || ^6.0 || ^7.0" 48 | }, 49 | "suggest": { 50 | "egeloen/form-extra-bundle": "Allows to load CKEditor asynchronously" 51 | }, 52 | "extra": { 53 | "branch-alias": { 54 | "dev-master": "3.x-dev" 55 | } 56 | }, 57 | "autoload": { 58 | "psr-4": { 59 | "FOS\\CKEditorBundle\\": "src/" 60 | } 61 | }, 62 | "autoload-dev": { 63 | "psr-4": { 64 | "FOS\\CKEditorBundle\\Tests\\": "tests/" 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import sys, os 4 | 5 | # If extensions (or modules to document with autodoc) are in another directory, 6 | # add these directories to sys.path here. If the directory is relative to the 7 | # documentation root, use os.path.abspath to make it absolute, like shown here. 8 | #sys.path.insert(0, os.path.abspath('.')) 9 | 10 | # -- General configuration ----------------------------------------------------- 11 | 12 | # If your documentation needs a minimal Sphinx version, state it here. 13 | #needs_sphinx = '1.0' 14 | 15 | # Add any Sphinx extension module names here, as strings. They can be extensions 16 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 17 | extensions = ['sensio.sphinx.refinclude', 'sensio.sphinx.configurationblock', 'sensio.sphinx.phpcode'] 18 | 19 | # Add any paths that contain templates here, relative to this directory. 20 | templates_path = ['_templates'] 21 | 22 | # The suffix of source filenames. 23 | source_suffix = '.rst' 24 | 25 | # The encoding of source files. 26 | #source_encoding = 'utf-8-sig' 27 | 28 | # The master toctree document. 29 | master_doc = 'index' 30 | 31 | # General information about the project. 32 | project = u'FriendsOfSymfony ~ CKEditorBundle' 33 | copyright = u'' 34 | 35 | 36 | # List of patterns, relative to source directory, that match files and 37 | # directories to ignore when looking for source files. 38 | exclude_patterns = ['_build'] 39 | 40 | # The name of the Pygments (syntax highlighting) style to use. 41 | pygments_style = 'sphinx' 42 | 43 | # This will be used when using the shorthand notation 44 | highlight_language = 'php' 45 | 46 | 47 | # -- Options for HTML output --------------------------------------------------- 48 | import sphinx_rtd_theme 49 | 50 | # The theme to use for HTML and HTML Help pages. See the documentation for 51 | # a list of builtin themes. 52 | html_theme = 'sphinx_rtd_theme' 53 | 54 | # Add any paths that contain custom themes here, relative to this directory. 55 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 56 | 57 | # Add any paths that contain custom static files (such as style sheets) here, 58 | # relative to this directory. They are copied after the builtin static files, 59 | # so a file named "default.css" will overwrite the builtin "default.css". 60 | html_static_path = [] 61 | 62 | 63 | # Output file base name for HTML help builder. 64 | htmlhelp_basename = 'doc' 65 | 66 | 67 | # -- Options for LaTeX output -------------------------------------------------- 68 | 69 | latex_elements = { 70 | # The paper size ('letterpaper' or 'a4paper'). 71 | #'papersize': 'letterpaper', 72 | 73 | # The font size ('10pt', '11pt' or '12pt'). 74 | #'pointsize': '10pt', 75 | 76 | # Additional stuff for the LaTeX preamble. 77 | #'preamble': '', 78 | } 79 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Getting started with FOSCKEditorBundle 2 | ======================================== 3 | 4 | .. toctree:: 5 | :hidden: 6 | 7 | installation 8 | migration 9 | usage/index 10 | 11 | Overview 12 | -------- 13 | 14 | The bundle integrates `CKEditor`_ into `Symfony`_ via the `Form Component`_. It 15 | automatically registers a new type called ``ckeditor`` which can be fully 16 | configured. This type extends the `textarea`_ one, meaning all textarea options 17 | are available. 18 | 19 | Here, an example where we customize the `CKEditor config`_:: 20 | 21 | use FOS\CKEditorBundle\Form\Type\CKEditorType; 22 | 23 | $builder->add('field', CKEditorType::class, [ 24 | 'config' => [ 25 | 'uiColor' => '#ffffff', 26 | //... 27 | ], 28 | ]); 29 | 30 | Installation 31 | ------------ 32 | 33 | To install the bundle, please, read the :doc:`Installation documentation `. 34 | 35 | Migration from IvoryCKEditorBundle to FOSCKEditorBundle 36 | ------------------------------------------------------- 37 | 38 | As IvoryCKEditorBundle was abandoned, FriendsOfSymfony took this bundle over, to help 39 | you easily migrate :doc:`just follow our guide `. 40 | 41 | Usage 42 | ----- 43 | 44 | If you want to learn more, this documentation covers the following use cases: 45 | 46 | .. include:: usage/index.rst.inc 47 | 48 | Contributing 49 | ------------ 50 | 51 | .. _`CKEditor`: http://ckeditor.com/ 52 | .. _`Symfony`: http://symfony.com/ 53 | .. _`Form Component`: http://symfony.com/doc/current/book/forms.html 54 | .. _`textarea`: http://symfony.com/doc/current/reference/forms/types/textarea.html 55 | .. _`CKEditor config`: http://docs.ckeditor.com/#!/api/CKEDITOR.config 56 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | Download the Bundle 5 | ------------------- 6 | 7 | Require the bundle in your ``composer.json`` file: 8 | 9 | .. code-block:: bash 10 | 11 | $ composer require friendsofsymfony/ckeditor-bundle 12 | 13 | Register the Bundle 14 | ------------------- 15 | 16 | If you're using Symfony >= 4.0, skip this step, as it is automatically done by Flex's recipe. 17 | 18 | If you choose to not execute the recipe, and if you're using Symfony >= 4.0, update your ``config/bundles.php``: 19 | 20 | .. code-block:: php 21 | 22 | return [ 23 | // ... 24 | FOS\CKEditorBundle\FOSCKEditorBundle::class => ['all' => true], 25 | ]; 26 | 27 | If you're using Symfony < 4.0, update your ``app/AppKernel.php``: 28 | 29 | .. code-block:: php 30 | 31 | class AppKernel extends Kernel 32 | { 33 | public function registerBundles() 34 | { 35 | $bundles = [ 36 | new FOS\CKEditorBundle\FOSCKEditorBundle(), 37 | // ... 38 | ]; 39 | 40 | // ... 41 | } 42 | } 43 | 44 | Download CKEditor 45 | ----------------- 46 | With bundle's command 47 | ~~~~~~~~~~~~~~~~~~~~~ 48 | 49 | Once, you have registered the bundle, you need to install CKEditor: 50 | 51 | If you're using Symfony <= 2.8: 52 | 53 | .. code-block:: bash 54 | 55 | $ php app/console ckeditor:install 56 | 57 | If you're using Symfony >= 3.0: 58 | 59 | .. code-block:: bash 60 | 61 | $ php bin/console ckeditor:install 62 | 63 | If you want to learn more about this command, you can read :doc:`its documentation `. 64 | 65 | Using Webpack Encore 66 | ~~~~~~~~~~~~~~~~~~~~ 67 | 68 | If you have installed Webpack Encore, you may want to have it as a `node_module` dependency. 69 | 70 | You can by running this command: 71 | 72 | .. code-block:: bash 73 | 74 | # if you are using NPM as package manager 75 | $ npm install --save ckeditor4@^4.13.0 76 | 77 | # if you are using Yarn as package manager 78 | $ yarn add ckeditor4@^4.13.0 79 | 80 | Once installed, add the following lines to your Webpack Encore configuration file (this excludes the samples directory from the ckeditor node module): 81 | 82 | .. code-block:: javascript 83 | 84 | // webpack.config.js 85 | var Encore = require('@symfony/webpack-encore'); 86 | 87 | Encore 88 | // ... 89 | .copyFiles([ 90 | {from: './node_modules/ckeditor4/', to: 'ckeditor/[path][name].[ext]', pattern: /\.(js|css)$/, includeSubdirectories: false}, 91 | {from: './node_modules/ckeditor4/adapters', to: 'ckeditor/adapters/[path][name].[ext]'}, 92 | {from: './node_modules/ckeditor4/lang', to: 'ckeditor/lang/[path][name].[ext]'}, 93 | {from: './node_modules/ckeditor4/plugins', to: 'ckeditor/plugins/[path][name].[ext]'}, 94 | {from: './node_modules/ckeditor4/skins', to: 'ckeditor/skins/[path][name].[ext]'}, 95 | {from: './node_modules/ckeditor4/vendor', to: 'ckeditor/vendor/[path][name].[ext]'} 96 | ]) 97 | // Uncomment the following line if you are using Webpack Encore <= 0.24 98 | // .addLoader({test: /\.json$/i, include: [require('path').resolve(__dirname, 'node_modules/ckeditor')], loader: 'raw-loader', type: 'javascript/auto'}) 99 | ; 100 | 101 | Then, override the bundle's configuration to point to the new CKEditor path: 102 | 103 | .. code-block:: yaml 104 | 105 | fos_ck_editor: 106 | # ... 107 | base_path: "build/ckeditor" 108 | js_path: "build/ckeditor/ckeditor.js" 109 | 110 | Finally, run encore command: 111 | 112 | .. code-block:: bash 113 | 114 | # if you are using NPM as package manager 115 | $ npm run dev 116 | 117 | # if you are using Yarn as package manager 118 | $ yarn run encore dev 119 | 120 | 121 | Install the Assets 122 | ------------------ 123 | 124 | .. note:: 125 | 126 | This step is not required if you are using Webpack Encore. 127 | 128 | Once, you have downloaded CKEditor, you need to install it in the web 129 | directory. 130 | 131 | If you're using Symfony <= 2.8: 132 | 133 | .. code-block:: bash 134 | 135 | $ php app/console assets:install web 136 | 137 | If you're using Symfony >= 3.0 without Symfony Flex: 138 | 139 | .. code-block:: bash 140 | 141 | $ php bin/console assets:install web 142 | 143 | If you're using Symfony Flex: 144 | 145 | .. code-block:: bash 146 | 147 | $ php bin/console assets:install public 148 | 149 | Configure Twig 150 | -------------- 151 | 152 | .. note:: 153 | 154 | This step is not required if you installed the bundle using Symfony Flex and the recipe was installed. 155 | 156 | Finally, add some configuration under the ``twig.form_themes`` config key: 157 | 158 | .. code-block:: yaml 159 | 160 | # Symfony 2/3: app/config/config.yml 161 | # Symfony 4: config/packages/twig.yaml 162 | 163 | twig: 164 | form_themes: 165 | - '@FOSCKEditor/Form/ckeditor_widget.html.twig' 166 | -------------------------------------------------------------------------------- /docs/migration.rst: -------------------------------------------------------------------------------- 1 | Migration from IvoryCKEditorBundle to FOSCKEditorBundle 2 | ======================================================= 3 | 4 | Here we will explain the process of migration. 5 | 6 | TL;DR: Check how we migrated `SonataFormatterBundle`_ 7 | 8 | Update composer.json 9 | -------------------- 10 | 11 | .. code-block:: bash 12 | 13 | composer remove egeloen/ckeditor-bundle 14 | composer require friendsofsymfony/ckeditor-bundle 15 | 16 | Update bundle definition 17 | ------------------------ 18 | 19 | Replace:: 20 | 21 | ['all' => true], 26 | ]; 27 | 28 | With:: 29 | 30 | ['all' => true], 35 | ]; 36 | 37 | If you are not using Symfony Flex, then replace this in your AppKernel. 38 | 39 | Replace:: 40 | 41 | add('body', CKEditorType::Class) 156 | 157 | After:: 158 | 159 | add('body', CKEditorType::Class) 164 | 165 | Update service definition 166 | ------------------------- 167 | 168 | If you are fetching any of the services directly from the container you 169 | will have to find all occurrences of ``ivory_ck_editor.*`` in your application 170 | and replace them with ``fos_ck_editor.*``. 171 | 172 | Instead of doing:: 173 | 174 | $this->get('ivory_ck_editor.form.type'); 175 | 176 | You would do:: 177 | 178 | $this-get('fos_ck_editor.form.type'); 179 | 180 | 181 | Regenerate assets 182 | ----------------- 183 | 184 | First fetch ckeditor assets: 185 | 186 | .. code-block:: bash 187 | 188 | bin/console ckeditor:install 189 | 190 | and then regenerate Symfony assets: 191 | 192 | .. code-block:: bash 193 | 194 | bin/console assets:install 195 | 196 | .. _`SonataFormatterBundle`: https://github.com/sonata-project/SonataFormatterBundle/pull/331 197 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | Sphinx!=1.8.0 2 | git+https://github.com/fabpot/sphinx-php.git 3 | sphinx_rtd_theme 4 | -------------------------------------------------------------------------------- /docs/usage/append-javascript.rst: -------------------------------------------------------------------------------- 1 | Append Custom Javascript 2 | ======================== 3 | 4 | The bundle allows you to easily append custom javascript code into 5 | all CKEditor widgets by simply overriding the default templates. Here, 6 | we will configure CKEditor to not remove empty span via the DTD. 7 | 8 | Twig Template 9 | ------------- 10 | 11 | The default Twig template is ``"@FOSCKEditor/Form/ckeditor_widget.html.twig"``. 12 | This one has some blocks you can override according to your needs. 13 | 14 | .. code-block:: twig 15 | 16 | {# app/Resources/views/Form/ckeditor_widget.html.twig #} 17 | {% extends '@FOSCKEditor/Form/ckeditor_widget.html.twig' %} 18 | 19 | {% block ckeditor_widget_extra %} 20 | CKEDITOR.dtd.$removeEmpty['span'] = false; 21 | {% endblock %} 22 | 23 | Then, just need to register your template as a form resources in the 24 | configuration and it will override the default one: 25 | 26 | .. code-block:: yaml 27 | 28 | # app/config/config.yml 29 | twig: 30 | form_themes: 31 | - "::Form/ckeditor_widget.html.twig" 32 | 33 | PHP Template 34 | ------------ 35 | 36 | The default PHP template is ``FOSCKEditorBundle:Form:ckeditor_widget.html.php``. 37 | This one has some slots you can override according to your needs. 38 | 39 | .. code-block:: php 40 | 41 | 42 | extend('FOSCKEditorBundle:Form:ckeditor_widget.html.php') ?> 43 | 44 | start('ckeditor_widget_extra') ?> 45 | CKEDITOR.dtd.$removeEmpty['span'] = false; 46 | stop() ?> 47 | 48 | .. code-block:: yaml 49 | 50 | # app/config/config.yml 51 | framework: 52 | templating: 53 | form: 54 | resources: 55 | - "::Form" 56 | -------------------------------------------------------------------------------- /docs/usage/autoinline.rst: -------------------------------------------------------------------------------- 1 | Disable auto inline 2 | =================== 3 | 4 | By default, CKEditor enables the auto inline feature meaning that any 5 | ``contenteditable`` attribute sets to ``true`` will be converted to CKEditor 6 | instance automatically. If you want to disable it, you can do it globally 7 | in your configuration: 8 | 9 | .. code-block:: yaml 10 | 11 | # app/config/config.yml 12 | fos_ck_editor: 13 | auto_inline: false 14 | 15 | Or you can disable it for a specific widget: 16 | 17 | .. code-block:: php 18 | 19 | $builder->add('field', 'ckeditor', ['auto_inline' => false]); 20 | 21 | .. note:: 22 | 23 | This option will only disable the CKEditor auto inline feature not the 24 | browser one if it supports it. 25 | -------------------------------------------------------------------------------- /docs/usage/ckeditor.rst: -------------------------------------------------------------------------------- 1 | CKEditor Installation 2 | ===================== 3 | 4 | The CKEditor source is not shipped with the bundle due to license restriction 5 | (GPL, LGPL and MPL) whereas the bundle relies on the MIT one which are not 6 | compatible together. To install CKEditor source, you can use the built-in 7 | Symfony command. 8 | 9 | Composer Script 10 | --------------- 11 | 12 | The easiest way to manage CKEditor installation and update is to integrate it 13 | at the middle of your composer routine (after the cache clear but before the 14 | assets installation). 15 | 16 | .. code-block:: json 17 | 18 | { 19 | "scripts": { 20 | "auto-scripts": { 21 | "cache:clear": "symfony-cmd", 22 | "ckeditor:install --clear=drop": "symfony-cmd", 23 | "assets:install --symlink --relative %PUBLIC_DIR%": "symfony-cmd" 24 | }, 25 | "post-install-cmd": [ 26 | "@auto-scripts" 27 | ], 28 | "post-update-cmd": [ 29 | "@auto-scripts" 30 | ] 31 | } 32 | } 33 | 34 | Symfony Command 35 | --------------- 36 | 37 | .. code-block:: bash 38 | 39 | $ php bin/console ckeditor:install 40 | 41 | By default, the command downloads the latest CKEditor full release (samples 42 | directory excluded) in the ``Resource/public`` directory of the bundle. Most of 43 | the time, this is exactly what you want but the command allows you to do more. 44 | 45 | Download Path 46 | ~~~~~~~~~~~~~ 47 | 48 | If you don't want to download CKEditor in the ``Resource/public`` directory of 49 | the bundle, you can use a custom path (absolute): 50 | 51 | .. code-block:: bash 52 | 53 | $ php bin/console ckeditor:install /var/www/html/web/ckeditor 54 | 55 | CKEditor Release 56 | ~~~~~~~~~~~~~~~~ 57 | 58 | You can choose which CKEditor release (full, standard or basic) to download: 59 | 60 | .. code-block:: bash 61 | 62 | $ php bin/console ckeditor:install --release=basic 63 | 64 | CKEditor Custom Build 65 | ~~~~~~~~~~~~~~~~~~~~~ 66 | 67 | It's also possible to use custom build generated using CKEditor online builder: 68 | https://ckeditor.com/cke4/builder. Download ZIP archive from CKEditor website 69 | and use your custom build ID from `build-config.js` file: 70 | 71 | .. code-block:: bash 72 | 73 | $ php bin/console ckeditor:install --release=custom --custom-build-id=574a82a0d3e9226d94b0e91d10eaa372 74 | 75 | CKEditor Version 76 | ~~~~~~~~~~~~~~~~ 77 | 78 | If your want a specific CKEditor version, you can use: 79 | 80 | .. code-block:: bash 81 | 82 | $ php bin/console ckeditor:install --tag=4.6.0 83 | 84 | Silence Progress bars 85 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 86 | 87 | While downloading files necessary for the install, a progress bar will be shown 88 | by default, if you would prefer hiding it, use: 89 | 90 | .. code-block:: bash 91 | 92 | $ php bin/console ckeditor:install --no-progress-bar 93 | 94 | Clear Previous Installation 95 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 96 | 97 | By default, the command will ask you what to do when there is a previous CKEditor 98 | installation detected but in non interactive mode, you can control automatically 99 | how to handle such case: 100 | 101 | .. code-block:: bash 102 | 103 | $ php bin/console ckeditor:install --clear=drop 104 | $ php bin/console ckeditor:install --clear=keep 105 | $ php bin/console ckeditor:install --clear=skip 106 | 107 | - ``drop``: Drop the previous installation & install. 108 | - ``keep``: Keep the previous installation & install by overriding files. 109 | - ``skip``: Keep the previous installation & skip install. 110 | 111 | Path Exclusion 112 | ~~~~~~~~~~~~~~ 113 | 114 | When extracting the downloaded CKEditor ZIP archive, you can exclude paths 115 | such as samples, adapters, whatever: 116 | 117 | .. code-block:: bash 118 | 119 | $ php bin/console ckeditor:install --exclude=samples --exclude=adapters 120 | 121 | Proxy 122 | ~~~~~ 123 | 124 | If you're using a proxy, you can use the following environment variables: 125 | 126 | .. code-block:: bash 127 | 128 | $ export HTTP_PROXY=http://127.0.0.1:8080 129 | $ export HTTPS_PROXY=http://127.0.0.1:8080 130 | 131 | You can also define if the request URI should be full with: 132 | 133 | .. code-block:: bash 134 | 135 | $ export HTTP_PROXY_REQUEST_FULLURI=true 136 | $ export HTTPS_PROXY_REQUEST_FULLURI=true 137 | 138 | Reminder 139 | ~~~~~~~~ 140 | 141 | The command has been well documented, if you want to check it out: 142 | 143 | .. code-block:: bash 144 | 145 | $ php bin/console ckeditor:install --help 146 | -------------------------------------------------------------------------------- /docs/usage/config.rst: -------------------------------------------------------------------------------- 1 | Define reusable configuration 2 | ============================= 3 | 4 | The CKEditor bundle provides an advanced configuration which can be reused on 5 | multiple CKEditor instances. Instead of duplicate the configuration on each form 6 | builder, you can directly configure it once and reuse it all the time. The 7 | bundle allows you to define as many configurations as you want. 8 | 9 | .. tip:: 10 | 11 | Check out the full list of `CKEditor configuration options`_. 12 | 13 | Define a configuration 14 | ---------------------- 15 | 16 | .. code-block:: yaml 17 | 18 | # app/config/config.yml 19 | fos_ck_editor: 20 | configs: 21 | my_config: 22 | toolbar: [ ["Source", "-", "Save"], "/", ["Anchor"], "/", ["Maximize"] ] 23 | uiColor: "#000000" 24 | filebrowserUploadRoute: "my_route" 25 | extraPlugins: "wordcount" 26 | # ... 27 | 28 | .. tip:: 29 | 30 | The config node is a variable node meaning you can put any CKEditor 31 | configuration options in it. 32 | 33 | .. note:: 34 | 35 | The first configuration defined will be used as default configuration 36 | if you don't explicitly configure it. 37 | 38 | Use a configuration 39 | ------------------- 40 | 41 | When you have defined a config, you can use it with the ``config_name`` option: 42 | 43 | .. code-block:: php 44 | 45 | $builder->add('field', 'ckeditor', [ 46 | 'config_name' => 'my_config', 47 | ]); 48 | 49 | Override a configuration 50 | ------------------------ 51 | 52 | If you want to override some parts of the defined config, you can still use the 53 | ``config`` option: 54 | 55 | .. code-block:: php 56 | 57 | $builder->add('field', 'ckeditor', [ 58 | 'config_name' => 'my_config', 59 | 'config' => ['uiColor' => '#ffffff'], 60 | ]); 61 | 62 | Define default configuration 63 | ---------------------------- 64 | 65 | If you want to define your configuration globally to use it by default without 66 | having to use the ``config_name`` option, you can use the ``default_config`` 67 | node: 68 | 69 | .. code-block:: yaml 70 | 71 | # app/config/config.yml 72 | fos_ck_editor: 73 | default_config: my_config 74 | configs: 75 | my_config: 76 | # ... 77 | 78 | .. _`CKEditor configuration options`: http://docs.ckeditor.com/#!/api/CKEDITOR.config 79 | -------------------------------------------------------------------------------- /docs/usage/file-browse-upload.rst: -------------------------------------------------------------------------------- 1 | How to handle file browse/upload 2 | ================================ 3 | 4 | Before starting, be aware there is nothing which will automatically handle file 5 | browse/upload for you in this bundle (it's out of scope). So, you will need to 6 | implement it by yourself and then configure your browse/upload URIs or routes in 7 | the CKEditor configuration or in the widget. 8 | 9 | Supported Options 10 | ----------------- 11 | 12 | CKEditor natively supports different options according to what you want to 13 | browse or upload. This options should be URIs which point to your controllers. 14 | The available options are: 15 | 16 | * filebrowserBrowseUrl 17 | * filebrowserFlashBrowseUrl 18 | * filebrowserImageBrowseUrl 19 | * filebrowserImageBrowseLinkUrl 20 | * filebrowserUploadUrl 21 | * filebrowserFlashUploadUrl 22 | * filebrowserImageUploadUrl 23 | 24 | Custom Options 25 | -------------- 26 | 27 | CKEditor also supports custom options which can be available if you install 28 | plugins. For example, the HTML5 video plugin adds the following options: 29 | 30 | * filebrowserVideoBrowseUrl 31 | * filebrowserVideoUploadUrl 32 | 33 | To make the bundle aware of these new options, you can configure it globally 34 | in your configuration file: 35 | 36 | .. code-block:: yaml 37 | 38 | # app/config/config.yml 39 | fos_ck_editor: 40 | filebrowsers: 41 | - VideoBrowse 42 | - VideoUpload 43 | 44 | Or you can configure it in your widget: 45 | 46 | .. code-block:: php 47 | 48 | $builder->add('field', 'ckeditor', [ 49 | 'filebrowsers' => [ 50 | 'VideoUpload', 51 | 'VideoBrowse', 52 | ], 53 | ]); 54 | 55 | Routing Options 56 | --------------- 57 | 58 | To ease the CKEditor file handling, the bundle adds options which are not in 59 | CKEditor by default. These options are related to the Symfony `Routing Component`_ 60 | and allow you to configure routes instead of URIs. For each ``*Url`` option, 61 | three new options are available. 62 | 63 | For example, the ``filebrowserBrowseUrl`` option can be generated with these 64 | three new options: 65 | 66 | * filebrowserBrowseRoute 67 | * filebrowserBrowseRouteParameters 68 | * filebrowserBrowseRouteType 69 | 70 | Static Routing 71 | ~~~~~~~~~~~~~~ 72 | 73 | If your routing is static, you can configure these options globally in your 74 | configuration: 75 | 76 | .. code-block:: yaml 77 | 78 | # app/config/config.yml 79 | fos_ck_editor: 80 | default_config: my_config 81 | configs: 82 | my_config: 83 | filebrowserBrowseRoute: "my_route" 84 | filebrowserBrowseRouteParameters: { slug: "my-slug" } 85 | filebrowserBrowseRouteType: 0 86 | 87 | Or you can configure it your widget: 88 | 89 | .. code-block:: php 90 | 91 | $builder->add('field', 'ckeditor', [ 92 | 'config' => [ 93 | 'filebrowserBrowseRoute' => 'my_route', 94 | 'filebrowserBrowseRouteParameters' => ['slug' => 'my-slug'], 95 | 'filebrowserBrowseRouteType' => UrlGeneratorInterface::ABSOLUTE_URL, 96 | ], 97 | ]); 98 | 99 | Dynamic Routing 100 | ~~~~~~~~~~~~~~~ 101 | 102 | If the static routing does not fit your needs, you can use the 103 | ``filebrowser*Handler`` option allowing you to build your own url with a simple 104 | but much more powerful closure and so make it aware of your dependencies: 105 | 106 | .. code-block:: php 107 | 108 | // A blog post... 109 | $post = $manager->find($id); 110 | 111 | $builder->add('field', 'ckeditor', [ 112 | 'config' => [ 113 | 'filebrowserBrowseHandler' => function (RouterInterface $router) use ($post) { 114 | return $router->generate( 115 | 'my_route', 116 | ['slug' => $post->getSlug()], 117 | UrlGeneratorInterface::ABSOLUTE_URL 118 | ); 119 | }, 120 | ], 121 | ]); 122 | 123 | Integration with Other Projects 124 | ------------------------------- 125 | 126 | If you want to simplify your life, you can directly use other bundles which have 127 | already integrated the concept explain in the previous chapter. 128 | 129 | Sonata integration 130 | ~~~~~~~~~~~~~~~~~~ 131 | 132 | The `CoopTilleulsCKEditorSonataMediaBundle`_ provides a `SonataMedia`_ 133 | integration with this bundle. 134 | 135 | ELFinder integration 136 | ~~~~~~~~~~~~~~~~~~~~ 137 | 138 | The `FMElfinderBundle`_ provides a `ELFinder`_ integration with this bundle. 139 | 140 | .. _`Routing Component`: http://symfony.com/doc/current/book/routing.html 141 | .. _`CoopTilleulsCKEditorSonataMediaBundle`: https://github.com/coopTilleuls/CoopTilleulsCKEditorSonataMediaBundle 142 | .. _`SonataMedia`: http://sonata-project.org/bundles/media 143 | .. _`FMElfinderBundle`: https://github.com/helios-ag/FMElfinderBundle 144 | .. _`ELFinder`: http://elfinder.org 145 | -------------------------------------------------------------------------------- /docs/usage/index.rst: -------------------------------------------------------------------------------- 1 | Usage 2 | ===== 3 | 4 | If you ever wondered something about this bundle, you are at the good place. 5 | Here, you can find all bundle features: 6 | 7 | .. toctree:: 8 | :hidden: 9 | 10 | ckeditor 11 | config 12 | toolbar 13 | loading 14 | language 15 | file-browse-upload 16 | jquery 17 | json-builder 18 | require-js 19 | append-javascript 20 | textarea-sync 21 | textarea-fallback 22 | autoinline 23 | inline 24 | plugin 25 | skin 26 | style 27 | template 28 | version 29 | 30 | .. include:: index.rst.inc 31 | -------------------------------------------------------------------------------- /docs/usage/index.rst.inc: -------------------------------------------------------------------------------- 1 | * :doc:`/usage/ckeditor` 2 | * :doc:`/usage/config` 3 | * :doc:`/usage/toolbar` 4 | * :doc:`/usage/loading` 5 | * :doc:`/usage/language` 6 | * :doc:`/usage/file-browse-upload` 7 | * :doc:`/usage/jquery` 8 | * :doc:`/usage/json-builder` 9 | * :doc:`/usage/require-js` 10 | * :doc:`/usage/append-javascript` 11 | * :doc:`/usage/textarea-sync` 12 | * :doc:`/usage/textarea-fallback` 13 | * :doc:`/usage/autoinline` 14 | * :doc:`/usage/inline` 15 | * :doc:`/usage/plugin` 16 | * :doc:`/usage/skin` 17 | * :doc:`/usage/style` 18 | * :doc:`/usage/template` 19 | * :doc:`/usage/version` 20 | -------------------------------------------------------------------------------- /docs/usage/inline.rst: -------------------------------------------------------------------------------- 1 | Use inline editing 2 | ================== 3 | 4 | By default, the bundle uses a `Classic Editing`_ which relies on 5 | ``CKEDITOR.replace``. If you want to use the `Inline Editing`_ which relies on 6 | ``CKEDITOR.inline``, you can configure it globally in your configuration: 7 | 8 | .. code-block:: yaml 9 | 10 | # app/config/config.yml 11 | fos_ck_editor: 12 | inline: true 13 | 14 | Or you can configure it in your widget: 15 | 16 | .. code-block:: php 17 | 18 | $builder->add('field', 'ckeditor', ['inline' => true]); 19 | 20 | .. _`Classic Editing`: http://docs.ckeditor.com/#!/guide/dev_framed 21 | .. _`Inline Editing`: http://docs.ckeditor.com/#!/guide/dev_inline 22 | -------------------------------------------------------------------------------- /docs/usage/jquery.rst: -------------------------------------------------------------------------------- 1 | jQuery adapter 2 | ============== 3 | 4 | If your application relies on jQuery, we recommend you to use the jQuery 5 | adapter. The bundle will automatically wrap the CKEditor instantiation into a 6 | ``jQuery(document).ready()`` block making the code more reliable. 7 | 8 | Enable the Adapter 9 | ------------------ 10 | 11 | The CKEditor jQuery adapter is by default not loaded even if the ``autoload`` 12 | option is enabled. In order to load it, the ``autoload`` flag must be enabled 13 | and you must explicitly enable the jQuery adapter. You can do it globally in 14 | your configuration: 15 | 16 | .. code-block:: yaml 17 | 18 | # app/config/config.yml 19 | fos_ck_editor: 20 | jquery: true 21 | 22 | Or you can do it in your widget: 23 | 24 | .. code-block:: php 25 | 26 | $builder->add('field', 'ckeditor', ['jquery' => true]); 27 | 28 | Use your Adapter 29 | ---------------- 30 | 31 | Additionally, the jQuery adapter used by default is the one shipped with the 32 | bundle in ``Resources/public/adapters/jquery.js``. If you would prefer use 33 | your own, you can configure it globally in your configuration: 34 | 35 | .. code-block:: yaml 36 | 37 | # app/config/config.yml 38 | fos_ck_editor: 39 | jquery_path: your/own/jquery.js 40 | 41 | Or you can configure it in your widget: 42 | 43 | .. code-block:: php 44 | 45 | $builder->add('field', 'ckeditor', ['jquery_path' => 'your/own/jquery.js']); 46 | 47 | .. note:: 48 | 49 | Each path must be relative to the web directory. 50 | -------------------------------------------------------------------------------- /docs/usage/json-builder.rst: -------------------------------------------------------------------------------- 1 | JsonBuilder 2 | =========== 3 | 4 | Overview 5 | -------- 6 | 7 | The JSON builder allows you to build your JSON through the Symfony 8 | `PropertyAccess Component`_ while keeping the control of the value escaping. 9 | 10 | Create a builder 11 | ~~~~~~~~~~~~~~~~ 12 | 13 | To build some JSON, you will need to instantiate a builder:: 14 | 15 | use FOS\CKEditorBundle\Builder\JsonBuilder; 16 | 17 | $builder = new JsonBuilder(); 18 | 19 | 20 | Set your values 21 | ~~~~~~~~~~~~~~~ 22 | 23 | To set your values on the builder, you can either use ``setValues`` or 24 | ``setValue`` but be aware they don't behave same. Basically, ``setValues`` 25 | allows you to append a set of values in the builder without escaping 26 | control whereas ``setValue`` allows you to append one value in the builder 27 | but with escaping control. 28 | 29 | Append a set of values 30 | ~~~~~~~~~~~~~~~~~~~~~~ 31 | 32 | To append a set of values in the builder, just use ``setValues`` and 33 | pass your values as first argument: 34 | 35 | .. code-block:: php 36 | 37 | $builder->setValues(['foo' => ['bar']]); 38 | 39 | Additionally, this method takes as second argument a path prefix (`PropertyAccess Component`_) 40 | which allows you to append your values where you want in the builder graph. 41 | So, the next sample is basically the equivalent of the precedent:: 42 | 43 | $builder->setValues(['bar'], '[foo]'); 44 | 45 | Append one value 46 | ~~~~~~~~~~~~~~~~ 47 | 48 | To append one value in the builder, just use ``setValue`` and pass the 49 | path as first argument and the value as second one:: 50 | 51 | $builder->setValue('[foo][0]','bar'); 52 | 53 | 54 | If you want to keep control of the value escaping, this part is for you. 55 | Basically, just pass ``false`` as third argument:: 56 | 57 | $builder->setValue('[foo][0]','bar', false); 58 | 59 | Configure the JSON encode options 60 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 61 | 62 | By default, the JSON builder uses the native ``json_encode`` options. 63 | To override it, you can use:: 64 | 65 | $builder->setJsonEncodeOptions(JSON_FORCE_OBJECT); 66 | $jsonEncodeOptions = $builder->getJsonEncodeOptions(); 67 | 68 | Values for those options can be found in `PHP documentation for json_decode()`_ 69 | 70 | Build your JSON 71 | ~~~~~~~~~~~~~~~ 72 | 73 | Once your builder is well configured, you can build your JSON:: 74 | 75 | $json = $builder->build(); 76 | 77 | Reset the builder 78 | ----------------- 79 | 80 | Because the builder is stateful (keep a track of every values), you 81 | need to reset it if you want to restart a json build:: 82 | 83 | $builder->reset(); 84 | 85 | Example 86 | ~~~~~~~ 87 | 88 | .. code-block:: php 89 | 90 | use FOS\CKEditorBundle\Builder\JsonBuilder; 91 | 92 | $builder = new JsonBuilder(); 93 | 94 | // {"0":"foo","1":bar} 95 | echo $builder 96 | ->setJsonEncodeOptions(JSON_FORCE_OBJECT) 97 | ->setValues(['foo']) 98 | ->setValue('[1]', 'bar', false) 99 | ->build(); 100 | 101 | // {"foo":["bar"],"baz":bat} 102 | echo $builder 103 | ->reset() 104 | ->setValues(['foo' => ['bar']]) 105 | ->setValue('[baz]', 'bat', false) 106 | ->build(); 107 | 108 | .. _`PHP documentation for json_decode()`: http://php.net/manual/en/function.json-encode.php 109 | .. _`PropertyAccess Component`: http://symfony.com/doc/current/components/property_access/index.html 110 | -------------------------------------------------------------------------------- /docs/usage/language.rst: -------------------------------------------------------------------------------- 1 | Manage language 2 | =============== 3 | 4 | Automatic language 5 | ------------------ 6 | 7 | By default, the bundle will try to automatically guess the language of your editor 8 | according to the request locale. If it is not available, it will fallback on the 9 | ``locale`` container parameter. If it is also not available, the editor language 10 | cannot be guessed and so, the editor will use the default editor language. 11 | 12 | Explicit language 13 | ----------------- 14 | 15 | CKEditor allows you to customize the language used by the editor via the 16 | ``language`` config option. If you define this option, this explicit language 17 | will be used instead of the automatic one. You can do it globally in your 18 | configuration: 19 | 20 | .. code-block:: yaml 21 | 22 | # app/config/config.yml 23 | fos_ck_editor: 24 | configs: 25 | my_config: 26 | language: fr 27 | 28 | Or you can do it in your widget: 29 | 30 | .. code-block:: php 31 | 32 | $builder->add('field', 'ckeditor', ['config' => [ 33 | 'language' => 'fr', 34 | ]]); 35 | -------------------------------------------------------------------------------- /docs/usage/loading.rst: -------------------------------------------------------------------------------- 1 | Manage CKEditor loading 2 | ======================= 3 | 4 | By default, in order to prototype your form really fast, the bundle loads 5 | the CKEditor library each time you declare a CKEditor form. Basically, it 6 | means that if you have three CKEditor fields in your form, then, there will 7 | be three CKEditor library loadings. 8 | 9 | Load CKEditor manually 10 | ---------------------- 11 | 12 | If you want to control the CKEditor loading, you can configure the bundle to 13 | not load the library at all and let you the control of it. To disable the 14 | CKEditor library loading, you can do it globally in your configuration: 15 | 16 | .. code-block:: yaml 17 | 18 | # app/config/config.yml 19 | fos_ck_editor: 20 | autoload: false 21 | 22 | Or you can disable it in your widget: 23 | 24 | .. code-block:: php 25 | 26 | $builder->add('field', 'ckeditor', ['autoload' => false]); 27 | 28 | .. note:: 29 | 30 | If you use this approach, be aware CKEditor must be loaded before any fields 31 | have been rendered, so we recommend you to register it in the ```` of 32 | your page. 33 | 34 | Load CKEditor asynchronously 35 | ---------------------------- 36 | 37 | If you want to load CKEditor at the bottom of your page, the best way is to still 38 | disable the CKEditor loading (in order to let you load CKEditor at the bottom of 39 | the page only one time) but also to configure the bundle to render the javascript 40 | latter with a dedicated function shipped in a third party bundle named 41 | IvoryFormExtraBundle_. 42 | 43 | So, first you need configure the bundle. You can do it globally in your 44 | configuration: 45 | 46 | .. code-block:: yaml 47 | 48 | # app/config/config.yml 49 | fos_ck_editor: 50 | autoload: false 51 | async: true 52 | 53 | Or you can configure it in your widget: 54 | 55 | .. code-block:: php 56 | 57 | $builder->add('field', 'ckeditor', [ 58 | 'autoload' => false, 59 | 'async' => true, 60 | ]); 61 | 62 | Then, install the third party bundles as explained in its 63 | `documentation `_. 64 | 65 | Finally, in your Twig template, you can render the form javascript with: 66 | 67 | .. code-block:: twig 68 | 69 | {{ form_javascript(form) }} 70 | 71 | Or if you use the PHP templating engine: 72 | 73 | .. code-block:: php 74 | 75 | javascript($form) ?> 76 | 77 | .. note:: 78 | 79 | If you use this approach, be aware CKEditor must be loaded before you render the 80 | form javascript. 81 | 82 | .. _`IvoryFormExtraBundle`: https://github.com/egeloen/IvoryFormExtraBundle 83 | -------------------------------------------------------------------------------- /docs/usage/plugin.rst: -------------------------------------------------------------------------------- 1 | Plugin support 2 | ============== 3 | 4 | The bundle offers you the ability to manage extra plugins. To understand how it 5 | works, you will enable the `Wordcount`_ plugin for our CKEditor widget. 6 | 7 | Install the Plugin 8 | ------------------ 9 | 10 | First, you need to download and extract it in the web directory. For that, you 11 | have two possibilities: 12 | 13 | #. Directly put the plugin in the web directory (``/web/ckeditor/plugins/`` for 14 | example). 15 | #. Put the plugin in the ``/Resources/public/`` directory of any of your bundles. 16 | 17 | Register the Plugin 18 | ------------------- 19 | 20 | In order to load it, you need to specify its location. For that, you can do it 21 | globally in your configuration: 22 | 23 | .. code-block:: yaml 24 | 25 | # app/config/config.yml 26 | fos_ck_editor: 27 | default_config: my_config 28 | configs: 29 | my_config: 30 | extraPlugins: "wordcount" 31 | plugins: 32 | wordcount: 33 | path: "/bundles/mybundle/wordcount/" # with trailing slash 34 | filename: "plugin.js" 35 | 36 | Or you can do it in your widget: 37 | 38 | .. code-block:: php 39 | 40 | $builder->add('field', 'ckeditor', [ 41 | 'config' => [ 42 | 'extraPlugins' => 'wordcount', 43 | ], 44 | 'plugins' => [ 45 | 'wordcount' => [ 46 | 'path' => '/bundles/mybundle/wordcount/', // with trailing slash 47 | 'filename' => 'plugin.js', 48 | ], 49 | ], 50 | ]); 51 | 52 | Plugin dependency 53 | ----------------- 54 | 55 | Once your plugin is installed and registered, you will also need to install and 56 | register these dependencies. Hopefully, the ``wordcount`` has no extra dependency 57 | but other plugin can require extra ones. So if it is the case, you need to redo 58 | the process for them and so on. 59 | 60 | Plugin icon 61 | ----------- 62 | 63 | If you don't configure a built-in toolbar or a custom toolbar, the plugin icon 64 | should be visible automatically according to the plugin configuration otherwise, 65 | it is your responsibility to configure it. Take a look to this 66 | :doc:`documentation `. 67 | 68 | .. _`Wordcount`: http://ckeditor.com/addon/wordcount 69 | -------------------------------------------------------------------------------- /docs/usage/require-js.rst: -------------------------------------------------------------------------------- 1 | RequireJS Support 2 | ================= 3 | 4 | If your application relies on RequireJS, we recommend you to enable its 5 | support. The bundle will automatically wrap the CKEditor instantiation into 6 | a ``require`` block making the code more reliable. 7 | 8 | Configure RequireJS 9 | ------------------- 10 | 11 | The first step is to configure RequireJS in order to make it aware of where 12 | CKEditor is located. For that, you can use the following snippet: 13 | 14 | .. code-block:: js 15 | 16 | { 17 | paths: { 18 | 'ckeditor': '{{ asset("bundles/fosckeditor/ckeditor") }}' 19 | }, 20 | shim: { 21 | 'ckeditor': { 22 | deps: ['jQuery'], 23 | exports: 'CKEDITOR' 24 | } 25 | } 26 | } 27 | 28 | Enable RequireJS 29 | ---------------- 30 | 31 | The second step is to enable RequireJS in the bundle. To do so, you can 32 | configure it globally in you configuration: 33 | 34 | .. code-block:: yaml 35 | 36 | # app/config/config.yml 37 | fos_ck_editor: 38 | require_js: true 39 | 40 | Or you can configure it in your widget: 41 | 42 | .. code-block:: php 43 | 44 | $builder->add('field', 'ckeditor', ['require_js' => true]); 45 | -------------------------------------------------------------------------------- /docs/usage/skin.rst: -------------------------------------------------------------------------------- 1 | Skin support 2 | ============ 3 | 4 | Install your Skin 5 | ----------------- 6 | 7 | First of all, you need to download and extract your skin in the web directory. 8 | For that, you have two possibilities: 9 | 10 | #. Directly put it in the web directory (``/web/ckeditor/`` for example). 11 | #. Put it in the ``/Resources/public/`` directory of any of your bundles and 12 | install the assets. 13 | 14 | Register your Skin 15 | ------------------ 16 | 17 | Then, to use your skin, just need to register it globally in your configuration: 18 | 19 | .. code-block:: yaml 20 | 21 | # app/config/config.yml 22 | fos_ck_editor: 23 | default_config: my_config 24 | configs: 25 | my_config: 26 | skin: "skin_name,/bundles/mybundle/skins/skin_name/" 27 | 28 | Or you can do it in your widget: 29 | 30 | .. code-block:: php 31 | 32 | $builder->add('field', 'ckeditor', [ 33 | 'config' => ['skin' => 'skin_name,/bundles/mybundle/skins/skin_name/'], 34 | ]); 35 | 36 | .. note:: 37 | 38 | The skin path must be an absolute path relative to the `web` directory. 39 | -------------------------------------------------------------------------------- /docs/usage/style.rst: -------------------------------------------------------------------------------- 1 | Style support 2 | ============= 3 | 4 | The bundle allows you to define your own styles. Like plugins, you can define 5 | them globally in your configuration: 6 | 7 | .. code-block:: yaml 8 | 9 | # app/config/config.yml 10 | fos_ck_editor: 11 | default_config: my_config 12 | configs: 13 | my_config: 14 | stylesSet: "my_styles" 15 | styles: 16 | my_styles: 17 | - { name: "Blue Title", element: "h2", styles: { color: "Blue" }} 18 | - { name: "CSS Style", element: "span", attributes: { class: "my_style" }} 19 | - { name: "Widget Style", type: widget, widget: "my_widget", attributes: { class: "my_widget_style" }} 20 | 21 | Or you can define them in your widget: 22 | 23 | .. code-block:: php 24 | 25 | $builder->add('field', 'ckeditor', [ 26 | 'config' => [ 27 | 'stylesSet' => 'my_styles', 28 | ], 29 | 'styles' => [ 30 | 'my_styles' => [ 31 | ['name' => 'Blue Title', 'element' => 'h2', 'styles' => ['color' => 'Blue']], 32 | ['name' => 'CSS Style', 'element' => 'span', 'attributes' => ['class' => 'my_style']], 33 | ['name' => 'Multiple Element Style', 'element' => ['h2', 'span'], 'attributes' => ['class' => 'my_class']], 34 | ['name' => 'Widget Style', 'type' => 'widget' , 'widget' => 'my_widget', 'attributes' => ['class' => 'my_widget_style']], 35 | ], 36 | ], 37 | ]); 38 | -------------------------------------------------------------------------------- /docs/usage/template.rst: -------------------------------------------------------------------------------- 1 | Template support 2 | ================ 3 | 4 | Enable the Templates Plugin 5 | --------------------------- 6 | 7 | The bundle offers you the ability to manage extra templates. To use this 8 | feature, you need to enable the ``templates`` plugins shipped with the bundle. 9 | You can define it globally in your configuration: 10 | 11 | .. code-block:: yaml 12 | 13 | # app/config/config.yml 14 | fos_ck_editor: 15 | default_config: my_config 16 | configs: 17 | my_config: 18 | extraPlugins: "templates" 19 | 20 | Or you can define it in your widget: 21 | 22 | .. code-block:: php 23 | 24 | $builder->add('field', 'ckeditor', [ 25 | 'config' => [ 26 | 'extraPlugins' => 'templates', 27 | ], 28 | ]); 29 | 30 | Configure your templates 31 | ------------------------ 32 | 33 | .. code-block:: yaml 34 | 35 | # app/config/config.yml 36 | fos_ck_editor: 37 | default_config: my_config 38 | configs: 39 | my_config: 40 | extraPlugins: "templates" 41 | templates: "my_templates" 42 | templates: 43 | my_templates: 44 | imagesPath: "/bundles/mybundle/templates/images" 45 | templates: 46 | - 47 | title: "My Template" 48 | image: "image.jpg" 49 | description: "My awesome template" 50 | html: "

Crazy template :)

" 51 | 52 | Or you can define them in your widget: 53 | 54 | .. code-block:: php 55 | 56 | $builder->add('field', 'ckeditor', [ 57 | 'config' => [ 58 | 'extraPlugins' => 'templates', 59 | 'templates' => 'my_template', 60 | ], 61 | 'templates' => [ 62 | 'my_template' => [ 63 | 'imagesPath' => '/bundles/mybundle/templates/images', 64 | 'templates' => [ 65 | [ 66 | 'title' => 'My Template', 67 | 'image' => 'images.jpg', 68 | 'description' => 'My awesome template', 69 | 'html' => '

Crazy template :)

', 70 | ], 71 | // ... 72 | ], 73 | ], 74 | ], 75 | ]); 76 | 77 | Use a dedicated template 78 | ------------------------ 79 | 80 | If you prefer define your html in a dedicated Twig or PHP template, you can 81 | replace the ``html`` node by the ``template`` one and provide the path of your 82 | template. You can optionally provide template parameters with the 83 | ``template_parameters`` node. 84 | 85 | .. code-block:: yaml 86 | 87 | # app/config/config.yml 88 | fos_ck_editor: 89 | default_config: my_config 90 | configs: 91 | my_config: 92 | extraPlugins: "templates" 93 | templates: "my_templates" 94 | templates: 95 | my_templates: 96 | imagesPath: "/bundles/mybundle/templates/images" 97 | templates: 98 | - 99 | title: "My Template" 100 | image: "image.jpg" 101 | description: "My awesome template" 102 | template: "AppBundle:CKEditor:template.html.twig" 103 | template_parameters: 104 | foo: bar 105 | 106 | Or you can define them in your widget: 107 | 108 | .. code-block:: php 109 | 110 | $builder->add('field', 'ckeditor', [ 111 | 'config' => [ 112 | 'extraPlugins' => 'templates', 113 | 'templates' => 'my_template', 114 | ], 115 | 'templates' => [ 116 | 'my_template' => [ 117 | 'imagesPath' => '/bundles/mybundle/templates/images', 118 | 'templates' => [ 119 | [ 120 | 'title' => 'My Template', 121 | 'image' => 'images.jpg', 122 | 'description' => 'My awesome template', 123 | 'template' => 'AppBundle:CKEditor:template.html.twig', 124 | 'template_parameters' => ['foo' => 'bar'], 125 | ], 126 | // ... 127 | ], 128 | ], 129 | ], 130 | ]); 131 | -------------------------------------------------------------------------------- /docs/usage/textarea-fallback.rst: -------------------------------------------------------------------------------- 1 | Fallback to textarea 2 | ==================== 3 | 4 | Sometimes, you don't want to use the CKEditor widget but a simple textarea (e.g 5 | testing purpose). As CKEditor uses an iFrame to render the widget, it can be 6 | difficult to automate something on it. To disable CKEditor and fallback on the 7 | parent widget (textarea), you can disable it globally in your configuration: 8 | 9 | .. code-block:: yaml 10 | 11 | # app/config/config_test.yml 12 | fos_ck_editor: 13 | enable: false 14 | 15 | Or you can disable it in your widget: 16 | 17 | .. code-block:: php 18 | 19 | $builder->add('field', 'ckeditor', ['enable' => false]); 20 | -------------------------------------------------------------------------------- /docs/usage/textarea-sync.rst: -------------------------------------------------------------------------------- 1 | Synchronize the textarea 2 | ======================== 3 | 4 | When the textarea is transformed into a CKEditor widget, the textarea value is 5 | no more populated except when the form is submitted. Then, it leads to issues 6 | when you try to serialize the form or you try to rely on the textarea value in 7 | JavaScript. To automatically synchronize the textarea value, you can do it 8 | globally in your configuration: 9 | 10 | .. code-block:: yaml 11 | 12 | # app/config/config.yml 13 | fos_ck_editor: 14 | input_sync: true 15 | 16 | Or you can do it in your widget: 17 | 18 | .. code-block:: php 19 | 20 | $builder->add('field', 'ckeditor', ['input_sync' => true]); 21 | -------------------------------------------------------------------------------- /docs/usage/toolbar.rst: -------------------------------------------------------------------------------- 1 | Customize the toolbar 2 | ===================== 3 | 4 | Built-in Toolbars 5 | ----------------- 6 | 7 | CKEditor provides three different packages with their own configurations (full, 8 | standard & basic). The bundle is shipped with the full edition but you can 9 | easily switch the toolbar configuration by using the ``full``, ``standard`` or 10 | ``basic`` keyword as toolbar. You can configure it globally in your configuration: 11 | 12 | .. code-block:: yaml 13 | 14 | # config/packages/fos_ck_editor.yaml 15 | fos_ck_editor: 16 | configs: 17 | my_config: 18 | toolbar: full 19 | 20 | Or directly configure your widget: 21 | 22 | .. code-block:: php 23 | 24 | $builder->add('field', 'ckeditor', [ 25 | 'config' => ['toolbar' => 'full'], 26 | ]); 27 | 28 | 29 | Custom Toolbar 30 | -------------- 31 | 32 | Build a toolbar in the configuration or especially in the widget is really a 33 | pain. Each time, you want a custom one, you need to redefine all the structure. 34 | To avoid this duplication, the bundle allows you to define your own toolbars or 35 | override the built-in ones in a separate node and reuse them. This feature is 36 | only available in your configuration. 37 | 38 | .. code-block:: yaml 39 | 40 | # config/packages/fos_ck_editor.yaml 41 | fos_ck_editor: 42 | configs: 43 | my_config_1: 44 | toolbar: "my_toolbar_1" 45 | uiColor: "#000000" 46 | # ... 47 | my_config_2: 48 | toolbar: "my_toolbar_2" 49 | uiColor: "#ffffff" 50 | # ... 51 | my_config_2: 52 | toolbar: "my_toolbar_1" 53 | uiColor: "#cccccc" 54 | toolbars: 55 | configs: 56 | my_toolbar_1: [ [ "Source", "-", "Save" ], "/", [ "Anchor" ], "/", [ "Maximize" ] ] 57 | my_toolbar_2: [ [ "Source" ], "/", [ "Anchor" ], "/", [ "Maximize" ] ] 58 | 59 | Here, we see how is structured a toolbar. A toolbar is an array of toolbars 60 | (strips), each one being also an array, containing a list of UI items. To do a 61 | carriage return, you just have to add the char ``/`` between strips. It relies 62 | on the exact same structure than CKEditor itself. 63 | 64 | Using the toolbars node is better but the config is still not perfect as you 65 | still have code duplications in the toolbar items. To avoid this part, you can 66 | define a group of items in a separate node & then, inject them in your toolbar 67 | by prefixing them with a ``@``. 68 | 69 | .. code-block:: yaml 70 | 71 | fos_ck_editor: 72 | configs: 73 | my_config_1: 74 | toolbar: "my_toolbar_1" 75 | uiColor: "#000000" 76 | # ... 77 | my_config_2: 78 | toolbar: "my_toolbar_2" 79 | uiColor: "#ffffff" 80 | # ... 81 | toolbars: 82 | configs: 83 | my_toolbar_1: [ "@document", "/", "@link" , "/", "@tool" ] 84 | my_toolbar_2: [ "@document", "/", "@tool" ] 85 | items: 86 | document: [ "Source", "-", "Save" ] 87 | link: [ "Anchor" ] 88 | tool: [ "Maximize" ] 89 | 90 | The built-in configurations (full, standard, basic) are also using items so if 91 | you want to just override one part of a configuration, just override it: 92 | 93 | .. code-block:: yaml 94 | 95 | fos_ck_editor: 96 | configs: 97 | my_config: 98 | toolbar: "full" 99 | toolbars: 100 | items: 101 | full.colors: [ "TextColor", "BGColor" ] 102 | full.document: [ "Source", "-", "Preview", "Print" ] 103 | 104 | .. note:: 105 | 106 | If you want the full list of built-in items, check the 107 | `FOS\\CKEditorBundle\\Config\\CKEditorConfiguration` class. 108 | -------------------------------------------------------------------------------- /docs/usage/version.rst: -------------------------------------------------------------------------------- 1 | Use your own CKEditor 2 | ===================== 3 | 4 | The bundle is shipped with the latest CKEditor 4 full release. If you don't want 5 | to use it, the bundle allows you to use your own by defining it in your 6 | configuration file or in your widget. 7 | 8 | Install your CKEditor 9 | --------------------- 10 | 11 | First of all, you need to download and extract your own CKEditor version in the 12 | public directory. For that, you have two possibilities: 13 | 14 | #. Directly put it in the web directory (``/public/ckeditor/`` for example). 15 | #. Put it in the ``/Resources/public/`` directory of any of your bundles and 16 | install the assets. 17 | 18 | Register your CKEditor 19 | ---------------------- 20 | 21 | Then, to use your own CKEditor instead of the built-in, just need to register it 22 | in your configuration or in your widget: 23 | 24 | .. code-block:: yaml 25 | 26 | # app/config/config.yml 27 | fos_ck_editor: 28 | base_path: "ckeditor" 29 | js_path: "ckeditor/ckeditor.js" 30 | 31 | .. code-block:: php 32 | 33 | $builder->add('field', 'ckeditor', [ 34 | 'base_path' => 'ckeditor', 35 | 'js_path' => 'ckeditor/ckeditor.js', 36 | ]); 37 | 38 | .. note:: 39 | 40 | Each path must be relative to the public directory. 41 | -------------------------------------------------------------------------------- /src/Builder/JsonBuilder.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please read the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace FOS\CKEditorBundle\Builder; 14 | 15 | use Symfony\Component\PropertyAccess\PropertyAccessorInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | final class JsonBuilder 21 | { 22 | /** 23 | * @var PropertyAccessorInterface 24 | */ 25 | private $propertyAccessor; 26 | 27 | /** 28 | * @var array 29 | */ 30 | private $values = []; 31 | 32 | /** 33 | * @var array 34 | */ 35 | private $escapes = []; 36 | 37 | /** 38 | * @var int 39 | */ 40 | private $jsonEncodeOptions = 0; 41 | 42 | public function __construct(PropertyAccessorInterface $propertyAccessor) 43 | { 44 | $this->propertyAccessor = $propertyAccessor; 45 | 46 | $this->reset(); 47 | } 48 | 49 | public function getJsonEncodeOptions(): int 50 | { 51 | return $this->jsonEncodeOptions; 52 | } 53 | 54 | public function setJsonEncodeOptions(int $jsonEncodeOptions): self 55 | { 56 | $this->jsonEncodeOptions = $jsonEncodeOptions; 57 | 58 | return $this; 59 | } 60 | 61 | public function hasValues(): bool 62 | { 63 | return !empty($this->values); 64 | } 65 | 66 | public function getValues(): array 67 | { 68 | return $this->values; 69 | } 70 | 71 | public function setValues(array $values, ?string $pathPrefix = null): self 72 | { 73 | foreach ($values as $key => $value) { 74 | $path = sprintf('%s[%s]', $pathPrefix, $key); 75 | 76 | if (\is_array($value) && !empty($value)) { 77 | $this->setValues($value, $path); 78 | } else { 79 | $this->setValue($path, $value); 80 | } 81 | } 82 | 83 | return $this; 84 | } 85 | 86 | public function setValue(string $path, mixed $value, bool $escapeValue = true): self 87 | { 88 | if (!$escapeValue) { 89 | $placeholder = uniqid('friendsofsymfony', true); 90 | $this->escapes[sprintf('"%s"', $placeholder)] = $value; 91 | 92 | $value = $placeholder; 93 | } 94 | 95 | $this->values[$path] = $value; 96 | 97 | return $this; 98 | } 99 | 100 | public function removeValue(string $path): self 101 | { 102 | unset($this->values[$path], $this->escapes[$path]); 103 | 104 | return $this; 105 | } 106 | 107 | public function reset(): self 108 | { 109 | $this->values = []; 110 | $this->escapes = []; 111 | $this->jsonEncodeOptions = 0; 112 | 113 | return $this; 114 | } 115 | 116 | public function build(): string 117 | { 118 | $values = []; 119 | 120 | foreach ($this->values as $path => $value) { 121 | $this->propertyAccessor->setValue($values, $path, $value); 122 | } 123 | 124 | $json = json_encode($values, $this->jsonEncodeOptions); 125 | 126 | \assert(\is_string($json)); 127 | 128 | return str_replace( 129 | array_keys($this->escapes), 130 | array_values($this->escapes), 131 | $json 132 | ); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/Command/CKEditorInstallerCommand.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please read the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace FOS\CKEditorBundle\Command; 14 | 15 | use FOS\CKEditorBundle\Installer\CKEditorInstaller; 16 | use Symfony\Component\Console\Command\Command; 17 | use Symfony\Component\Console\Helper\ProgressBar; 18 | use Symfony\Component\Console\Helper\QuestionHelper; 19 | use Symfony\Component\Console\Input\InputArgument; 20 | use Symfony\Component\Console\Input\InputInterface; 21 | use Symfony\Component\Console\Input\InputOption; 22 | use Symfony\Component\Console\Output\NullOutput; 23 | use Symfony\Component\Console\Output\OutputInterface; 24 | use Symfony\Component\Console\Question\ChoiceQuestion; 25 | 26 | /** 27 | * @author GeLo 28 | */ 29 | final class CKEditorInstallerCommand extends Command 30 | { 31 | /** 32 | * @var CKEditorInstaller 33 | */ 34 | private $installer; 35 | 36 | public function __construct(CKEditorInstaller $installer) 37 | { 38 | parent::__construct(); 39 | 40 | $this->installer = $installer; 41 | } 42 | 43 | protected function configure(): void 44 | { 45 | $this 46 | ->setName('ckeditor:install') 47 | ->setDescription('Install CKEditor') 48 | ->addArgument('path', InputArgument::OPTIONAL, 'Where to install CKEditor') 49 | ->addOption( 50 | 'release', 51 | null, 52 | InputOption::VALUE_OPTIONAL, 53 | 'CKEditor release (basic, standard, full or custom)' 54 | ) 55 | ->addOption( 56 | 'custom-build-id', 57 | null, 58 | InputOption::VALUE_OPTIONAL, 59 | 'CKEditor custom build ID' 60 | ) 61 | ->addOption('tag', null, InputOption::VALUE_OPTIONAL, 'CKEditor tag (x.y.z or latest)') 62 | ->addOption( 63 | 'clear', 64 | null, 65 | InputOption::VALUE_OPTIONAL, 66 | 'How to clear previous CKEditor installation (drop, keep or skip)' 67 | ) 68 | ->addOption( 69 | 'exclude', 70 | null, 71 | InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 72 | 'Path to exclude when extracting CKEditor' 73 | ) 74 | ->addOption( 75 | 'no-progress-bar', 76 | 'nobar', 77 | InputOption::VALUE_NONE, 78 | 'Hide the progress bars?' 79 | ) 80 | ->setHelp( 81 | <<<'EOF' 82 | The %command.name% command install CKEditor in your application: 83 | 84 | php %command.full_name% 85 | 86 | You can install it at a specific path (absolute): 87 | 88 | php %command.full_name% path 89 | 90 | You can install a specific release (basic, standard or full): 91 | 92 | php %command.full_name% --release=full 93 | 94 | You can install a specific version: 95 | 96 | php %command.full_name% --tag=4.7.0 97 | 98 | You can install custom build generated on https://ckeditor.com/cke4/builder: 99 | 100 | php %command.full_name% --release=custom --custom-build-id=574a82a0d3e9226d94b0e91d10eaa372 101 | 102 | If there is a previous CKEditor installation detected, 103 | you can control how it should be handled in non-interactive mode: 104 | 105 | php %command.full_name% --clear=drop 106 | php %command.full_name% --clear=keep 107 | php %command.full_name% --clear=skip 108 | 109 | You can exclude path(s) when extracting CKEditor: 110 | 111 | php %command.full_name% --exclude=samples --exclude=adapters 112 | EOF 113 | ); 114 | } 115 | 116 | protected function execute(InputInterface $input, OutputInterface $output): int 117 | { 118 | $this->title($output); 119 | 120 | $success = $this->installer->install($this->createOptions($input, $output)); 121 | 122 | if ($success) { 123 | $this->success('CKEditor has been successfully installed...', $output); 124 | } else { 125 | $this->info('CKEditor installation has been skipped...', $output); 126 | } 127 | 128 | return 0; 129 | } 130 | 131 | private function createOptions(InputInterface $input, OutputInterface $output): array 132 | { 133 | $options = ['notifier' => $this->createNotifier($input, $output)]; 134 | 135 | if ($input->hasArgument('path')) { 136 | $options['path'] = $input->getArgument('path'); 137 | } 138 | 139 | if ($input->hasOption('release')) { 140 | $options['release'] = $input->getOption('release'); 141 | } 142 | 143 | if ($input->hasOption('custom-build-id')) { 144 | $options['custom_build_id'] = $input->getOption('custom-build-id'); 145 | } 146 | 147 | if ($input->hasOption('tag')) { 148 | $options['version'] = $input->getOption('tag'); 149 | } 150 | 151 | if ($input->hasOption('exclude')) { 152 | $options['excludes'] = $input->getOption('exclude'); 153 | } 154 | 155 | if ($input->hasOption('clear')) { 156 | $options['clear'] = $input->getOption('clear'); 157 | } 158 | 159 | return array_filter($options); 160 | } 161 | 162 | private function createNotifier(InputInterface $input, OutputInterface $output): \Closure 163 | { 164 | $barOutput = $input->getOption('no-progress-bar') ? new NullOutput() : $output; 165 | 166 | $clear = new ProgressBar($barOutput); 167 | $download = new ProgressBar($barOutput); 168 | $extract = new ProgressBar($barOutput); 169 | 170 | return function ($type, $data) use ($input, $output, $barOutput, $clear, $download, $extract) { 171 | switch ($type) { 172 | case CKEditorInstaller::NOTIFY_CLEAR: 173 | $result = $this->choice( 174 | [ 175 | sprintf('CKEditor is already installed in "%s"...', $data), 176 | '', 177 | 'What do you want to do?', 178 | ], 179 | $choices = [ 180 | CKEditorInstaller::CLEAR_DROP => 'Drop the directory & reinstall CKEditor', 181 | CKEditorInstaller::CLEAR_KEEP => 'Keep the directory & reinstall CKEditor by overriding files', 182 | CKEditorInstaller::CLEAR_SKIP => 'Skip installation', 183 | ], 184 | CKEditorInstaller::CLEAR_DROP, 185 | $input, 186 | $output 187 | ); 188 | 189 | if (false !== ($key = array_search($result, $choices, true))) { 190 | $result = $key; 191 | } 192 | 193 | if (CKEditorInstaller::CLEAR_DROP === $result) { 194 | $this->comment(sprintf('Dropping CKEditor from "%s"', $data), $output); 195 | } 196 | 197 | return $result; 198 | 199 | case CKEditorInstaller::NOTIFY_CLEAR_ARCHIVE: 200 | $this->comment(sprintf('Dropping CKEditor ZIP archive "%s"', $data), $output); 201 | 202 | break; 203 | 204 | case CKEditorInstaller::NOTIFY_CLEAR_COMPLETE: 205 | $this->finishProgressBar($clear, $barOutput); 206 | 207 | break; 208 | 209 | case CKEditorInstaller::NOTIFY_CLEAR_PROGRESS: 210 | $clear->advance(); 211 | 212 | break; 213 | 214 | case CKEditorInstaller::NOTIFY_CLEAR_SIZE: 215 | $clear->start($data); 216 | 217 | break; 218 | 219 | case CKEditorInstaller::NOTIFY_DOWNLOAD: 220 | $this->comment(sprintf('Downloading CKEditor ZIP archive from "%s"', $data), $output); 221 | 222 | break; 223 | 224 | case CKEditorInstaller::NOTIFY_DOWNLOAD_COMPLETE: 225 | $this->finishProgressBar($download, $barOutput); 226 | 227 | break; 228 | 229 | case CKEditorInstaller::NOTIFY_DOWNLOAD_PROGRESS: 230 | $download->setProgress($data); 231 | 232 | break; 233 | 234 | case CKEditorInstaller::NOTIFY_DOWNLOAD_SIZE: 235 | $download->start($data); 236 | 237 | break; 238 | 239 | case CKEditorInstaller::NOTIFY_EXTRACT: 240 | $this->comment(sprintf('Extracting CKEditor ZIP archive to "%s"', $data), $output); 241 | 242 | break; 243 | 244 | case CKEditorInstaller::NOTIFY_EXTRACT_COMPLETE: 245 | $this->finishProgressBar($extract, $barOutput); 246 | 247 | break; 248 | 249 | case CKEditorInstaller::NOTIFY_EXTRACT_PROGRESS: 250 | $extract->advance(); 251 | 252 | break; 253 | 254 | case CKEditorInstaller::NOTIFY_EXTRACT_SIZE: 255 | $extract->start($data); 256 | 257 | break; 258 | } 259 | }; 260 | } 261 | 262 | private function title(OutputInterface $output): void 263 | { 264 | $output->writeln( 265 | [ 266 | '----------------------', 267 | '| CKEditor Installer |', 268 | '----------------------', 269 | '', 270 | ] 271 | ); 272 | } 273 | 274 | private function comment(string $message, OutputInterface $output): void 275 | { 276 | $output->writeln(' // '.$message); 277 | $output->writeln(''); 278 | } 279 | 280 | private function success(string $message, OutputInterface $output): void 281 | { 282 | $this->block('[OK] - '.$message, $output, 'green', 'black'); 283 | } 284 | 285 | private function info(string $message, OutputInterface $output): void 286 | { 287 | $this->block('[INFO] - '.$message, $output, 'yellow', 'black'); 288 | } 289 | 290 | private function block( 291 | string $message, 292 | OutputInterface $output, 293 | ?string $background = null, 294 | ?string $font = null 295 | ): void { 296 | $options = []; 297 | 298 | if (null !== $background) { 299 | $options[] = 'bg='.$background; 300 | } 301 | 302 | if (null !== $font) { 303 | $options[] = 'fg='.$font; 304 | } 305 | 306 | $pattern = ' %s '; 307 | 308 | if (!empty($options)) { 309 | $pattern = '<'.implode(';', $options).'>'.$pattern.''; 310 | } 311 | 312 | $output->writeln($block = sprintf($pattern, str_repeat(' ', strlen($message)))); 313 | $output->writeln(sprintf($pattern, $message)); 314 | $output->writeln($block); 315 | } 316 | 317 | /** 318 | * @param string[] $question 319 | * @param string[] $choices 320 | */ 321 | private function choice( 322 | array $question, 323 | array $choices, 324 | string $default, 325 | InputInterface $input, 326 | OutputInterface $output 327 | ): ?string { 328 | $helper = new QuestionHelper(); 329 | 330 | if (is_array($question)) { 331 | $question = implode("\n", $question); 332 | } 333 | 334 | $result = $helper->ask( 335 | $input, 336 | $output, 337 | new ChoiceQuestion($question, $choices, $default) 338 | ); 339 | 340 | $output->writeln(''); 341 | 342 | return $result; 343 | } 344 | 345 | private function finishProgressBar(ProgressBar $progress, OutputInterface $output): void 346 | { 347 | $progress->finish(); 348 | $output->writeln(['', '']); 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /src/Config/CKEditorConfiguration.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please read the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace FOS\CKEditorBundle\Config; 14 | 15 | use FOS\CKEditorBundle\Exception\ConfigException; 16 | 17 | final class CKEditorConfiguration implements CKEditorConfigurationInterface 18 | { 19 | private $toolbarItems = [ 20 | 'basic.about' => ['About'], 21 | 'basic.basic_styles' => ['Bold', 'Italic'], 22 | 'basic.links' => ['Link', 'Unlink'], 23 | 'basic.paragraph' => ['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent'], 24 | 'standard.about' => ['Styles', 'Format', 'About'], 25 | 'standard.basic_styles' => ['Bold', 'Italic', 'Strike', '-', 'RemoveFormat'], 26 | 'standard.clipboard' => ['Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo'], 27 | 'standard.document' => ['Source'], 28 | 'standard.editing' => ['Scayt'], 29 | 'standard.links' => ['Link', 'Unlink', 'Anchor'], 30 | 'standard.insert' => ['Image', 'Table', 'HorizontalRule', 'SpecialChar'], 31 | 'standard.paragraph' => ['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote'], 32 | 'standard.tools' => ['Maximize'], 33 | 'full.about' => ['About'], 34 | 'full.basic_styles' => [ 35 | 'Bold', 36 | 'Italic', 37 | 'Underline', 38 | 'Strike', 39 | 'Subscript', 40 | 'Superscript', 41 | '-', 42 | 'RemoveFormat', 43 | ], 44 | 'full.clipboard' => ['Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo'], 45 | 'full.colors' => ['TextColor', 'BGColor'], 46 | 'full.document' => ['Source', '-', 'NewPage', 'Preview', 'Print', '-', 'Templates'], 47 | 'full.editing' => ['Find', 'Replace', '-', 'SelectAll', '-', 'Scayt'], 48 | 'full.forms' => [ 49 | 'Form', 50 | 'Checkbox', 51 | 'Radio', 52 | 'TextField', 53 | 'Textarea', 54 | 'SelectField', 55 | 'Button', 56 | 'ImageButton', 57 | 'HiddenField', 58 | ], 59 | 'full.insert' => ['Image', 'Flash', 'Table', 'HorizontalRule', 'SpecialChar', 'Smiley', 'PageBreak', 'Iframe'], 60 | 'full.links' => ['Link', 'Unlink', 'Anchor'], 61 | 'full.paragraph' => [ 62 | 'NumberedList', 63 | 'BulletedList', 64 | '-', 65 | 'Outdent', 66 | 'Indent', 67 | '-', 68 | 'Blockquote', 69 | 'CreateDiv', 70 | '-', 71 | 'JustifyLeft', 72 | 'JustifyCenter', 73 | 'JustifyRight', 74 | 'JustifyBlock', 75 | '-', 76 | 'BidiLtr', 77 | 'BidiRtl', 78 | ], 79 | 'full.styles' => ['Styles', 'Format', 'Font', 'FontSize', 'TextColor', 'BGColor'], 80 | 'full.tools' => ['Maximize', 'ShowBlocks'], 81 | ]; 82 | 83 | private $toolbarConfigs = [ 84 | 'basic' => [ 85 | '@basic.basic_styles', 86 | '@basic.paragraph', 87 | '@basic.links', 88 | '@basic.about', 89 | ], 90 | 'standard' => [ 91 | '@standard.clipboard', 92 | '@standard.editing', 93 | '@standard.links', 94 | '@standard.insert', 95 | '@standard.tools', 96 | '@standard.document', 97 | '/', 98 | '@standard.basic_styles', 99 | '@standard.paragraph', 100 | '@standard.about', 101 | ], 102 | 'full' => [ 103 | '@full.document', 104 | '@full.clipboard', 105 | '@full.editing', 106 | '@full.forms', 107 | '/', 108 | '@full.basic_styles', 109 | '@full.paragraph', 110 | '@full.links', 111 | '@full.insert', 112 | '/', 113 | '@full.styles', 114 | '@full.colors', 115 | '@full.tools', 116 | '@full.about', 117 | ], 118 | ]; 119 | 120 | /** 121 | * @var bool 122 | */ 123 | private $enable; 124 | 125 | /** 126 | * @var bool 127 | */ 128 | private $async; 129 | 130 | /** 131 | * @var bool 132 | */ 133 | private $autoload; 134 | 135 | /** 136 | * @var bool 137 | */ 138 | private $autoInline; 139 | 140 | /** 141 | * @var bool 142 | */ 143 | private $inline; 144 | 145 | /** 146 | * @var bool 147 | */ 148 | private $jquery; 149 | 150 | /** 151 | * @var bool 152 | */ 153 | private $requireJs; 154 | 155 | /** 156 | * @var bool 157 | */ 158 | private $inputSync; 159 | 160 | /** 161 | * @var array 162 | */ 163 | private $filebrowsers; 164 | 165 | /** 166 | * @var string 167 | */ 168 | private $basePath; 169 | 170 | /** 171 | * @var string 172 | */ 173 | private $jsPath; 174 | 175 | /** 176 | * @var string 177 | */ 178 | private $jqueryPath; 179 | 180 | /** 181 | * @var string|null 182 | */ 183 | private $defaultConfig; 184 | 185 | /** 186 | * @var array 187 | */ 188 | private $configs; 189 | 190 | /** 191 | * @var array 192 | */ 193 | private $templates; 194 | 195 | /** 196 | * @var array 197 | */ 198 | private $styles; 199 | 200 | /** 201 | * @var array 202 | */ 203 | private $plugins; 204 | 205 | public function __construct(array $config) 206 | { 207 | if ($config['enable']) { 208 | $config = $this->resolveConfigs($config); 209 | $config = $this->resolveStylesSet($config); 210 | } 211 | 212 | $this->enable = $config['enable']; 213 | $this->async = $config['async']; 214 | $this->autoload = $config['autoload']; 215 | $this->autoInline = $config['auto_inline']; 216 | $this->inline = $config['inline']; 217 | $this->jquery = $config['jquery']; 218 | $this->requireJs = $config['require_js']; 219 | $this->inputSync = $config['input_sync']; 220 | $this->filebrowsers = $config['filebrowsers']; 221 | $this->basePath = $config['base_path']; 222 | $this->jsPath = $config['js_path']; 223 | $this->jqueryPath = $config['jquery_path']; 224 | $this->defaultConfig = $config['default_config']; 225 | $this->plugins = $config['plugins']; 226 | $this->styles = $config['styles']; 227 | $this->templates = $config['templates']; 228 | $this->configs = $config['configs']; 229 | $this->toolbarConfigs = array_merge($this->toolbarConfigs, $config['toolbars']['configs']); 230 | $this->toolbarItems = array_merge($this->toolbarItems, $config['toolbars']['items']); 231 | } 232 | 233 | /** 234 | * @throws ConfigException 235 | */ 236 | private function resolveConfigs(array $config): array 237 | { 238 | if (empty($config['configs'])) { 239 | return $config; 240 | } 241 | 242 | if (!isset($config['default_config']) && !empty($config['configs'])) { 243 | reset($config['configs']); 244 | $config['default_config'] = key($config['configs']); 245 | } 246 | 247 | if (isset($config['default_config']) && !isset($config['configs'][$config['default_config']])) { 248 | throw ConfigException::invalidDefaultConfig($config['default_config']); 249 | } 250 | 251 | return $config; 252 | } 253 | 254 | private function resolveStylesSet(array $config): array 255 | { 256 | if (empty($config['styles'])) { 257 | return $config; 258 | } 259 | 260 | $stylesSets = $config['styles']; 261 | 262 | foreach ($stylesSets as &$stylesSet) { 263 | foreach ($stylesSet as &$value) { 264 | $value = array_filter($value); 265 | } 266 | } 267 | 268 | return $config; 269 | } 270 | 271 | public function getToolbar(string $name): array 272 | { 273 | $items = []; 274 | 275 | foreach ($this->toolbarConfigs[$name] as $name => $item) { 276 | $items[] = is_string($item) && '@' === substr($item, 0, 1) 277 | ? $this->toolbarItems[substr($item, 1)] 278 | : $item; 279 | } 280 | 281 | return $items; 282 | } 283 | 284 | public function getStyles(): array 285 | { 286 | return $this->styles; 287 | } 288 | 289 | public function getPlugins(): array 290 | { 291 | return $this->plugins; 292 | } 293 | 294 | public function getTemplates(): array 295 | { 296 | return $this->templates; 297 | } 298 | 299 | public function isEnable(): bool 300 | { 301 | return $this->enable; 302 | } 303 | 304 | public function isAsync(): bool 305 | { 306 | return $this->async; 307 | } 308 | 309 | public function isAutoload(): bool 310 | { 311 | return $this->autoload; 312 | } 313 | 314 | public function isAutoInline(): bool 315 | { 316 | return $this->autoInline; 317 | } 318 | 319 | public function isInline(): bool 320 | { 321 | return $this->inline; 322 | } 323 | 324 | public function isJquery(): bool 325 | { 326 | return $this->jquery; 327 | } 328 | 329 | public function isRequireJs(): bool 330 | { 331 | return $this->requireJs; 332 | } 333 | 334 | public function isInputSync(): bool 335 | { 336 | return $this->inputSync; 337 | } 338 | 339 | public function getFilebrowsers(): array 340 | { 341 | return $this->filebrowsers; 342 | } 343 | 344 | public function getBasePath(): string 345 | { 346 | return $this->basePath; 347 | } 348 | 349 | public function getJsPath(): string 350 | { 351 | return $this->jsPath; 352 | } 353 | 354 | public function getJqueryPath(): string 355 | { 356 | return $this->jqueryPath; 357 | } 358 | 359 | public function getDefaultConfig(): ?string 360 | { 361 | return $this->defaultConfig; 362 | } 363 | 364 | public function getConfigs(): array 365 | { 366 | return $this->configs; 367 | } 368 | 369 | /** 370 | * @throws ConfigException 371 | */ 372 | public function getConfig(string $name): array 373 | { 374 | if (!isset($this->configs[$name])) { 375 | throw ConfigException::configDoesNotExist($name); 376 | } 377 | 378 | return $this->configs[$name]; 379 | } 380 | } 381 | -------------------------------------------------------------------------------- /src/Config/CKEditorConfigurationInterface.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please read the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace FOS\CKEditorBundle\Config; 14 | 15 | use FOS\CKEditorBundle\Exception\ConfigException; 16 | 17 | interface CKEditorConfigurationInterface 18 | { 19 | public function getToolbar(string $name): array; 20 | 21 | public function getStyles(): array; 22 | 23 | public function getPlugins(): array; 24 | 25 | public function getTemplates(): array; 26 | 27 | public function isEnable(): bool; 28 | 29 | public function isAsync(): bool; 30 | 31 | public function isAutoload(): bool; 32 | 33 | public function isAutoInline(): bool; 34 | 35 | public function isInline(): bool; 36 | 37 | public function isJquery(): bool; 38 | 39 | public function isRequireJs(): bool; 40 | 41 | public function isInputSync(): bool; 42 | 43 | public function getFilebrowsers(): array; 44 | 45 | public function getBasePath(): string; 46 | 47 | public function getJsPath(): string; 48 | 49 | public function getJqueryPath(): string; 50 | 51 | public function getDefaultConfig(): ?string; 52 | 53 | public function getConfigs(): array; 54 | 55 | /** 56 | * @throws ConfigException 57 | */ 58 | public function getConfig(string $name): array; 59 | } 60 | -------------------------------------------------------------------------------- /src/DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please read the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace FOS\CKEditorBundle\DependencyInjection; 14 | 15 | use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; 16 | use Symfony\Component\Config\Definition\Builder\TreeBuilder; 17 | use Symfony\Component\Config\Definition\ConfigurationInterface; 18 | 19 | /** 20 | * @author GeLo 21 | */ 22 | final class Configuration implements ConfigurationInterface 23 | { 24 | public function getConfigTreeBuilder(): TreeBuilder 25 | { 26 | if (\method_exists(TreeBuilder::class, 'getRootNode')) { 27 | $treeBuilder = new TreeBuilder('fos_ck_editor'); 28 | $rootNode = $treeBuilder->getRootNode(); 29 | } else { 30 | // BC layer for symfony/config 4.1 and older 31 | $treeBuilder = new TreeBuilder(); 32 | $rootNode = $treeBuilder->root('fos_ck_editor'); 33 | } 34 | 35 | $rootNode 36 | ->children() 37 | ->booleanNode('enable')->defaultTrue()->end() 38 | ->booleanNode('async')->defaultFalse()->end() 39 | ->booleanNode('auto_inline')->defaultTrue()->end() 40 | ->booleanNode('inline')->defaultFalse()->end() 41 | ->booleanNode('autoload')->defaultTrue()->end() 42 | ->booleanNode('jquery')->defaultFalse()->end() 43 | ->booleanNode('require_js')->defaultFalse()->end() 44 | ->booleanNode('input_sync')->defaultFalse()->end() 45 | ->scalarNode('base_path')->defaultValue('bundles/fosckeditor/')->end() 46 | ->scalarNode('js_path')->defaultValue('bundles/fosckeditor/ckeditor.js')->end() 47 | ->scalarNode('jquery_path')->defaultValue('bundles/fosckeditor/adapters/jquery.js')->end() 48 | ->scalarNode('default_config')->defaultValue(null)->end() 49 | ->append($this->createConfigsNode()) 50 | ->append($this->createPluginsNode()) 51 | ->append($this->createStylesNode()) 52 | ->append($this->createTemplatesNode()) 53 | ->append($this->createFilebrowsersNode()) 54 | ->append($this->createToolbarsNode()) 55 | ->end(); 56 | 57 | return $treeBuilder; 58 | } 59 | 60 | private function createConfigsNode(): ArrayNodeDefinition 61 | { 62 | return $this->createPrototypeNode('configs') 63 | ->arrayPrototype() 64 | ->normalizeKeys(false) 65 | ->useAttributeAsKey('name') 66 | ->variablePrototype()->end() 67 | ->end(); 68 | } 69 | 70 | private function createPluginsNode(): ArrayNodeDefinition 71 | { 72 | return $this->createPrototypeNode('plugins') 73 | ->arrayPrototype() 74 | ->children() 75 | ->scalarNode('path')->end() 76 | ->scalarNode('filename')->end() 77 | ->end() 78 | ->end(); 79 | } 80 | 81 | private function createStylesNode(): ArrayNodeDefinition 82 | { 83 | return $this->createPrototypeNode('styles') 84 | ->arrayPrototype() 85 | ->arrayPrototype() 86 | ->children() 87 | ->scalarNode('name')->end() 88 | ->scalarNode('type')->end() 89 | ->scalarNode('widget')->end() 90 | ->variableNode('element')->end() 91 | ->append($this->createPrototypeNode('styles')->prototype('scalar')->end()) 92 | ->append($this->createPrototypeNode('attributes')->prototype('scalar')->end()) 93 | ->end() 94 | ->end() 95 | ->end(); 96 | } 97 | 98 | private function createTemplatesNode(): ArrayNodeDefinition 99 | { 100 | return $this->createPrototypeNode('templates') 101 | ->arrayPrototype() 102 | ->children() 103 | ->scalarNode('imagesPath')->end() 104 | ->arrayNode('templates') 105 | ->arrayPrototype() 106 | ->children() 107 | ->scalarNode('title')->end() 108 | ->scalarNode('image')->end() 109 | ->scalarNode('description')->end() 110 | ->scalarNode('html')->end() 111 | ->scalarNode('template')->end() 112 | ->append($this->createPrototypeNode('template_parameters')->prototype('scalar')->end()) 113 | ->end() 114 | ->end() 115 | ->end() 116 | ->end() 117 | ->end(); 118 | } 119 | 120 | private function createFilebrowsersNode(): ArrayNodeDefinition 121 | { 122 | $node = $this->createNode('filebrowsers') 123 | ->useAttributeAsKey('name') 124 | ->scalarPrototype() 125 | ->end(); 126 | 127 | \assert($node instanceof ArrayNodeDefinition); 128 | 129 | return $node; 130 | } 131 | 132 | private function createToolbarsNode(): ArrayNodeDefinition 133 | { 134 | return $this->createNode('toolbars') 135 | ->addDefaultsIfNotSet() 136 | ->children() 137 | ->arrayNode('configs') 138 | ->useAttributeAsKey('name') 139 | ->arrayPrototype() 140 | ->variablePrototype()->end() 141 | ->end() 142 | ->end() 143 | ->arrayNode('items') 144 | ->useAttributeAsKey('name') 145 | ->arrayPrototype() 146 | ->variablePrototype()->end() 147 | ->end() 148 | ->end() 149 | ->end(); 150 | } 151 | 152 | private function createPrototypeNode(string $name): ArrayNodeDefinition 153 | { 154 | return $this->createNode($name) 155 | ->normalizeKeys(false) 156 | ->useAttributeAsKey('name'); 157 | } 158 | 159 | private function createNode(string $name): ArrayNodeDefinition 160 | { 161 | if (\method_exists(TreeBuilder::class, 'getRootNode')) { 162 | $treeBuilder = new TreeBuilder($name); 163 | $node = $treeBuilder->getRootNode(); 164 | } else { 165 | // BC layer for symfony/config 4.1 and older 166 | $treeBuilder = new TreeBuilder(); 167 | $node = $treeBuilder->root($name); 168 | } 169 | 170 | \assert($node instanceof ArrayNodeDefinition); 171 | 172 | return $node; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/DependencyInjection/FOSCKEditorExtension.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please read the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace FOS\CKEditorBundle\DependencyInjection; 14 | 15 | use Symfony\Component\Config\FileLocator; 16 | use Symfony\Component\DependencyInjection\ContainerBuilder; 17 | use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; 18 | use Symfony\Component\HttpKernel\DependencyInjection\ConfigurableExtension; 19 | 20 | /** 21 | * @author GeLo 22 | */ 23 | final class FOSCKEditorExtension extends ConfigurableExtension 24 | { 25 | protected function loadInternal(array $config, ContainerBuilder $container): void 26 | { 27 | $this->loadResources($container); 28 | 29 | $container->getDefinition('fos_ck_editor.configuration') 30 | ->setArgument(0, $config); 31 | 32 | $bundles = $container->getParameter('kernel.bundles'); 33 | 34 | if (isset($bundles['IvoryCKEditorBundle'])) { 35 | @trigger_error( 36 | "IvoryCKEditorBundle isn't maintained anymore and should be replaced with FOSCKEditorBundle.", 37 | E_USER_DEPRECATED 38 | ); 39 | } 40 | } 41 | 42 | private function loadResources(ContainerBuilder $container): void 43 | { 44 | $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); 45 | 46 | $resources = [ 47 | 'builder', 48 | 'command', 49 | 'config', 50 | 'form', 51 | 'installer', 52 | 'renderer', 53 | 'twig', 54 | ]; 55 | 56 | foreach ($resources as $resource) { 57 | $loader->load($resource.'.xml'); 58 | } 59 | } 60 | 61 | public function getAlias(): string 62 | { 63 | return 'fos_ck_editor'; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Exception/BadProxyUrlException.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please read the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace FOS\CKEditorBundle\Exception; 14 | 15 | /** 16 | * @author Marko Kunic 17 | */ 18 | final class BadProxyUrlException extends \RuntimeException implements FOSCKEditorException 19 | { 20 | public static function fromEnvUrl(string $url): self 21 | { 22 | return new static(sprintf('Unable to parse provided proxy url "%s".', $url)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Exception/ConfigException.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please read the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace FOS\CKEditorBundle\Exception; 14 | 15 | /** 16 | * @author GeLo 17 | */ 18 | final class ConfigException extends \RuntimeException implements FOSCKEditorException 19 | { 20 | public static function configDoesNotExist(string $name): self 21 | { 22 | return new static(sprintf('The CKEditor config "%s" does not exist.', $name)); 23 | } 24 | 25 | public static function invalidDefaultConfig(string $name): self 26 | { 27 | return new static(sprintf('The default config "%s" does not exist.', $name)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Exception/FOSCKEditorException.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please read the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace FOS\CKEditorBundle\Exception; 14 | 15 | /** 16 | * @author GeLo 17 | */ 18 | interface FOSCKEditorException extends \Throwable 19 | { 20 | } 21 | -------------------------------------------------------------------------------- /src/FOSCKEditorBundle.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please read the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace FOS\CKEditorBundle; 14 | 15 | use FOS\CKEditorBundle\DependencyInjection\FOSCKEditorExtension; 16 | use Symfony\Component\HttpKernel\Bundle\Bundle; 17 | 18 | /** 19 | * @author GeLo 20 | */ 21 | final class FOSCKEditorBundle extends Bundle 22 | { 23 | public function getContainerExtension(): FOSCKEditorExtension 24 | { 25 | return new FOSCKEditorExtension(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Form/Type/CKEditorType.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please read the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace FOS\CKEditorBundle\Form\Type; 14 | 15 | use FOS\CKEditorBundle\Config\CKEditorConfigurationInterface; 16 | use Symfony\Component\Form\AbstractType; 17 | use Symfony\Component\Form\Extension\Core\Type\TextareaType; 18 | use Symfony\Component\Form\FormBuilderInterface; 19 | use Symfony\Component\Form\FormInterface; 20 | use Symfony\Component\Form\FormView; 21 | use Symfony\Component\OptionsResolver\Options; 22 | use Symfony\Component\OptionsResolver\OptionsResolver; 23 | 24 | /** 25 | * @author GeLo 26 | */ 27 | final class CKEditorType extends AbstractType 28 | { 29 | /** 30 | * @var CKEditorConfigurationInterface 31 | */ 32 | private $configuration; 33 | 34 | public function __construct(CKEditorConfigurationInterface $configuration) 35 | { 36 | $this->configuration = $configuration; 37 | } 38 | 39 | public function buildForm(FormBuilderInterface $builder, array $options): void 40 | { 41 | $builder->setAttribute('enable', $options['enable']); 42 | 43 | if (!$options['enable']) { 44 | return; 45 | } 46 | 47 | $builder->setAttribute('async', $options['async']); 48 | $builder->setAttribute('autoload', $options['autoload']); 49 | $builder->setAttribute('auto_inline', $options['auto_inline']); 50 | $builder->setAttribute('inline', $options['inline']); 51 | $builder->setAttribute('jquery', $options['jquery']); 52 | $builder->setAttribute('require_js', $options['require_js']); 53 | $builder->setAttribute('input_sync', $options['input_sync']); 54 | $builder->setAttribute('filebrowsers', $options['filebrowsers']); 55 | $builder->setAttribute('base_path', $options['base_path']); 56 | $builder->setAttribute('js_path', $options['js_path']); 57 | $builder->setAttribute('jquery_path', $options['jquery_path']); 58 | $builder->setAttribute('config', $this->resolveConfig($options)); 59 | $builder->setAttribute('config_name', $options['config_name']); 60 | $builder->setAttribute('plugins', array_merge($this->configuration->getPlugins(), $options['plugins'])); 61 | $builder->setAttribute('styles', array_merge($this->configuration->getStyles(), $options['styles'])); 62 | $builder->setAttribute('templates', array_merge($this->configuration->getTemplates(), $options['templates'])); 63 | } 64 | 65 | private function resolveConfig(array $options): array 66 | { 67 | $config = $options['config']; 68 | 69 | if (null === $options['config_name']) { 70 | $options['config_name'] = uniqid('fos', true); 71 | } else { 72 | $config = array_merge($this->configuration->getConfig($options['config_name']), $config); 73 | } 74 | 75 | if (isset($config['toolbar']) && is_string($config['toolbar'])) { 76 | $config['toolbar'] = $this->configuration->getToolbar($config['toolbar']); 77 | } 78 | 79 | return $config; 80 | } 81 | 82 | public function buildView(FormView $view, FormInterface $form, array $options): void 83 | { 84 | $config = $form->getConfig(); 85 | $view->vars['enable'] = $config->getAttribute('enable'); 86 | 87 | if (!$view->vars['enable']) { 88 | return; 89 | } 90 | 91 | $view->vars['async'] = $config->getAttribute('async'); 92 | $view->vars['autoload'] = $config->getAttribute('autoload'); 93 | $view->vars['auto_inline'] = $config->getAttribute('auto_inline'); 94 | $view->vars['inline'] = $config->getAttribute('inline'); 95 | $view->vars['jquery'] = $config->getAttribute('jquery'); 96 | $view->vars['require_js'] = $config->getAttribute('require_js'); 97 | $view->vars['input_sync'] = $config->getAttribute('input_sync'); 98 | $view->vars['filebrowsers'] = $config->getAttribute('filebrowsers'); 99 | $view->vars['base_path'] = $config->getAttribute('base_path'); 100 | $view->vars['js_path'] = $config->getAttribute('js_path'); 101 | $view->vars['jquery_path'] = $config->getAttribute('jquery_path'); 102 | $view->vars['config'] = $config->getAttribute('config'); 103 | $view->vars['config_name'] = $config->getAttribute('config_name'); 104 | $view->vars['plugins'] = $config->getAttribute('plugins'); 105 | $view->vars['styles'] = $config->getAttribute('styles'); 106 | $view->vars['templates'] = $config->getAttribute('templates'); 107 | } 108 | 109 | public function configureOptions(OptionsResolver $resolver): void 110 | { 111 | $resolver 112 | ->setDefaults([ 113 | 'enable' => $this->configuration->isEnable(), 114 | 'async' => $this->configuration->isAsync(), 115 | 'autoload' => $this->configuration->isAutoload(), 116 | 'auto_inline' => $this->configuration->isAutoInline(), 117 | 'inline' => $this->configuration->isInline(), 118 | 'jquery' => $this->configuration->isJquery(), 119 | 'require_js' => $this->configuration->isRequireJs(), 120 | 'input_sync' => $this->configuration->isInputSync(), 121 | 'filebrowsers' => $this->configuration->getFilebrowsers(), 122 | 'base_path' => $this->configuration->getBasePath(), 123 | 'js_path' => $this->configuration->getJsPath(), 124 | 'jquery_path' => $this->configuration->getJqueryPath(), 125 | 'config_name' => $this->configuration->getDefaultConfig(), 126 | 'config' => [], 127 | 'plugins' => [], 128 | 'styles' => [], 129 | 'templates' => [], 130 | ]) 131 | ->addAllowedTypes('enable', 'bool') 132 | ->addAllowedTypes('async', 'bool') 133 | ->addAllowedTypes('autoload', 'bool') 134 | ->addAllowedTypes('auto_inline', 'bool') 135 | ->addAllowedTypes('inline', 'bool') 136 | ->addAllowedTypes('jquery', 'bool') 137 | ->addAllowedTypes('require_js', 'bool') 138 | ->addAllowedTypes('input_sync', 'bool') 139 | ->addAllowedTypes('filebrowsers', 'array') 140 | ->addAllowedTypes('config_name', ['string', 'null']) 141 | ->addAllowedTypes('base_path', 'string') 142 | ->addAllowedTypes('js_path', 'string') 143 | ->addAllowedTypes('jquery_path', 'string') 144 | ->addAllowedTypes('config', 'array') 145 | ->addAllowedTypes('plugins', 'array') 146 | ->addAllowedTypes('styles', 'array') 147 | ->addAllowedTypes('templates', 'array') 148 | ->setNormalizer('base_path', function (Options $options, $value) { 149 | if ('/' !== substr($value, -1)) { 150 | $value .= '/'; 151 | } 152 | 153 | return $value; 154 | }); 155 | } 156 | 157 | public function getParent(): string 158 | { 159 | return TextareaType::class; 160 | } 161 | 162 | public function getBlockPrefix(): string 163 | { 164 | return 'ckeditor'; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/Installer/CKEditorInstaller.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please read the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace FOS\CKEditorBundle\Installer; 14 | 15 | use FOS\CKEditorBundle\Exception\BadProxyUrlException; 16 | use Symfony\Component\OptionsResolver\Options; 17 | use Symfony\Component\OptionsResolver\OptionsResolver; 18 | 19 | /** 20 | * @author GeLo 21 | */ 22 | final class CKEditorInstaller 23 | { 24 | public const RELEASE_BASIC = 'basic'; 25 | 26 | public const RELEASE_FULL = 'full'; 27 | 28 | public const RELEASE_STANDARD = 'standard'; 29 | 30 | public const RELEASE_CUSTOM = 'custom'; 31 | 32 | public const VERSION_LATEST = 'latest'; 33 | 34 | public const CLEAR_DROP = 'drop'; 35 | 36 | public const CLEAR_KEEP = 'keep'; 37 | 38 | public const CLEAR_SKIP = 'skip'; 39 | 40 | public const NOTIFY_CLEAR = 'clear'; 41 | 42 | public const NOTIFY_CLEAR_ARCHIVE = 'clear-archive'; 43 | 44 | public const NOTIFY_CLEAR_COMPLETE = 'clear-complete'; 45 | 46 | public const NOTIFY_CLEAR_PROGRESS = 'clear-progress'; 47 | 48 | public const NOTIFY_CLEAR_QUESTION = 'clear-question'; 49 | 50 | public const NOTIFY_CLEAR_SIZE = 'clear-size'; 51 | 52 | public const NOTIFY_DOWNLOAD = 'download'; 53 | 54 | public const NOTIFY_DOWNLOAD_COMPLETE = 'download-complete'; 55 | 56 | public const NOTIFY_DOWNLOAD_PROGRESS = 'download-progress'; 57 | 58 | public const NOTIFY_DOWNLOAD_SIZE = 'download-size'; 59 | 60 | public const NOTIFY_EXTRACT = 'extract'; 61 | 62 | public const NOTIFY_EXTRACT_COMPLETE = 'extract-complete'; 63 | 64 | public const NOTIFY_EXTRACT_PROGRESS = 'extract-progress'; 65 | 66 | public const NOTIFY_EXTRACT_SIZE = 'extract-size'; 67 | 68 | /** 69 | * @var string 70 | */ 71 | private static $archive = 'https://github.com/ckeditor/ckeditor4-releases/archive/%s/%s.zip'; 72 | 73 | /** 74 | * @var string 75 | */ 76 | private static $customBuildArchive = 'https://ckeditor.com/cke4/builder/download/%s'; 77 | 78 | /** 79 | * @var OptionsResolver 80 | */ 81 | private $resolver; 82 | 83 | public function __construct(array $options = []) 84 | { 85 | $this->resolver = (new OptionsResolver()) 86 | ->setDefaults(array_merge([ 87 | 'clear' => null, 88 | 'excludes' => ['samples'], 89 | 'notifier' => null, 90 | 'path' => dirname(__DIR__).'/Resources/public', 91 | 'release' => self::RELEASE_FULL, 92 | 'custom_build_id' => null, 93 | 'version' => self::VERSION_LATEST, 94 | ], $options)) 95 | ->setAllowedTypes('excludes', 'array') 96 | ->setAllowedTypes('notifier', ['null', 'callable']) 97 | ->setAllowedTypes('path', 'string') 98 | ->setAllowedTypes('custom_build_id', ['null', 'string']) 99 | ->setAllowedTypes('version', 'string') 100 | ->setAllowedValues('clear', [self::CLEAR_DROP, self::CLEAR_KEEP, self::CLEAR_SKIP, null]) 101 | ->setAllowedValues('release', [self::RELEASE_BASIC, self::RELEASE_FULL, self::RELEASE_STANDARD, self::RELEASE_CUSTOM]) 102 | ->setNormalizer('path', function (Options $options, $path) { 103 | return rtrim($path, '/'); 104 | }); 105 | } 106 | 107 | public function install(array $options = []): bool 108 | { 109 | $options = $this->resolver->resolve($options); 110 | 111 | if (self::CLEAR_SKIP === $this->clear($options)) { 112 | return false; 113 | } 114 | 115 | $this->extract($this->download($options), $options); 116 | 117 | return true; 118 | } 119 | 120 | private function clear(array $options): string 121 | { 122 | if (!file_exists($options['path'].'/ckeditor.js')) { 123 | return self::CLEAR_DROP; 124 | } 125 | 126 | if (null === $options['clear'] && null !== $options['notifier']) { 127 | $options['clear'] = $this->notify($options['notifier'], self::NOTIFY_CLEAR, $options['path']); 128 | } 129 | 130 | if (null === $options['clear']) { 131 | $options['clear'] = self::CLEAR_SKIP; 132 | } 133 | 134 | if (self::CLEAR_DROP === $options['clear']) { 135 | $files = new \RecursiveIteratorIterator( 136 | new \RecursiveDirectoryIterator($options['path'], \RecursiveDirectoryIterator::SKIP_DOTS), 137 | \RecursiveIteratorIterator::CHILD_FIRST 138 | ); 139 | 140 | $this->notify($options['notifier'], self::NOTIFY_CLEAR_SIZE, iterator_count($files)); 141 | 142 | foreach ($files as $file) { 143 | $filePath = $file->getRealPath(); 144 | $this->notify($options['notifier'], self::NOTIFY_CLEAR_PROGRESS, $filePath); 145 | 146 | if ($dir = $file->isDir()) { 147 | $success = @rmdir($filePath); 148 | } else { 149 | $success = @unlink($filePath); 150 | } 151 | 152 | if (!$success) { 153 | throw $this->createException(sprintf('Unable to remove the %s "%s".', $dir ? 'directory' : 'file', $filePath)); 154 | } 155 | } 156 | 157 | $this->notify($options['notifier'], self::NOTIFY_CLEAR_COMPLETE); 158 | } 159 | 160 | return $options['clear']; 161 | } 162 | 163 | private function download(array $options): string 164 | { 165 | $url = $this->getDownloadUrl($options); 166 | $this->notify($options['notifier'], self::NOTIFY_DOWNLOAD, $url); 167 | 168 | $zip = @file_get_contents($url, false, $this->createStreamContext($options['notifier'])); 169 | 170 | if (false === $zip) { 171 | throw $this->createException(sprintf('Unable to download CKEditor ZIP archive from "%s".', $url)); 172 | } 173 | 174 | $path = (string) tempnam(sys_get_temp_dir(), 'ckeditor-'.$options['release'].'-'.$options['version'].'.zip'); 175 | 176 | if (!@file_put_contents($path, $zip)) { 177 | throw $this->createException(sprintf('Unable to write CKEditor ZIP archive to "%s".', $path)); 178 | } 179 | 180 | $this->notify($options['notifier'], self::NOTIFY_DOWNLOAD_COMPLETE, $path); 181 | 182 | return $path; 183 | } 184 | 185 | private function getDownloadUrl(array $options): string 186 | { 187 | if (self::RELEASE_CUSTOM !== $options['release']) { 188 | return sprintf(self::$archive, $options['release'], $options['version']); 189 | } 190 | 191 | if (null === $options['custom_build_id']) { 192 | throw $this->createException('Unable to download CKEditor ZIP archive. Custom build ID is not specified.'); 193 | } 194 | 195 | if (self::VERSION_LATEST !== $options['version']) { 196 | throw $this->createException('Unable to download CKEditor ZIP archive. Specifying version for custom build is not supported.'); 197 | } 198 | 199 | return sprintf(self::$customBuildArchive, $options['custom_build_id']); 200 | } 201 | 202 | /** 203 | * @return resource 204 | */ 205 | private function createStreamContext(?callable $notifier = null) 206 | { 207 | $context = []; 208 | $proxy = getenv('https_proxy') ?: getenv('http_proxy'); 209 | 210 | if ($proxy) { 211 | $proxyUrl = parse_url($proxy); 212 | 213 | if (false === $proxyUrl || !isset($proxyUrl['host']) || !isset($proxyUrl['port'])) { 214 | throw BadProxyUrlException::fromEnvUrl($proxy); 215 | } 216 | 217 | $context['http'] = [ 218 | 'proxy' => 'tcp://'.$proxyUrl['host'].':'.$proxyUrl['port'], 219 | 'request_fulluri' => (bool) getenv('https_proxy_request_fulluri') ?: 220 | getenv('http_proxy_request_fulluri'), 221 | ]; 222 | } 223 | 224 | return stream_context_create($context, [ 225 | 'notification' => function ( 226 | $code, 227 | $severity, 228 | $message, 229 | $messageCode, 230 | $transferred, 231 | $size 232 | ) use ($notifier) { 233 | if (null === $notifier) { 234 | return; 235 | } 236 | 237 | switch ($code) { 238 | case STREAM_NOTIFY_FILE_SIZE_IS: 239 | $this->notify($notifier, self::NOTIFY_DOWNLOAD_SIZE, $size); 240 | 241 | break; 242 | 243 | case STREAM_NOTIFY_PROGRESS: 244 | $this->notify($notifier, self::NOTIFY_DOWNLOAD_PROGRESS, $transferred); 245 | 246 | break; 247 | } 248 | }, 249 | ]); 250 | } 251 | 252 | private function extract(string $path, array $options): void 253 | { 254 | $this->notify($options['notifier'], self::NOTIFY_EXTRACT, $options['path']); 255 | 256 | $zip = new \ZipArchive(); 257 | $zip->open($path); 258 | 259 | $this->notify($options['notifier'], self::NOTIFY_EXTRACT_SIZE, $zip->numFiles); 260 | 261 | if (self::RELEASE_CUSTOM === $options['release']) { 262 | $offset = 9; 263 | } else { 264 | $offset = 20 + strlen($options['release']) + strlen($options['version']); 265 | } 266 | 267 | for ($i = 0; $i < $zip->numFiles; ++$i) { 268 | $filename = $zip->getNameIndex($i); 269 | $isDirectory = ('/' === substr($filename, -1, 1)); 270 | 271 | if (!$isDirectory) { 272 | $this->extractFile($filename, substr($filename, $offset), $path, $options); 273 | } 274 | } 275 | 276 | $zip->close(); 277 | 278 | $this->notify($options['notifier'], self::NOTIFY_EXTRACT_COMPLETE); 279 | $this->notify($options['notifier'], self::NOTIFY_CLEAR_ARCHIVE, $path); 280 | 281 | if (!@unlink($path)) { 282 | throw $this->createException(sprintf('Unable to remove the CKEditor ZIP archive "%s".', $path)); 283 | } 284 | } 285 | 286 | private function extractFile(string $file, string $rewrite, string $origin, array $options): void 287 | { 288 | $this->notify($options['notifier'], self::NOTIFY_EXTRACT_PROGRESS, $rewrite); 289 | 290 | $from = 'zip://'.$origin.'#'.$file; 291 | $to = $options['path'].'/'.$rewrite; 292 | 293 | foreach ($options['excludes'] as $exclude) { 294 | if (0 === strpos(ltrim($rewrite, '\\/'), $exclude)) { 295 | return; 296 | } 297 | } 298 | 299 | $targetDirectory = dirname($to); 300 | if (!is_dir($targetDirectory) && !@mkdir($targetDirectory, 0777, true)) { 301 | throw $this->createException(sprintf('Unable to create the directory "%s".', $targetDirectory)); 302 | } 303 | 304 | if (!@copy($from, $to)) { 305 | throw $this->createException(sprintf('Unable to extract the file "%s" to "%s".', $file, $to)); 306 | } 307 | } 308 | 309 | private function notify(?callable $notifier = null, ?string $type = null, mixed $data = null): mixed 310 | { 311 | if (null !== $notifier) { 312 | return $notifier($type, $data); 313 | } 314 | 315 | return null; 316 | } 317 | 318 | private function createException(string $message): \RuntimeException 319 | { 320 | $error = error_get_last(); 321 | 322 | if (isset($error['message'])) { 323 | $message .= sprintf(' (%s)', $error['message']); 324 | } 325 | 326 | return new \RuntimeException($message); 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /src/Renderer/CKEditorRenderer.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please read the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace FOS\CKEditorBundle\Renderer; 14 | 15 | use FOS\CKEditorBundle\Builder\JsonBuilder; 16 | use Symfony\Component\Asset\Packages; 17 | use Symfony\Component\HttpFoundation\RequestStack; 18 | use Symfony\Component\Routing\Generator\UrlGeneratorInterface; 19 | use Symfony\Component\Routing\RouterInterface; 20 | use Twig\Environment; 21 | 22 | /** 23 | * @author GeLo 24 | */ 25 | final class CKEditorRenderer implements CKEditorRendererInterface 26 | { 27 | /** 28 | * @var JsonBuilder 29 | */ 30 | private $jsonBuilder; 31 | 32 | /** 33 | * @var RouterInterface 34 | */ 35 | private $router; 36 | 37 | /** 38 | * @var Packages 39 | */ 40 | private $assetsPackages; 41 | 42 | /** 43 | * @var Environment 44 | */ 45 | private $twig; 46 | 47 | /** 48 | * @var RequestStack 49 | */ 50 | private $requestStack; 51 | 52 | /** 53 | * @var string|null 54 | */ 55 | private $locale; 56 | 57 | public function __construct( 58 | JsonBuilder $jsonBuilder, 59 | RouterInterface $router, 60 | Packages $packages, 61 | RequestStack $requestStack, 62 | Environment $twig, 63 | $locale = null 64 | ) { 65 | $this->jsonBuilder = $jsonBuilder; 66 | $this->router = $router; 67 | $this->assetsPackages = $packages; 68 | $this->twig = $twig; 69 | $this->requestStack = $requestStack; 70 | $this->locale = $locale; 71 | } 72 | 73 | public function renderBasePath(string $basePath): string 74 | { 75 | return $this->fixPath($basePath); 76 | } 77 | 78 | public function renderJsPath(string $jsPath): string 79 | { 80 | return $this->fixPath($jsPath); 81 | } 82 | 83 | public function renderWidget(string $id, array $config, array $options = []): string 84 | { 85 | $config = $this->fixConfigLanguage($config); 86 | $config = $this->fixConfigContentsCss($config); 87 | $config = $this->fixConfigFilebrowsers( 88 | $config, 89 | isset($options['filebrowsers']) ? $options['filebrowsers'] : [] 90 | ); 91 | 92 | $autoInline = isset($options['auto_inline']) && !$options['auto_inline'] 93 | ? 'CKEDITOR.disableAutoInline = true;'."\n" 94 | : null; 95 | 96 | $builder = $this->jsonBuilder->reset()->setValues($config); 97 | $this->fixConfigEscapedValues($builder, $config); 98 | 99 | $widget = sprintf( 100 | 'CKEDITOR.%s("%s", %s);', 101 | isset($options['inline']) && $options['inline'] ? 'inline' : 'replace', 102 | $id, 103 | $this->fixConfigConstants($builder->build()) 104 | ); 105 | 106 | if (isset($options['input_sync']) && $options['input_sync']) { 107 | $variable = 'fos_ckeditor_'.$id; 108 | $widget = 'var '.$variable.' = '.$widget."\n"; 109 | 110 | return $autoInline.$widget.$variable.'.on(\'change\', function() { '.$variable.'.updateElement(); });'; 111 | } 112 | 113 | return $autoInline.$widget; 114 | } 115 | 116 | public function renderDestroy(string $id): string 117 | { 118 | return sprintf( 119 | 'if (CKEDITOR.instances["%1$s"]) { '. 120 | 'CKEDITOR.instances["%1$s"].destroy(true); '. 121 | 'delete CKEDITOR.instances["%1$s"]; '. 122 | '}', 123 | $id 124 | ); 125 | } 126 | 127 | public function renderPlugin(string $name, array $plugin): string 128 | { 129 | return sprintf( 130 | 'CKEDITOR.plugins.addExternal("%s", "%s", "%s");', 131 | $name, 132 | $this->fixPath($plugin['path']), 133 | $plugin['filename'] 134 | ); 135 | } 136 | 137 | public function renderStylesSet(string $name, array $stylesSet): string 138 | { 139 | return sprintf( 140 | 'if (CKEDITOR.stylesSet.get("%1$s") === null) { '. 141 | 'CKEDITOR.stylesSet.add("%1$s", %2$s); '. 142 | '}', 143 | $name, 144 | $this->jsonBuilder->reset()->setValues($stylesSet)->build() 145 | ); 146 | } 147 | 148 | public function renderTemplate(string $name, array $template): string 149 | { 150 | if (isset($template['imagesPath'])) { 151 | $template['imagesPath'] = $this->fixPath($template['imagesPath']); 152 | } 153 | 154 | if (isset($template['templates'])) { 155 | foreach ($template['templates'] as &$rawTemplate) { 156 | if (isset($rawTemplate['template'])) { 157 | $rawTemplate['html'] = $this->twig->render( 158 | $rawTemplate['template'], 159 | isset($rawTemplate['template_parameters']) ? $rawTemplate['template_parameters'] : [] 160 | ); 161 | } 162 | 163 | unset($rawTemplate['template'], $rawTemplate['template_parameters']); 164 | } 165 | } 166 | 167 | return sprintf( 168 | 'CKEDITOR.addTemplates("%s", %s);', 169 | $name, 170 | $this->jsonBuilder->reset()->setValues($template)->build() 171 | ); 172 | } 173 | 174 | private function fixConfigLanguage(array $config): array 175 | { 176 | if (!isset($config['language']) && null !== ($language = $this->getLanguage())) { 177 | $config['language'] = $language; 178 | } 179 | 180 | if (isset($config['language'])) { 181 | $config['language'] = strtolower(str_replace('_', '-', $config['language'])); 182 | } 183 | 184 | return $config; 185 | } 186 | 187 | private function fixConfigContentsCss(array $config): array 188 | { 189 | if (isset($config['contentsCss'])) { 190 | $cssContents = (array) $config['contentsCss']; 191 | 192 | $config['contentsCss'] = []; 193 | foreach ($cssContents as $cssContent) { 194 | $config['contentsCss'][] = $this->fixPath($cssContent); 195 | } 196 | } 197 | 198 | return $config; 199 | } 200 | 201 | private function fixConfigFilebrowsers(array $config, array $filebrowsers): array 202 | { 203 | $filebrowsers = array_unique(array_merge([ 204 | 'Browse', 205 | 'FlashBrowse', 206 | 'ImageBrowse', 207 | 'ImageBrowseLink', 208 | 'Upload', 209 | 'FlashUpload', 210 | 'ImageUpload', 211 | ], $filebrowsers)); 212 | 213 | foreach ($filebrowsers as $filebrowser) { 214 | $fileBrowserKey = 'filebrowser'.$filebrowser; 215 | $handler = $fileBrowserKey.'Handler'; 216 | $url = $fileBrowserKey.'Url'; 217 | $route = $fileBrowserKey.'Route'; 218 | $routeParameters = $fileBrowserKey.'RouteParameters'; 219 | $routeType = $fileBrowserKey.'RouteType'; 220 | 221 | if (isset($config[$handler])) { 222 | $config[$url] = $config[$handler]($this->router); 223 | } elseif (isset($config[$route])) { 224 | $config[$url] = $this->router->generate( 225 | $config[$route], 226 | isset($config[$routeParameters]) ? $config[$routeParameters] : [], 227 | isset($config[$routeType]) ? $config[$routeType] : UrlGeneratorInterface::ABSOLUTE_PATH 228 | ); 229 | } 230 | 231 | unset($config[$handler], $config[$route], $config[$routeParameters], $config[$routeType]); 232 | } 233 | 234 | return $config; 235 | } 236 | 237 | private function fixConfigEscapedValues(JsonBuilder $builder, array $config): void 238 | { 239 | if (isset($config['protectedSource'])) { 240 | foreach ($config['protectedSource'] as $key => $value) { 241 | $builder->setValue(sprintf('[protectedSource][%s]', $key), $value, false); 242 | } 243 | } 244 | 245 | $escapedValueKeys = [ 246 | 'stylesheetParser_skipSelectors', 247 | 'stylesheetParser_validSelectors', 248 | ]; 249 | 250 | foreach ($escapedValueKeys as $escapedValueKey) { 251 | if (isset($config[$escapedValueKey])) { 252 | $builder->setValue(sprintf('[%s]', $escapedValueKey), $config[$escapedValueKey], false); 253 | } 254 | } 255 | } 256 | 257 | private function fixConfigConstants(string $json): string 258 | { 259 | return preg_replace('/"(CKEDITOR\.[A-Z_]+)"/', '$1', $json); 260 | } 261 | 262 | private function fixPath(string $path): string 263 | { 264 | if (null === $this->assetsPackages) { 265 | return $path; 266 | } 267 | 268 | $url = $this->assetsPackages->getUrl($path); 269 | 270 | if ('/' === substr($path, -1) && false !== ($position = strpos($url, '?'))) { 271 | $url = substr($url, 0, (int) $position); 272 | } 273 | 274 | return $url; 275 | } 276 | 277 | private function getLanguage(): ?string 278 | { 279 | $request = $this->requestStack->getCurrentRequest(); 280 | 281 | if (null !== $request && '' !== $request->getLocale()) { 282 | return $request->getLocale(); 283 | } 284 | 285 | return $this->locale; 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /src/Renderer/CKEditorRendererInterface.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please read the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace FOS\CKEditorBundle\Renderer; 14 | 15 | /** 16 | * @author GeLo 17 | */ 18 | interface CKEditorRendererInterface 19 | { 20 | public function renderBasePath(string $basePath): string; 21 | 22 | public function renderJsPath(string $jsPath): string; 23 | 24 | /** 25 | * The available options are: 26 | * - auto_inline: bool 27 | * - inline: bool 28 | * - input_sync: bool. 29 | */ 30 | public function renderWidget(string $id, array $config, array $options = []): string; 31 | 32 | public function renderDestroy(string $id): string; 33 | 34 | public function renderPlugin(string $name, array $plugin): string; 35 | 36 | public function renderStylesSet(string $name, array $stylesSet): string; 37 | 38 | public function renderTemplate(string $name, array $template): string; 39 | } 40 | -------------------------------------------------------------------------------- /src/Resources/config/builder.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Resources/config/command.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Resources/config/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Resources/config/form.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Resources/config/installer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Resources/config/renderer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | container.hasParameter('locale') ? parameter('locale') : null 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Resources/config/twig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Resources/views/Form/ckeditor_widget.html.twig: -------------------------------------------------------------------------------- 1 | {% block ckeditor_widget %} 2 | 3 | 4 | {% if enable and not async %} 5 | {{ block('_ckeditor_javascript') }} 6 | {% endif %} 7 | {% endblock %} 8 | 9 | {% block ckeditor_javascript %} 10 | {% if enable and async %} 11 | {{ block('_ckeditor_javascript') }} 12 | {% endif %} 13 | {% endblock %} 14 | 15 | {% block _ckeditor_javascript %} 16 | {% if autoload %} 17 | 20 | 21 | {% if jquery %} 22 | 23 | {% endif %} 24 | {% endif %} 25 | 65 | {% endblock %} 66 | -------------------------------------------------------------------------------- /src/Twig/CKEditorExtension.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please read the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace FOS\CKEditorBundle\Twig; 14 | 15 | use FOS\CKEditorBundle\Renderer\CKEditorRendererInterface; 16 | use Twig\Extension\AbstractExtension; 17 | use Twig\TwigFunction; 18 | 19 | /** 20 | * @author GeLo 21 | */ 22 | final class CKEditorExtension extends AbstractExtension implements CKEditorRendererInterface 23 | { 24 | /** 25 | * @var CKEditorRendererInterface 26 | */ 27 | private $renderer; 28 | 29 | public function __construct(CKEditorRendererInterface $renderer) 30 | { 31 | $this->renderer = $renderer; 32 | } 33 | 34 | public function getFunctions(): array 35 | { 36 | $options = ['is_safe' => ['html']]; 37 | 38 | return [ 39 | new TwigFunction('ckeditor_base_path', [$this, 'renderBasePath'], $options), 40 | new TwigFunction('ckeditor_js_path', [$this, 'renderJsPath'], $options), 41 | new TwigFunction('ckeditor_widget', [$this, 'renderWidget'], $options), 42 | new TwigFunction('ckeditor_destroy', [$this, 'renderDestroy'], $options), 43 | new TwigFunction('ckeditor_plugin', [$this, 'renderPlugin'], $options), 44 | new TwigFunction('ckeditor_styles_set', [$this, 'renderStylesSet'], $options), 45 | new TwigFunction('ckeditor_template', [$this, 'renderTemplate'], $options), 46 | ]; 47 | } 48 | 49 | public function renderBasePath(string $basePath): string 50 | { 51 | return $this->renderer->renderBasePath($basePath); 52 | } 53 | 54 | public function renderJsPath(string $jsPath): string 55 | { 56 | return $this->renderer->renderJsPath($jsPath); 57 | } 58 | 59 | public function renderWidget(string $id, array $config, array $options = []): string 60 | { 61 | return $this->renderer->renderWidget($id, $config, $options); 62 | } 63 | 64 | public function renderDestroy(string $id): string 65 | { 66 | return $this->renderer->renderDestroy($id); 67 | } 68 | 69 | public function renderPlugin(string $name, array $plugin): string 70 | { 71 | return $this->renderer->renderPlugin($name, $plugin); 72 | } 73 | 74 | public function renderStylesSet(string $name, array $stylesSet): string 75 | { 76 | return $this->renderer->renderStylesSet($name, $stylesSet); 77 | } 78 | 79 | public function renderTemplate(string $name, array $template): string 80 | { 81 | return $this->renderer->renderTemplate($name, $template); 82 | } 83 | 84 | public function getName(): string 85 | { 86 | return 'fos_ckeditor'; 87 | } 88 | } 89 | --------------------------------------------------------------------------------