mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
640691f544
BUG Prevent external plugins from being passed to the minifier API Change TinyMCEConfig::requireJS to getScriptURL() for testabilitiy
474 lines
14 KiB
PHP
474 lines
14 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Default configuration for HtmlEditor specific to tinymce
|
|
*/
|
|
class TinyMCEConfig extends HtmlEditorConfig {
|
|
|
|
/**
|
|
* Location of module relative to BASE_DIR. This must contain the following dirs
|
|
* - plugins
|
|
* - themes
|
|
* - skins
|
|
*
|
|
* @config
|
|
* @var string
|
|
*/
|
|
private static $base_dir = 'framework/thirdparty/tinymce';
|
|
|
|
/**
|
|
* TinyMCE JS settings
|
|
*
|
|
* @link https://www.tinymce.com/docs/configure/
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $settings = array(
|
|
'fix_list_elements' => true, // https://www.tinymce.com/docs/configure/content-filtering/#fix_list_elements
|
|
'friendly_name' => '(Please set a friendly name for this config)',
|
|
'priority' => 0, // used for Per-member config override
|
|
'browser_spellcheck' => true,
|
|
'body_class' => 'typography',
|
|
'elementpath' => false, // https://www.tinymce.com/docs/configure/editor-appearance/#elementpath
|
|
'relative_urls' => true,
|
|
'remove_script_host' => true,
|
|
'convert_urls' => false, // Prevent site-root images being rewritten to base relative
|
|
'menubar' => false,
|
|
'language' => 'en',
|
|
);
|
|
|
|
/**
|
|
* Holder list of enabled plugins
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $plugins = array(
|
|
'table' => null,
|
|
'emoticons' => null,
|
|
'paste' => null,
|
|
'code' => null,
|
|
'link' => null,
|
|
'importcss' => null,
|
|
);
|
|
|
|
/**
|
|
* Theme name
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $theme = 'modern';
|
|
|
|
/**
|
|
* Get the theme
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getTheme() {
|
|
return $this->theme;
|
|
}
|
|
|
|
/**
|
|
* Set the theme name
|
|
*
|
|
* @param string $theme
|
|
* @return $this
|
|
*/
|
|
public function setTheme($theme) {
|
|
$this->theme = $theme;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Holder list of buttons, organised by line. This array is 1-based indexed array
|
|
*
|
|
* {@link https://www.tinymce.com/docs/advanced/editor-control-identifiers/#toolbarcontrols}
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $buttons = array(
|
|
1 => array(
|
|
'bold', 'italic', 'underline', 'removeformat', '|',
|
|
'alignleft', 'aligncenter', 'alignright', 'alignjustify', '|',
|
|
'bullist', 'numlist', 'outdent', 'indent',
|
|
),
|
|
2 => array(
|
|
'formatselect', '|',
|
|
'paste', 'pastetext', '|',
|
|
'table', 'ssmedia', 'sslink', 'unlink', '|',
|
|
'code'
|
|
),
|
|
3 => array()
|
|
);
|
|
|
|
public function getOption($key) {
|
|
if(isset($this->settings[$key])) {
|
|
return $this->settings[$key];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public function setOption($key,$value) {
|
|
$this->settings[$key] = $value;
|
|
return $this;
|
|
}
|
|
|
|
public function setOptions($options) {
|
|
foreach ($options as $key => $value) {
|
|
$this->settings[$key] = $value;
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Get all settings
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function getSettings() {
|
|
return $this->settings;
|
|
}
|
|
|
|
public function getAttributes() {
|
|
return [
|
|
'data-editor' => 'tinyMCE', // Register ss.editorWrappers.tinyMCE
|
|
'data-config' => Convert::array2json($this->getConfig())
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Enable one or several plugins. Will maintain unique list if already
|
|
* enabled plugin is re-passed. If passed in as a map of plugin-name to path,
|
|
* the plugin will be loaded by tinymce.PluginManager.load() instead of through tinyMCE.init().
|
|
* Keep in mind that these externals plugins require a dash-prefix in their name.
|
|
*
|
|
* @see http://wiki.moxiecode.com/index.php/TinyMCE:API/tinymce.PluginManager/load
|
|
*
|
|
* If passing in a non-associative array, the plugin name should be located in the standard tinymce
|
|
* plugins folder.
|
|
*
|
|
* If passing in an associative array, the key of each item should be the plugin name.
|
|
* The value of each item is one of:
|
|
* - null - Will be treated as a stardard plugin in the standard location
|
|
* - relative path - Will be treated as a relative url
|
|
* - absolute url - Some url to an external plugin
|
|
*
|
|
* @param string $plugin,... a string, or several strings, or a single array of strings - The plugins to enable
|
|
* @return $this
|
|
*/
|
|
public function enablePlugins($plugin) {
|
|
$plugins = func_get_args();
|
|
if (is_array(current($plugins))) {
|
|
$plugins = current($plugins);
|
|
}
|
|
foreach ($plugins as $name => $path) {
|
|
// if plugins are passed without a path
|
|
if(is_numeric($name)) {
|
|
$name = $path;
|
|
$path = null;
|
|
}
|
|
if (!array_key_exists($name, $this->plugins)) {
|
|
$this->plugins[$name] = $path;
|
|
}
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Enable one or several plugins. Will properly handle being passed a plugin that is already disabled
|
|
* @param string $plugin,... a string, or several strings, or a single array of strings - The plugins to enable
|
|
* @return $this
|
|
*/
|
|
public function disablePlugins($plugin) {
|
|
$plugins = func_get_args();
|
|
if (is_array(current($plugins))) {
|
|
$plugins = current($plugins);
|
|
}
|
|
foreach ($plugins as $name) {
|
|
unset($this->plugins[$name]);
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Gets the list of all enabled plugins as an associative array.
|
|
* Array keys are the plugin names, and values are potentially the plugin location
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getPlugins() {
|
|
return $this->plugins;
|
|
}
|
|
|
|
/**
|
|
* Get list of plugins without custom locations, which is the set of
|
|
* plugins which can be loaded via the standard plugin path, and could
|
|
* potentially be minified
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getInternalPlugins() {
|
|
// Return only plugins with no custom url
|
|
$plugins = [];
|
|
foreach($this->getPlugins() as $name => $url) {
|
|
if(empty($url)) {
|
|
$plugins[] = $name;
|
|
}
|
|
}
|
|
return $plugins;
|
|
}
|
|
|
|
/**
|
|
* Get all button rows, skipping empty rows
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getButtons() {
|
|
return array_filter($this->buttons);
|
|
}
|
|
|
|
/**
|
|
* Totally re-set the buttons on a given line
|
|
*
|
|
* @param int $line The line number to redefine, from 1 to 3
|
|
* @param string $buttons,... A string or several strings, or a single array of strings.
|
|
* The button names to assign to this line.
|
|
* @return $this
|
|
*/
|
|
public function setButtonsForLine($line, $buttons) {
|
|
if (func_num_args() > 2) {
|
|
$buttons = func_get_args();
|
|
array_shift($buttons);
|
|
}
|
|
$this->buttons[$line] = is_array($buttons) ? $buttons : array($buttons);
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Add buttons to the end of a line
|
|
* @param int $line The line number to redefine, from 1 to 3
|
|
* @param string $buttons,... A string or several strings, or a single array of strings.
|
|
* The button names to add to this line
|
|
* @return $this
|
|
*/
|
|
public function addButtonsToLine($line, $buttons) {
|
|
if(func_num_args() > 2) {
|
|
$buttons = func_get_args();
|
|
array_shift($buttons);
|
|
}
|
|
if(!is_array($buttons)) {
|
|
$buttons = [$buttons];
|
|
}
|
|
foreach ($buttons as $button) {
|
|
$this->buttons[$line][] = $button;
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Internal function for adding and removing buttons related to another button
|
|
* @param string $name The name of the button to modify
|
|
* @param int $offset The offset relative to that button to perform an array_splice at.
|
|
* 0 for before $name, 1 for after.
|
|
* @param int $del The number of buttons to remove at the position given by index(string) + offset
|
|
* @param mixed $add An array or single item to insert at the position given by index(string) + offset,
|
|
* or null for no insertion
|
|
* @return bool True if $name matched a button, false otherwise
|
|
*/
|
|
protected function modifyButtons($name, $offset, $del=0, $add=null) {
|
|
foreach ($this->buttons as &$buttons) {
|
|
if (($idx = array_search($name, $buttons)) !== false) {
|
|
if ($add) {
|
|
array_splice($buttons, $idx + $offset, $del, $add);
|
|
} else {
|
|
array_splice($buttons, $idx + $offset, $del);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Insert buttons before the first occurance of another button
|
|
* @param string $before the name of the button to insert other buttons before
|
|
* @param string $buttons,... a string, or several strings, or a single array of strings.
|
|
* The button names to insert before that button
|
|
* @return bool True if insertion occured, false if it did not (because the given button name was not found)
|
|
*/
|
|
public function insertButtonsBefore($before, $buttons) {
|
|
if(func_num_args() > 2) {
|
|
$buttons = func_get_args();
|
|
array_shift($buttons);
|
|
}
|
|
if(!is_array($buttons)) {
|
|
$buttons = [$buttons];
|
|
}
|
|
return $this->modifyButtons($before, 0, 0, $buttons);
|
|
}
|
|
|
|
/**
|
|
* Insert buttons after the first occurance of another button
|
|
* @param string $after the name of the button to insert other buttons before
|
|
* @param string $buttons,... a string, or several strings, or a single array of strings.
|
|
* The button names to insert after that button
|
|
* @return bool True if insertion occured, false if it did not (because the given button name was not found)
|
|
*/
|
|
public function insertButtonsAfter($after, $buttons) {
|
|
if(func_num_args() > 2) {
|
|
$buttons = func_get_args();
|
|
array_shift($buttons);
|
|
}
|
|
if(!is_array($buttons)) {
|
|
$buttons = [$buttons];
|
|
}
|
|
return $this->modifyButtons($after, 1, 0, $buttons);
|
|
}
|
|
|
|
/**
|
|
* Remove the first occurance of buttons
|
|
* @param string $buttons,... one or more strings - the name of the buttons to remove
|
|
* @return null
|
|
*/
|
|
public function removeButtons($buttons) {
|
|
if(func_num_args() > 1) {
|
|
$buttons = func_get_args();
|
|
}
|
|
if(!is_array($buttons)) {
|
|
$buttons = [$buttons];
|
|
}
|
|
foreach ($buttons as $button) {
|
|
$this->modifyButtons($button, 0, 1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate the JavaScript that will set TinyMCE's configuration:
|
|
* - Parse all configurations into JSON objects to be used in JavaScript
|
|
* - Includes TinyMCE and configurations using the {@link Requirements} system
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function getConfig() {
|
|
$settings = $this->getSettings();
|
|
|
|
// https://www.tinymce.com/docs/configure/url-handling/#document_base_url
|
|
$settings['document_base_url'] = Director::absoluteBaseURL();
|
|
|
|
// https://www.tinymce.com/docs/api/class/tinymce.editormanager/#baseURL
|
|
$tinyMCEBaseURL = Controller::join_links(
|
|
Director::absoluteBaseURL(),
|
|
$this->config()->base_dir
|
|
);
|
|
$settings['baseURL'] = $tinyMCEBaseURL;
|
|
|
|
// map all plugins to absolute urls for loading
|
|
$plugins = array();
|
|
foreach($this->getPlugins() as $plugin => $path) {
|
|
if(!$path) {
|
|
// Empty paths: Convert to urls in standard base url
|
|
$path = Controller::join_links(
|
|
$tinyMCEBaseURL,
|
|
"plugins/{$plugin}/plugin.min.js"
|
|
);
|
|
} elseif(!Director::is_absolute_url($path)) {
|
|
// Non-absolute urls are made absolute
|
|
$path = Director::absoluteURL($path);
|
|
}
|
|
$plugins[$plugin] = $path;
|
|
}
|
|
|
|
// https://www.tinymce.com/docs/configure/integration-and-setup/#external_plugins
|
|
if($plugins) {
|
|
$settings['external_plugins'] = $plugins;
|
|
}
|
|
|
|
// https://www.tinymce.com/docs/configure/editor-appearance/#groupingtoolbarcontrols
|
|
$buttons = $this->getButtons();
|
|
$settings['toolbar'] = [];
|
|
foreach($buttons as $rowButtons) {
|
|
$row = implode(' ', $rowButtons);
|
|
if(count($buttons) > 1) {
|
|
$settings['toolbar'][] = $row;
|
|
} else {
|
|
$settings['toolbar'] = $row;
|
|
}
|
|
}
|
|
|
|
// https://www.tinymce.com/docs/configure/content-appearance/#content_css
|
|
$settings['content_css'] = $this->getEditorCSS();
|
|
|
|
// https://www.tinymce.com/docs/configure/editor-appearance/#theme_url
|
|
$theme = $this->getTheme();
|
|
if(!Director::is_absolute_url($theme)) {
|
|
$theme = Controller::join_links($tinyMCEBaseURL, "themes/{$theme}/theme.min.js");
|
|
}
|
|
$settings['theme_url'] = $theme;
|
|
|
|
// Send back
|
|
return $settings;
|
|
}
|
|
|
|
/**
|
|
* Get location of all editor.css files
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function getEditorCSS() {
|
|
$editor = array();
|
|
$editor[] = Controller::join_links(
|
|
Director::absoluteBaseURL(),
|
|
FRAMEWORK_ADMIN_DIR . '/css/editor.css'
|
|
);
|
|
if($theme = SSViewer::get_theme_folder()) {
|
|
$editorDir = $theme . '/css/editor.css';
|
|
if(file_exists(BASE_PATH . '/' . $editorDir)) {
|
|
$editor[] = Controller::join_links(
|
|
Director::absoluteBaseURL(),
|
|
$editorDir
|
|
);
|
|
}
|
|
}
|
|
return $editor;
|
|
}
|
|
|
|
/**
|
|
* Generate gzipped TinyMCE configuration including plugins and languages.
|
|
* This ends up "pre-loading" TinyMCE bundled with the required plugins
|
|
* so that multiple HTTP requests on the client don't need to be made.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getScriptURL() {
|
|
// If gzip is disabled just return core script url
|
|
$useGzip = Config::inst()->get('HtmlEditorField', 'use_gzip');
|
|
if(!$useGzip) {
|
|
return THIRDPARTY_DIR . '/tinymce/tinymce.min.js';
|
|
}
|
|
|
|
// tinyMCE JS requirement
|
|
require_once THIRDPARTY_PATH . '/tinymce/tiny_mce_gzip.php';
|
|
$tag = TinyMCE_Compressor::renderTag(array(
|
|
'url' => THIRDPARTY_DIR . '/tinymce/tiny_mce_gzip.php',
|
|
'plugins' => implode(',', $this->getInternalPlugins()),
|
|
'themes' => $this->getTheme(),
|
|
'languages' => $this->getOption('language')
|
|
), true);
|
|
preg_match('/src="([^"]*)"/', $tag, $matches);
|
|
return html_entity_decode($matches[1]);
|
|
}
|
|
|
|
public function init() {
|
|
// These should be 'provides' by bundle-dist.js
|
|
Requirements::javascript(FRAMEWORK_DIR . "/thirdparty/jquery/jquery.js");
|
|
Requirements::javascript(THIRDPARTY_DIR . '/jquery-ui/jquery-ui.js');
|
|
Requirements::javascript(THIRDPARTY_DIR . '/jquery-entwine/dist/jquery.entwine-dist.js');
|
|
Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/javascript/dist/ssui.core.js');
|
|
|
|
// include TinyMCE Javascript
|
|
Requirements::javascript($this->getScriptURL());
|
|
Requirements::javascript(FRAMEWORK_DIR ."/javascript/dist/HtmlEditorField.js");
|
|
|
|
Requirements::css(THIRDPARTY_DIR . '/jquery-ui-themes/smoothness/jquery-ui.css');
|
|
}
|
|
}
|