Last active
May 4, 2020 17:39
-
-
Save dgrammatiko/1ffc213290914024817a106fc9f5f89c to your computer and use it in GitHub Desktop.
Joomla 4 tinyMCE DnD using own handler
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @copyright Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved. | |
* @license GNU General Public License version 2 or later; see LICENSE.txt | |
*/ | |
((tinyMCE, Joomla, window, document) => { | |
'use strict'; | |
Joomla.JoomlaTinyMCE = { | |
/** | |
* Find all TinyMCE elements and initialize TinyMCE instance for each | |
* | |
* @param {HTMLElement} target Target Element where to search for the editor element | |
* | |
* @since 3.7.0 | |
*/ | |
setupEditors: (target) => { | |
const container = target || document; | |
const pluginOptions = Joomla.getOptions ? Joomla.getOptions('plg_editor_tinymce', {}) | |
: (Joomla.optionsStorage.plg_editor_tinymce || {}); | |
const editors = [].slice.call(container.querySelectorAll('.js-editor-tinymce')); | |
editors.forEach((editor) => { | |
const currentEditor = editor.querySelector('textarea'); | |
Joomla.JoomlaTinyMCE.setupEditor(currentEditor, pluginOptions); | |
}); | |
}, | |
/** | |
* Initialize TinyMCE editor instance | |
* | |
* @param {HTMLElement} element | |
* @param {Object} pluginOptions | |
* | |
* @since 3.7.0 | |
*/ | |
setupEditor: (element, pluginOptions) => { | |
// Check whether the editor already has ben set | |
if (Joomla.editors.instances[element.id]) { | |
return; | |
} | |
const name = element ? element.getAttribute('name').replace(/\[\]|\]/g, '').split('[').pop() : 'default'; // Get Editor name | |
const tinyMCEOptions = pluginOptions ? pluginOptions.tinyMCE || {} : {}; | |
const defaultOptions = tinyMCEOptions.default || {}; | |
// Check specific options by the name | |
let options = tinyMCEOptions[name] ? tinyMCEOptions[name] : defaultOptions; | |
// Avoid an unexpected changes, and copy the options object | |
if (options.joomlaMergeDefaults) { | |
options = Joomla.extend(Joomla.extend({}, defaultOptions), options); | |
} else { | |
options = Joomla.extend({}, options); | |
} | |
if (element) { | |
// We already have the Target, so reset the selector and assign given element as target | |
options.selector = null; | |
options.target = element; | |
} | |
const buttonValues = []; | |
const arr = Object.keys(options.joomlaExtButtons.names) | |
.map((key) => options.joomlaExtButtons.names[key]); | |
const icons = { | |
joomla: '<svg viewBox="0 0 32 32" width="24" height="24"><path d="M8.313 8.646c1.026-1.026 2.688-1.026 3.713-0.001l0.245 0.246 3.159-3.161-0.246-0.246c-1.801-1.803-4.329-2.434-6.638-1.891-0.331-2.037-2.096-3.591-4.224-3.592-2.364 0-4.28 1.92-4.28 4.286 0 2.042 1.425 3.75 3.333 4.182-0.723 2.42-0.133 5.151 1.776 7.062l7.12 7.122 3.156-3.163-7.119-7.121c-1.021-1.023-1.023-2.691 0.006-3.722zM31.96 4.286c0-2.368-1.916-4.286-4.281-4.286-2.164 0-3.952 1.608-4.24 3.695-2.409-0.708-5.118-0.109-7.020 1.794l-7.12 7.122 3.159 3.162 7.118-7.12c1.029-1.030 2.687-1.028 3.709-0.006 1.025 1.026 1.025 2.691-0.001 3.717l-0.244 0.245 3.157 3.164 0.246-0.248c1.889-1.893 2.49-4.586 1.8-6.989 2.098-0.276 3.717-2.074 3.717-4.25zM28.321 23.471c0.566-2.327-0.062-4.885-1.878-6.703l-7.109-7.125-3.159 3.16 7.11 7.125c1.029 1.031 1.027 2.691 0.006 3.714-1.025 1.025-2.688 1.025-3.714-0.001l-0.243-0.243-3.156 3.164 0.242 0.241c1.922 1.925 4.676 2.514 7.105 1.765 0.395 1.959 2.123 3.431 4.196 3.431 2.363 0 4.28-1.917 4.28-4.285 0-2.163-1.599-3.952-3.679-4.244zM19.136 16.521l-7.111 7.125c-1.022 1.024-2.689 1.026-3.717-0.004-1.026-1.028-1.026-2.691-0.001-3.718l0.244-0.243-3.159-3.16-0.242 0.241c-1.836 1.838-2.455 4.432-1.858 6.781-1.887 0.446-3.292 2.145-3.292 4.172-0.001 2.367 1.917 4.285 4.281 4.285 2.034-0.001 3.737-1.419 4.173-3.324 2.334 0.58 4.906-0.041 6.729-1.867l7.109-7.124-3.157-3.163z"></path></svg>', | |
}; | |
arr.forEach((xtdButton) => { | |
const tmp = {}; | |
tmp.text = xtdButton.name; | |
tmp.icon = xtdButton.icon; | |
tmp.type = 'menuitem'; | |
if (xtdButton.iconSVG) { | |
icons[tmp.icon] = xtdButton.iconSVG; | |
} | |
if (xtdButton.href) { | |
tmp.onAction = () => { | |
document.getElementById(`${xtdButton.id}Modal`).open(); | |
}; | |
} else { | |
tmp.onAction = () => { | |
// eslint-disable-next-line no-new-func | |
new Function(xtdButton.click)(); | |
}; | |
} | |
buttonValues.push(tmp); | |
}); | |
// Ensure tinymce is initialised in readonly mode if the textarea has readonly applied | |
let readOnlyMode = false; | |
if (element) { | |
readOnlyMode = element.readOnly; | |
} | |
if (buttonValues.length) { | |
options.setup = (editor) => { | |
editor.settings.readonly = readOnlyMode; | |
Object.keys(icons).forEach((icon) => { | |
editor.ui.registry.addIcon(icon, icons[icon]); | |
}); | |
editor.ui.registry.addMenuButton('jxtdbuttons', { | |
text: Joomla.JText._('PLG_TINY_CORE_BUTTONS'), | |
icon: 'joomla', | |
fetch: (callback) => callback(buttonValues), | |
}); | |
}; | |
} else { | |
options.setup = (editor) => { | |
editor.settings.readonly = readOnlyMode; | |
}; | |
} | |
if (options.images_upload_handler === 'Joomla.JoomlaTinyMCE.uploadHandler') { | |
options.images_upload_handler = Joomla.JoomlaTinyMCE.uploadHandler.bind(null, options); | |
} else if (options.images_upload_handler | |
&& typeof(options.images_upload_handler) === 'string' && window[options.images_upload_handler]) { | |
options.images_upload_handler = window[options.images_upload_handler].bind(null, options); | |
} | |
// Create a new instance | |
// eslint-disable-next-line no-undef | |
const ed = new tinyMCE.Editor(element.id, options, tinymce.EditorManager); | |
ed.render(); | |
/** Register the editor's instance to Joomla Object */ | |
Joomla.editors.instances[element.id] = { | |
// Required by Joomla's API for the XTD-Buttons | |
getValue: () => Joomla.editors.instances[element.id].instance.getContent(), | |
setValue: (text) => Joomla.editors.instances[element.id].instance.setContent(text), | |
getSelection: () => Joomla.editors.instances[element.id].instance.selection.getContent({ format: 'text' }), | |
replaceSelection: (text) => Joomla.editors.instances[element.id].instance.execCommand('mceInsertContent', false, text), | |
// Required by Joomla's API for Mail Component Integration | |
disable: (disabled) => Joomla.editors.instances[element.id].instance.setMode(disabled ? 'readonly' : 'design'), | |
// Some extra instance dependent | |
id: element.id, | |
instance: ed, | |
onSave: () => { if (Joomla.editors.instances[element.id].instance.isHidden()) { Joomla.editors.instances[element.id].instance.show(); } return ''; }, | |
}; | |
/** On save * */ | |
document.getElementById(ed.id).form.addEventListener('submit', () => Joomla.editors.instances[ed.targetElm.id].onSave()); | |
}, | |
}; | |
/** | |
* Use custom Upload handler, to make upload compatible with com_media API | |
* | |
* @param options | |
* @param blobInfo | |
* @param success | |
* @param failure | |
* @param progress | |
*/ | |
Joomla.JoomlaTinyMCE.uploadHandler = function (options, blobInfo, success, failure, progress) { | |
Joomla.request({ | |
url: `${tinyMCE.activeEditor.settings.uploadUri}&path=${tinyMCE.activeEditor.settings.comMediaAdapter}`, | |
method: 'POST', | |
data: JSON.stringify({ | |
[tinyMCE.activeEditor.settings.csrfToken]: '1', | |
name: blobInfo.filename(), | |
content: blobInfo.base64(), | |
parent: tinyMCE.activeEditor.settings.parentUploadFolder || '/', | |
}), | |
headers: {'Content-Type': 'application/json'}, | |
onSuccess: (resp) => { | |
console.log(resp) | |
let response; | |
try { | |
response = JSON.parse(resp); | |
} catch (e) { | |
editor.windowManager.alert(`${Joomla.Text._('JERROR')}: {e}`); | |
} | |
if (response.data && response.data.path) { | |
let urlPath; | |
// For local adapters use relative paths | |
if (/local-/.test(response.data.adapter)) { | |
const {rootFull} = Joomla.getOptions('system.paths'); | |
urlPath = `/${response.data.thumb_path.split(rootFull)[1]}`; | |
} else if (response.data.thumb_path) { | |
// Absolute path for different domain | |
urlPath = response.data.thumb_path; | |
} | |
return success(urlPath); | |
// tinyMCE.activeEditor.execCommand('mceInsertContent', false, `<img loading="lazy" src="${urlPath}" />`); | |
} | |
}, | |
onError: (xhr) => { | |
console.log(xhr) | |
return failure(xhr.statusText) | |
// tinyMCE.activeEditor.windowManager.alert(`Error: ${xhr.statusText}`); | |
} | |
}); | |
console.log(options); | |
//console.log(blobInfo.filename(), blobInfo.base64()); | |
}; | |
/** | |
* Initialize at an initial page load | |
*/ | |
document.addEventListener('DOMContentLoaded', () => { Joomla.JoomlaTinyMCE.setupEditors(document); }); | |
/** | |
* Initialize when a part of the page was updated | |
*/ | |
document.addEventListener('joomla:updated', (event) => Joomla.JoomlaTinyMCE.setupEditors(event.target)); | |
})(window.tinyMCE, Joomla, window, document); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* @package Joomla.Plugin | |
* @subpackage Editors.tinymce | |
* | |
* @copyright Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved. | |
* @license GNU General Public License version 2 or later; see LICENSE.txt | |
*/ | |
defined('_JEXEC') or die; | |
use Joomla\CMS\Access\Access; | |
use Joomla\CMS\Component\ComponentHelper; | |
use Joomla\CMS\Factory; | |
use Joomla\CMS\Filesystem\Folder; | |
use Joomla\CMS\Filter\InputFilter; | |
use Joomla\CMS\Language\Text; | |
use Joomla\CMS\Layout\LayoutHelper; | |
use Joomla\CMS\Log\Log; | |
use Joomla\CMS\Plugin\CMSPlugin; | |
use Joomla\CMS\Session\Session; | |
use Joomla\CMS\Uri\Uri; | |
use Joomla\Event\Event; | |
/** | |
* TinyMCE Editor Plugin | |
* | |
* @since 1.5 | |
*/ | |
class PlgEditorTinymce extends CMSPlugin | |
{ | |
/** | |
* Base path for editor files | |
* | |
* @since 3.5 | |
*/ | |
protected $_basePath = 'media/vendor/tinymce'; | |
/** | |
* Load the language file on instantiation. | |
* | |
* @var boolean | |
* @since 3.1 | |
*/ | |
protected $autoloadLanguage = true; | |
/** | |
* Loads the application object | |
* | |
* @var \Joomla\CMS\Application\CMSApplication | |
* @since 3.2 | |
*/ | |
protected $app = null; | |
/** | |
* Initialises the Editor. | |
* | |
* @return void | |
* | |
* @since 1.5 | |
*/ | |
public function onInit() | |
{ | |
$wa = $this->app->getDocument()->getWebAssetManager(); | |
if (!$wa->assetExists('script', 'tinymce')) | |
{ | |
$wa->registerScript('tinymce', $this->_basePath . '/tinymce.js', [], ['defer' => true]); | |
} | |
if (!$wa->assetExists('script', 'plg_editors_tinymce')) | |
{ | |
$wa->registerScript('plg_editors_tinymce', 'plg_editors_tinymce/tinymce.min.js', [], ['defer' => true], ['core', 'tinymce']); | |
} | |
$wa->useScript('tinymce') | |
->useScript('plg_editors_tinymce'); | |
} | |
/** | |
* Display the editor area. | |
* | |
* @param string $name The name of the editor area. | |
* @param string $content The content of the field. | |
* @param string $width The width of the editor area. | |
* @param string $height The height of the editor area. | |
* @param int $col The number of columns for the editor area. | |
* @param int $row The number of rows for the editor area. | |
* @param boolean $buttons True and the editor buttons will be displayed. | |
* @param string $id An optional ID for the textarea. If not supplied the name is used. | |
* @param string $asset The object asset | |
* @param object $author The author. | |
* @param array $params Associative array of editor parameters. | |
* | |
* @return string | |
*/ | |
public function onDisplay( | |
$name, $content, $width, $height, $col, $row, $buttons = true, $id = null, $asset = null, $author = null, $params = array()) | |
{ | |
if (empty($id)) | |
{ | |
$id = $name; | |
} | |
$id = preg_replace('/(\s|[^A-Za-z0-9_])+/', '_', $id); | |
$nameGroup = explode('[', preg_replace('/\[\]|\]/', '', $name)); | |
$fieldName = end($nameGroup); | |
$scriptOptions = array(); | |
$externalPlugins = array(); | |
// Check for existing options | |
$doc = Factory::getDocument(); | |
$options = $doc->getScriptOptions('plg_editor_tinymce'); | |
// Only add "px" to width and height if they are not given as a percentage | |
if (is_numeric($width)) | |
{ | |
$width .= 'px'; | |
} | |
if (is_numeric($height)) | |
{ | |
$height .= 'px'; | |
} | |
// Data object for the layout | |
$textarea = new stdClass; | |
$textarea->name = $name; | |
$textarea->id = $id; | |
$textarea->class = 'mce_editable joomla-editor-tinymce'; | |
$textarea->cols = $col; | |
$textarea->rows = $row; | |
$textarea->width = $width; | |
$textarea->height = $height; | |
$textarea->content = $content; | |
// Set editor to readonly mode | |
$textarea->readonly = !empty($params['readonly']); | |
// Render Editor markup | |
$editor = '<div class="js-editor-tinymce">'; | |
$editor .= LayoutHelper::render('joomla.tinymce.textarea', $textarea); | |
$editor .= $this->_toogleButton($id); | |
$editor .= '</div>'; | |
// Prepare the instance specific options, actually the ext-buttons | |
if (empty($options['tinyMCE'][$fieldName]['joomlaExtButtons'])) | |
{ | |
$btns = $this->tinyButtons($id, $buttons); | |
// Set editor to readonly mode | |
if (!empty($params['readonly'])) | |
{ | |
$options['tinyMCE'][$fieldName]['readonly'] = 1; | |
} | |
$options['tinyMCE'][$fieldName]['joomlaMergeDefaults'] = true; | |
$options['tinyMCE'][$fieldName]['joomlaExtButtons'] = $btns; | |
$doc->addScriptOptions('plg_editor_tinymce', $options, false); | |
} | |
// Setup Default (common) options for the Editor script | |
// Check whether we already have them | |
if (!empty($options['tinyMCE']['default'])) | |
{ | |
return $editor; | |
} | |
$user = Factory::getUser(); | |
$language = Factory::getLanguage(); | |
$theme = 'silver'; | |
$ugroups = array_combine($user->getAuthorisedGroups(), $user->getAuthorisedGroups()); | |
// Prepare the parameters | |
$levelParams = new Joomla\Registry\Registry; | |
$extraOptions = new stdClass; | |
$toolbarParams = new stdClass; | |
$extraOptionsAll = $this->params->get('configuration.setoptions', array()); | |
$toolbarParamsAll = $this->params->get('configuration.toolbars', array()); | |
// Get configuration depend from User group | |
foreach ($extraOptionsAll as $set => $val) | |
{ | |
$val->access = empty($val->access) ? array() : $val->access; | |
// Check whether User in one of allowed group | |
foreach ($val->access as $group) | |
{ | |
if (isset($ugroups[$group])) | |
{ | |
$extraOptions = $val; | |
$toolbarParams = $toolbarParamsAll->$set; | |
} | |
} | |
} | |
// load external plugins | |
if (isset($extraOptions->external_plugins) && $extraOptions->external_plugins) | |
{ | |
foreach (json_decode(json_encode($extraOptions->external_plugins), true) as $external) | |
{ | |
// get the path for readability | |
$path = $external['path']; | |
// if we have a name and path, add it to the list | |
if ($external['name'] != '' && $path != '') | |
{ | |
if (substr($path, 0, 1) == '/') | |
{ | |
// treat as a local path, so add the root | |
$path = Uri::root() . substr($path, 1); | |
} | |
$externalPlugins[$external['name']] = $path; | |
} | |
} | |
} | |
// Merge the params | |
$levelParams->loadObject($toolbarParams); | |
$levelParams->loadObject($extraOptions); | |
// Set the selected skin | |
$skin = $levelParams->get($this->app->isClient('administrator') ? 'skin_admin' : 'skin', 'oxide'); | |
// Check that selected skin exists. | |
$skin = Folder::exists(JPATH_ROOT . '/media/vendor/tinymce/skins/ui/' . $skin) ? $skin : 'oxide'; | |
$langMode = $levelParams->get('lang_mode', 1); | |
$langPrefix = $levelParams->get('lang_code', 'en'); | |
if ($langMode) | |
{ | |
if (file_exists(JPATH_ROOT . '/media/vendor/tinymce/langs/' . $language->getTag() . '.js')) | |
{ | |
$langPrefix = $language->getTag(); | |
} | |
elseif (file_exists(JPATH_ROOT . '/media/vendor/tinymce/langs/' . substr($language->getTag(), 0, strpos($language->getTag(), '-')) . '.js')) | |
{ | |
$langPrefix = substr($language->getTag(), 0, strpos($language->getTag(), '-')); | |
} | |
else | |
{ | |
$langPrefix = 'en'; | |
} | |
} | |
$text_direction = 'ltr'; | |
if ($language->isRtl()) | |
{ | |
$text_direction = 'rtl'; | |
} | |
$use_content_css = $levelParams->get('content_css', 1); | |
$content_css_custom = $levelParams->get('content_css_custom', ''); | |
/* | |
* Lets get the default template for the site application | |
*/ | |
$db = Factory::getDbo(); | |
$query = $db->getQuery(true) | |
->select($db->quoteName('template')) | |
->from($db->quoteName('#__template_styles')) | |
->where( | |
[ | |
$db->quoteName('client_id') . ' = 0', | |
$db->quoteName('home') . ' = ' . $db->quote('1') | |
] | |
); | |
$db->setQuery($query); | |
try | |
{ | |
$template = $db->loadResult(); | |
} | |
catch (RuntimeException $e) | |
{ | |
$this->app->enqueueMessage(Text::_('JERROR_AN_ERROR_HAS_OCCURRED'), 'error'); | |
return ''; | |
} | |
$content_css = null; | |
$templates_path = JPATH_SITE . '/templates'; | |
// Loading of css file for 'styles' dropdown | |
if ($content_css_custom) | |
{ | |
// If URL, just pass it to $content_css | |
if (strpos($content_css_custom, 'http') !== false) | |
{ | |
$content_css = $content_css_custom; | |
} | |
// If it is not a URL, assume it is a file name in the current template folder | |
else | |
{ | |
$content_css = Uri::root(true) . '/templates/' . $template . '/css/' . $content_css_custom; | |
// Issue warning notice if the file is not found (but pass name to $content_css anyway to avoid TinyMCE error | |
if (!file_exists($templates_path . '/' . $template . '/css/' . $content_css_custom)) | |
{ | |
$msg = sprintf(Text::_('PLG_TINY_ERR_CUSTOMCSSFILENOTPRESENT'), $content_css_custom); | |
Log::add($msg, Log::WARNING, 'jerror'); | |
} | |
} | |
} | |
else | |
{ | |
// Process when use_content_css is Yes and no custom file given | |
if ($use_content_css) | |
{ | |
// First check templates folder for default template | |
// if no editor.css file in templates folder, check system template folder | |
if (!file_exists($templates_path . '/' . $template . '/css/editor.css')) | |
{ | |
// If no editor.css file in system folder, show alert | |
if (!file_exists($templates_path . '/system/css/editor.css')) | |
{ | |
Log::add(Text::_('PLG_TINY_ERR_EDITORCSSFILENOTPRESENT'), Log::WARNING, 'jerror'); | |
} | |
else | |
{ | |
$content_css = Uri::root(true) . '/templates/system/css/editor.css'; | |
} | |
} | |
else | |
{ | |
$content_css = Uri::root(true) . '/templates/' . $template . '/css/editor.css'; | |
} | |
} | |
} | |
$ignore_filter = false; | |
// Text filtering | |
if ($levelParams->get('use_config_textfilters', 0)) | |
{ | |
// Use filters from com_config | |
$filter = static::getGlobalFilters(); | |
$ignore_filter = $filter === false; | |
$tagBlacklist = !empty($filter->tagBlacklist) ? $filter->tagBlacklist : array(); | |
$attrBlacklist = !empty($filter->attrBlacklist) ? $filter->attrBlacklist : array(); | |
$tagArray = !empty($filter->tagArray) ? $filter->tagArray : array(); | |
$attrArray = !empty($filter->attrArray) ? $filter->attrArray : array(); | |
$invalid_elements = implode(',', array_merge($tagBlacklist, $attrBlacklist, $tagArray, $attrArray)); | |
// Valid elements are all whitelist entries in com_config, which are now missing in the tagBlacklist | |
$default_filter = InputFilter::getInstance(); | |
$valid_elements = implode(',', array_diff($default_filter->tagBlacklist, $tagBlacklist)); | |
$extended_elements = ''; | |
} | |
else | |
{ | |
// Use filters from TinyMCE params | |
$invalid_elements = trim($levelParams->get('invalid_elements', 'script,applet,iframe')); | |
$extended_elements = trim($levelParams->get('extended_elements', '')); | |
$valid_elements = trim($levelParams->get('valid_elements', '')); | |
} | |
$html_height = $this->params->get('html_height', '550'); | |
$html_width = $this->params->get('html_width', ''); | |
if ($html_width == 750) | |
{ | |
$html_width = ''; | |
} | |
if (is_numeric($html_width)) | |
{ | |
$html_width .= 'px'; | |
} | |
if (is_numeric($html_height)) | |
{ | |
$html_height .= 'px'; | |
} | |
// The param is true for vertical resizing only, false or both | |
$resizing = (bool) $levelParams->get('resizing', true); | |
$resize_horizontal = (bool) $levelParams->get('resize_horizontal', true); | |
if ($resizing && $resize_horizontal) | |
{ | |
$resizing = 'both'; | |
} | |
// Set of always available plugins | |
$plugins = array( | |
'autolink', | |
'lists', | |
'importcss', | |
); | |
// Allowed elements | |
$elements = array( | |
'hr[id|title|alt|class|width|size|noshade]', | |
); | |
if ($extended_elements) | |
{ | |
$elements = array_merge($elements, explode(',', $extended_elements)); | |
} | |
// Prepare the toolbar/menubar | |
$knownButtons = static::getKnownButtons(); | |
// Check if there no value at all | |
if (!$levelParams->get('menu') && !$levelParams->get('toolbar1') && !$levelParams->get('toolbar2')) | |
{ | |
// Get from preset | |
$presets = static::getToolbarPreset(); | |
/* | |
* Predefine group as: | |
* Set 0: for Administrator, Editor, Super Users (4,7,8) | |
* Set 1: for Registered, Manager (2,6), all else are public | |
*/ | |
switch (true) | |
{ | |
case isset($ugroups[4]) || isset($ugroups[7]) || isset($ugroups[8]): | |
$preset = $presets['advanced']; | |
break; | |
case isset($ugroups[2]) || isset($ugroups[6]): | |
$preset = $presets['medium']; | |
break; | |
default: | |
$preset = $presets['simple']; | |
} | |
$levelParams->loadArray($preset); | |
} | |
$menubar = (array) $levelParams->get('menu', []); | |
$toolbar1 = (array) $levelParams->get('toolbar1', []); | |
$toolbar2 = (array) $levelParams->get('toolbar2', []); | |
// Make an easy way to check which button is enabled | |
$allButtons = array_merge($toolbar1, $toolbar2); | |
$allButtons = array_combine($allButtons, $allButtons); | |
// Check for button-specific plugins | |
foreach ($allButtons as $btnName) | |
{ | |
if (!empty($knownButtons[$btnName]['plugin'])) | |
{ | |
$plugins[] = $knownButtons[$btnName]['plugin']; | |
} | |
} | |
// Template | |
$templates = []; | |
if (!empty($allButtons['template'])) | |
{ | |
foreach (glob(JPATH_ROOT . '/media/vendor/tinymce/templates/*.html') as $filename) | |
{ | |
$filename = basename($filename, '.html'); | |
if ($filename !== 'index') | |
{ | |
$lang = Factory::getLanguage(); | |
$title = $filename; | |
$description = ' '; | |
if ($lang->hasKey('PLG_TINY_TEMPLATE_' . strtoupper($filename) . '_TITLE')) | |
{ | |
$title = Text::_('PLG_TINY_TEMPLATE_' . strtoupper($filename) . '_TITLE'); | |
} | |
if ($lang->hasKey('PLG_TINY_TEMPLATE_' . strtoupper($filename) . '_DESC')) | |
{ | |
$description = Text::_('PLG_TINY_TEMPLATE_' . strtoupper($filename) . '_DESC'); | |
} | |
$templates[] = array( | |
'title' => $title, | |
'description' => $description, | |
'url' => Uri::root(true) . '/media/vendor/tinymce/templates/' . $filename . '.html', | |
); | |
} | |
} | |
} | |
// Check for extra plugins, from the setoptions form | |
foreach (array('wordcount' => 1, 'advlist' => 1, 'autosave' => 1) as $pName => $def) | |
{ | |
if ($levelParams->get($pName, $def)) | |
{ | |
$plugins[] = $pName; | |
} | |
} | |
// Drag and drop Images always FALSE, reverting this allows for inlining the images | |
$allowImgPaste = false; | |
$dragdrop = $levelParams->get('drag_drop', 1); | |
if ($dragdrop && $user->authorise('core.create', 'com_media')) | |
{ | |
$allowImgPaste = true; | |
$uploadUrl = Uri::base(false) . 'index.php?option=com_media&format=json&task=api.files'; | |
if ($this->app->isClient('site')) | |
{ | |
$uploadUrl = htmlentities($uploadUrl, null, 'UTF-8', null); | |
} | |
Text::script('PLG_TINY_ERR_UNSUPPORTEDBROWSER'); | |
Text::script('JERROR'); | |
$scriptOptions['parentUploadFolder'] = '/'; | |
$scriptOptions['csrfToken'] = Session::getFormToken(); | |
$scriptOptions['uploadUri'] = $uploadUrl; | |
$scriptOptions['images_reuse_filename'] = true; | |
// @TODO have a way to select the adapter, used to be $levelParams->get('path', ''); | |
$scriptOptions['comMediaAdapter'] = 'local-0:'; | |
$scriptOptions['images_reuse_filename'] = true; | |
$scriptOptions['images_upload_base_path'] = '/'; | |
$scriptOptions['images_upload_handler'] = 'Joomla.JoomlaTinyMCE.uploadHandler'; | |
} | |
// Convert pt to px in dropdown | |
$scriptOptions['fontsize_formats'] = '8px 10px 12px 14px 18px 24px 36px'; | |
// User custom plugins and buttons | |
$custom_plugin = trim($levelParams->get('custom_plugin', '')); | |
$custom_button = trim($levelParams->get('custom_button', '')); | |
if ($custom_plugin) | |
{ | |
$separator = strpos($custom_plugin, ',') !== false ? ',' : ' '; | |
$plugins = array_merge($plugins, explode($separator, $custom_plugin)); | |
} | |
if ($custom_button) | |
{ | |
$separator = strpos($custom_button, ',') !== false ? ',' : ' '; | |
$toolbar1 = array_merge($toolbar1, explode($separator, $custom_button)); | |
} | |
// Merge the two toolbars for backwards compatibility | |
$toolbar = array_merge($toolbar1, $toolbar2); | |
// Build the final options set | |
$scriptOptions = array_merge( | |
$scriptOptions, | |
array( | |
'suffix' => '.min', | |
'baseURL' => Uri::root(true) . '/media/vendor/tinymce', | |
'directionality' => $text_direction, | |
'language' => $langPrefix, | |
'autosave_restore_when_empty' => false, | |
'skin' => $skin, | |
'theme' => $theme, | |
'schema' => 'html5', | |
// Toolbars | |
'menubar' => empty($menubar) ? false : implode(' ', array_unique($menubar)), | |
'toolbar' => empty($toolbar) ? null : 'jxtdbuttons ' . implode(' ', $toolbar), | |
'plugins' => implode(',', array_unique($plugins)), | |
// Cleanup/Output | |
'inline_styles' => true, | |
'gecko_spellcheck' => true, | |
'entity_encoding' => $levelParams->get('entity_encoding', 'raw'), | |
'verify_html' => !$ignore_filter, | |
'valid_elements' => $valid_elements, | |
'extended_valid_elements' => implode(',', $elements), | |
'invalid_elements' => $invalid_elements, | |
// URL | |
'relative_urls' => (bool) $levelParams->get('relative_urls', true), | |
'remove_script_host' => false, | |
// Layout | |
'content_css' => $content_css, | |
'document_base_url' => Uri::root(true) . '/', | |
'paste_data_images' => $allowImgPaste, | |
'importcss_append' => true, | |
'image_title' => true, | |
'height' => $html_height, | |
'width' => $html_width, | |
'elementpath' => (bool) $levelParams->get('element_path', true), | |
'resize' => $resizing, | |
'templates' => $templates, | |
'image_advtab' => (bool) $levelParams->get('image_advtab', false), | |
'external_plugins' => empty($externalPlugins) ? null : $externalPlugins, | |
'contextmenu' => (bool) $levelParams->get('contextmenu', true) ? null : false, | |
'toolbar_sticky' => true, | |
'toolbar_mode' => 'sliding', | |
// Drag and drop specific | |
'dndEnabled' => $dragdrop, | |
// Disable TinyMCE Branding | |
'branding' => false, | |
) | |
); | |
if ($levelParams->get('newlines')) | |
{ | |
// Break | |
$scriptOptions['force_br_newlines'] = true; | |
$scriptOptions['force_p_newlines'] = false; | |
$scriptOptions['forced_root_block'] = ''; | |
} | |
else | |
{ | |
// Paragraph | |
$scriptOptions['force_br_newlines'] = false; | |
$scriptOptions['force_p_newlines'] = true; | |
$scriptOptions['forced_root_block'] = 'p'; | |
} | |
$scriptOptions['rel_list'] = array( | |
array('title' => 'None', 'value' => ''), | |
array('title' => 'Alternate', 'value' => 'alternate'), | |
array('title' => 'Author', 'value' => 'author'), | |
array('title' => 'Bookmark', 'value' => 'bookmark'), | |
array('title' => 'Help', 'value' => 'help'), | |
array('title' => 'License', 'value' => 'license'), | |
array('title' => 'Lightbox', 'value' => 'lightbox'), | |
array('title' => 'Next', 'value' => 'next'), | |
array('title' => 'No Follow', 'value' => 'nofollow'), | |
array('title' => 'No Referrer', 'value' => 'noreferrer'), | |
array('title' => 'Prefetch', 'value' => 'prefetch'), | |
array('title' => 'Prev', 'value' => 'prev'), | |
array('title' => 'Search', 'value' => 'search'), | |
array('title' => 'Tag', 'value' => 'tag'), | |
); | |
$options['tinyMCE']['default'] = $scriptOptions; | |
$doc->addScriptOptions('plg_editor_tinymce', $options); | |
return $editor; | |
} | |
/** | |
* Get the toggle editor button | |
* | |
* @param string $name Editor name | |
* | |
* @return string | |
*/ | |
private function _toogleButton($name) | |
{ | |
if (!$this->app->client->mobile) | |
{ | |
return LayoutHelper::render('joomla.tinymce.togglebutton', $name); | |
} | |
} | |
/** | |
* Get the XTD buttons and render them inside tinyMCE | |
* | |
* @param string $name the id of the editor field | |
* @param string $excluded the buttons that should be hidden | |
* | |
* @return array | |
*/ | |
private function tinyButtons($name, $excluded) | |
{ | |
// Get the available buttons | |
$buttonsEvent = new Event( | |
'getButtons', | |
[ | |
'editor' => $name, | |
'buttons' => $excluded, | |
] | |
); | |
$buttonsResult = $this->getDispatcher()->dispatch('getButtons', $buttonsEvent); | |
$buttons = $buttonsResult['result']; | |
if (is_array($buttons) || (is_bool($buttons) && $buttons)) | |
{ | |
Text::script('PLG_TINY_CORE_BUTTONS'); | |
// Init the arrays for the buttons | |
$btnsNames = []; | |
// Build the script | |
foreach ($buttons as $i => $button) | |
{ | |
$button->id = $name . '_' . $button->text . 'Modal'; | |
echo LayoutHelper::render('joomla.editors.buttons.modal', $button); | |
if ($button->get('name')) | |
{ | |
// Set some vars | |
$btnName = $button->get('text'); | |
$modalId = $name . '_' . str_replace(' ', '', $button->get('text')); | |
$onclick = $button->get('onclick') ?: null; | |
$icon = $button->get('name'); | |
if ($button->get('link') !== '#') | |
{ | |
$href = Uri::base() . $button->get('link'); | |
} | |
else | |
{ | |
$href = null; | |
} | |
$coreButton = []; | |
$coreButton['name'] = $btnName; | |
$coreButton['href'] = $href; | |
$coreButton['id'] = $modalId; | |
$coreButton['icon'] = $icon; | |
$coreButton['click'] = $onclick; | |
$coreButton['iconSVG'] = $button->get('iconSVG'); | |
// The array with the toolbar buttons | |
$btnsNames[] = $coreButton; | |
} | |
} | |
sort($btnsNames); | |
return ['names' => $btnsNames]; | |
} | |
} | |
/** | |
* Get the global text filters to arbitrary text as per settings for current user groups | |
* | |
* @return JFilterInput | |
* | |
* @since 3.6 | |
*/ | |
protected static function getGlobalFilters() | |
{ | |
// Filter settings | |
$config = ComponentHelper::getParams('com_config'); | |
$user = Factory::getUser(); | |
$userGroups = Access::getGroupsByUser($user->get('id')); | |
$filters = $config->get('filters'); | |
$blackListTags = array(); | |
$blackListAttributes = array(); | |
$customListTags = array(); | |
$customListAttributes = array(); | |
$whiteListTags = array(); | |
$whiteListAttributes = array(); | |
$whiteList = false; | |
$blackList = false; | |
$customList = false; | |
$unfiltered = false; | |
// Cycle through each of the user groups the user is in. | |
// Remember they are included in the public group as well. | |
foreach ($userGroups as $groupId) | |
{ | |
// May have added a group but not saved the filters. | |
if (!isset($filters->$groupId)) | |
{ | |
continue; | |
} | |
// Each group the user is in could have different filtering properties. | |
$filterData = $filters->$groupId; | |
$filterType = strtoupper($filterData->filter_type); | |
if ($filterType === 'NH') | |
{ | |
// Maximum HTML filtering. | |
} | |
elseif ($filterType === 'NONE') | |
{ | |
// No HTML filtering. | |
$unfiltered = true; | |
} | |
else | |
{ | |
// Blacklist or whitelist. | |
// Preprocess the tags and attributes. | |
$tags = explode(',', $filterData->filter_tags); | |
$attributes = explode(',', $filterData->filter_attributes); | |
$tempTags = []; | |
$tempAttributes = []; | |
foreach ($tags as $tag) | |
{ | |
$tag = trim($tag); | |
if ($tag) | |
{ | |
$tempTags[] = $tag; | |
} | |
} | |
foreach ($attributes as $attribute) | |
{ | |
$attribute = trim($attribute); | |
if ($attribute) | |
{ | |
$tempAttributes[] = $attribute; | |
} | |
} | |
// Collect the blacklist or whitelist tags and attributes. | |
// Each list is cumulative. | |
if ($filterType === 'BL') | |
{ | |
$blackList = true; | |
$blackListTags = array_merge($blackListTags, $tempTags); | |
$blackListAttributes = array_merge($blackListAttributes, $tempAttributes); | |
} | |
elseif ($filterType === 'CBL') | |
{ | |
// Only set to true if Tags or Attributes were added | |
if ($tempTags || $tempAttributes) | |
{ | |
$customList = true; | |
$customListTags = array_merge($customListTags, $tempTags); | |
$customListAttributes = array_merge($customListAttributes, $tempAttributes); | |
} | |
} | |
elseif ($filterType === 'WL') | |
{ | |
$whiteList = true; | |
$whiteListTags = array_merge($whiteListTags, $tempTags); | |
$whiteListAttributes = array_merge($whiteListAttributes, $tempAttributes); | |
} | |
} | |
} | |
// Remove duplicates before processing (because the blacklist uses both sets of arrays). | |
$blackListTags = array_unique($blackListTags); | |
$blackListAttributes = array_unique($blackListAttributes); | |
$customListTags = array_unique($customListTags); | |
$customListAttributes = array_unique($customListAttributes); | |
$whiteListTags = array_unique($whiteListTags); | |
$whiteListAttributes = array_unique($whiteListAttributes); | |
// Unfiltered assumes first priority. | |
if ($unfiltered) | |
{ | |
// Dont apply filtering. | |
return false; | |
} | |
else | |
{ | |
// Custom blacklist precedes Default blacklist | |
if ($customList) | |
{ | |
$filter = InputFilter::getInstance([], [], 1, 1); | |
// Override filter's default blacklist tags and attributes | |
if ($customListTags) | |
{ | |
$filter->tagBlacklist = $customListTags; | |
} | |
if ($customListAttributes) | |
{ | |
$filter->attrBlacklist = $customListAttributes; | |
} | |
} | |
// Blacklists take second precedence. | |
elseif ($blackList) | |
{ | |
// Remove the white-listed tags and attributes from the black-list. | |
$blackListTags = array_diff($blackListTags, $whiteListTags); | |
$blackListAttributes = array_diff($blackListAttributes, $whiteListAttributes); | |
$filter = InputFilter::getInstance($blackListTags, $blackListAttributes, 1, 1); | |
// Remove whitelisted tags from filter's default blacklist | |
if ($whiteListTags) | |
{ | |
$filter->tagBlacklist = array_diff($filter->tagBlacklist, $whiteListTags); | |
} | |
// Remove whitelisted attributes from filter's default blacklist | |
if ($whiteListAttributes) | |
{ | |
$filter->attrBlacklist = array_diff($filter->attrBlacklist, $whiteListAttributes); | |
} | |
} | |
// Whitelists take third precedence. | |
elseif ($whiteList) | |
{ | |
// Turn off XSS auto clean | |
$filter = InputFilter::getInstance($whiteListTags, $whiteListAttributes, 0, 0, 0); | |
} | |
// No HTML takes last place. | |
else | |
{ | |
$filter = InputFilter::getInstance(); | |
} | |
return $filter; | |
} | |
} | |
/** | |
* Return list of known TinyMCE buttons | |
* | |
* @return array | |
* | |
* @since 3.7.0 | |
*/ | |
public static function getKnownButtons() | |
{ | |
// See https://www.tinymce.com/docs/demo/full-featured/ | |
// And https://www.tinymce.com/docs/plugins/ | |
$buttons = [ | |
// General buttons | |
'|' => array('label' => Text::_('PLG_TINY_TOOLBAR_BUTTON_SEPARATOR'), 'text' => '|'), | |
'undo' => array('label' => 'Undo'), | |
'redo' => array('label' => 'Redo'), | |
'bold' => array('label' => 'Bold'), | |
'italic' => array('label' => 'Italic'), | |
'underline' => array('label' => 'Underline'), | |
'strikethrough' => array('label' => 'Strikethrough'), | |
'styleselect' => array('label' => Text::_('PLG_TINY_TOOLBAR_BUTTON_STYLESELECT'), 'text' => 'Formats'), | |
'formatselect' => array('label' => Text::_('PLG_TINY_TOOLBAR_BUTTON_FORMATSELECT'), 'text' => 'Paragraph'), | |
'fontselect' => array('label' => Text::_('PLG_TINY_TOOLBAR_BUTTON_FONTSELECT'), 'text' => 'Font Family'), | |
'fontsizeselect' => array('label' => Text::_('PLG_TINY_TOOLBAR_BUTTON_FONTSIZESELECT'), 'text' => 'Font Sizes'), | |
'alignleft' => array('label' => 'Align left'), | |
'aligncenter' => array('label' => 'Align center'), | |
'alignright' => array('label' => 'Align right'), | |
'alignjustify' => array('label' => 'Justify'), | |
'outdent' => array('label' => 'Decrease indent'), | |
'indent' => array('label' => 'Increase indent'), | |
'forecolor' => array('label' => 'Text colour'), | |
'backcolor' => array('label' => 'Background text colour'), | |
'bullist' => array('label' => 'Bullet list'), | |
'numlist' => array('label' => 'Numbered list'), | |
'link' => array('label' => 'Insert/edit link', 'plugin' => 'link'), | |
'unlink' => array('label' => 'Remove link', 'plugin' => 'link'), | |
'subscript' => array('label' => 'Subscript'), | |
'superscript' => array('label' => 'Superscript'), | |
'blockquote' => array('label' => 'Blockquote'), | |
'cut' => array('label' => 'Cut'), | |
'copy' => array('label' => 'Copy'), | |
'paste' => array('label' => 'Paste', 'plugin' => 'paste'), | |
'pastetext' => array('label' => 'Paste as text', 'plugin' => 'paste'), | |
'removeformat' => array('label' => 'Clear formatting'), | |
// Buttons from the plugins | |
'anchor' => array('label' => 'Anchor', 'plugin' => 'anchor'), | |
'hr' => array('label' => 'Horizontal line', 'plugin' => 'hr'), | |
'ltr' => array('label' => 'Left to right', 'plugin' => 'directionality'), | |
'rtl' => array('label' => 'Right to left', 'plugin' => 'directionality'), | |
'code' => array('label' => 'Source code', 'plugin' => 'code'), | |
'codesample' => array('label' => 'Insert/Edit code sample', 'plugin' => 'codesample'), | |
'table' => array('label' => 'Table', 'plugin' => 'table'), | |
'charmap' => array('label' => 'Special character', 'plugin' => 'charmap'), | |
'visualchars' => array('label' => 'Show invisible characters', 'plugin' => 'visualchars'), | |
'visualblocks' => array('label' => 'Show blocks', 'plugin' => 'visualblocks'), | |
'nonbreaking' => array('label' => 'Nonbreaking space', 'plugin' => 'nonbreaking'), | |
'emoticons' => array('label' => 'Emoticons', 'plugin' => 'emoticons'), | |
'media' => array('label' => 'Insert/edit video', 'plugin' => 'media'), | |
'image' => array('label' => 'Insert/edit image', 'plugin' => 'image'), | |
'pagebreak' => array('label' => 'Page break', 'plugin' => 'pagebreak'), | |
'print' => array('label' => 'Print', 'plugin' => 'print'), | |
'preview' => array('label' => 'Preview', 'plugin' => 'preview'), | |
'fullscreen' => array('label' => 'Fullscreen', 'plugin' => 'fullscreen'), | |
'template' => array('label' => 'Insert template', 'plugin' => 'template'), | |
'searchreplace' => array('label' => 'Find and replace', 'plugin' => 'searchreplace'), | |
'insertdatetime' => array('label' => 'Insert date/time', 'plugin' => 'insertdatetime'), | |
// 'spellchecker' => array('label' => 'Spellcheck', 'plugin' => 'spellchecker'), | |
]; | |
return $buttons; | |
} | |
/** | |
* Return toolbar presets | |
* | |
* @return array | |
* | |
* @since 3.7.0 | |
*/ | |
public static function getToolbarPreset() | |
{ | |
$preset = []; | |
$preset['simple'] = [ | |
'menu' => [], | |
'toolbar' => [ | |
'bold', 'underline', 'strikethrough', '|', | |
'undo', 'redo', '|', | |
'bullist', 'numlist', '|', | |
'pastetext', 'jxtdbuttons' | |
], | |
'toolbar2' => [], | |
]; | |
$preset['medium'] = array( | |
'menu' => array('edit', 'insert', 'view', 'format', 'table', 'tools'), | |
'toolbar1' => array( | |
'bold', 'italic', 'underline', 'strikethrough', '|', | |
'alignleft', 'aligncenter', 'alignright', 'alignjustify', '|', | |
'formatselect', '|', | |
'bullist', 'numlist', '|', | |
'outdent', 'indent', '|', | |
'undo', 'redo', '|', | |
'link', 'unlink', 'anchor', 'code', '|', | |
'hr', 'table', '|', | |
'subscript', 'superscript', '|', | |
'charmap', 'pastetext', 'preview', 'jxtdbuttons' | |
), | |
'toolbar2' => array(), | |
); | |
$preset['advanced'] = array( | |
'menu' => array('edit', 'insert', 'view', 'format', 'table', 'tools'), | |
'toolbar1' => array( | |
'bold', 'italic', 'underline', 'strikethrough', '|', | |
'alignleft', 'aligncenter', 'alignright', 'alignjustify', '|', | |
'styleselect', '|', | |
'formatselect', 'fontselect', 'fontsizeselect', '|', | |
'searchreplace', '|', | |
'bullist', 'numlist', '|', | |
'outdent', 'indent', '|', | |
'undo', 'redo', '|', | |
'link', 'unlink', 'anchor', 'image', '|', | |
'code', '|', | |
'forecolor', 'backcolor', '|', | |
'fullscreen', '|', | |
'table', '|', | |
'subscript', 'superscript', '|', | |
'charmap', 'emoticons', 'media', 'hr', 'ltr', 'rtl', '|', | |
'cut', 'copy', 'paste', 'pastetext', '|', | |
'visualchars', 'visualblocks', 'nonbreaking', 'blockquote', 'template', '|', | |
'print', 'preview', 'codesample', 'insertdatetime', 'removeformat', 'jxtdbuttons' | |
), | |
'toolbar2' => array(), | |
); | |
return $preset; | |
} | |
/** | |
* Gets the plugin extension id. | |
* | |
* @return int The plugin id. | |
* | |
* @since 3.7.0 | |
*/ | |
private function getPluginId() | |
{ | |
$db = Factory::getDbo(); | |
$query = $db->getQuery(true) | |
->select($db->quoteName('extension_id')) | |
->from($db->quoteName('#__extensions')) | |
->where($db->quoteName('folder') . ' = :folder') | |
->where($db->quoteName('element') . ' = :element') | |
->bind(':folder', $this->_type) | |
->bind(':element', $this->_name); | |
$db->setQuery($query); | |
return (int) $db->loadResult(); | |
} | |
/** | |
* Array helper funtion to remove specific arrays by key-value | |
* | |
* @param array $array the parent array | |
* @param string $key the key | |
* @param string $value the value | |
* | |
* @return array | |
*/ | |
private function removeElementWithValue($array, $key, $value) | |
{ | |
foreach ($array as $subKey => $subArray) | |
{ | |
if ($subArray[$key] == $value) | |
{ | |
unset($array[$subKey]); | |
} | |
} | |
return $array; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment