first commit

This commit is contained in:
2025-06-17 11:53:18 +02:00
commit 9f0f7ba12b
8804 changed files with 1369176 additions and 0 deletions

View File

@ -0,0 +1,168 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="editors" method="upgrade">
<name>plg_editors_codemirror</name>
<version>6.0.0</version>
<creationDate>28 March 2011</creationDate>
<author>Marijn Haverbeke</author>
<authorEmail>marijnh@gmail.com</authorEmail>
<authorUrl>https://codemirror.net/</authorUrl>
<copyright>Copyright (C) 2014 - 2021 by Marijn Haverbeke &lt;marijnh@gmail.com&gt; and others</copyright>
<license>MIT license: https://codemirror.net/LICENSE</license>
<description>PLG_CODEMIRROR_XML_DESCRIPTION</description>
<namespace path="src">Joomla\Plugin\Editors\CodeMirror</namespace>
<files>
<folder>layouts</folder>
<folder plugin="codemirror">services</folder>
<folder>src</folder>
</files>
<languages>
<language tag="en-GB">language/en-GB/plg_editors_codemirror.ini</language>
<language tag="en-GB">language/en-GB/plg_editors_codemirror.sys.ini</language>
</languages>
<config>
<fields name="params">
<fieldset name="basic">
<field
name="lineNumbers"
type="radio"
layout="joomla.form.field.radio.switcher"
label="PLG_CODEMIRROR_FIELD_LINENUMBERS_LABEL"
default="1"
filter="integer"
>
<option value="0">JOFF</option>
<option value="1">JON</option>
</field>
<field
name="codeFolding"
type="radio"
label="PLG_CODEMIRROR_FIELD_CODEFOLDING_LABEL"
layout="joomla.form.field.radio.switcher"
default="1"
filter="integer"
>
<option value="0">JOFF</option>
<option value="1">JON</option>
</field>
<field
name="lineWrapping"
type="radio"
label="PLG_CODEMIRROR_FIELD_LINEWRAPPING_LABEL"
layout="joomla.form.field.radio.switcher"
default="1"
filter="integer"
>
<option value="0">JOFF</option>
<option value="1">JON</option>
</field>
<field
name="activeLine"
type="radio"
label="PLG_CODEMIRROR_FIELD_ACTIVELINE_LABEL"
layout="joomla.form.field.radio.switcher"
default="1"
filter="integer"
>
<option value="0">JOFF</option>
<option value="1">JON</option>
</field>
<field
name="selectionMatches"
type="radio"
layout="joomla.form.field.radio.switcher"
label="PLG_CODEMIRROR_FIELD_SELECTIONMATCHES_LABEL"
default="1"
filter="integer"
>
<option value="0">JOFF</option>
<option value="1">JON</option>
</field>
<field
name="autoCloseBrackets"
type="radio"
layout="joomla.form.field.radio.switcher"
label="PLG_CODEMIRROR_FIELD_AUTOCLOSEBRACKET_LABEL"
default="1"
filter="integer"
>
<option value="0">JOFF</option>
<option value="1">JON</option>
</field>
<field
name="keyMap"
type="list"
label="PLG_CODEMIRROR_FIELD_KEYMAP_LABEL"
description="PLG_CODEMIRROR_FIELD_KEYMAP_DESC"
default=""
validate="options"
>
<option value="">JDEFAULT</option>
<option value="emacs">PLG_CODEMIRROR_FIELD_KEYMAP_EMACS</option>
</field>
<field
name="fullScreen"
type="list"
label="PLG_CODEMIRROR_FIELD_FULLSCREEN_LABEL"
default="F10"
validate="options"
>
<option value="F1">F1</option>
<option value="F2">F2</option>
<option value="F3">F3</option>
<option value="F4">F4</option>
<option value="F5">F5</option>
<option value="F6">F6</option>
<option value="F7">F7</option>
<option value="F8">F8</option>
<option value="F9">F9</option>
<option value="F10">F10</option>
<option value="F11">F11</option>
<option value="F12">F12</option>
</field>
<field
name="fullScreenMod"
type="checkboxes"
label="PLG_CODEMIRROR_FIELD_FULLSCREEN_MOD_LABEL"
>
<option value="Shift">PLG_CODEMIRROR_FIELD_VALUE_FULLSCREEN_MOD_SHIFT</option>
<option value="Cmd">PLG_CODEMIRROR_FIELD_VALUE_FULLSCREEN_MOD_CMD</option>
<option value="Ctrl">PLG_CODEMIRROR_FIELD_VALUE_FULLSCREEN_MOD_CTRL</option>
<option value="Alt">PLG_CODEMIRROR_FIELD_VALUE_FULLSCREEN_MOD_ALT</option>
</field>
</fieldset>
<fieldset name="advanced">
<field
name="customExtensions"
type="subform"
label="PLG_CODEMIRROR_FIELD_CUSTOM_EXTENSIONS_LABEL"
multiple="true"
>
<form>
<field
name="module"
type="text"
label="PLG_CODEMIRROR_FIELD_CUSTOM_EXTENSIONS_MODULE_LABEL"
description="PLG_CODEMIRROR_FIELD_CUSTOM_EXTENSIONS_MODULE_DESCR"
/>
<field
name="methods"
type="text"
label="PLG_CODEMIRROR_FIELD_CUSTOM_EXTENSIONS_METHOD_LABEL"
description="PLG_CODEMIRROR_FIELD_CUSTOM_EXTENSIONS_METHOD_DESCR"
/>
</form>
</field>
</fieldset>
</fields>
</config>
</extension>

View File

@ -0,0 +1,66 @@
<?php
/**
* @package Joomla.Plugin
* @subpackage Editors.codemirror
*
* @copyright (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
// No direct access
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\Registry\Registry;
extract($displayData);
/**
* Layout variables
*
* @var array $options JS options for editor
* @var Registry $params Plugin parameters
* @var string $id The id of the input
* @var string $name The name of the input
* @var integer $cols Textarea cols attribute
* @var integer $rows Textarea rows attribute
* @var string $content The value
* @var string $buttons Editor XTD buttons
*/
$option = ' options="' . $this->escape(json_encode($options)) . '"';
$style = '';
if ($options->width) {
$style .= 'width:' . $options->width . ';';
}
if ($options->height) {
$style .= 'height:' . $options->height . ';';
}
// Fullscreen combo
$fsCombo = '';
if (empty($options->readOnly)) {
$fskeys = $params->get('fullScreenMod', []);
$fskeys[] = $params->get('fullScreen', 'F10');
$fullScreenCombo = implode('-', $fskeys);
$fsCombo = ' fs-combo=' . $this->escape($fullScreenCombo);
}
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->getRegistry()->addExtensionRegistryFile('plg_editors_codemirror');
$wa->useStyle('plg_editors_codemirror')
->useScript('webcomponent.editor-codemirror');
?>
<joomla-editor-codemirror <?php echo $fsCombo . $option; ?>>
<?php echo '<textarea name="' . $name . '" id="' . $id . '" cols="' . $cols . '" rows="' . $rows . '" style="' . $style . '">' . $content . '</textarea>'; ?>
<?php if ($fsCombo) : ?>
<p class="small float-end">
<?php echo Text::sprintf('PLG_CODEMIRROR_TOGGLE_FULL_SCREEN', $fullScreenCombo); ?>
</p>
<?php endif; ?>
<?php echo $buttons ?? ''; ?>
</joomla-editor-codemirror>

View File

@ -0,0 +1,46 @@
<?php
/**
* @package Joomla.Plugin
* @subpackage Editors.codemirror
*
* @copyright (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
\defined('_JEXEC') or die;
use Joomla\CMS\Extension\PluginInterface;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Joomla\Event\DispatcherInterface;
use Joomla\Plugin\Editors\CodeMirror\Extension\Codemirror;
return new class () implements ServiceProviderInterface {
/**
* Registers the service provider with a DI container.
*
* @param Container $container The DI container.
*
* @return void
*
* @since 4.3.0
*/
public function register(Container $container)
{
$container->set(
PluginInterface::class,
function (Container $container) {
$plugin = new Codemirror(
$container->get(DispatcherInterface::class),
(array) PluginHelper::getPlugin('editors', 'codemirror')
);
$plugin->setApplication(Factory::getApplication());
return $plugin;
}
);
}
};

View File

@ -0,0 +1,57 @@
<?php
/**
* @package Joomla.Plugin
* @subpackage Editors.codemirror
*
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Plugin\Editors\CodeMirror\Extension;
use Joomla\CMS\Event\Editor\EditorSetupEvent;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Event\SubscriberInterface;
use Joomla\Plugin\Editors\CodeMirror\Provider\CodeMirrorProvider;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* CodeMirror Editor Plugin.
*
* @since 1.6
*/
final class Codemirror extends CMSPlugin implements SubscriberInterface
{
/**
* Returns an array of events this subscriber will listen to.
*
* @return array
*
* @since 5.0.0
*/
public static function getSubscribedEvents(): array
{
return ['onEditorSetup' => 'onEditorSetup'];
}
/**
* Register Editor instance
*
* @param EditorSetupEvent $event
*
* @return void
*
* @since 5.0.0
*/
public function onEditorSetup(EditorSetupEvent $event)
{
$this->loadLanguage();
$event->getEditorsRegistry()
->add(new CodeMirrorProvider($this->params, $this->getApplication(), $this->getDispatcher()));
}
}

View File

