Skip to content

Instantly share code, notes, and snippets.

@dgrammatiko
Last active May 4, 2020 17:39
Show Gist options
  • Save dgrammatiko/1ffc213290914024817a106fc9f5f89c to your computer and use it in GitHub Desktop.
Save dgrammatiko/1ffc213290914024817a106fc9f5f89c to your computer and use it in GitHub Desktop.
Joomla 4 tinyMCE DnD using own handler
/**
* @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);
<?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