tinymce.class.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. <?php
  2. /**
  3. * @package tinymce
  4. */
  5. class TinyMCE {
  6. public $config = array();
  7. public $properties = array();
  8. public $jsLoaded = false;
  9. /** @var modContext $context */
  10. public $context;
  11. /**
  12. * The TinyMCE constructor.
  13. *
  14. * @param modX $modx A reference to the modX constructor.
  15. * @param array $config An array of configuration properties
  16. */
  17. function __construct(modX &$modx,array $config = array()) {
  18. $this->modx =& $modx;
  19. $assetsUrl = $this->modx->getOption('tiny.assets_url',$config,$this->modx->getOption('assets_url',null,MODX_ASSETS_URL).'components/tinymce/');
  20. $assetsPath = $this->modx->getOption('tiny.assets_path',$config,$this->modx->getOption('assets_path',null,MODX_ASSETS_PATH).'components/tinymce/');
  21. $corePath = $this->modx->getOption('tiny.core_path',$config,$this->modx->getOption('core_path',null,MODX_CORE_PATH).'components/tinymce/');
  22. $baseUrl = $this->modx->getOption('tiny.base_url',$config,'');
  23. if (empty($baseUrl)) $baseUrl = $this->modx->getOption('assets_url',null,MODX_ASSETS_URL).'components/tinymce/';
  24. $this->config = array_merge(array(
  25. 'assetsPath' => $assetsPath,
  26. 'assetsUrl' => $assetsUrl,
  27. 'path' => $assetsPath,
  28. 'corePath' => $corePath,
  29. 'baseUrl' => $baseUrl,
  30. ),$config);
  31. $this->getEditingContext();
  32. }
  33. /**
  34. * Set the properties for this instance
  35. *
  36. * @param array $properties
  37. * @return void
  38. */
  39. public function setProperties(array $properties = array()) {
  40. $browserAction = $this->_getBrowserAction();
  41. $this->properties = array_merge(array(
  42. 'accessibility_warnings' => false,
  43. 'browserUrl' => $browserAction ? $this->modx->getOption('manager_url',null,MODX_MANAGER_URL).'index.php?a='.$browserAction : null,
  44. 'cleanup' => true,
  45. 'cleanup_on_startup' => false,
  46. 'compressor' => '',
  47. 'content_css' => $this->context->getOption('editor_css_path'),
  48. 'element_list' => '',
  49. 'entities' => '',
  50. 'execcommand_callback' => 'Tiny.onExecCommand',
  51. 'file_browser_callback' => 'Tiny.loadBrowser',
  52. 'force_p_newlines' => true,
  53. 'force_br_newlines' => false,
  54. 'formats' => array(
  55. 'alignleft' => array('selector' => 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', 'classes' => 'justifyleft'),
  56. 'alignright' => array('selector' => 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', 'classes' => 'justifyright'),
  57. 'alignfull' => array('selector' => 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', 'classes' => 'justifyfull'),
  58. ),
  59. 'frontend' => false,
  60. 'height' => '400px',
  61. 'plugin_insertdate_dateFormat' => '%Y-%m-%d',
  62. 'plugin_insertdate_timeFormat' => '%H:%M:%S',
  63. 'preformatted' => false,
  64. 'resizable' => true,
  65. 'relative_urls' => true,
  66. 'remove_script_host' => true,
  67. 'resource_browser_path' => $this->modx->getOption('manager_url',null,MODX_MANAGER_URL).'controllers/browser/index.php?',
  68. 'template_external_list_url' => $this->config['assetsUrl'].'template.list.php',
  69. 'theme_advanced_disable' => '',
  70. 'theme_advanced_resizing' => true,
  71. 'theme_advanced_resize_horizontal' => true,
  72. 'theme_advanced_statusbar_location' => 'bottom',
  73. 'theme_advanced_toolbar_align' => 'left',
  74. 'theme_advanced_toolbar_location' => 'top',
  75. 'width' => '100%',
  76. ),$properties);
  77. /* now do user/context/system setting overrides - these must override properties */
  78. $this->properties = array_merge($this->properties,array(
  79. 'buttons1' => $this->context->getOption('tiny.custom_buttons1','undo,redo,selectall,separator,pastetext,pasteword,separator,search,replace,separator,nonbreaking,hr,charmap,separator,image,modxlink,unlink,anchor,media,separator,cleanup,removeformat,separator,fullscreen,print,code,help',$this->properties),
  80. 'buttons2' => $this->context->getOption('tiny.custom_buttons2','bold,italic,underline,strikethrough,sub,sup,separator,bullist,numlist,outdent,indent,separator,justifyleft,justifycenter,justifyright,justifyfull,separator,styleselect,formatselect,separator,styleprops',$this->properties),
  81. 'buttons3' => $this->context->getOption('tiny.custom_buttons3','',$this->properties),
  82. 'buttons4' => $this->context->getOption('tiny.custom_buttons4','',$this->properties),
  83. 'buttons5' => $this->context->getOption('tiny.custom_buttons5','',$this->properties),
  84. 'convert_fonts_to_spans' => $this->context->getOption('tiny.convert_fonts_to_spans',true,$this->properties),
  85. 'convert_newlines_to_brs' => $this->context->getOption('tiny.convert_newlines_to_brs',false,$this->properties),
  86. 'css_path' => $this->context->getOption('editor_css_path','',$this->properties),
  87. 'directionality' => $this->context->getOption('manager_direction','ltr',$this->properties),
  88. 'element_format' => $this->context->getOption('tiny.element_format','xhtml',$this->properties),
  89. 'entity_encoding' => $this->context->getOption('tiny.element_format','named',$this->properties),
  90. 'fix_nesting' => $this->context->getOption('tiny.fix_nesting',false,$this->properties),
  91. 'fix_table_elements' => $this->context->getOption('tiny.fix_table_elements',false,$this->properties),
  92. 'font_size_classes' => $this->context->getOption('tiny.font_size_classes','',$this->properties),
  93. 'font_size_style_values' => $this->context->getOption('tiny.font_size_style_values','xx-small,x-small,small,medium,large,x-large,xx-large',$this->properties),
  94. 'forced_root_block' => $this->context->getOption('tiny.forced_root_block','p',$this->properties),
  95. 'indentation' => $this->context->getOption('tiny.indentation','30px',$this->properties),
  96. 'invalid_elements' => $this->context->getOption('tiny.invalid_elements','',$this->properties),
  97. 'language' => $this->context->getOption('manager_language',$this->context->getOption('cultureKey','en',$this->properties),$this->properties),
  98. 'nowrap' => $this->context->getOption('tiny.nowrap',false,$this->properties),
  99. 'object_resizing' => $this->context->getOption('tiny.object_resizing',true,$this->properties),
  100. 'path_options' => $this->context->getOption('tiny.path_options','',$this->properties),
  101. 'plugins' => $this->context->getOption('tiny.custom_plugins','style,advimage,advlink,modxlink,searchreplace,print,contextmenu,paste,fullscreen,noneditable,nonbreaking,xhtmlxtras,visualchars,media',$this->properties),
  102. 'remove_linebreaks' => $this->context->getOption('tiny.remove_linebreaks',false,$this->properties),
  103. 'remove_redundant_brs' => $this->context->getOption('tiny.remove_redundant_brs',true,$this->properties),
  104. 'removeformat_selector' => $this->context->getOption('tiny.removeformat_selector','b,strong,em,i,span,ins',$this->properties),
  105. 'skin' => $this->context->getOption('tiny.skin','cirkuit',$this->properties),
  106. 'skin_variant' => $this->context->getOption('tiny.skin_variant','',$this->properties),
  107. 'table_inline_editing' => $this->context->getOption('tiny.table_inline_editing',false,$this->properties),
  108. 'theme' => $this->context->getOption('tiny.editor_theme','advanced',$this->properties),
  109. 'theme_advanced_blockformats' => $this->context->getOption('tiny.theme_advanced_blockformats','p,h1,h2,h3,h4,h5,h6,div,blockquote,code,pre,address',$this->properties),
  110. 'theme_advanced_buttons1' => $this->context->getOption('tiny.custom_buttons1','undo,redo,selectall,separator,pastetext,pasteword,separator,search,replace,separator,nonbreaking,hr,charmap,separator,image,modxlink,unlink,anchor,media,separator,cleanup,removeformat,separator,fullscreen,print,code,help',$this->properties),
  111. 'theme_advanced_buttons2' => $this->context->getOption('tiny.custom_buttons2','bold,italic,underline,strikethrough,sub,sup,separator,bullist,numlist,outdent,indent,separator,justifyleft,justifycenter,justifyright,justifyfull,separator,styleselect,formatselect,separator,styleprops',$this->properties),
  112. 'theme_advanced_buttons3' => $this->context->getOption('tiny.custom_buttons3','',$this->properties),
  113. 'theme_advanced_buttons4' => $this->context->getOption('tiny.custom_buttons4','',$this->properties),
  114. 'theme_advanced_buttons5' => $this->context->getOption('tiny.custom_buttons5','',$this->properties),
  115. 'theme_advanced_font_sizes' => $this->context->getOption('tiny.theme_advanced_font_sizes','80%,90%,100%,120%,140%,160%,180%,220%,260%,320%,400%,500%,700%',$this->properties),
  116. 'theme_advanced_styles' => $this->context->getOption('tiny.css_selectors','',$this->properties),
  117. 'use_browser' => $this->context->getOption('use_browser',true,$this->properties),
  118. ));
  119. /* add properties that only have a value, due to TinyMCE bug with empty value here */
  120. $tp = $this->context->getOption('tiny.template_selected_content_classes','',$this->properties);
  121. if (!empty($tp)) {
  122. $this->properties['template_selected_content_classes'] = $tp;
  123. }
  124. }
  125. /**
  126. * Initialize TinyMCE
  127. * @return string
  128. */
  129. public function initialize() {
  130. if (!$this->jsLoaded) {
  131. $scriptFile = ((!$this->properties['frontend'] && $this->properties['compressor'] == 'enabled') ? 'tiny_mce_gzip.php' : 'tiny_mce.js');
  132. if ($this->context->getOption('tiny.use_uncompressed_library',false)) {
  133. $scriptFile = 'tiny_mce_src.js';
  134. }
  135. $this->modx->lexicon->load('tinymce:default');
  136. $lang = $this->modx->lexicon->fetch('tiny.',true);
  137. $compressJs = $this->context->getOption('tiny.compress_js',false);
  138. $this->modx->getVersionData();
  139. $inRevo20 = (boolean)version_compare($this->modx->version['full_version'],'2.1.0-rc1','<');
  140. $this->modx->controller->addJavascript($this->config['assetsUrl'].'jscripts/tiny_mce/'.$scriptFile);
  141. $this->modx->controller->addJavascript($this->config['assetsUrl'].'xconfig.js');
  142. if ($compressJs) {
  143. $this->modx->controller->addJavascript($this->config['assetsUrl'].'tiny.min.js');
  144. } else {
  145. $this->modx->controller->addJavascript($this->config['assetsUrl'].'tiny.js');
  146. }
  147. $source = $this->context->getOption('default_media_source',1);
  148. $this->modx->controller->addHtml('<script type="text/javascript">' . "\n//<![CDATA[" . "\nvar inRevo20 = ".($inRevo20 ? 1 : 0).";MODx.source = '".$source."';Tiny.lang = " . $this->modx->toJSON($lang). ';' . "\n//]]>" . "\n</script>");
  149. if (!$compressJs) {
  150. $this->modx->controller->addJavascript($this->config['assetsUrl'].'tinymce.panel.js');
  151. }
  152. $this->jsLoaded = true;
  153. }
  154. return $this->getScript();
  155. }
  156. public function getEditingContext() {
  157. if ($this->modx->context->get('key') == 'mgr') {
  158. /** @var modResource $resource */
  159. $resource = !empty($this->config['resource']) ? $this->config['resource'] : '';
  160. if ($resource and $resource instanceof modResource) {
  161. $this->context = $this->modx->getObject('modContext',$resource->get('context_key'));
  162. if ($this->context) {
  163. $this->context->prepare();
  164. }
  165. }
  166. }
  167. if (empty($this->context)) {
  168. $this->context =& $this->modx->context;
  169. }
  170. return $this->context;
  171. }
  172. /**
  173. * Renders the TinyMCE script code.
  174. *
  175. * @return string
  176. */
  177. public function getScript() {
  178. /* backwards compat */
  179. if ($this->properties['theme'] == 'editor' || $this->properties['theme'] == 'custom') {
  180. $this->properties['theme'] = 'advanced';
  181. }
  182. $this->properties['document_base_url'] = $this->config['baseUrl'];
  183. /* Set relative URL options */
  184. switch ($this->properties['path_options']) {
  185. default:
  186. case 'docrelative':
  187. $this->properties['relative_urls'] = true;
  188. $this->properties['remove_script_host'] = true;
  189. $baseUrl = $this->modx->context->getOption('base_url',MODX_BASE_URL);
  190. if (!empty($this->config['resource']) && $this->context) {
  191. $baseUrl = $this->context->getOption('base_url',$baseUrl);
  192. }
  193. $this->properties['document_base_url'] = $baseUrl;
  194. break;
  195. case 'rootrelative':
  196. $this->properties['relative_urls'] = false;
  197. $this->properties['remove_script_host'] = true;
  198. break;
  199. case 'fullpathurl':
  200. $this->properties['relative_urls'] = false;
  201. $this->properties['remove_script_host'] = false;
  202. break;
  203. }
  204. $richtextResource = true;
  205. if (!empty($this->config['resource'])) {
  206. if (!$this->config['resource']->get('richtext')) {
  207. unset($this->properties['elements']);
  208. $richtextResource = false; /* workaround for modx ui bug with rte tvs */
  209. }
  210. }
  211. $templates = $this->getTemplateList();
  212. /* get JS */
  213. unset($this->properties['resource']);
  214. ob_start();
  215. include_once dirname(__FILE__).'/templates/script.tpl';
  216. $script = ob_get_contents();
  217. ob_end_clean();
  218. $this->modx->controller->addHtml($script);
  219. return '';
  220. }
  221. /**
  222. * Allows for custom templates
  223. *
  224. * @return array
  225. */
  226. public function getTemplateList() {
  227. $list = array();
  228. $templateListSnippet = $this->context->getOption('tiny.template_list_snippet','',$this->properties);
  229. if (!empty($templateListSnippet)) {
  230. $templateList = $this->modx->runSnippet($templateListSnippet);
  231. } else {
  232. $templateList = $this->context->getOption('tiny.template_list','',$this->properties);
  233. }
  234. if (empty($templateList)) return $list;
  235. $templateList = explode(',',$templateList);
  236. foreach ($templateList as $template) {
  237. if (empty($template)) continue;
  238. $templateParams = explode(':',$template);
  239. if (count($templateParams) < 2) continue;
  240. $t = array($templateParams[0],$templateParams[1]);
  241. if (!empty($templateParams[2])) array_push($t,$templateParams[2]);
  242. $list[] = $t;
  243. }
  244. return $list;
  245. }
  246. /**
  247. * Gets the MODx modAction for the rte browser.
  248. * @return modAction
  249. */
  250. private function _getBrowserAction() {
  251. if (intval($_REQUEST['a']) > 0 || empty($_REQUEST['a'])) {
  252. /** @var modAction $actionObj */
  253. $actionObj = $this->modx->getObject('modAction',array('controller' => 'browser'));
  254. $action = $actionObj ? $actionObj->get('id') : 1;
  255. } else {
  256. $action = 'browser';
  257. }
  258. return $action;
  259. }
  260. /**
  261. * Allows for custom formats.
  262. *
  263. * TODO: Figure out proprietary storage format to have this work. Currently
  264. * ignored.
  265. *
  266. * @return string
  267. */
  268. public function getFormats() {
  269. $formats = explode(',',$this->properties['formats']);
  270. $fs = array();
  271. foreach ($formats as $format) {
  272. $fs[$format] = new stdClass();
  273. }
  274. $formats = json_encode($fs);
  275. unset($this->properties['formats']);
  276. return '';
  277. }
  278. }
  279. /*
  280. *
  281. $formatMap = array(
  282. 'alignleft' => array(
  283. 'selector' => 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img',
  284. 'classes' => 'left',
  285. ),
  286. 'aligncenter' => array(
  287. 'selector' => 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img',
  288. 'classes' => 'center',
  289. ),
  290. 'alignright' => array(
  291. 'selector' => 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img',
  292. 'classes' => 'right',
  293. ),
  294. 'alignfull' => array(
  295. 'selector' => 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img',
  296. 'classes' => 'full',
  297. ),
  298. 'bold' => array(
  299. 'inline' => 'span',
  300. 'classes' => 'bold',
  301. ),
  302. 'italic' => array(
  303. 'inline' => 'span',
  304. 'classes' => 'italic',
  305. ),
  306. 'underline' => array(
  307. 'inline' => 'span',
  308. 'classes' => 'underline',
  309. 'exact' => true,
  310. ),
  311. 'strikethrough' => array(
  312. 'inline' => 'del',
  313. ),
  314. 'forecolor' => array(
  315. 'inline' => 'span',
  316. 'classes' => 'hilitecolor',
  317. 'styles' => array(
  318. 'backgroundColor' => '%value',
  319. ),
  320. ),
  321. );
  322. */