@ -0,0 +1,165 @@
<?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Plugin\Editors\CodeMirror\Provider;
use Joomla\CMS\Application\CMSApplicationInterface;
use Joomla\CMS\Editor\AbstractEditorProvider;
use Joomla\CMS\Layout\LayoutHelper;
use Joomla\CMS\Uri\Uri;
use Joomla\Event\DispatcherInterface;
use Joomla\Registry\Registry;
/**
* Editor provider class
*
* @since 5.0.0
*/
final class CodeMirrorProvider extends AbstractEditorProvider
{
/**
* A Registry object holding the parameters for the plugin
*
* @var Registry
* @since 5.0.0
*/
protected $params;
/**
* The application object
*
* @var CMSApplicationInterface
*
* @since 5.0.0
*/
protected $application;
/**
* Class constructor
*
* @param Registry $params
* @param CMSApplicationInterface $application
* @param DispatcherInterface $dispatcher
*
* @since 5.0.0
*/
public function __construct(Registry $params, CMSApplicationInterface $application, DispatcherInterface $dispatcher)
{
$this->params = $params;
$this->application = $application;
$this->setDispatcher($dispatcher);
}
/**
* Return Editor name, CMD string.
*
* @return string
* @since 5.0.0
*/
public function getName(): string
{
return 'codemirror';
}
/**
* Gets the editor HTML markup
*
* @param string $name Input name.
* @param string $content The content of the field.
* @param array $attributes Associative array of editor attributes.
* @param array $params Associative array of editor parameters.
*
* @return string The HTML markup of the editor
*
* @since 5.0.0
*/
public function display(string $name, string $content = '', array $attributes = [], array $params = []): string
{
$col = $attributes['col'] ?? '';
$row = $attributes['row'] ?? '';
$width = $attributes['width'] ?? '';
$height = $attributes['height'] ?? '';
$id = $attributes['id'] ?? '';
$buttons = $params['buttons'] ?? true;
$asset = $params['asset'] ?? 0;
$author = $params['author'] ?? 0;
// Must pass the field id to the buttons in this editor.
$buttonsStr = $this->displayButtons($buttons, ['asset' => $asset, 'author' => $author, 'editorId' => $id]);
// Options for the CodeMirror constructor.
$options = new \stdClass();
// Is field readonly?
if (!empty($params['readonly'])) {
$options->readOnly = true;
}
// Only add "px" to width and height if they are not given as a percentage.
$options->width = is_numeric($width) ? $width . 'px' : $width;
$options->height = is_numeric($height) ? $height . 'px' : $height;
$options->lineNumbers = (bool) $this->params->get('lineNumbers', 1);
$options->foldGutter = (bool) $this->params->get('codeFolding', 1);
$options->lineWrapping = (bool) $this->params->get('lineWrapping', 1);
$options->activeLine = (bool) $this->params->get('activeLine', 1);
$options->highlightSelection = (bool) $this->params->get('selectionMatches', 1);
// Load the syntax mode.
$modeAlias = [
'scss' => 'css',
'sass' => 'css',
'less' => 'css',
];
$options->mode = !empty($params['syntax']) ? $params['syntax'] : $this->params->get('syntax', 'html');
$options->mode = $modeAlias[$options->mode] ?? $options->mode;
// Special options for non-tagged modes.
if (!\in_array($options->mode, ['xml', 'html'])) {
// Autogenerate closing brackets.
$options->autoCloseBrackets = (bool) $this->params->get('autoCloseBrackets', 1);
}
// KeyMap settings.
$options->keyMap = $this->params->get('keyMap', '');
// Check for custom extensions
$customExtensions = $this->params->get('customExtensions', []);
$options->customExtensions = [];
if ($customExtensions) {
foreach ($customExtensions as $item) {
$methods = array_filter(array_map('trim', explode(',', $item->methods ?? '')));
if (empty($item->module) || !$methods) {
continue;
}
// Prepend root path if we have a file
$module = str_ends_with($item->module, '.js') ? Uri::root(true) . '/' . $item->module : $item->module;
$options->customExtensions[] = [$module, $methods];
}
}
$displayData = [
'options' => $options,
'params' => $this->params,
'name' => $name,
'id' => $id,
'cols' => $col,
'rows' => $row,
'content' => $content,
'buttons' => $buttonsStr,
];
return LayoutHelper::render('editors.codemirror.codemirror', $displayData, JPATH_PLUGINS . '/editors/codemirror/layouts');
}
}

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="editors" method="upgrade">
<name>plg_editors_none</name>
<version>3.0.0</version>
<creationDate>2005-09</creationDate>
<author>Joomla! Project</author>
<authorEmail>admin@joomla.org</authorEmail>
<authorUrl>www.joomla.org</authorUrl>
<copyright>(C) 2005 Open Source Matters, Inc.</copyright>
<license>GNU General Public License version 2 or later; see LICENSE.txt</license>
<description>PLG_NONE_XML_DESCRIPTION</description>
<namespace path="src">Joomla\Plugin\Editors\None</namespace>
<files>
<folder plugin="none">services</folder>
<folder>src</folder>
</files>
<languages>
<language tag="en-GB">language/en-GB/plg_editors_none.ini</language>
<language tag="en-GB">language/en-GB/plg_editors_none.sys.ini</language>
</languages>
</extension>

View File

@ -0,0 +1,46 @@
<?php
/**
* @package Joomla.Plugin
* @subpackage Editors.none
*
* @copyright (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
\defined('_JEXEC') or die;
use Joomla\CMS\Extension\PluginInterface;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Joomla\Event\DispatcherInterface;
use Joomla\Plugin\Editors\None\Extension\None;
return new class () implements ServiceProviderInterface {
/**
* Registers the service provider with a DI container.
*
* @param Container $container The DI container.
*
* @return void
*
* @since 4.3.0
*/
public function register(Container $container)
{
$container->set(
PluginInterface::class,
function (Container $container) {
$plugin = new None(
$container->get(DispatcherInterface::class),
(array) PluginHelper::getPlugin('editors', 'none')
);
$plugin->setApplication(Factory::getApplication());
return $plugin;
}
);
}
};

View File

@ -0,0 +1,116 @@
<?php
/**
* @package Joomla.Plugin
* @subpackage Editors.none
*
* @copyright (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Plugin\Editors\None\Extension;
use Joomla\CMS\Layout\LayoutHelper;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Event\Event;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Plain Textarea Editor Plugin
*
* @since 1.5
*/
final class None extends CMSPlugin
{
/**
* Display the editor area.
*
* @param string $name The control name.
* @param string $content The contents of the text area.
* @param string $width The width of the text area (px or %).
* @param string $height The height of the text area (px or %).
* @param integer $col The number of columns for the textarea.
* @param integer $row The number of rows for the textarea.
* @param boolean $buttons True and the editor buttons will be displayed.
* @param string $id An optional ID for the textarea (note: since 1.6). 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 = []
) {
if (empty($id)) {
$id = $name;
}
// 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';
}
$readonly = !empty($params['readonly']) ? ' readonly disabled' : '';
$this->getApplication()->getDocument()->getWebAssetManager()
->registerAndUseScript(
'webcomponent.editor-none',
'plg_editors_none/joomla-editor-none.min.js',
[],
['type' => 'module'],
['editors']
);
return '<joomla-editor-none>'
. '<textarea name="' . $name . '" id="' . $id . '" cols="' . $col . '" rows="' . $row
. '" style="width: ' . $width . '; height: ' . $height . ';"' . $readonly . '>' . $content . '</textarea>'
. $this->displayButtons($id, $buttons, $asset, $author)
. '</joomla-editor-none>';
}
/**
* Displays the editor buttons.
*
* @param string $name The control name.
* @param mixed $buttons [array with button objects | boolean true to display buttons]
* @param string $asset The object asset
* @param object $author The author.
*
* @return void|string HTML
*/
private function displayButtons($name, $buttons, $asset, $author)
{
if (\is_array($buttons) || (\is_bool($buttons) && $buttons)) {
$buttonsEvent = new Event(
'getButtons',
[
'editor' => $name,
'buttons' => $buttons,
]
);
$buttonsResult = $this->getDispatcher()->dispatch('getButtons', $buttonsEvent);
$buttons = $buttonsResult['result'];
return LayoutHelper::render('joomla.editors.buttons', $buttons);
}
}
}

View File

