Skip to the content
Documentation
Help Start
Reading in:
  • German: 9,059 of 9,059 pages
About the help in other languages
All help pages

The online editor engines: TinyMCE 3 and TinyMCE 8

Read this page if your editors use the online editor (ezoe) to edit XML text (ezxmltext) attributes, or if you want to offer TinyMCE 8 or your own editor. Since Exponential 6.0.15 ezoe runs on an editor engine chosen…

Download as PDF

Address: https://doc.exponential.earth/exphelp/viewer/bc/6.0/ezoe-tinymce8.md

On this page

The online editor engines: TinyMCE 3 and TinyMCE 8

Read this page if your editors use the online editor (ezoe) to edit XML text (ezxmltext) attributes, or if you want to offer TinyMCE 8 or your own editor. Since Exponential 6.0.15 ezoe runs on an editor engine chosen from a registry. Nothing changes for an existing installation: the default stays TinyMCE 3. One server-side check is new: the embed dialog's upload refuses some file types (see "Upload check").

In short

What changedTwo engines ship: TinyMCE 3.5.12 (default) and TinyMCE 8.9.2 (opt-in, GPL-2.0-or-later, license_key: 'gpl', no cloud call). ezoe/upload checks file types.
Who is affectedSites that switch to TinyMCE 8; editors who upload unusual file types through the embed dialog.
How to checkgrep -n "EditorEngine|EngineSwitch|UploadExtensionCheck" extension/ezoe/settings/ezoe.ini
How to fixAdd missing upload types to UploadFileExtensions[]. Switch engines with EditorEngine or per user.

Both engines write the same XHTML that eZOEXMLInput and eZOEInputParser read, so the stored XML is the same whichever engine an editor uses.

Try TinyMCE 8 in one minute

  1. As an editor, open /user/preferences/set/ezoe_engine/tinymce8.
  2. Edit an article. The editor is TinyMCE 8.
  3. To go back, open /ezoe/engine and choose TinyMCE 3, or reset to the site default.

To make TinyMCE 8 the default for everyone without a preference, set EditorEngine=tinymce8 (see "Settings").

Settings

File extension/ezoe/settings/ezoe.ini, block [EditorSettings]. Every key can be overridden per siteaccess as for any INI file.

