<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace editor_tiny;
/**
* Tiny Editor.
*
* @package editor_tiny
* @copyright 2021 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class editor extends \texteditor {
/** @var manager The Tiny Manager instace */
protected $manager;
/** @var \stdClass|null The default configuration to use if none is provided */
protected static $defaultconfiguration = null;
/**
* Instantiate the new editor instance.
*/
public function __construct() {
$this->manager = new manager();
}
/**
* Set the default configuration for the editor.
*
* @param manager $manager The editor manager
*/
public static function set_default_configuration(manager $manager): void {
global $PAGE;
if (self::is_default_configuration_set()) {
return;
}
$context = $PAGE->context;
$config = (object) [
'css' => $PAGE->theme->editor_css_url()->out(false),
'context' => $context->id,
'plugins' => $manager->get_plugin_configuration($context, [], []),
];
$config = json_encode($config);
$inlinejs = <<<EOF
M.util.js_pending('editor_tiny/editor:defaultConfiguration');
require(['editor_tiny/editor'], (Tiny) => {
Tiny.configureDefaultEditor({$config});
M.util.js_complete('editor_tiny/editor:defaultConfiguration');
});
EOF;
$PAGE->requires->js_amd_inline($inlinejs);
self::$defaultconfiguration = $config;
}
/**
* Fetch the current defautl configuration.
*
* @return \stdClass|null The default configuration or null if not set.
*/
public static function get_default_configuration(): ?\stdClass {
return self::$defaultconfiguration;
}
/**
* Reset the default configuration.
*/
public static function reset_default_configuration(): void {
self::$defaultconfiguration = null;
}
/**
* Check if the default configuration is set.
*
* @return bool True if the default configuration is set.
*/
public static function is_default_configuration_set(): bool {
return !empty(self::$defaultconfiguration);
}
/**
* Is the current browser supported by this editor?
*
* @return bool
*/
public function supported_by_browser() {
return true;
}
/**
* List of supported text field formats.
*
* @return array
*/
public function get_supported_formats() {
return [
FORMAT_HTML => FORMAT_HTML,
];
}
/**
* Returns text format preferred by this editor.
*
* @return int
*/
public function get_preferred_format() {
return FORMAT_HTML;
}
/**
* Does this editor support picking from repositories?
*
* @return bool
*/
public function supports_repositories() {
return true;
}
/**
* Use this editor for given element.
*
* @param string $elementid
* @param array $options
* @param null $fpoptions
*/
public function use_editor($elementid, array $options = null, $fpoptions = null) {
global $PAGE;
// Ensure that the default configuration is set.
self::set_default_configuration($this->manager);
if ($fpoptions === null) {
$fpoptions = [];
}
$context = $PAGE->context;
if (isset($options['context']) && ($options['context'] instanceof \context)) {
// A different context was provided.
// Use that instead.
$context = $options['context'];
}
// Generate the configuration for this editor.
$siteconfig = get_config('editor_tiny');
$config = (object) [
// The URL to the CSS file for the editor.
'css' => $PAGE->theme->editor_css_url()->out(false),
// The current context for this page or editor.
'context' => $context->id,
// File picker options.
'filepicker' => $fpoptions,
'currentLanguage' => current_language(),
'branding' => property_exists($siteconfig, 'branding') ? !empty($siteconfig->branding) : true,
// Language options.
'language' => [
'currentlang' => current_language(),
'installed' => get_string_manager()->get_list_of_translations(true),
'available' => get_string_manager()->get_list_of_languages()
],
// Placeholder selectors.
// Some contents (Example: placeholder elements) are only shown in the editor, and not to users. It is unrelated to the
// real display. We created a list of placeholder selectors, so we can decide to or not to apply rules, styles... to
// these elements.
// The default of this list will be empty.
// Other plugins can register their placeholder elements to placeholderSelectors list by calling
// editor_tiny/options::registerPlaceholderSelectors.
'placeholderSelectors' => [],
// Plugin configuration.
'plugins' => $this->manager->get_plugin_configuration($context, $options, $fpoptions, $this),
// Nest menu inside parent DOM.
'nestedmenu' => true,
];
if (defined('BEHAT_SITE_RUNNING') && BEHAT_SITE_RUNNING) {
// Add sample selectors for Behat test.
$config->placeholderSelectors = ['.behat-tinymce-placeholder'];
}
foreach ($fpoptions as $fp) {
// Guess the draftitemid for the editor.
// Note: This is the best we can do at the moment.
if (!empty($fp->itemid)) {
$config->draftitemid = $fp->itemid;
break;
}
}
$configoptions = json_encode(convert_to_array($config));
// Note: This is not ideal but the editor does not have control over any HTML output.
// The Editor API only allows you to run JavaScript.
// In the future we will extend the editor API to allow it to generate the textarea, or attributes to use in the
// textarea or its wrapper.
// For now we cannot use the `js_call_amd()` API call because it warns if the parameters passed exceed a
// relatively low character limit.
$config = json_encode($config);
$inlinejs = <<<EOF
M.util.js_pending('editor_tiny/editor');
require(['editor_tiny/editor'], (Tiny) => {
Tiny.setupForElementId({
< elementId: "${elementid}",
< options: ${configoptions},
> elementId: "{$elementid}",
> options: {$configoptions},
});
M.util.js_complete('editor_tiny/editor');
});
EOF;
$PAGE->requires->js_amd_inline($inlinejs);
}
}