@ -0,0 +1,387 @@
<?xml version="1.0" encoding="UTF-8"?>
<form>
<field
name="access"
type="usergrouplist"
label="PLG_TINY_FIELD_SETACCESS_LABEL"
multiple="true"
class="access-select"
labelclass="h3"
layout="joomla.form.field.list-fancy-select"
/>
<fieldset name="basic">
<field
name="skins"
type="note"
label="PLG_TINY_FIELD_SKIN_INFO_LABEL"
description="PLG_TINY_FIELD_SKIN_INFO_DESC"
/>
<field
name="skin"
type="folderlist"
label="PLG_TINY_FIELD_SKIN_LABEL"
directory="media/vendor/tinymce/skins/ui"
recursive="false"
hide_none="true"
validate="options"
/>
<field
name="skin_admin"
type="folderlist"
label="PLG_TINY_FIELD_SKIN_ADMIN_LABEL"
directory="media/vendor/tinymce/skins/ui"
recursive="false"
hide_none="true"
validate="options"
/>
<field
name="toolbar_mode"
type="list"
label="PLG_TINY_FIELD_TOOLBAR_MODE_LABEL"
default="sliding"
validate="options"
>
<option value="floating">PLG_TINY_FIELD_VALUE_FLOATING</option>
<option value="scrolling">PLG_TINY_FIELD_VALUE_SCROLLING</option>
<option value="sliding">PLG_TINY_FIELD_VALUE_SLIDING</option>
<option value="wrap">PLG_TINY_FIELD_VALUE_WRAP</option>
</field>
<field
name="drag_drop"
type="radio"
label="PLG_TINY_FIELD_DRAG_DROP_LABEL"
layout="joomla.form.field.radio.switcher"
default="1"
>
<option value="0">JOFF</option>
<option value="1">JON</option>
</field>
<field
name="path"
type="uploaddirs"
label="PLG_TINY_FIELD_CUSTOM_PATH_LABEL"
showon="drag_drop:1"
/>
<field
name="content_template_path"
type="templateslist"
label="PLG_TINY_FIELD_CUSTOM_CONTENT_TEMPLATE_PATH_LABEL"
description="PLG_TINY_FIELD_CUSTOM_CONTENT_TEMPLATE_PATH_DESC"
default=""
folderFilter="^tinymce"
validate="options"
/>
<field
name="entity_encoding"
type="list"
label="PLG_TINY_FIELD_ENCODING_LABEL"
default="raw"
validate="options"
>
<option value="named">PLG_TINY_FIELD_VALUE_NAMED</option>
<option value="numeric">PLG_TINY_FIELD_VALUE_NUMERIC</option>
<option value="raw">PLG_TINY_FIELD_VALUE_RAW</option>
</field>
<field
name="lang_mode"
type="radio"
label="PLG_TINY_FIELD_LANGSELECT_LABEL"
layout="joomla.form.field.radio.switcher"
default="1"
>
<option value="0">JOFF</option>
<option value="1">JON</option>
</field>
<field
name="lang_code"
type="filelist"
label="PLG_TINY_FIELD_LANGCODE_LABEL"
stripext="1"
directory="media/vendor/tinymce/langs/"
hide_none="1"
hide_default="1"
fileFilter="\.js$"
exclude="\.min\.js$"
showon="lang_mode:0"
validate="options"
>
<option value="">en</option>
</field>
<field
name="text_direction"
type="list"
label="PLG_TINY_FIELD_DIRECTION_LABEL"
default="ltr"
validate="options"
>
<option value="ltr">PLG_TINY_FIELD_VALUE_LTR</option>
<option value="rtl">PLG_TINY_FIELD_VALUE_RTL</option>
</field>
<field
name="content_css"
type="radio"
label="PLG_TINY_FIELD_CSS_LABEL"
description="PLG_TINY_FIELD_CSS_DESC"
layout="joomla.form.field.radio.switcher"
default="1"
>
<option value="0">JOFF</option>
<option value="1">JON</option>
</field>
<field
name="content_css_custom"
type="text"
label="PLG_TINY_FIELD_CUSTOM_CSS_LABEL"
description="PLG_TINY_FIELD_CUSTOM_CSS_DESC"
/>
<field
name="relative_urls"
type="list"
label="PLG_TINY_FIELD_URLS_LABEL"
default="1"
validate="options"
>
<option value="0">PLG_TINY_FIELD_VALUE_ABSOLUTE</option>
<option value="1">PLG_TINY_FIELD_VALUE_RELATIVE</option>
</field>
<field
name="newlines"
type="list"
label="PLG_TINY_FIELD_NEWLINES_LABEL"
default="0"
validate="options"
>
<option value="1">PLG_TINY_FIELD_VALUE_BR</option>
<option value="0">PLG_TINY_FIELD_VALUE_P</option>
</field>
<field
name="use_config_textfilters"
type="radio"
label="PLG_TINY_CONFIG_TEXTFILTER_ACL_LABEL"
layout="joomla.form.field.radio.switcher"
default="0"
>
<option value="0">JOFF</option>
<option value="1">JON</option>
</field>
<field
name="invalid_elements"
type="text"
label="PLG_TINY_FIELD_PROHIBITED_LABEL"
showon="use_config_textfilters:0"
default="script,applet,iframe"
/>
<field
name="valid_elements"
type="text"
label="PLG_TINY_FIELD_VALIDELEMENTS_LABEL"
description="PLG_TINY_FIELD_VALIDELEMENTS_DESC"
showon="use_config_textfilters:0"
/>
<field
name="extended_elements"
type="text"
label="PLG_TINY_FIELD_ELEMENTS_LABEL"
showon="use_config_textfilters:0"
/>
<!-- Extra plugins -->
<field
name="resizing"
type="radio"
label="PLG_TINY_FIELD_RESIZING_LABEL"
layout="joomla.form.field.radio.switcher"
default="1"
>
<option value="0">JOFF</option>
<option value="1">JON</option>
</field>
<field
name="resize_horizontal"
type="radio"
label="PLG_TINY_FIELD_RESIZE_HORIZONTAL_LABEL"
layout="joomla.form.field.radio.switcher"
default="1"
showon="resizing:1"
>
<option value="0">JOFF</option>
<option value="1">JON</option>
</field>
<field
name="element_path"
type="radio"
label="PLG_TINY_FIELD_PATH_LABEL"
layout="joomla.form.field.radio.switcher"
default="0"
>
<option value="0">JOFF</option>
<option value="1">JON</option>
</field>
<field
name="wordcount"
type="radio"
label="PLG_TINY_FIELD_WORDCOUNT_LABEL"
layout="joomla.form.field.radio.switcher"
default="1"
>
<option value="0">JOFF</option>
<option value="1">JON</option>
</field>
<field
name="textpattern"
type="radio"
label="PLG_TINY_FIELD_TEXTPATTERN_LABEL"
description="PLG_TINY_FIELD_TEXTPATTERN_DESC"
layout="joomla.form.field.radio.switcher"
default="0"
>
<option value="0">JOFF</option>
<option value="1">JON</option>
</field>
<field
name="image_advtab"
type="radio"
label="PLG_TINY_FIELD_ADVIMAGE_LABEL"
layout="joomla.form.field.radio.switcher"
default="1"
>
<option value="0">JOFF</option>
<option value="1">JON</option>
</field>
<field
name="advlist"
type="radio"
label="PLG_TINY_FIELD_ADVLIST_LABEL"
layout="joomla.form.field.radio.switcher"
default="1"
>
<option value="0">JOFF</option>
<option value="1">JON</option>
</field>
<field
name="contextmenu"
type="radio"
label="PLG_TINY_FIELD_CONTEXTMENU_LABEL"
layout="joomla.form.field.radio.switcher"
default="1"
>
<option value="0">JOFF</option>
<option value="1">JON</option>
</field>
<field
name="paste_as_text"
type="radio"
label="PLG_TINY_FIELD_PASTE_AS_TEXT_LABEL"
layout="joomla.form.field.radio.switcher"
default="0"
>
<option value="0">JOFF</option>
<option value="1">JON</option>
</field>
<field
name="sourcecode"
type="radio"
label="PLG_TINY_FIELD_SOURCECODE_LABEL"
layout="joomla.form.field.radio.switcher"
default="1"
>
<option value="0">JOFF</option>
<option value="1">JON</option>
</field>
<field
name="content_languages"
type="subform"
label="PLG_TINY_FIELD_CONTENTLANGS_LABEL"
description="PLG_TINY_FIELD_CONTENTLANGS_DESC"
layout="joomla.form.field.subform.repeatable-table"
icon="list"
multiple="true"
default=''
>
<form hidden="true" name="content_languages" repeat="true">
<field
name="content_language_name"
type="text"
label="PLG_TINY_FIELD_CONTENTLANGUAGE_LABEL"
size="30"
filter="string"
/>
<field
name="content_language_code"
type="text"
label="PLG_TINY_FIELD_CONTENTCODE_LABEL"
size="10"
filter="string"
/>
</form>
</field>
<field
name="custom_plugin"
type="text"
label="PLG_TINY_FIELD_CUSTOMPLUGIN_LABEL"
/>
<field
name="custom_button"
type="text"
label="PLG_TINY_FIELD_CUSTOMBUTTON_LABEL"
/>
<field
name="external_plugins"
type="subform"
label="PLG_TINY_FIELD_EXTERNALPLUGINS_LABEL"
description="PLG_TINY_FIELD_EXTERNALPLUGINS_DESC"
layout="joomla.form.field.subform.repeatable-table"
icon="list"
multiple="true"
>
<form hidden="true" name="list_templates_modal" repeat="true">
<field
name="name"
type="text"
label="PLG_TINY_FIELD_EXTERNALPLUGINS_NAME_LABEL"
/>
<field
name="path"
type="text"
label="PLG_TINY_FIELD_EXTERNALPLUGINS_PATH_LABEL"
/>
</form>
</field>
</fieldset>
</form>

View File

@ -0,0 +1,48 @@
<?php
/**
* @package Joomla.Plugin
* @subpackage Editors.tinymce
*
* @copyright (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
\defined('_JEXEC') or die;
use Joomla\CMS\Extension\PluginInterface;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\Database\DatabaseInterface;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Joomla\Event\DispatcherInterface;
use Joomla\Plugin\Editors\TinyMCE\Extension\TinyMCE;
return new class () implements ServiceProviderInterface {
/**
* Registers the service provider with a DI container.
*
* @param Container $container The DI container.
*
* @return void
*
* @since 4.3.0
*/
public function register(Container $container)
{
$container->set(
PluginInterface::class,
function (Container $container) {
$plugin = new TinyMCE(
$container->get(DispatcherInterface::class),
(array) PluginHelper::getPlugin('editors', 'tinymce')
);
$plugin->setApplication(Factory::getApplication());
$plugin->setDatabase($container->get(DatabaseInterface::class));
return $plugin;
}
);
}
};

