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 for frontend-editors + public function addEditorScriptToBody() + { + global $modx; + + if (isset($_SESSION['usertype']) && $_SESSION['usertype'] == 'manager') { // Show only when logged in manager + // Add only once + if (!defined($this->editorKey . '_ADDED_TO_BODY')) { + define($this->editorKey . '_ADDED_TO_BODY', 1); + $initJs = $this->getEditorScript(); + + // @todo: How to avoid caching of plugins on event "OnParseDocument"? + if (strpos($modx->documentOutput, "") === false) { // Avoid double init if already cached.. + if (strpos($modx->documentOutput, '') !== false) { + // Append to + $modx->documentOutput = str_replace('', $initJs . "", $modx->documentOutput); + } else { + // No - 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'];