KeyDefaultMeaning
EditorEnginetinymce3The engine of every user without a preference.
EngineSwitchdisabledenabled shows the engine switch below the editor: a group named "Editor" with every available engine, toggle buttons, the one in use pressed (aria-pressed="true"), each other one a submit button that switches to it.
Engines[<id>]tinymce3, tinymce8The registry: identifier = class implementing expOEEditorEngine.
UploadFileExtensions[]images, pdf, office, text, zip, audio, videoFile types of the embed dialog's upload tab.
UploadExtensionCheckengineServer-side check of those types: engine (only when the user's engine is not tinymce3), always, disabled.

The block [Engine_tinymce8] holds ToolbarMap[<[EditorLayout] button>]=<toolbar item> and ExternalPlugins[<name>]=<design path>. The engine class reads them; they are not hard coded in the template.

Table dialog

[Engine_tinymce8] TableDialog=classic|modern picks the dialog of "Insert table" and "Table properties":

ValueDialog
classic (default)The design of the TinyMCE 3 dialog of tag_table.tpl: window title "New <table> tag" / "Edit <table> tag", Properties tab, the clickable 6 x 5 size grid with Columns and Rows (new tables only), Width and Border with a px / % selector, Class, then the custom attributes of content.ini [table] (Summary (WAI), Caption), OK and Cancel. Texts and help titles are the translations of design/standard/ezoe, so German needs no new strings. The dialog is a TinyMCE 8 window with a small adapter in plugins/eztable/plugin.js (the TinyMCE 3 page itself depends on tinyMCEPopup and the TinyMCE 3 API and does not run in TinyMCE 8).
modernThe TinyMCE 8 form dialog (Rows, Columns, Width, Border, Class and the custom attributes as plain fields).

Both write the same markup (class, width, border, customattributes), so the stored ezxml is identical. Cell and row dialogs are not affected by the setting. The value reaches the editor as the option ez_table_dialog.

Which engine an editor gets

expOEEditor::resolve() takes the first of: the user preference ezoe_engine, the siteaccess EditorEngine, the global EditorEngine, then tinymce3. An unknown, broken or unavailable choice is skipped, never an error.

Under an enforced Content Security Policy

TinyMCE 3 cannot run where the siteaccess enforces its Content Security Policy without 'unsafe-inline' in script-src (site.ini [ContentSecurityPolicy] Mode=enforce): its edit template starts it with an inline script, and tiny_mce.js and its dialogs write onclick attributes and javascript: links themselves, which neither a nonce nor a hash can allow. There the engine is blocked for that request:

  • a preference, EditorEngine or ForceEngine naming tinymce3 is skipped; when the configured engine is blocked, editors get the first engine of the registry that runs (TinyMCE 8 as shipped), so the field never stays without an editor;
  • the engine switch and the page /ezoe/engine leave it out of the choices (the page lists it with the status "Cannot be used" and the reason), and choosing it is refused;
  • under report-only (the shipped Mode=auto) and with 'unsafe-inline' in script-src nothing changes.

An engine says it needs inline scripts with the optional method needsInlineScripts() (expOETinyMCE3Engine: true; an engine built on expOEEditorEngineBase reads [Engine_<id>] NeedsInlineScripts=true, false when not set). expOEEditor::blockedByPolicy( $engine ) and expOEEditor::inlineScriptsAllowed() (which asks expContentSecurityPolicy::allowsInlineScripts()) are the checks. A siteaccess that must edit with TinyMCE 3 sets, for itself only:

# settings/siteaccess/<siteaccess>/site.ini.append.php
[ContentSecurityPolicy]
Directives[script-src]='self' 'unsafe-inline'
Directives[style-src]='self' 'unsafe-inline'

The kernel then sends no nonce in that script-src (a nonce would switch 'unsafe-inline' off in the browser).

A user chooses with:

  • the page /ezoe/engine (a table of every registered engine with its version, description and status, the ones that cannot be used included, and a select over the available ones; needs the policy ezoe/editor);
  • the engine switch below the editor (EngineSwitch=enabled);
  • the URL /user/preferences/set/ezoe_engine/tinymce8;
  • the remote service expeditor::set (POST engine; empty means the site default).

Each choice through the page, the button or the service writes the audit event content.ezoe.engine.change (channel content; before and after engine).

Upload check

  • The embed dialog uploads through ezoe/upload and eZContentUpload (class set by upload.ini; access by the policy ezoe/relations and canEdit()).
  • Since 6.0.15 that view refuses a file whose type is not in UploadFileExtensions[] (see UploadExtensionCheck), and any name that contains an executable extension (shell.php.jpg), because a request can skip the dialog.
  • If an editor's upload is refused, add the type to UploadFileExtensions[] in an override.

Upload from a URL

The Upload tab also takes the address of a file ("From a URL"; ezoe/upload field uploadUrl, class expOEUrlFetcher). Settings in ezoe.ini [EditorSettings]: UploadFromUrl=enabled|disabled, UploadFromUrlMaxSize=145M (only this limit, PHP's upload_max_filesize does not apply), UploadFromUrlTimeout=300. Security: http/https only, no credentials, every resolved address must be public and is the one connected to, at most 3 redirects each re-checked, size enforced while streaming to a temporary file under var/tmp/ezoe_url/, extension checked against UploadFileExtensions[] and content checked with finfo. eZContentUpload::handleLocalFile() got an optional $publish argument (default true) so the view can set the attributes before publishing, like handleUpload(). The kernel change needs exp:velocity deploy --kernel. Tests: tests/tests/extension/ezoe/expOEUrlFetcherTest.php.

Other server-side notes

  • The TinyMCE 8 plugins call only existing endpoints (ezoe views, ezjscore functions of ezoe). They add no ezjscore function and no new unauthenticated endpoint.
  • The Exp Debug bar shows and changes EditorEngine and EngineSwitch (group Extensions).
  • setup/rad counts every Engines[] entry as an extension point ("Online editor engines") and shows an entry whose class is missing or is not an expOEEditorEngine as broken.

Remote services (ezjscore/call/expeditor::<service>)

ServiceAccessWritesReturns
enginesezoe/editornoregistered engines, the current one, the default, problems
getezoe/editornopreference, engine in use, default, switch enabled
setezoe/editorPOST + tokenchooses the engine of the current user
uploadExtensionsezoe/editornoallowed extensions, check mode, whether it is enforced
configezoe/editornoengine, toolbar map, plugins, buttons of the layout, upload types

Extension points

  1. An engine: one class and one INI line.
  2. Toolbar items and plugins of an engine: [Engine_<id>] ToolbarMap[] and ExternalPlugins[].
  3. Upload types: UploadFileExtensions[].
  4. Audit: the branch expOEAuditBranch (audit.ini.append.php).

Example: add a third engine

// extension/myeditor/classes/myeditorengine.php
class myEditorEngine extends expOEEditorEngineBase
{
    public function identifier() { return 'myeditor'; }
    public function label()      { return 'My editor'; }          // translated in design/standard/ezoe
    public function isAvailable() { return is_file( __DIR__ . '/../design/standard/javascript/my.js' ); }
    public function template()   { return 'design:content/datatype/edit/ezxmltext_myeditor.tpl'; }
    public function version()    { return '1.0.0'; }               // optional, shown on /ezoe/engine
    public function description() { return 'My own editor.'; }    // optional, translated in design/standard/ezoe
}
# extension/myeditor/settings/ezoe.ini.append.php
[EditorSettings]
Engines[myeditor]=myEditorEngine
[Engine_myeditor]
ToolbarMap[bold]=bold

Then:

php bin/php/ezpgenerateautoloads.php -e
php bin/php/ezcache.php --clear-tag=ini --allow-root-user

The template receives attribute, input_handler, attribute_base and editorRow; $input_handler.engine gives its identifier, label, toolbar map, plugins and config. The engine now appears on /ezoe/engine, in the switch buttons, in expeditor::engines and in setup/rad. An engine that returns '' from template() is rendered by the built-in TinyMCE 3 markup of ezxmltext_ezoe.tpl.

version() and description() are optional (they are not part of expOEEditorEngine, so engines written before them keep working): expOEEditor::details() asks for them when an engine has them, and expOEEditorEngineBase answers both from [Engine_<id>] Version and Description. /ezoe/engine shows them with the status of each engine: available, not available (isAvailable() is false: its files are missing) or why it cannot be used.

The Browse tabs navigate the whole content tree like the TinyMCE 3 dialogs: a row of the top level roots the user may read (content, media and users root, and the object being edited once it is published), a path that starts at "Top level nodes" (node 1) with a link for every level, an "Up" link to the parent, paging inside large folders and selecting an object from any level. The roots come from ez_settings.browse_roots, built in the template from content.ini [NodeSettings] and the ezoe/browse policy.

The "Insert/edit link" dialog has a Bookmarks tab like the embed dialog and the TinyMCE 3 link dialog. It lists the bookmarks of the current user through ezoe::bookmarks (paged, with the same preview column as Search and Browse). Choosing a row fills the link target with eznode://<node id> (ezobject://<object id> when "Link to the object instead of the node" is checked), exactly like Search and Browse, so the stored ezxml is the same <link node_id="..."> that TinyMCE 3 stores for a node link. Without bookmarks the tab says "You have no bookmarks" and how to add one ("Add to bookmarks" on a content item in the administration interface). The Browse tab keeps its full-tree navigation. The texts are in the plugin's German translation (ezlink/plugin.js); the server side message reuses the existing design/standard/content/view translations.

Upstream code

The TinyMCE 8 distribution is vendored unmodified in extension/ezoe/design/standard/javascript/tinymce8/ (README, license and notices included); only German is bundled. The browser tests are in extension/ezoe/tests/tinymce8/ (npm install && npm test).

Tests

php vendor/bin/phpunit tests/tests/extension/ezoe/

The tests run against the live installation, with no test database. Every test puts the admin's engine preference and the INI values back.

FileCovers
expOEEditorRegistryTestregistry, resolution (preference, siteaccess, global), unknown / broken / unavailable engines, third engine, the details (version, description, status) of every entry, audit, setup/rad
expOEContentSecurityPolicyEngineTestan engine that needs inline scripts (TinyMCE 3) under an enforced policy: fallback of preference, configuration and ForceEngine, left out of the switch and the engine page, choosing it refused; unchanged when allowed
expOEEngineConfigTesteach engine's template, toolbar map, plugins and config, shipped defaults, vendored TinyMCE 8 and its license, translations
expOEUploadRulesTestallowed and refused file names, the three check modes, the order of the check in the upload view
expOEViewsJsonTestevery ezoe PHP file has no text before <?php or after ?>, the module definition prints nothing, ezoe/load answers JSON with nothing in front of it, browse and bookmarks
expOEEngineViewTestthe page /ezoe/engine: the table of engines, select, saving, reset, unknown engine, policy
expOEEditorServicesTestthe expeditor services, their guards and the catalogue
expOERoundTripTestthe XHTML of both editors gives the same XML; XML shown to an editor and saved again keeps its content

tests/tests/extension/ezoe/helpers/run_ezoe_view.php runs one view in a process of its own, because a view that ends with cleanExit() cannot run inside PHPUnit.

A view or module definition with text outside the PHP tags breaks the editor's dialogs (JSON.parse: unexpected character on the final OK of the embed dialog). That is why the tests look at the raw output.

Something wrong on this page? Report an error

Documentation license

This documentation is free documentation, licensed under the GNU Free Documentation License 1.3 or later.

Exponential Copyright © 1998-2026 Exponential Foundation and 7x