View File

@ -0,0 +1,118 @@
<?php
/**
* @package Joomla.Plugin
* @subpackage Editors.tinymce
*
* @copyright (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Plugin\Editors\TinyMCE\Extension;
use Joomla\CMS\Event\Editor\EditorSetupEvent;
use Joomla\CMS\Filesystem\Folder;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Session\Session;
use Joomla\Database\DatabaseAwareTrait;
use Joomla\Event\SubscriberInterface;
use Joomla\Plugin\Editors\TinyMCE\PluginTraits\KnownButtons;
use Joomla\Plugin\Editors\TinyMCE\PluginTraits\ToolbarPresets;
use Joomla\Plugin\Editors\TinyMCE\Provider\TinyMCEProvider;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* TinyMCE Editor Plugin
*
* @since 1.5
*/
final class TinyMCE extends CMSPlugin implements SubscriberInterface
{
use DatabaseAwareTrait;
// @todo: KnownButtons, ToolbarPresets for backward compatibility. Remove in Joomla 6
use KnownButtons;
use ToolbarPresets;
/**
* Returns an array of events this subscriber will listen to.
*
* @return array
*
* @since 5.0.0
*/
public static function getSubscribedEvents(): array
{
return ['onEditorSetup' => 'onEditorSetup'];
}
/**
* Register Editor instance
*
* @param EditorSetupEvent $event
*
* @return void
*
* @since 5.0.0
*/
public function onEditorSetup(EditorSetupEvent $event)
{
$this->loadLanguage();
$event->getEditorsRegistry()
->add(new TinyMCEProvider($this->params, $this->getApplication(), $this->getDispatcher(), $this->getDatabase()));
}
/**
* Returns the templates
*
* @return void
*
* @since 5.0.0
*/
public function onAjaxTinymce()
{
if (!Session::checkToken('request')) {
echo json_encode([]);
exit();
}
$this->loadLanguage();
$templates = [];
$language = $this->getApplication()->getLanguage();
$template = $this->getApplication()->getInput()->getPath('template', '');
if ('' === $template) {
echo json_encode([]);
exit();
}
$filepaths = Folder::exists(JPATH_ROOT . '/templates/' . $template)
? Folder::files(JPATH_ROOT . '/templates/' . $template, '\.(html|txt)$', false, true)
: [];
foreach ($filepaths as $filepath) {
$fileinfo = pathinfo($filepath);
$filename = $fileinfo['filename'];
$title_upper = strtoupper($filename);
if ($filename === 'index') {
continue;
}
$templates[] = (object) [
'title' => $language->hasKey('PLG_TINY_TEMPLATE_' . $title_upper . '_TITLE') ? Text::_('PLG_TINY_TEMPLATE_' . $title_upper . '_TITLE') : $filename,
'description' => $language->hasKey('PLG_TINY_TEMPLATE_' . $title_upper . '_DESC') ? Text::_('PLG_TINY_TEMPLATE_' . $title_upper . '_DESC') : ' ',
'content' => file_get_contents($filepath),
];
}
echo json_encode($templates);
exit();
}
}

View File

@ -0,0 +1,95 @@
<?php
/**
* @package Joomla.Plugin
* @subpackage Editors.tinymce
*
* @copyright (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Plugin\Editors\TinyMCE\Field;
use Joomla\CMS\Form\Field\FolderlistField;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Generates the list of directories available for template snippets.
*
* @since 4.1.0
*/
class TemplatesListField extends FolderlistField
{
protected $type = 'templatesList';
/**
* Method to attach a JForm object to the field.
*
* @param \SimpleXMLElement $element The SimpleXMLElement object representing the `<field>` tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control value. This acts as an array container for the field.
* For example if the field has name="foo" and the group value is set to "bar" then the
* full field name would end up being "bar[foo]".
*
* @return boolean True on success.
*
* @see \Joomla\CMS\Form\FormField::setup()
* @since 4.1.0
*/
public function setup(\SimpleXMLElement $element, $value, $group = null)
{
$return = parent::setup($element, $value, $group);
// Set some defaults.
$this->recursive = true;
$this->hideDefault = true;
$this->exclude = 'system';
$this->hideNone = true;
return $return;
}
/**
* Method to get the directories options.
*
* @return array The dirs option objects.
*
* @since 4.1.0
*/
public function getOptions()
{
$def = new \stdClass();
$def->value = '';
$def->text = Text::_('JOPTION_DO_NOT_USE');
$options = [0 => $def];
$this->directory = JPATH_ROOT . '/templates';
return array_merge($options, parent::getOptions());
}
/**
* Method to get the field input markup for the list of directories.
*
* @return string The field input markup.
*
* @since 4.1.0
*/
protected function getInput()
{
return HTMLHelper::_(
'select.genericlist',
(array) $this->getOptions(),
$this->name,
'class="form-select"',
'value',
'text',
$this->value,
$this->id
);
}
}

View File

@ -0,0 +1,174 @@
<?php
/**
* @package Joomla.Plugin
* @subpackage Editors.tinymce
*
* @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Plugin\Editors\TinyMCE\Field;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormField;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\Plugin\Editors\TinyMCE\Provider\TinyMCEProvider;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Form Field class for the TinyMCE editor.
*
* @package Joomla.Plugin
* @subpackage Editors.tinymce
* @since 3.7.0
*/
class TinymcebuilderField extends FormField
{
/**
* The form field type.
*
* @var string
* @since 3.7.0
*/
protected $type = 'tinymcebuilder';
/**
* Name of the layout being used to render the field
*
* @var string
* @since 3.7.0
*/
protected $layout = 'plugins.editors.tinymce.field.tinymcebuilder';
/**
* The prepared layout data
*
* @var array
* @since 3.7.0
*/
protected $layoutData = [];
/**
* Method to get the data to be passed to the layout for rendering.
*
* @return array
*
* @since 3.7.0
*/
protected function getLayoutData()
{
if (!empty($this->layoutData)) {
return $this->layoutData;
}
$data = parent::getLayoutData();
$paramsAll = (object) $this->form->getValue('params');
$setsAmount = empty($paramsAll->sets_amount) ? 3 : $paramsAll->sets_amount;
if (empty($data['value'])) {
$data['value'] = [];
}
$menus = [
'edit' => ['label' => 'Edit'],
'insert' => ['label' => 'Insert'],
'view' => ['label' => 'View'],
'format' => ['label' => 'Format'],
'table' => ['label' => 'Table'],
'tools' => ['label' => 'Tools'],
'help' => ['label' => 'Help'],
];
$data['menus'] = $menus;
$data['menubarSource'] = array_keys($menus);
$data['buttons'] = TinyMCEProvider::getKnownButtons();
$data['buttonsSource'] = array_keys($data['buttons']);
$data['toolbarPreset'] = TinyMCEProvider::getToolbarPreset();
$data['setsAmount'] = $setsAmount;
// Get array of sets names
for ($i = 0; $i < $setsAmount; $i++) {
$data['setsNames'][$i] = Text::sprintf('PLG_TINY_SET_TITLE', $i);
}
// Prepare the forms for each set
$setsForms = [];
$formsource = JPATH_PLUGINS . '/editors/tinymce/forms/setoptions.xml';
// Preload an old params for B/C
$setParams = new \stdClass();
if (!empty($paramsAll->html_width) && empty($paramsAll->configuration['setoptions'])) {
$plugin = PluginHelper::getPlugin('editors', 'tinymce');
Factory::getApplication()->enqueueMessage(Text::sprintf('PLG_TINY_LEGACY_WARNING', '#'), 'warning');
if (\is_object($plugin) && !empty($plugin->params)) {
$setParams = (object) json_decode($plugin->params);
}
}
// Collect already used groups
$groupsInUse = [];
// Prepare the Set forms, for the set options
foreach (array_keys($data['setsNames']) as $num) {
$formname = 'set.form.' . $num;
$control = $this->name . '[setoptions][' . $num . ']';
$setsForms[$num] = Form::getInstance($formname, $formsource, ['control' => $control]);
// Check whether we already have saved values or it first time or even old params
if (empty($this->value['setoptions'][$num])) {
$formValues = $setParams;
/*
* Predefine group:
* Set 0: for Administrator, Editor, Super Users (4,7,8)
* Set 1: for Registered, Manager (2,6), all else are public
*/
$formValues->access = !$num ? [4, 7, 8] : ($num === 1 ? [2, 6] : []);
// Assign Public to the new Set, but only when it not in use already
if (empty($formValues->access) && !\in_array(1, $groupsInUse)) {
$formValues->access = [1];
}
} else {
$formValues = (object) $this->value['setoptions'][$num];
}
// Collect already used groups
if (!empty($formValues->access)) {
$groupsInUse = array_merge($groupsInUse, $formValues->access);
}
// Bind the values
$setsForms[$num]->bind($formValues);
}
$data['setsForms'] = $setsForms;
// Check for TinyMCE language file
$language = Factory::getLanguage();
$languageFile1 = 'media/vendor/tinymce/langs/' . $language->getTag() . (JDEBUG ? '.js' : '.min.js');
$languageFile2 = 'media/vendor/tinymce/langs/' . substr($language->getTag(), 0, strpos($language->getTag(), '-')) . (JDEBUG ? '.js' : '.min.js');
$data['languageFile'] = '';
if (file_exists(JPATH_ROOT . '/' . $languageFile1)) {
$data['languageFile'] = $languageFile1;
} elseif (file_exists(JPATH_ROOT . '/' . $languageFile2)) {
$data['languageFile'] = $languageFile2;
}
$this->layoutData = $data;
return $data;
}
}

