diff --git a/assets/lib/class.modxRTEbridge.php b/assets/lib/class.modxRTEbridge.php
new file mode 100644
index 0000000000..07902c3bfa
--- /dev/null
+++ b/assets/lib/class.modxRTEbridge.php
@@ -0,0 +1,904 @@
+debugMessages[] = 'Message';
+
+ public function __construct($editorKey = NULL, $basePath='', $tvOptions=array())
+ {
+ global $modx, $settings, $usersettings;
+
+ if ($editorKey == NULL) {
+ exit('modxRTEbridge: No editorKey set in plugin-initialization.');
+ };
+
+ // Check right path
+ $file = !empty($basePath) ? $basePath : __FILE__;
+ $current_path = str_replace('\\', '/', dirname($file)) . '/';
+ if (strpos($current_path, MODX_BASE_PATH) !== false) {
+ $path = substr($current_path, strlen(MODX_BASE_PATH));
+ $basePath = MODX_BASE_PATH . $path;
+ $baseUrl = MODX_BASE_URL . $path;
+ } else exit('modxRTEbridge: Path-Error');
+
+ // Init language before bridge so bridge can alter translations via $this->setLang()
+ $this->initLang($basePath);
+
+ // Get modxRTEbridge-config
+ if (is_readable("{$basePath}gsettings/bridge.{$editorKey}.inc.php")) {
+ include("{$basePath}gsettings/bridge.{$editorKey}.inc.php");
+ $this->bridgeParams = isset($bridgeParams) ? $bridgeParams : array();
+ $this->gSettingsCustom = isset($gSettingsCustom) ? $gSettingsCustom : array();
+ $this->gSettingsDefaultValues = isset($gSettingsDefaultValues) ? $gSettingsDefaultValues : array();
+ } else exit("modxRTEbridge: {$basePath}gsettings/bridge.{$editorKey}.inc.php not found");
+
+ // Determine settings from Modx
+ $mgrAction = isset($modx->manager->action) ? $modx->manager->action : 11;
+ switch ($mgrAction) {
+ // Create empty array()
+ case 11: // Create new user
+ $editorConfig = array();
+ break;
+ // Get user-config
+ case 12: // Edit user
+ case 119: // Purge plugin processor
+ $editorConfig = $usersettings;
+ if (!empty($usersettings[$this->editorKey . '_theme'])) {
+ $usersettings[$this->editorKey . '_theme'] = $settings[$this->editorKey . '_theme'];
+ }
+ break;
+ // Get Modx-config
+ case 17: // Modx-configuration
+ default:
+ $editorConfig = $settings;
+ break;
+ };
+
+ // Modx default WYSIWYG-params
+ $modxParamsArr = array(
+ 'theme', 'skin', 'entermode', 'element_format', 'schema', 'css_selectors',
+ 'custom_plugins', 'custom_buttons1', 'custom_buttons2', 'custom_buttons3', 'custom_buttons4',
+ 'template_docs', 'template_chunks'
+ );
+
+ // Add custom settings from bridge
+ foreach ($this->gSettingsCustom as $name => $x) {
+ if (!in_array($name, $modxParamsArr)) $modxParamsArr[] = $name;
+ };
+
+ // Take over editor-configuration from Modx
+ foreach ($modxParamsArr as $p) {
+ $value = isset($editorConfig[$editorKey . '_' . $p]) ? $editorConfig[$editorKey . '_' . $p] : NULL;
+ $value = $value === NULL && isset($this->gSettingsDefaultValues[$p]) ? $this->gSettingsDefaultValues[$p] : $value;
+
+ $this->modxParams[$p] = $value;
+ };
+
+ // Set TV-options
+ $this->tvOptions = $tvOptions;
+
+ // Get/set pluginParams
+ $this->editorKey = $editorKey;
+ $this->theme = isset($this->modxParams['theme']) ? $this->modxParams['theme'] : 'base';
+ $this->pluginParams = isset($modx->event->params) ? $modx->event->params : array();
+ $this->pluginParams['pluginName'] = $modx->event->activePlugin;
+ $this->pluginParams['editorLabel'] = isset($editorLabel) ? $editorLabel : 'No editorLabel set for "' . $editorKey . '"';
+ $this->pluginParams['editorVersion'] = isset($editorVersion) ? $editorVersion : 'No editorVersion set';
+ $this->pluginParams['editorLogo'] = isset($editorLogo) ? $editorLogo : '';
+ $this->pluginParams['skinsDirectory'] = isset($skinsDirectory) && !empty($skinsDirectory) ? trim($skinsDirectory, "/") . "/" : '';
+ $this->pluginParams['base_path'] = $basePath;
+ $this->pluginParams['base_url'] = $baseUrl;
+ }
+
+ // Function to set editor-parameters
+ // $value = NULL deletes key completely from editor-config
+ public function set($key, $value, $type=false, $emptyAllowed=false)
+ {
+ if ($value === NULL) {
+ $this->themeConfig[$key] = NULL; // Delete Parameter completely from JS-initialization
+ } else {
+ if(!isset($this->themeConfig[$key])) $this->themeConfig[$key] = array();
+ $this->themeConfig[$key]['value'] = $value;
+ $this->themeConfig[$key]['default'] = !isset($this->themeConfig[$key]['default']) ? $value : $this->themeConfig[$key]['default'];
+ $this->themeConfig[$key]['type'] = $type == false ? 'string' : $type;
+ $this->themeConfig[$key]['empty'] = $emptyAllowed;
+ }
+ }
+
+ // Function to append string to existing parameters
+ public function appendSet($key, $value, $separator = ',')
+ {
+ if ($value === '') { return; };
+
+ if (isset($this->themeConfig[$key])) {
+ $this->themeConfig[$key]['value'] .= $this->themeConfig[$key]['value'] != '' ? $separator.$value : $value;
+ };
+ }
+
+ // Function to force editor-setting via plugin-code
+ // $value = NULL deletes key completely from editor-config
+ public function force($key, $value)
+ {
+ if ($value === NULL) {
+ $this->themeConfig[$key] = NULL; // Delete Parameter completely from JS-initialization
+ } else {
+ if(!isset($this->themeConfig[$key])) $this->themeConfig[$key] = array();
+ $this->themeConfig[$key]['force'] = $value;
+ }
+ }
+
+ // Function to append custom HTML-Code to tpl.editor.init_once.html
+ public function appendInitOnce($str)
+ {
+ if (!in_array($str, $this->initOnceArr)) { // Avoid doubling..
+ $this->initOnceArr[] = $str;
+ };
+ }
+
+ // Function to force pluginParams like "elements" via plugin-code
+ // $value = NULL deletes key completely from editor-config
+ public function setPluginParam($param, $value)
+ {
+ if ($value === NULL) {
+ unset($this->pluginParams[$param]); // Delete Parameter completely
+ } else {
+ $this->pluginParams[$param] = $value;
+ }
+ }
+
+ // Function to set custom-placeholders like renders javascript-objects, arrays etc
+ // $value = NULL deletes key completely from custom-placeholders
+ public function setPlaceholder($ph, $value)
+ {
+ if ($value === NULL) {
+ unset($this->customPlaceholders[$ph]); // Delete placeholder completely
+ } else {
+ $this->customPlaceholders[$ph] = $value;
+ }
+ }
+
+ // Set new/overwrite translations manually (via bridge)
+ public function setLang($key, $string, $overwriteExisting = false)
+ {
+ if (is_array($string)) {
+ $this->langArr = $overwriteExisting == false ? array_merge($this->langArr, $string) : array_merge($string, $this->langArr);
+ } else {
+ $this->langArr[$key] = isset($this->langArr[$key]) && $overwriteExisting == false ? $this->langArr[$key] : $string;
+ };
+ }
+
+ // Get translation
+ public function lang($key = '', $returnNull = false)
+ {
+ if (!$key) return;
+ if (isset($this->langArr[$key])) return $this->langArr[$key];
+ return $returnNull ? NULL : 'lang_' . $key; // Show missing key as fallback
+ }
+
+ // Renders complete JS-Script
+ public function getEditorScript()
+ {
+ global $modx;
+ $ph = array();
+ $output = "\n";
+
+ // Init via elements
+ if (isset($this->pluginParams['elements'])) {
+
+ $this->pluginParams['elements'] = !is_array($this->pluginParams['elements']) ? explode(',', $this->pluginParams['elements']) : $this->pluginParams['elements']; // Allow setting via plugin-configuration
+
+ // Now loop through tvs
+ foreach ($this->pluginParams['elements'] as $selector) {
+
+ $this->initTheme($selector);
+ $this->renderBridgeParams($selector);
+
+ // Prepare config output
+ $ph['configString'] = $this->renderConfigString();
+ $ph['configRawString'] = $this->renderConfigRawString();
+ $ph['editorKey'] = $this->editorKey;
+ $ph['themeKey'] = $this->theme;
+ $ph['selector'] = $selector;
+ $ph['documentIdentifier'] = $modx->documentIdentifier;
+
+ $ph = array_merge($ph, $this->customPlaceholders, $this->mergeParamArrays()); // Big list..
+
+ // Init only once at all - Load Editors-Library, CSS etc
+ if (!defined($this->editorKey . '_INIT_ONCE')) {
+ define($this->editorKey . '_INIT_ONCE', 1);
+ $output .= file_get_contents("{$this->pluginParams['base_path']}tpl/tpl.{$this->editorKey}.init_once.html") ."\n";
+ if (!empty($this->initOnceArr)) {
+ $output .= implode("\n", $this->initOnceArr);
+ }
+ }
+
+ // Init only once per config (enables multiple config-objects i.e. for richtext / richtextmini via [+configJs+])
+ if (!defined($this->editorKey . '_INIT_CONFIG_' . $this->theme)) {
+ define($this->editorKey . '_INIT_CONFIG_' . $this->theme, 1);
+ $output .= file_get_contents("{$this->pluginParams['base_path']}tpl/tpl.{$this->editorKey}.config.html") ."\n";
+ }
+
+ // Loop through tvs
+ $output .= file_get_contents("{$this->pluginParams['base_path']}tpl/tpl.{$this->editorKey}.init.html") ."\n";
+ $output = $modx->parseText($output, $ph);
+ }
+
+ } else {
+ exit; // @todo: prepare for editors that need no elements
+ }
+
+ // Remove empty placeholders !
+ $placeholderArr = $modx->getTagsFromContent($output, '[+', '+]');
+ if (!empty($placeholderArr)) {
+ foreach ($placeholderArr[1] as $key => $val) {
+ $output = str_replace($placeholderArr[0][$key], '', $output);
+ $this->debugMessages[] = 'Removed empty placeholder: '.$placeholderArr[1];
+ }
+ }
+
+ $output .= $this->renderDebugMessages($ph);
+ $output .= "\n";
+
+ return $output;
+ }
+
+ // Init/load theme
+ public function initTheme($selector)
+ {
+ global $modx;
+
+ $this->theme = isset($this->tvOptions[$selector]['theme']) ? $this->tvOptions[$selector]['theme'] : $this->theme;
+
+ // Load theme for user or webuser
+ if ($modx->isBackend() || (intval($_GET['quickmanagertv']) == 1 || isset($_SESSION['mgrValidated']))) {
+ // User is logged into Manager
+ // Load base first to assure Modx settings like entermode, editor_css_path are given set, can be overwritten in custom theme
+ include("{$this->pluginParams['base_path']}theme/theme.{$this->editorKey}.base.inc.php");
+ include("{$this->pluginParams['base_path']}theme/theme.{$this->editorKey}.{$this->theme}.inc.php");
+ $this->pluginParams['language'] = !isset($this->pluginParams['language']) ? $this->lang('lang_code') : $this->pluginParams['language'];
+ } else {
+ // User is a webuser
+ $webuserTheme = !empty($this->pluginParams['webTheme']) ? $this->pluginParams['webTheme'] : 'webuser';
+ // Load base first or set EVERYTHING for webuser only in webuser-theme?
+ // include("{$this->pluginParams['base_path']}theme/theme.{$this->editorKey}.base.inc.php");
+ include("{$this->pluginParams['base_path']}theme/theme.{$this->editorKey}.{$webuserTheme}.inc.php");
+ // @todo: determine user-language?
+ $this->pluginParams['language'] = !isset($this->pluginParams['language']) ? $this->lang('lang_code') : $this->pluginParams['language'];
+ }
+ }
+
+ // Call bridge-functions and receive optional bridged-values
+ public function renderBridgeParams($selector)
+ {
+ // Call functions - for optional translation of params/values via bridge.xxxxxxxxxx.inc.php
+ foreach ($this->bridgeParams as $editorParam => $editorKey) {
+ if (is_callable($this->bridgeParams[$editorParam])) { // Call function, get return
+ $return = $this->bridgeParams[$editorParam]();
+ if ($return !== NULL && isset($this->themeConfig[$editorParam])) {
+ $this->themeConfig[$editorParam]['bridged'] = $return;
+ }
+ }
+ }
+ // Load Tv-Options as bridged-params
+ foreach($this->themeConfig as $key=>$conf) {
+ if (isset($this->tvOptions[$selector][$key])) {
+ $this->themeConfig[$key]['bridged'] = $this->tvOptions[$selector][$key];
+ }
+ }
+ }
+
+ // Renders String for initialization via JS
+ public function renderConfigString()
+ {
+ $config = array();
+
+ // Build config-string as per themeConfig
+ $raw = '';
+ foreach ($this->themeConfig as $key => $conf) {
+
+ if ($conf === NULL) { continue; }; // Skip nulled parameters
+ $value = $this->determineValue($key, $conf);
+ if ($value === NULL) { continue; }; // Skip none-allowed empty settings
+
+ // Escape quotes
+ if (!is_array($value) && strpos($value, "'") !== false && $conf['type'] != 'raw')
+ $value = str_replace("'", "\\'", $value);
+
+ // Determine output-type
+ switch ($conf['type']) {
+ case 'string': case 'str':
+ $config[$key] = " {$key}:'{$value}'";
+ break;
+ case 'array': case 'arr':
+ if (is_array($value)) { $value = "['" . implode("','", $value) . "']"; };
+ $config[$key] = " {$key}:{$value}";
+ break;
+ case 'boolean': case 'bool':
+ $value = $value == true ? 'true' : 'false';
+ $config[$key] = " {$key}:{$value}";
+ break;
+ case 'json':
+ if (is_array($value)) $value = json_encode($value);
+ $config[$key] = " {$key}:{$value}";
+ break;
+ case 'int':
+ case 'constant': case 'const':
+ case 'number': case 'num':
+ case 'object': case 'obj':
+ $config[$key] = " {$key}:{$value}";
+ break;
+ case 'raw':
+ $raw .= "{$value}\n";
+ break;
+ };
+ }
+
+ return implode(",\n", $config) . $raw;
+ }
+
+ // Renders String for initialization via JS
+ public function renderConfigRawString()
+ {
+ // Build config-string as per themeConfig
+ $raw = '';
+ foreach ($this->themeConfig as $key => $conf) {
+
+ if ($conf === NULL) { continue; }; // Skip nulled parameters
+ $value = $this->determineValue($key, $conf);
+ if ($value === NULL) { continue; }; // Skip none-allowed empty settings
+
+ if ($conf['type'] == 'raw') {
+ $raw .= "{$value}\n";
+ break;
+ };
+ };
+
+ return $raw;
+ }
+
+ // Get final value of editor-config
+ public function determineValue($key, $conf=NULL)
+ {
+ if($conf == NULL) { $conf = $this->themeConfig[$key]; };
+
+ $value = isset($this->themeConfig[$key]['bridged']) ? $this->themeConfig[$key]['bridged'] : NULL;
+ $value = $value === NULL && isset($this->themeConfig[$key]['force']) ? $this->themeConfig[$key]['force'] : $value;
+ $value = $value === NULL ? $this->themeConfig[$key]['value'] : $value;
+
+ if(!in_array($conf['type'], array('boolean','bool'))) {
+ if ($value === '' && $conf['empty'] === false) { // Empty values not allowed
+ if ($conf['default'] === '') return NULL; // Skip none-allowed empty setting
+ $value = $conf['default'];
+ };
+ };
+
+ return $value;
+ }
+
+ // Adds initilization before
+ $modx->documentOutput = str_replace(' - append to source
+ $modx->documentOutput .= $initJs;
+ }
+ };
+
+ };
+ };
+ }
+
+ /***************************************************************
+ * SETTINGS PARTS
+ * @todo: make options dynamic to add for example additional options to setting "schema" like html5-strict, html5-bla, or just to "html4 and html5"..
+ ***************************************************************/
+
+ // Outputs Modx- / user-configuration settings
+ public function getModxSettings()
+ {
+ global $modx, $usersettings, $settings;
+ $params = &$this->pluginParams;
+
+ if (defined('INTERFACE_RENDERED_' . $this->editorKey)) {
+ return '';
+ }
+ define('INTERFACE_RENDERED_' . $this->editorKey, 1);
+
+ // Avoid conflicts with older TinyMCE base configs, prepend editorKey to configKey like [+ckeditor4_custom_plugins+]
+ $prependModxParams = array();
+ foreach ($this->modxParams as $key => $val) {
+ $prependModxParams[$this->editorKey . '_' . $key] = $val;
+ }
+
+ $ph = array_merge($prependModxParams, $params);
+
+ // Prepare [+display+]
+ $ph['display'] = ($_SESSION['browser'] === 'modern') ? 'table-row' : 'block';
+ $ph['display'] = $modx->config['use_editor'] == 1 ? $ph['display'] : 'none';
+
+ // Prepare setting "editor_theme"
+ $theme_options = '';
+ switch ($modx->manager->action) {
+ case '11';
+ case '12';
+ case '119';
+ $selected = empty($ph[$this->editorKey . '_theme']) ? '"selected"' : '';
+ $theme_options .= '\n";
+ }
+
+ // Prepare setting "theme"
+ $ph['theme_options'] = $this->getThemeNames();
+
+ // Prepare setting "skin"
+ $ph['skin_options'] = $this->getSkinNames();
+
+ // Prepare setting "entermode_options"
+ $entermode = !empty($ph[$this->editorKey . '_entermode']) ? $ph[$this->editorKey . '_entermode'] : 'p';
+ $ph['entermode_options'] = '
';
+ $ph['entermode_options'] .= '';
+ switch ($modx->manager->action) {
+ case '11':
+ case '12':
+ case '119':
+ $ph['entermode_options'] .= '
';
+ $ph['entermode_options'] .= '
';
+ break;
+ }
+
+ // Prepare setting "element_format_options"
+ $element_format = !empty($ph[$this->editorKey . '_element_format']) ? $ph[$this->editorKey . '_element_format'] : 'xhtml';
+ $ph['element_format_options'] = '
';
+ $ph['element_format_options'] .= '';
+ switch ($modx->manager->action) {
+ case '11':
+ case '12':
+ case '119':
+ $ph['element_format_options'] .= '
';
+ $ph['element_format_options'] .= '
';
+ break;
+ }
+
+ // Prepare setting "schema_options"
+ $schema = !empty($ph[$this->editorKey . '_schema']) ? $ph[$this->editorKey . '_schema'] : 'html5';
+ $ph['schema_options'] = '
';
+ $ph['schema_options'] .= '
';
+ $ph['schema_options'] .= '';
+ switch ($modx->manager->action) {
+ case '11':
+ case '12':
+ case '119':
+ $ph['schema_options'] .= '
';
+ $ph['schema_options'] .= '
';
+ break;
+ };
+
+ // Prepare settings rows output
+ include($params['base_path'] . 'gsettings/gsettings.rows.inc.php');
+ $settingsRowTpl = file_get_contents("{$params['base_path']}gsettings/gsettings.row.inc.html");
+ $settingsRows = isset($settingsRows) ? array_merge($settingsRows, $this->gSettingsCustom) : $this->gSettingsCustom;
+
+ $ph['rows'] = '';
+ foreach ($settingsRows as $name => $row) {
+
+ if ($row == NULL) {
+ continue;
+ }; // Skip disabled config-settings
+
+ $row['name'] = $this->editorKey . '_' . $name;
+ $row['editorKey'] = $this->editorKey;
+ $row['title'] = $this->lang($row['title']);
+ $row['message'] = $this->lang($row['message']);
+ $row['messageVal'] = !empty($row['messageVal']) ? $row['messageVal'] : '';
+
+ // Prepare displaying of default values
+ $row['default'] = isset($this->gSettingsDefaultValues[$name]) ? '' . $this->lang('default') . '' . $this->gSettingsDefaultValues[$name] . '' : '';
+
+ // Enable nested parsing
+ $output = $modx->parseText($settingsRowTpl, $row); // Replace general translations
+ $output = $modx->parseText($output, $ph); // Replace values / settings
+ $output = $modx->parseText($output, $row); // Replace new PHs from values / settings
+
+ // Replace missing translations
+ $output = $this->replaceTranslations($output);
+
+ $ph['rows'] .= $output . "\n";
+ };
+
+ $settingsBody = file_get_contents("{$params['base_path']}gsettings/gsettings.body.inc.html");
+
+ $ph['editorLogo'] = !empty($this->pluginParams['editorLogo']) ? '
' : '';
+
+ $settingsBody = $modx->parseText($settingsBody, $ph);
+ $settingsBody = $this->replaceTranslations($settingsBody);
+
+ return $settingsBody;
+ }
+
+ // Replace all translation-placeholders
+ public function replaceTranslations($output)
+ {
+ global $modx;
+
+ $placeholderArr = $modx->getTagsFromContent($output, '[+', '+]');
+ if (!empty($placeholderArr)) {
+ foreach ($placeholderArr[1] as $key => $val) {
+ $trans = $this->lang($val, true);
+
+ if ($trans !== NULL)
+ $output = str_replace($placeholderArr[0][$key], $trans, $output);
+ };
+ };
+ return $output;
+ }
+
+ // helpers for getModxSettings()
+ public function getThemeNames()
+ {
+ global $modx;
+ $params = $this->pluginParams;
+
+ $themeDir = "{$params['base_path']}theme/";
+
+ switch ($modx->manager->action) {
+ case '11':
+ case '12':
+ case '119':
+ $selected = $this->selected(empty($params[$this->editorKey . '_skin']));
+ $option[] = '';
+ break;
+ }
+
+ foreach (glob("{$themeDir}*") as $file) {
+ $file = str_replace('\\', '/', $file);
+ $file = str_replace($themeDir, '', $file);
+ $file = str_replace('theme.' . $this->editorKey . '.', '', $file);
+
+ $theme = trim(str_replace('.inc.php', '', $file));
+ if ($theme == 'base') continue; // Why should user select base-theme?
+ $label = $this->lang("theme_{$theme}", true) ? $this->lang("theme_{$theme}") : $theme; // Get optional translation or show raw themeKey
+ $selected = $this->selected($theme == $this->modxParams['theme']);
+
+ $label = $modx->parseText($label, $this->pluginParams); // Enable [+editorLabel+] in options-label
+
+ $option[] = '";
+ }
+
+ return isset($option) && is_array($option) ? implode("\n", $option) : '';
+ }
+
+ public function getSkinNames()
+ {
+ global $modx, $usersettings, $settings;
+ $params = $this->pluginParams;
+
+ if (empty($params['skinsDirectory'])) {
+ return '';
+ };
+
+ $skinDir = "{$params['base_path']}{$params['skinsDirectory']}";
+
+ switch ($modx->manager->action) {
+ case '11':
+ case '12':
+ case '119':
+ $selected = $this->selected(empty($params[$this->editorKey . '_skin']));
+ $option[] = '';
+ break;
+ }
+ foreach (glob("{$skinDir}*", GLOB_ONLYDIR) as $dir) {
+ $dir = str_replace('\\', '/', $dir);
+ $skin_name = substr($dir, strrpos($dir, '/') + 1);
+ $skins[$skin_name][] = 'default';
+ $styles = glob("{$dir}/ui_*.css");
+ if (is_array($styles) && 0 < count($styles)) {
+ foreach ($styles as $css) {
+ $skin_variant = substr($css, strrpos($css, '_') + 1);
+ $skin_variant = substr($skin_variant, 0, strrpos($skin_variant, '.'));
+ $skins[$skin_name][] = $skin_variant;
+ }
+ }
+ foreach ($skins as $k => $o) ;
+ {
+ foreach ($o as $v) {
+ if ($v === 'default') $value = $k;
+ else $value = "{$k}:{$v}";
+ $selected = $this->selected($value == $this->modxParams['skin']);
+ $option[] = '";
+ }
+ }
+ }
+
+ return is_array($option) ? implode("\n", $option) : '';
+ }
+
+ public function selected($cond = false)
+ {
+ if ($cond !== false) return ' selected="selected"';
+ else return '';
+ }
+ public function checked($cond = false)
+ {
+ if ($cond !== false) return ' checked="checked"';
+ else return '';
+ }
+
+
+
+ // Init translations
+ public function initLang($basePath)
+ {
+ global $modx;
+
+ // Init langArray once
+ if (empty($this->langArr)) {
+ $lang_name = $modx->config['manager_language'];
+ $gsettings_path = $basePath . "lang/gsettings/"; // Holds general translations
+ $custom_path = $basePath . "lang/custom/"; // Holds custom translations
+ $lang_file = $lang_name . '.inc.php';
+ $fallback_file = 'english.inc.php';
+ $lang_code = '';
+
+ // Load gsettings fallback language (show at least english translations instead of empty)
+ if (is_file($gsettings_path . $fallback_file)) include($gsettings_path . $fallback_file);
+ if (isset($_lang['lang_code'])) $lang_code = $_lang['lang_code']; // Set langcode for RTE
+
+ // Load gsettings user language
+ if (is_file($custom_path . $fallback_file)) include($custom_path . $fallback_file);
+ if (isset($_lang['lang_code'])) $lang_code = $_lang['lang_code']; // Set langcode for RTE
+
+ // Load custom settings fallback language
+ if (is_file($gsettings_path . $lang_file)) include($gsettings_path . $lang_file);
+ if (isset($_lang['lang_code'])) $lang_code = $_lang['lang_code']; // Set langcode for RTE
+
+ // Load custom settings user language
+ if (is_file($custom_path . $lang_file)) include($custom_path . $lang_file);
+ if (isset($_lang['lang_code'])) $lang_code = $_lang['lang_code']; // Set langcode for RTE
+
+ $this->langArr = $_lang;
+ $this->langArr['lang_code'] = $lang_code;
+ };
+ }
+
+ // Merges all available config-params with prefixes into single array
+ public function mergeParamArrays()
+ {
+ $p = array();
+ foreach($this->pluginParams as $param=>$value) { $p['pp.'.$param] = is_array($value) ? join(',',$value) : $value; };
+ foreach($this->modxParams as $param=>$value) { $p['mp.'.$param] = is_array($value) ? join(',',$value) : $value; };
+ foreach($this->themeConfig as $param=>$arr) {
+ if (isset($arr['force'])) $p['tc.' . $param] = $arr['force'];
+ elseif (isset($arr['bridged'])) $p['tc.' . $param] = $arr['bridged'];
+ else $p['tc.' . $param] = $arr['value'];
+ };
+ foreach($this->gSettingsDefaultValues as $param=>$value) { $p['gd.'.$param] = is_array($value) ? join(',',$value) : $value; };
+ foreach($this->langArr as $param=>$value) { $p['l.'.$param] = $value; };
+ return $p;
+ }
+
+ // Get PluginConfiguration by Connectors
+ public function getModxPluginConfiguration($pluginName)
+ {
+ global $modx;
+
+ if( $pluginName != NULL ) {
+ if (empty ($modx->config)) { $modx->getSettings(); };
+ $modx->db->connect();
+
+ $plugin = $modx->getPluginCode($pluginName);
+ $parameter = $modx->parseProperties($plugin['props'], $pluginName, 'plugin');
+
+ if (is_array($parameter)) {
+ $this->pluginParams = array_merge($parameter, $this->pluginParams);
+ };
+ };
+ return $this->pluginParams;
+ }
+
+ // Remove all but numbers
+ public function onlyNumbers($string)
+ {
+ return preg_replace("/[^0-9]/", "", $string); // Remove px, % etc
+ }
+
+ // Helper to translate "bold,strike,underline,italic" to "bold","strike","underline","italic"
+ // Translates Modx Plugin-configuration strings to JSON-compatible string
+ public function addQuotesToCommaList($str, $quote = '"')
+ {
+ if (empty($str)) { return ''; }
+
+ $elements = explode(',', $str);
+ foreach ($elements as $key => $val) {
+ $elements[$key] = $quote . trim($val) . $quote;
+ };
+ return implode(',', $elements);
+ }
+
+ // Helper to avoid Placeholder-/Snippet-Execution for Frontend-Editors
+ public function protectModxPhs($placeholderArr, $setSep=',', $phLink='->')
+ {
+ global $modx;
+
+ if(!is_array($placeholderArr)) {
+ $editablesArr = explode($setSep, $placeholderArr);
+ foreach ($editablesArr as $idStr) {
+ $exp = explode($phLink, $idStr);
+ $editableIds[$exp[0]] = $exp[1];
+ }
+ } else {
+ $editableIds = $placeholderArr;
+ }
+
+ foreach ($editableIds as $modxPh=>$cssId) {
+ if (isset($modx->documentObject[$modxPh]))
+ $modx->documentObject[$modxPh] = $this->protectModxPlaceholders($modx->documentObject[$modxPh]);
+ }
+ }
+
+ public function protectModxPlaceholders($output)
+ {
+ return str_replace(
+ array('[*', '*]', '[(', ')]', '{{', '}}', '[[', ']]', '[!', '!]', '[+', '+]', '[~', '~]'),
+ array('[*', '*]', '[(', ')]', '{{', '}}', '[[', ']]', '[!', '!]', '[+', '+]', '[~', '~]'),
+ $output
+ );
+ }
+ public function unprotectModxPlaceholders($output)
+ {
+ return str_replace(
+ array('[*', '*]', '[(', ')]', '{{', '}}', '[[', ']]', '[!', '!]', '[+', '+]', '[~', '~]'),
+ array('[*', '*]', '[(', ')]', '{{', '}}', '[[', ']]', '[!', '!]', '[+', '+]', '[~', '~]'),
+ $output
+ );
+ }
+
+ // Handle debug-modes
+ public function setDebug($state)
+ {
+ if($state == 'full') $this->debug = 'full';
+ else if($state != false) $this->debug = true;
+ else $this->debug = false;
+ }
+
+ public function renderDebugMessages($placeholderArr) {
+ $output = '';
+ if($this->debug)
+ {
+ $output .= "\n";
+ }
+ return $output;
+ }
+
+ /***************************************************************
+ * Connectors
+ **************************************************************/
+ public function getTemplateChunkList()
+ {
+ global $modx;
+
+ $templatesArr = array();
+ /* only display if manager user is logged in */
+ if ($modx->getLoginUserType() === 'manager') {
+
+ $modx->getSettings();
+ $ids = $modx->config[$this->editorKey.'_template_docs'];
+ $chunks = $modx->config[$this->editorKey.'_template_chunks'];
+ $templatesArr = array();
+
+ if (!empty($ids)) {
+ $docs = $modx->getDocuments($modx->db->escape($ids), 1, 0, $fields = 'id,pagetitle,menutitle,description,content');
+ foreach ($docs as $i => $a) {
+ $newTemplate = array(
+ 'title'=>($docs[$i]['menutitle'] !== '') ? $docs[$i]['menutitle'] : $docs[$i]['pagetitle'],
+ 'description'=>$docs[$i]['description'],
+ 'content'=>$docs[$i]['content']
+ );
+ $templatesArr[] = $newTemplate;
+ }
+ }
+
+ if (!empty($chunks)) {
+ $tbl_site_htmlsnippets = $modx->getFullTableName('site_htmlsnippets');
+ if (strpos($chunks, ',') !== false) {
+ $chunks = array_filter(array_map('trim', explode(',', $chunks)));
+ $chunks = $modx->db->escape($chunks);
+ $chunks = implode("','", $chunks);
+ $where = "`name` IN ('{$chunks}')";
+ $orderby = "FIELD(name, '{$chunks}')";
+ } else {
+ $where = "`name`='{$chunks}'";
+ $orderby = '';
+ }
+
+ $rs = $modx->db->select('id,name,description,snippet', $tbl_site_htmlsnippets, $where, $orderby);
+
+ while ($row = $modx->db->getRow($rs)) {
+ $newTemplate = array(
+ 'title'=>$row['name'],
+ 'description'=>$row['description'],
+ 'content'=>$row['snippet']
+ );
+ $templatesArr[] = $newTemplate;
+ }
+ }
+ }
+ return $templatesArr;
+ }
+
+ // Plugin-configuration: &editableIds=Editable Ids
Modx-Phs->CSS-IDs;text;longtitle->#modx_longtitle,content->#modx_content
+ public function saveContentProcessor($rid, $ppPluginName, $ppEditableIds='editableIds')
+ {
+ global $modx;
+
+ if ($rid > 0 && $modx->getLoginUserType() === 'manager')
+ {
+ $this->getModxPluginConfiguration($ppPluginName);
+
+ $editableIds = explode(',', $this->pluginParams[$ppEditableIds]);
+
+ if($editableIds) {
+ include_once(MODX_BASE_PATH . "assets/lib/MODxAPI/modResource.php");
+
+ $modx->doc = new modResource($modx);
+ $modx->doc->edit($rid);
+
+ foreach ($editableIds as $idStr) {
+
+ $editable = explode('->', $idStr);
+ $modxPh = trim($editable[0]);
+
+ if (isset($_POST[$modxPh]) && $_POST[$modxPh] != 'undefined') // Prevent if Javascript returned "undefined"
+ $modx->doc->set($modxPh, $this->unprotectModxPlaceholders($_POST[$modxPh]));
+ };
+ return $modx->doc->save(true, true); // Returns ressource-ID
+ }
+
+ return 'editableIds not given in plugin-configuration with config-key "'. $ppEditableIds .'"';
+
+ } else {
+ return 'Not logged into manager!';
+ }
+ }
+}
\ No newline at end of file
diff --git a/assets/plugins/codemirror/codemirror.plugin.php b/assets/plugins/codemirror/codemirror.plugin.php
index aa064c5d29..7d56d46d93 100644
--- a/assets/plugins/codemirror/codemirror.plugin.php
+++ b/assets/plugins/codemirror/codemirror.plugin.php
@@ -11,7 +11,7 @@
*
* @confirmed MODX Evolution 1.0.15
*
- * @author Mihanik71
+ * @author Mihanik71
*
* @see https://github.com/Mihanik71/CodeMirror-MODx
*/
@@ -19,242 +19,284 @@
$textarea_name = 'post';
$mode = 'htmlmixed';
$lang = 'htmlmixed';
-$object_id = md5($evt->name.'-'.$content['id']);
+$object_id = md5($evt->name . '-' . $content['id']);
/*
* Default Plugin configuration
*/
-$theme = (isset($theme) ? $theme : 'default');
-$indentUnit = (isset($indentUnit) ? $indentUnit : 4);
-$tabSize = (isset($tabSize) ? $tabSize : 4);
-$lineWrapping = (isset($lineWrapping) ? $lineWrapping : false);
-$matchBrackets = (isset($matchBrackets) ? $matchBrackets : false);
-$activeLine = (isset($activeLine) ? $activeLine : false);
-$emmet = (($emmet == 'true')? '' : "");
-$search = (($search == 'true')? '' : "");
+$theme = (isset($theme) ? $theme : 'default');
+$indentUnit = (isset($indentUnit) ? $indentUnit : 4);
+$tabSize = (isset($tabSize) ? $tabSize : 4);
+$lineWrapping = (isset($lineWrapping) ? $lineWrapping : false);
+$matchBrackets = (isset($matchBrackets) ? $matchBrackets : false);
+$activeLine = (isset($activeLine) ? $activeLine : false);
+$emmet = (($emmet == 'true') ? '' : "");
+$search = (($search == 'true') ? '' : "");
/*
* This plugin is only valid in "text" mode. So check for the current Editor
*/
-$prte = (isset($_POST['which_editor']) ? $_POST['which_editor'] : '');
-$srte = ($modx->config['use_editor'] ? $modx->config['which_editor'] : 'none');
-$xrte = $content['richtext'];
+$prte = (isset($_POST['which_editor']) ? $_POST['which_editor'] : '');
+$srte = ($modx->config['use_editor'] ? $modx->config['which_editor'] : 'none');
+$xrte = $content['richtext'];
+$tvMode = false;
/*
* Switch event
*/
-switch($modx->Event->name) {
+switch ($modx->Event->name) {
case 'OnTempFormRender' :
$object_name = $content['templatename'];
- $rte = ($prte ? $prte : 'none');
+ $rte = ($prte ? $prte : 'none');
break;
case 'OnChunkFormRender' :
- $rte = isset($which_editor) ? $which_editor : 'none';
+ $rte = isset($which_editor) ? $which_editor : 'none';
break;
- case 'OnDocFormRender' :
- $textarea_name = 'ta';
+ case 'OnRichTextEditorInit':
+ if ($editor !== 'Codemirror') return;
+ $textarea_name = $modx->event->params['elements'];
$object_name = $content['pagetitle'];
- $xrte = (('htmlmixed' == $mode) ? $xrte : 0);
- $rte = ($prte ? $prte : ($content['id'] ? ($xrte ? $srte : 'none') : $srte));
- $contentType = $content['contentType'];
- /*
- * Switch contentType for doc
- */
- switch($contentType){
- case "text/css":
- $mode = "text/css";
- $lang = "css";
- break;
- case "text/javascript":
- $mode = "text/javascript";
- $lang = "javascript";
- break;
- case "application/json":
- $mode = "application/json";
- $lang = "javascript";
- break;
- }
+ $rte = 'none';
+ $tvMode = true;
+ $contentType = $content['contentType'];
+ /*
+ * Switch contentType for doc
+ */
+ switch ($contentType) {
+ case "text/css":
+ $mode = "text/css";
+ $lang = "css";
+ break;
+ case "text/javascript":
+ $mode = "text/javascript";
+ $lang = "javascript";
+ break;
+ case "application/json":
+ $mode = "application/json";
+ $lang = "javascript";
+ break;
+ }
+ break;
+ case 'OnDocFormRender' :
+ $textarea_name = 'ta';
+ $object_name = $content['pagetitle'];
+ $xrte = (('htmlmixed' == $mode) ? $xrte : 0);
+ $rte = ($prte ? $prte : ($content['id'] ? ($xrte ? $srte : 'none') : $srte));
+ $contentType = $content['contentType'];
+ /*
+ * Switch contentType for doc
+ */
+ switch ($contentType) {
+ case "text/css":
+ $mode = "text/css";
+ $lang = "css";
+ break;
+ case "text/javascript":
+ $mode = "text/javascript";
+ $lang = "javascript";
+ break;
+ case "application/json":
+ $mode = "application/json";
+ $lang = "javascript";
+ break;
+ }
break;
case 'OnSnipFormRender' :
case 'OnPluginFormRender' :
case 'OnModFormRender' :
- $mode = 'application/x-httpd-php-open';
- $rte = ($prte ? $prte : 'none');
- $lang = "php";
+ $mode = 'application/x-httpd-php-open';
+ $rte = ($prte ? $prte : 'none');
+ $lang = "php";
break;
case 'OnManagerPageRender':
if ((31 == $action) && (('view' == $_REQUEST['mode']) || ('edit' == $_REQUEST['mode']))) {
$textarea_name = 'content';
- $rte = 'none';
+ $rte = 'none';
}
break;
default:
- $this->logEvent(1, 2, 'Undefined event : '.$modx->Event->name.' in '.$this->Event->activePlugin.' Plugin', 'CodeMirror Plugin : '.$modx->Event->name);
+ $this->logEvent(1, 2, 'Undefined event : ' . $modx->Event->name . ' in ' . $this->Event->activePlugin . ' Plugin', 'CodeMirror Plugin : ' . $modx->Event->name);
}
-if (('none' == $rte) && $mode) {
+$output = '';
+if (('none' == $rte) && $mode && !defined('INIT_CODEMIRROR')) {
+ define('INIT_CODEMIRROR', 1);
$output = <<< HEREDOC
-
-
-
-
-
- {$emmet}{$search}
-
-
+
+
+ {$emmet}{$search}
+
+
HEREDOC;
- $modx->Event->output($output);
}
+
+if (!$tvMode) {
+ $elements = array($textarea_name);
+}
+
+if (('none' == $rte) && $mode && $elements !== NULL) {
+ foreach ($elements as $el) {
+ $output .= "
+ \n";
+ };
+};
+
+$modx->Event->output($output);
\ No newline at end of file
diff --git a/assets/plugins/tinymce/js/tinymce.linklist.php b/assets/plugins/tinymce/js/tinymce.linklist.php
index f6b9af0669..46090f7422 100644
--- a/assets/plugins/tinymce/js/tinymce.linklist.php
+++ b/assets/plugins/tinymce/js/tinymce.linklist.php
@@ -269,14 +269,11 @@ function getPage($doc_id)
"sc.published=1 AND sc.deleted=0 AND sc.id='{$doc_id}'"
);
$resourceArray = $modx->db->makeArray($result);
-
- // If we have got this far, it must not have been cached already, so lets do it now.
- $page_cache[$doc_id] = $resourceArray[0];
-
+
if (isset($resourceArray[0]))
{
- $page_cache[$doc_id] = $resourceArray[0];
- return $resourceArray[0];
+ $page_cache[$doc_id] = $resourceArray[0];
+ return $resourceArray[0];
}
return;
}
diff --git a/manager/actions/mutate_content.dynamic.php b/manager/actions/mutate_content.dynamic.php
index 726b821b22..e832a79874 100644
--- a/manager/actions/mutate_content.dynamic.php
+++ b/manager/actions/mutate_content.dynamic.php
@@ -702,7 +702,7 @@ function decode(s) {
$htmlContent = $content['content'];
?>
-
+
array(
- 'id'=>'ta',
- 'options'=>''
- ));
+ // Richtext-[*content*]
+ $richtexteditorIds = array();
+ $richtexteditorOptions = array();
+ $richtexteditorIds[$which_editor][] = 'ta';
+ $richtexteditorOptions[$which_editor]['ta'] = '';
} else {
echo "\t".''."\n";
}
@@ -762,11 +763,15 @@ function decode(s) {
while ($row = $modx->db->getRow($rs)) {
// Go through and display all Template Variables
if ($row['type'] == 'richtext' || $row['type'] == 'htmlarea') {
+ // determine TV-options
+ $tvOptions = $modx->parseProperties($row['elements']);
+ if(!empty($tvOptions)) {
+ // Allow different Editor with TV-option {"editor":"CKEditor4"} or &editor=Editor;text;CKEditor4
+ $editor = isset($tvOptions['editor']) ? $tvOptions['editor']: $which_editor;
+ };
// Add richtext editor to the list
- $replace_richtexteditor[] = array(
- "id"=>"tv".$row['id'],
- "options"=>$row['elements']
- );
+ $richtexteditorIds[$editor][] = "tv".$row['id'];
+ $richtexteditorOptions[$editor]["tv".$row['id']] = $tvOptions;
}
// splitter
if ($i++ > 0)
@@ -1187,13 +1192,13 @@ function makePublic(b) {
$elements) {
// invoke OnRichTextEditorInit event
$evtOut = $modx->invokeEvent('OnRichTextEditorInit', array(
- 'editor' => $which_editor,
- 'elements' => array($editor['id']),
- 'options' => $editor['options']
+ 'editor' => $editor,
+ 'elements' => $elements,
+ 'options' => $richtexteditorOptions[$editor]
));
if (is_array($evtOut))
echo implode('', $evtOut);
diff --git a/manager/includes/document.parser.class.inc.php b/manager/includes/document.parser.class.inc.php
index 341309f946..ac3b680551 100644
--- a/manager/includes/document.parser.class.inc.php
+++ b/manager/includes/document.parser.class.inc.php
@@ -57,6 +57,7 @@ class DocumentParser {
var $dumpPlugins;
var $pluginsCode;
var $pluginsTime=array();
+ var $pluginCache=array();
var $aliasListing;
private $version=array();
public $extensions = array();
@@ -3776,19 +3777,9 @@ function invokeEvent($evtName, $extParams= array ()) {
$e->activePlugin= $pluginName;
// get plugin code
- if (isset ($this->pluginCache[$pluginName])) {
- $pluginCode= $this->pluginCache[$pluginName];
- $pluginProperties= isset($this->pluginCache[$pluginName . "Props"]) ? $this->pluginCache[$pluginName . "Props"] : '';
- } else {
- $result = $this->db->select('name, plugincode, properties', $this->getFullTableName("site_plugins"), "name='{$pluginName}' AND disabled=0");
- if ($row= $this->db->getRow($result)) {
- $pluginCode= $this->pluginCache[$row['name']]= $row['plugincode'];
- $pluginProperties= $this->pluginCache[$row['name'] . "Props"]= $row['properties'];
- } else {
- $pluginCode= $this->pluginCache[$pluginName]= "return false;";
- $pluginProperties= '';
- }
- }
+ $plugin = $this->getPluginCode($pluginName);
+ $pluginCode= $plugin['code'];
+ $pluginProperties= $plugin['props'];
// load default params/properties
$parameter= $this->parseProperties($pluginProperties, $pluginName, 'plugin');
@@ -3816,6 +3807,35 @@ function invokeEvent($evtName, $extParams= array ()) {
return $results;
}
+ /**
+ * Returns plugin-code and properties
+ *
+ * @param string $pluginName
+ * @return array Associative array consisting of 'code' and 'props'
+ */
+ public function getPluginCode($pluginName)
+ {
+ $plugin = array();
+ if (isset ($this->pluginCache[$pluginName])) {
+ $pluginCode = $this->pluginCache[$pluginName];
+ $pluginProperties = isset($this->pluginCache[$pluginName . "Props"]) ? $this->pluginCache[$pluginName . "Props"] : '';
+ } else {
+ $pluginName = $this->db->escape($pluginName);
+ $result = $this->db->select('name, plugincode, properties', $this->getFullTableName("site_plugins"), "name='{$pluginName}' AND disabled=0");
+ if ($row = $this->db->getRow($result)) {
+ $pluginCode = $this->pluginCache[$row['name']]= $row['plugincode'];
+ $pluginProperties = $this->pluginCache[$row['name'] . "Props"]= $row['properties'];
+ } else {
+ $pluginCode = $this->pluginCache[$pluginName]= "return false;";
+ $pluginProperties = '';
+ }
+ }
+ $plugin['code'] = $pluginCode;
+ $plugin['props'] = $pluginProperties;
+
+ return $plugin;
+ }
+
/**
* Parses a resource property string and returns the result as an array
*
@@ -3855,8 +3875,12 @@ function parseProperties($propertyString, $elementName = null, $elementType = nu
if(is_array($row)) {
switch ($key) {
case 'pluginConfig':
- if (isset($row[0]['events'])) $property['pluginEvents'] = explode(',', $row['events']);
- if (isset($row[0]['filePath'])) $property['pluginFilePath'] = $row['filePath'];
+ if (isset($row[0]['legacy_names'])) $property['pluginName'] = $row[0]['legacy_names'];
+ if (isset($row[0]['description'])) $property['pluginDesc'] = $row[0]['description'];
+ if (isset($row[0]['modx_category'])) $property['pluginCategory'] = $row[0]['modx_category'];
+ if (isset($row[0]['events'])) $property['pluginEvents'] = explode(',', $row[0]['events']);
+ if (isset($row[0]['filePath'])) $property['pluginFilePath'] = $row[0]['filePath'];
+ if (isset($row[0]['installset'])) $property['pluginInstallSet'] = $row[0]['installset'];
break;
default:
$property[$key] = $row[0]['value'];