View File

@ -0,0 +1,94 @@
<?php
/**
* @package Joomla.Plugin
* @subpackage Editors.tinymce
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Plugin\Editors\TinyMCE\Field;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Form\Field\FolderlistField;
use Joomla\CMS\HTML\HTMLHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Generates the list of directories available for drag and drop upload.
*
* @package Joomla.Plugin
* @subpackage Editors.tinymce
* @since 3.7.0
*/
class UploaddirsField extends FolderlistField
{
protected $type = 'uploaddirs';
/**
* Method to attach a JForm object to the field.
*
* @param \SimpleXMLElement $element The SimpleXMLElement object representing the `<field>` tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control value. This acts as an array container for the field.
* For example if the field has name="foo" and the group value is set to "bar" then the
* full field name would end up being "bar[foo]".
*
* @return boolean True on success.
*
* @see \Joomla\CMS\Form\FormField::setup()
* @since 3.7.0
*/
public function setup(\SimpleXMLElement $element, $value, $group = null)
{
$return = parent::setup($element, $value, $group);
// Get the path in which to search for file options.
$this->directory = JPATH_ROOT . '/' . ComponentHelper::getParams('com_media')->get('image_path');
$this->recursive = true;
$this->hideDefault = true;
return $return;
}
/**
* Method to get the directories options.
*
* @return array The dirs option objects.
*
* @since 3.7.0
*/
public function getOptions()
{
return parent::getOptions();
}
/**
* Method to get the field input markup for the list of directories.
*
* @return string The field input markup.
*
* @since 3.7.0
*/
protected function getInput()
{
$html = [];
// Get the field options.
$options = (array) $this->getOptions();
// Reset the non selected value to null
if ($options[0]->value === '-1') {
$options[0]->value = '';
}
// Create a regular list.
$html[] = HTMLHelper::_('select.genericlist', $options, $this->name, 'class="form-select"', 'value', 'text', $this->value, $this->id);
return implode($html);
}
}

View File

@ -0,0 +1,56 @@
<?php
/**
* @package Joomla.Plugin
* @subpackage Editors.tinymce
*
* @copyright (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Plugin\Editors\TinyMCE\PluginTraits;
use Joomla\CMS\Language\Text;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Gets the active Site template style.
*
* @since 4.1.0
*/
trait ActiveSiteTemplate
{
/**
* Helper function to get the active Site template style.
*
* @return object
*
* @since 4.1.0
*/
protected function getActiveSiteTemplate()
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('*')
->from($db->quoteName('#__template_styles'))
->where(
[
$db->quoteName('client_id') . ' = 0',
$db->quoteName('home') . ' = ' . $db->quote('1'),
]
);
$db->setQuery($query);
try {
return $db->loadObject();
} catch (\RuntimeException $e) {
$this->getApplication()->enqueueMessage(Text::_('JERROR_AN_ERROR_HAS_OCCURRED'), 'error');
return new \stdClass();
}
}
}

View File

@ -0,0 +1,509 @@
<?php
/**
* @package Joomla.Plugin
* @subpackage Editors.tinymce
*
* @copyright (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Plugin\Editors\TinyMCE\PluginTraits;
use Joomla\CMS\Filesystem\Folder;
use Joomla\CMS\Filter\InputFilter;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Layout\LayoutHelper;
use Joomla\CMS\Session\Session;
use Joomla\CMS\Uri\Uri;
use Joomla\Registry\Registry;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Handles the onDisplay event for the TinyMCE editor.
*
* @since 4.1.0
*/
trait DisplayTrait
{
use GlobalFilters;
use KnownButtons;
use ResolveFiles;
use ToolbarPresets;
use XTDButtons;
/**
* Gets the editor HTML markup
*
* @param string $name Input name.
* @param string $content The content of the field.
* @param array $attributes Associative array of editor attributes.
* @param array $params Associative array of editor parameters.
*
* @return string The HTML markup of the editor
*
* @since 5.0.0
*/
public function display(string $name, string $content = '', array $attributes = [], array $params = []): string
{
// General variables
$app = $this->application;
$user = $app->getIdentity();
$language = $app->getLanguage();
$doc = $app->getDocument();
$wa = $doc->getWebAssetManager();
$options = $doc->getScriptOptions('plg_editor_tinymce');
$csrf = Session::getFormToken();
// Editor variables
$col = $attributes['col'] ?? '';
$row = $attributes['row'] ?? '';
$width = $attributes['width'] ?? '';
$height = $attributes['height'] ?? '';
$id = $attributes['id'] ?? $name;
$id = preg_replace('/(\s|[^A-Za-z0-9_])+/', '_', $id);
$nameGroup = explode('[', preg_replace('/\[\]|\]/', '', $name));
$fieldName = end($nameGroup);
$buttons = $params['buttons'] ?? true;
$asset = $params['asset'] ?? 0;
$author = $params['author'] ?? 0;
$scriptOptions = [];
$externalPlugins = [];
$theme = 'silver';
// Register assets
$wa->getRegistry()->addExtensionRegistryFile('plg_editors_tinymce');
// 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 = is_numeric($width) ? $width . 'px' : $width;
$textarea->height = is_numeric($height) ? $height . 'px' : $height;
$textarea->content = $content;
$textarea->readonly = !empty($params['readonly']);
// Render Editor markup
$editor = '<div class="js-editor-tinymce">';
$editor .= LayoutHelper::render('joomla.tinymce.textarea', $textarea);
$editor .= !$app->client->mobile ? LayoutHelper::render('joomla.tinymce.togglebutton') : '';
$editor .= '</div>';
// Prepare the instance specific options
if (empty($options['tinyMCE'][$fieldName])) {
$options['tinyMCE'][$fieldName] = [];
}
// Width and height
if ($width && empty($options['tinyMCE'][$fieldName]['width'])) {
$options['tinyMCE'][$fieldName]['width'] = $width;
}
if ($height && empty($options['tinyMCE'][$fieldName]['height'])) {
$options['tinyMCE'][$fieldName]['height'] = $height;
}
// Set editor to readonly mode
if (!empty($params['readonly'])) {
$options['tinyMCE'][$fieldName]['readonly'] = 1;
}
// The ext-buttons
if (empty($options['tinyMCE'][$fieldName]['joomlaExtButtons'])) {
$tinyButtons = $this->tinyButtons($buttons, ['asset' => $asset, 'author' => $author, 'editorId' => $id]);
$options['tinyMCE'][$fieldName]['joomlaMergeDefaults'] = true;
$options['tinyMCE'][$fieldName]['joomlaExtButtons'] = $tinyButtons;
}
$doc->addScriptOptions('plg_editor_tinymce', $options, false);
// Setup Default (common) options for all Editor instances
// Check whether we already have them
if (!empty($options['tinyMCE']['default'])) {
return $editor;
}
$ugroups = array_combine($user->getAuthorisedGroups(), $user->getAuthorisedGroups());
// Prepare the parameters
$levelParams = new Registry();
$extraOptions = new \stdClass();
$toolbarParams = new \stdClass();
$extraOptionsAll = (array) $this->params->get('configuration.setoptions', []);
$toolbarParamsAll = (array) $this->params->get('configuration.toolbars', []);
// Sort the array in reverse, so the items with the lowest access level goes first
krsort($extraOptionsAll);
// Get configuration depend from User group
foreach ($extraOptionsAll as $set => $val) {
$val = (object) $val;
$val->access = empty($val->access) ? [] : $val->access;
// Check whether User in one of allowed group
foreach ($val->access as $group) {
if (isset($ugroups[$group])) {
$extraOptions = $val;
$toolbarParams = (object) $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 != '') {
$externalPlugins[$external['name']] = substr($path, 0, 1) == '/' ? Uri::root() . substr($path, 1) : $path;
}
}
}
// Merge the params
$levelParams->loadObject($toolbarParams);
$levelParams->loadObject($extraOptions);
// Set the selected skin
$skin = $levelParams->get($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';
if (!$levelParams->get('lang_mode', 1)) {
// Admin selected language
$langPrefix = $levelParams->get('lang_code', 'en');
} else {
// Reflect the current language
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';
}
}
$use_content_css = $levelParams->get('content_css', 1);
$content_css_custom = $levelParams->get('content_css_custom', '');
$content_css = null;
// Loading of css file for 'styles' dropdown
if ($content_css_custom) {
/**
* If URL, just pass it to $content_css
* else, assume it is a file name in the current template folder
*/
$content_css = strpos($content_css_custom, 'http') !== false
? $content_css_custom
: $this->includeRelativeFiles('css', $content_css_custom);
} else {
// Process when use_content_css is Yes and no custom file given
$content_css = $use_content_css ? $this->includeRelativeFiles('css', 'editor' . (JDEBUG ? '' : '.min') . '.css') : $content_css;
}
$ignore_filter = false;
// Text filtering
if ($levelParams->get('use_config_textfilters', 0)) {
// Use filters from com_config
$filter = static::getGlobalFilters($user);
$ignore_filter = $filter === false;
$blockedTags = !empty($filter->blockedTags) ? $filter->blockedTags : [];
$blockedAttributes = !empty($filter->blockedAttributes) ? $filter->blockedAttributes : [];
$tagArray = !empty($filter->tagsArray) ? $filter->tagsArray : [];
$attrArray = !empty($filter->attrArray) ? $filter->attrArray : [];
$invalid_elements = implode(',', array_merge($blockedTags, $blockedAttributes, $tagArray, $attrArray));
// Valid elements are all entries listed as allowed in com_config, which are now missing in the filter blocked properties
$default_filter = InputFilter::getInstance();
$valid_elements = implode(',', array_diff($default_filter->blockedTags, $blockedTags));
$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', ''));
}
// 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 = [
'autolink',
'lists',
'importcss',
'quickbars',
'jxtdbuttons',
];
$wa->useScript('plg_editors_tinymce.jxtdbuttons');
// Allowed elements
$elements = [
'hr[id|title|alt|class|width|size|noshade]',
];
$elements = $extended_elements ? array_merge($elements, explode(',', $extended_elements)) : $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'];
}
}
// Check for extra plugins, from the setoptions form
foreach (['wordcount' => 1, 'advlist' => 1, 'autosave' => 1] as $pName => $def) {
if ($levelParams->get($pName, $def)) {
$plugins[] = $pName;
}
}
// Use CodeMirror in the code view instead of plain text to provide syntax highlighting
if ($levelParams->get('sourcecode', 1)) {
// Enable joomla-highlighter plugin
$wa->getRegistry()->addExtensionRegistryFile('plg_editors_codemirror');
$wa->useScript('plg_editors_tinymce.highlighter');
$plugins[] = 'joomlaHighlighter';
}
$dragdrop = $levelParams->get('drag_drop', 1);
if ($dragdrop && $user->authorise('core.create', 'com_media')) {
$wa->useScript('plg_editors_tinymce.jdragndrop');
$plugins[] = 'jdragndrop';
$uploadUrl = Uri::base(true) . '/index.php?option=com_media&format=json&url=1&task=api.files';
Text::script('PLG_TINY_ERR_UNSUPPORTEDBROWSER');
Text::script('ERROR');
Text::script('PLG_TINY_DND_ADDITIONALDATA');
Text::script('PLG_TINY_DND_ALTTEXT');
Text::script('PLG_TINY_DND_LAZYLOADED');
Text::script('PLG_TINY_DND_EMPTY_ALT');
$scriptOptions['parentUploadFolder'] = $levelParams->get('path', '');
$scriptOptions['csrfToken'] = $csrf;
$scriptOptions['uploadUri'] = $uploadUrl;
// @TODO have a way to select the adapter, similar to $levelParams->get('path', '');
$scriptOptions['comMediaAdapter'] = 'local-images:';
}
// Convert pt to px in dropdown
$scriptOptions['font_size_formats'] = '8px 10px 12px 14px 18px 24px 36px';
// select the languages for the "language of parts" menu
if (isset($extraOptions->content_languages) && $extraOptions->content_languages) {
foreach (json_decode(json_encode($extraOptions->content_languages), true) as $content_language) {
// if we have a language name and a language code then add to the menu
if ($content_language['content_language_name'] != '' && $content_language['content_language_code'] != '') {
$ctemp[] = ['title' => $content_language['content_language_name'], 'code' => $content_language['content_language_code']];
}
}
if (isset($ctemp)) {
$scriptOptions['content_langs'] = array_merge($ctemp);
}
}
// Should load the template plugin?
if (!empty($allButtons['jtemplate'])) {
$wa->useScript('plg_editors_tinymce.jtemplate');
$plugins[] = 'jtemplate';
$scriptOptions['jtemplates'] = Uri::base(true) . '/index.php?option=com_ajax&plugin=tinymce&group=editors&format=json&format=json&template='
. $levelParams->get('content_template_path') . '&' . $csrf . '=1';
}
// User custom plugins and buttons
$custom_plugin = trim($levelParams->get('custom_plugin', ''));
$custom_button = trim($levelParams->get('custom_button', ''));
if ($custom_plugin) {
$plugins = array_merge($plugins, explode(strpos($custom_plugin, ',') !== false ? ',' : ' ', $custom_plugin));
}
if ($custom_button) {
$toolbar1 = array_merge($toolbar1, explode(strpos($custom_button, ',') !== false ? ',' : ' ', $custom_button));
}
// Merge the two toolbars for backwards compatibility
$toolbar = array_merge($toolbar1, $toolbar2);
// Build the final options set
$scriptOptions = array_merge(
$scriptOptions,
[
'suffix' => JDEBUG ? '' : '.min',
'baseURL' => Uri::root(true) . '/media/vendor/tinymce',
'directionality' => $language->isRtl() ? 'rtl' : 'ltr',
'language' => $langPrefix,
'autosave_restore_when_empty' => false,
'skin' => $skin,
'theme' => $theme,
'schema' => 'html5',
// Prevent cursor from getting stuck in blocks when nested or at end of document.
'end_container_on_empty_block' => true,
// Toolbars
'menubar' => empty($menubar) ? false : implode(' ', array_unique($menubar)),
'toolbar' => empty($toolbar) ? null : 'jxtdbuttons ' . implode(' ', $toolbar),
'plugins' => implode(',', array_unique($plugins)),
// Quickbars
'quickbars_image_toolbar' => false,
'quickbars_insert_toolbar' => false,
'quickbars_selection_toolbar' => 'bold italic underline | H2 H3 | link blockquote',
// Cleanup/Output
'browser_spellcheck' => true,
'entity_encoding' => $levelParams->get('entity_encoding', 'raw'),
'verify_html' => !$ignore_filter,
'paste_as_text' => (bool) $levelParams->get('paste_as_text', false),
'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,
// Drag and drop Images always FALSE, reverting this allows for inlining the images
'paste_data_images' => false,
// Layout
'content_css' => $content_css,
'document_base_url' => Uri::root(true) . '/',
'image_caption' => true,
'importcss_append' => true,
'height' => $this->params->get('html_height', '550px'),
'width' => $this->params->get('html_width', ''),
'elementpath' => (bool) $levelParams->get('element_path', true),
'resize' => $resizing,
'external_plugins' => empty($externalPlugins) ? null : $externalPlugins,
'contextmenu' => (bool) $levelParams->get('contextmenu', true) ? null : false,
'toolbar_sticky' => true,
'toolbar_mode' => $levelParams->get('toolbar_mode', 'sliding'),
// Image plugin options
'a11y_advanced_options' => true,
'image_advtab' => (bool) $levelParams->get('image_advtab', false),
'image_title' => true,
'image_class_list' => [
['title' => 'None', 'value' => 'float-none'],
['title' => 'Left', 'value' => 'float-start'],
['title' => 'Right', 'value' => 'float-end'],
['title' => 'Center', 'value' => 'mx-auto d-block'],
],
// Drag and drop specific
'dndEnabled' => $dragdrop,
// Disable TinyMCE Branding
'branding' => false,
'promotion' => false,
// Specify the attributes to be used when previewing a style. This prevents white text on a white background making the preview invisible.
'preview_styles' => 'font-family font-size font-weight font-style text-decoration text-transform background-color border border-radius outline text-shadow',
]
);
if ($levelParams->get('newlines')) {
// Break
$scriptOptions['force_br_newlines'] = true;
} else {
// Paragraph
$scriptOptions['force_br_newlines'] = false;
$scriptOptions['forced_root_block'] = 'p';
}
$scriptOptions['link_rel_list'] = [
['title' => 'None', 'value' => ''],
['title' => 'Alternate', 'value' => 'alternate'],
['title' => 'Author', 'value' => 'author'],
['title' => 'Bookmark', 'value' => 'bookmark'],
['title' => 'Help', 'value' => 'help'],
['title' => 'License', 'value' => 'license'],
['title' => 'Lightbox', 'value' => 'lightbox'],
['title' => 'Next', 'value' => 'next'],
['title' => 'No Follow', 'value' => 'nofollow'],
['title' => 'No Referrer', 'value' => 'noreferrer'],
['title' => 'Prefetch', 'value' => 'prefetch'],
['title' => 'Prev', 'value' => 'prev'],
['title' => 'Search', 'value' => 'search'],
['title' => 'Tag', 'value' => 'tag'],
];
$scriptOptions['style_formats'] = [
[
'title' => Text::_('PLG_TINY_MENU_CONTAINER'),
'items' => [
['title' => 'article', 'block' => 'article', 'wrapper' => true, 'merge_siblings' => false],
['title' => 'aside', 'block' => 'aside', 'wrapper' => true, 'merge_siblings' => false],
['title' => 'section', 'block' => 'section', 'wrapper' => true, 'merge_siblings' => false],
],
],
];
$scriptOptions['style_formats_merge'] = true;
$options['tinyMCE']['default'] = $scriptOptions;
$doc->addScriptOptions('plg_editor_tinymce', $options);
return $editor;
}
}

View File

@ -0,0 +1,181 @@
<?php
/**
* @package Joomla.Plugin
* @subpackage Editors.tinymce
*
* @copyright (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Plugin\Editors\TinyMCE\PluginTraits;
use Joomla\CMS\Access\Access;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Filter\InputFilter;
use Joomla\CMS\User\User;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Handles the Joomla filters for the TinyMCE editor.
*
* @since 4.1.0
*/
trait GlobalFilters
{
/**
* Get the global text filters to arbitrary text as per settings for current user groups
* @param User $user The user object
*
* @return InputFilter
*
* @since 4.1.0
*/
protected static function getGlobalFilters($user)
{
// Filter settings
$config = ComponentHelper::getParams('com_config');
$userGroups = Access::getGroupsByUser($user->get('id'));
$filters = $config->get('filters');
$forbiddenListTags = [];
$forbiddenListAttributes = [];
$customListTags = [];
$customListAttributes = [];
$allowedListTags = [];
$allowedListAttributes = [];
$allowedList = false;
$forbiddenList = 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 {
/**
* Forbidden or allowed lists.
* 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 list of forbidden or allowed tags and attributes.
* Each list is cumulative.
* "BL" is deprecated in Joomla! 4, will be removed in Joomla! 5
*/
if (\in_array($filterType, ['BL', 'FL'])) {
$forbiddenList = true;
$forbiddenListTags = array_merge($forbiddenListTags, $tempTags);
$forbiddenListAttributes = array_merge($forbiddenListAttributes, $tempAttributes);
} elseif (\in_array($filterType, ['CBL', 'CFL'])) {
// "CBL" is deprecated in Joomla! 4, will be removed in Joomla! 5
// 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 (\in_array($filterType, ['WL', 'AL'])) {
// "WL" is deprecated in Joomla! 4, will be removed in Joomla! 5
$allowedList = true;
$allowedListTags = array_merge($allowedListTags, $tempTags);
$allowedListAttributes = array_merge($allowedListAttributes, $tempAttributes);
}
}
}
// Remove duplicates before processing (because the forbidden list uses both sets of arrays).
$forbiddenListTags = array_unique($forbiddenListTags);
$forbiddenListAttributes = array_unique($forbiddenListAttributes);
$customListTags = array_unique($customListTags);
$customListAttributes = array_unique($customListAttributes);
$allowedListTags = array_unique($allowedListTags);
$allowedListAttributes = array_unique($allowedListAttributes);
// Unfiltered assumes first priority.
if ($unfiltered) {
// Don't apply filtering.
return false;
}
// Custom forbidden list precedes Default forbidden list.
if ($customList) {
$filter = InputFilter::getInstance([], [], 1, 1);
// Override filter's default forbidden tags and attributes
if ($customListTags) {
$filter->blockedTags = $customListTags;
}
if ($customListAttributes) {
$filter->blockedAttributes = $customListAttributes;
}
} elseif ($forbiddenList) {
// Forbidden list takes second precedence.
// Remove the allowed tags and attributes from the forbidden list.
$forbiddenListTags = array_diff($forbiddenListTags, $allowedListTags);
$forbiddenListAttributes = array_diff($forbiddenListAttributes, $allowedListAttributes);
$filter = InputFilter::getInstance($forbiddenListTags, $forbiddenListAttributes, 1, 1);
// Remove allowed tags from filter's default forbidden list
if ($allowedListTags) {
$filter->blockedTags = array_diff($filter->blockedTags, $allowedListTags);
}
// Remove allowed attributes from filter's default forbidden list
if ($allowedListAttributes) {
$filter->blockedAttributes = array_diff($filter->blockedAttributes, $allowedListAttributes);
}
} elseif ($allowedList) {
// Allowed list take third precedence.
// Turn off XSS auto clean
$filter = InputFilter::getInstance($allowedListTags, $allowedListAttributes, 0, 0, 0);
} else {
// No HTML takes last place.
$filter = InputFilter::getInstance();
}
return $filter;
}
}

View File

@ -0,0 +1,108 @@
<?php
/**
* @package Joomla.Plugin
* @subpackage Editors.tinymce
*
* @copyright (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Plugin\Editors\TinyMCE\PluginTraits;
use Joomla\CMS\Language\Text;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Returns a list of known TinyMCE buttons.
*
* @since 4.1.0
*/
trait KnownButtons
{
/**
* Return list of known TinyMCE buttons
* @link https://www.tiny.cloud/docs/demo/full-featured/
* @link https://www.tiny.cloud/apps/#core-plugins
*
* @return array
*
* @since 4.1.0
*/
public static function getKnownButtons()
{
return [
// General buttons
'|' => ['label' => Text::_('PLG_TINY_TOOLBAR_BUTTON_SEPARATOR'), 'text' => '|'],
'undo' => ['label' => 'Undo'],
'redo' => ['label' => 'Redo'],
'bold' => ['label' => 'Bold'],
'italic' => ['label' => 'Italic'],
'underline' => ['label' => 'Underline'],
'strikethrough' => ['label' => 'Strikethrough'],
'styles' => ['label' => Text::_('PLG_TINY_TOOLBAR_BUTTON_STYLESELECT'), 'text' => 'Formats'],
'blocks' => ['label' => Text::_('PLG_TINY_TOOLBAR_BUTTON_FORMATSELECT'), 'text' => 'Paragraph'],
'fontfamily' => ['label' => Text::_('PLG_TINY_TOOLBAR_BUTTON_FONTSELECT'), 'text' => 'Font Family'],
'fontsize' => ['label' => Text::_('PLG_TINY_TOOLBAR_BUTTON_FONTSIZESELECT'), 'text' => 'Font Sizes'],
'alignleft' => ['label' => 'Align left'],
'aligncenter' => ['label' => 'Align center'],
'alignright' => ['label' => 'Align right'],
'alignjustify' => ['label' => 'Justify'],
'lineheight' => ['label' => 'Line height'],
'outdent' => ['label' => 'Decrease indent'],
'indent' => ['label' => 'Increase indent'],
'forecolor' => ['label' => 'Text colour'],
'backcolor' => ['label' => 'Background text colour'],
'bullist' => ['label' => 'Bullet list'],
'numlist' => ['label' => 'Numbered list'],
'link' => ['label' => 'Insert/edit link', 'plugin' => 'link'],
'unlink' => ['label' => 'Remove link', 'plugin' => 'link'],
'subscript' => ['label' => 'Subscript'],
'superscript' => ['label' => 'Superscript'],
'blockquote' => ['label' => 'Blockquote'],
'cut' => ['label' => 'Cut'],
'copy' => ['label' => 'Copy'],
'paste' => ['label' => 'Paste'],
'pastetext' => ['label' => 'Paste as text'],
'removeformat' => ['label' => 'Clear formatting'],
'language' => ['label' => 'Language'],
// Buttons from the plugins
'anchor' => ['label' => 'Anchor', 'plugin' => 'anchor'],
'hr' => ['label' => 'Horizontal line'],
'ltr' => ['label' => 'Left to right', 'plugin' => 'directionality'],
'rtl' => ['label' => 'Right to left', 'plugin' => 'directionality'],
'code' => ['label' => 'Source code', 'plugin' => 'code'],
'codesample' => ['label' => 'Insert/Edit code sample', 'plugin' => 'codesample'],
'table' => ['label' => 'Table', 'plugin' => 'table'],
'charmap' => ['label' => 'Special character', 'plugin' => 'charmap'],
'visualchars' => ['label' => 'Show invisible characters', 'plugin' => 'visualchars'],
'visualblocks' => ['label' => 'Show blocks', 'plugin' => 'visualblocks'],
'nonbreaking' => ['label' => 'Nonbreaking space', 'plugin' => 'nonbreaking'],
'emoticons' => ['label' => 'Emoticons', 'plugin' => 'emoticons'],
'media' => ['label' => 'Insert/edit video', 'plugin' => 'media'],
'image' => ['label' => 'Insert/edit image', 'plugin' => 'image'],
'pagebreak' => ['label' => 'Page break', 'plugin' => 'pagebreak'],
'print' => ['label' => 'Print'],
'preview' => ['label' => 'Preview', 'plugin' => 'preview'],
'fullscreen' => ['label' => 'Fullscreen', 'plugin' => 'fullscreen'],
'jtemplate' => ['label' => 'Insert template', 'plugin' => 'jtemplate'],
'searchreplace' => ['label' => 'Find and replace', 'plugin' => 'searchreplace'],
'insertdatetime' => ['label' => 'Insert date/time', 'plugin' => 'insertdatetime'],
'help' => ['label' => 'Help', 'plugin' => 'help'],
];
}
}

View File

@ -0,0 +1,117 @@
<?php
/**
* @package Joomla.Plugin
* @subpackage Editors.tinymce
*
* @copyright (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Plugin\Editors\TinyMCE\PluginTraits;
use Joomla\CMS\Filesystem\File;
use Joomla\CMS\Uri\Uri;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Handles the editor.css files.
*
* @since 4.1.0
*/
trait ResolveFiles
{
use ActiveSiteTemplate;
/**
* Compute the file paths to be included
*
* @param string $folder Folder name to search in (i.e. images, css, js).
* @param string $file Path to file.
*
* @return array files to be included.
*
* @since 4.1.0
*/
protected function includeRelativeFiles($folder, $file)
{
$fallback = Uri::root(true) . '/media/system/css/editor' . (JDEBUG ? '' : '.min') . '.css';
$template = $this->getActiveSiteTemplate();
if (!(array) $template) {
return $fallback;
}
// Extract extension and strip the file
$file = File::stripExt($file) . '.' . File::getExt($file);
$templaPath = $template->inheritable || (isset($template->parent) && $template->parent !== '')
? JPATH_ROOT . '/media/templates/site'
: JPATH_ROOT . '/templates';
if (isset($template->parent) && $template->parent !== '') {
$found = static::resolveFileUrl("$templaPath/$template->template/$folder/$file");
if (empty($found)) {
$found = static::resolveFileUrl("$templaPath/$template->parent/$folder/$file");
}
} else {
$found = static::resolveFileUrl("$templaPath/$template->template/$folder/$file");
}
if (empty($found)) {
return $fallback;
}
return $found;
}
/**
* Method that searches if file exists in given path and returns the relative path.
* If a minified version exists it will be preferred.
*
* @param string $path The actual path of the file
*
* @return string The relative path of the file
*
* @since 4.1.0
*/
protected static function resolveFileUrl($path = '')
{
$position = strrpos($path, '.min.');
// We are handling a name.min.ext file:
if ($position !== false) {
$minifiedPath = $path;
$nonMinifiedPath = substr_replace($path, '', $position, 4);
if (JDEBUG && is_file($nonMinifiedPath)) {
return Uri::root(true) . str_replace(JPATH_ROOT, '', $nonMinifiedPath);
}
if (is_file($minifiedPath)) {
return Uri::root(true) . str_replace(JPATH_ROOT, '', $minifiedPath);
}
if (is_file($nonMinifiedPath)) {
return Uri::root(true) . str_replace(JPATH_ROOT, '', $nonMinifiedPath);
}
return '';
}
$minifiedPath = pathinfo($path, PATHINFO_DIRNAME) . '/' . pathinfo($path, PATHINFO_FILENAME) . '.min.' . pathinfo($path, PATHINFO_EXTENSION);
if (JDEBUG && is_file($path)) {
return Uri::root(true) . str_replace(JPATH_ROOT, '', $path);
}
if (is_file($minifiedPath)) {
return Uri::root(true) . str_replace(JPATH_ROOT, '', $minifiedPath);
}
return '';
}
}

View File

@ -0,0 +1,88 @@
<?php
/**
* @package Joomla.Plugin
* @subpackage Editors.tinymce
*
* @copyright (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Plugin\Editors\TinyMCE\PluginTraits;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* The ToolbarPresets trait holds the default presets for the toolbar.
*
* @since 4.1.0
*/
trait ToolbarPresets
{
/**
* Return toolbar presets
*
* @return array
*
* @since 4.1.0
*/
public static function getToolbarPreset()
{
return [
'simple' => [
'menu' => [],
'toolbar1' => [
'bold', 'underline', 'strikethrough', '|',
'undo', 'redo', '|',
'bullist', 'numlist', '|',
'pastetext', 'jxtdbuttons',
],
'toolbar2' => [],
],
'medium' => [
'menu' => ['edit', 'insert', 'view', 'format', 'table', 'tools', 'help'],
'toolbar1' => [
'bold', 'italic', 'underline', 'strikethrough', '|',
'alignleft', 'aligncenter', 'alignright', 'alignjustify', '|',
'blocks', '|',
'bullist', 'numlist', '|',
'outdent', 'indent', '|',
'undo', 'redo', '|',
'link', 'unlink', 'anchor', 'code', '|',
'hr', 'table', '|',
'subscript', 'superscript', '|',
'charmap', 'pastetext', 'preview', 'jxtdbuttons',
],
'toolbar2' => [],
],
'advanced' => [
'menu' => ['edit', 'insert', 'view', 'format', 'table', 'tools', 'help'],
'toolbar1' => [
'bold', 'italic', 'underline', 'strikethrough', '|',
'alignleft', 'aligncenter', 'alignright', 'alignjustify', '|',
'lineheight', '|',
'styles', '|',
'blocks', 'fontfamily', 'fontsize', '|',
'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', 'jtemplate', '|',
'print', 'preview', 'codesample', 'insertdatetime', 'removeformat', 'jxtdbuttons',
'language',
],
'toolbar2' => [],
],
];
}
}

View File

@ -0,0 +1,118 @@
<?php
/**
* @package Joomla.Plugin
* @subpackage Editors.tinymce
*
* @copyright (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Plugin\Editors\TinyMCE\PluginTraits;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Layout\LayoutHelper;
use Joomla\CMS\Uri\Uri;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Resolves the XTD Buttons for the current TinyMCE editor.
*
* @since 4.1.0
*/
trait XTDButtons
{
/**
* Get the XTD buttons and render them inside tinyMCE
*
* @param mixed $buttons the buttons that should be hidden
* @param array $options Associative array with additional parameters
*
* @return array
*
* @since 4.1.0
*/
private function tinyButtons($buttons, array $options = []): array
{
// Get buttons from plugins
$buttonsList = $this->getButtons($buttons, $options);
if (!$buttonsList) {
return [];
}
/** @var \Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = $this->application->getDocument()->getWebAssetManager();
$editorId = $options['editorId'] ?? '';
Text::script('PLG_TINY_CORE_BUTTONS');
// Build a buttons option for TinyMCE
$tinyButtons = [];
foreach ($buttonsList as $button) {
$title = $button->get('title') ?: $button->get('text', '');
$icon = $button->get('icon');
$link = $button->get('link');
$action = $button->get('action', '');
$options = (array) $button->get('options');
$btnAsset = 'editor-button.' . $button->getButtonName();
// Enable the button assets if any
if ($wa->assetExists('style', $btnAsset)) {
$wa->useStyle($btnAsset);
}
if ($wa->assetExists('script', $btnAsset)) {
$wa->useScript($btnAsset);
}
// Correct the link
if ($link && $link[0] !== '#') {
$link = str_contains($link, '&amp;') ? htmlspecialchars_decode($link) : $link;
$link = Uri::base(true) . '/' . $link;
$options['src'] = $options['src'] ?? $link;
}
// Set action to "modal" for legacy buttons, when possible
$legacyModal = $button->get('modal');
// Prepare default values for modal
if ($action === 'modal') {
$wa->useScript('joomla.dialog');
$legacyModal = false;
$options['popupType'] = $options['popupType'] ?? 'iframe';
$options['textHeader'] = $options['textHeader'] ?? $title;
$options['iconHeader'] = $options['iconHeader'] ?? 'icon-' . $icon;
}
$coreButton = [];
$coreButton['name'] = $title;
$coreButton['icon'] = $icon;
$coreButton['click'] = $button->get('onclick');
$coreButton['iconSVG'] = $button->get('iconSVG');
$coreButton['action'] = $action;
$coreButton['options'] = $options;
if ($legacyModal) {
$coreButton['bsModal'] = true;
$coreButton['id'] = $editorId . '_' . $button->name;
$button->id = $editorId . '_' . $button->name . '_modal';
echo LayoutHelper::render('joomla.editors.buttons.modal', $button);
}
// The array with the toolbar buttons
$tinyButtons[] = $coreButton;
}
sort($tinyButtons);
return ['names' => $tinyButtons];
}
}

View File

@ -0,0 +1,80 @@
<?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Plugin\Editors\TinyMCE\Provider;
use Joomla\CMS\Application\CMSApplicationInterface;
use Joomla\CMS\Editor\AbstractEditorProvider;
use Joomla\Database\DatabaseAwareTrait;
use Joomla\Database\DatabaseInterface;
use Joomla\Event\DispatcherInterface;
use Joomla\Plugin\Editors\TinyMCE\PluginTraits\DisplayTrait;
use Joomla\Registry\Registry;
/**
* Editor provider class
*
* @since 5.0.0
*/
final class TinyMCEProvider extends AbstractEditorProvider
{
use DisplayTrait;
use DatabaseAwareTrait;
/**
* A Registry object holding the parameters for the plugin
*
* @var Registry
* @since 5.0.0
*/
protected $params;
/**
* The application object
*
* @var CMSApplicationInterface
*
* @since 5.0.0
*/
protected $application;
/**
* Class constructor
*
* @param Registry $params
* @param CMSApplicationInterface $application
* @param DispatcherInterface $dispatcher
* @param DatabaseInterface $database
*
* @since 5.0.0
*/
public function __construct(
Registry $params,
CMSApplicationInterface $application,
DispatcherInterface $dispatcher,
DatabaseInterface $database
) {
$this->params = $params;
$this->application = $application;
$this->setDispatcher($dispatcher);
$this->setDatabase($database);
}
/**
* Return Editor name, CMD string.
*
* @return string
* @since 5.0.0
*/
public function getName(): string
{
return 'tinymce';
}
}

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="editors" method="upgrade">
<name>plg_editors_tinymce</name>
<version>6.7.0</version>
<creationDate>2005-08</creationDate>
<author>Tiny Technologies, Inc</author>
<authorEmail>N/A</authorEmail>
<authorUrl>https://www.tiny.cloud</authorUrl>
<copyright>Tiny Technologies, Inc</copyright>
<license>MIT</license>
<description>PLG_TINY_XML_DESCRIPTION</description>
<namespace path="src">Joomla\Plugin\Editors\TinyMCE</namespace>
<files>
<folder>forms</folder>
<folder plugin="tinymce">services</folder>
<folder>src</folder>
</files>
<media destination="editors" folder="media">
<folder>tinymce</folder>
</media>
<languages>
<language tag="en-GB">language/en-GB/plg_editors_tinymce.ini</language>
<language tag="en-GB">language/en-GB/plg_editors_tinymce.sys.ini</language>
</languages>
<config>
<fields name="params">
<fieldset name="basic" addfieldprefix="Joomla\Plugin\Editors\TinyMCE\Field">
<field
name="configuration"
type="tinymcebuilder"
label="PLG_TINY_BUILDER"
hiddenLabel="true"
/>
</fieldset>
<fieldset name="advanced" label="PLG_TINY_FIELD_LABEL_ADVANCEDPARAMS">
<field
name="sets_amount"
type="number"
label="PLG_TINY_FIELD_NUMBER_OF_SETS_LABEL"
filter="int"
validate="number"
min="3"
default="3"
/>
<field
name="html_height"
type="text"
label="PLG_TINY_FIELD_HTMLHEIGHT_LABEL"
default="550px"
/>
<field
name="html_width"
type="text"
label="PLG_TINY_FIELD_HTMLWIDTH_LABEL"
default=""
/>
</fieldset>
</fields>
</config>
</extension>