dolibarr  20.0.0-beta
doleditor.class.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (C) 2006-2008 Laurent Destailleur <eldy@users.sourceforge.net>
3  * Copyright (C) 2021 GaĆ«tan MAISON <gm@ilad.org>
4  * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program. If not, see <https://www.gnu.org/licenses/>.
18  * or see https://www.gnu.org/
19  */
20 
31 class DolEditor
32 {
33  public $tool; // Store the selected tool
34 
35  // If using fckeditor
36  public $editor;
37 
38  // If not using fckeditor
39  public $content;
40  public $htmlname;
41  public $toolbarname;
42  public $toolbarstartexpanded;
43  public $rows;
44  public $cols;
45  public $height;
46  public $width;
47  public $uselocalbrowser;
48  public $readonly;
49  public $posx;
50  public $posy;
51 
52 
72  public function __construct($htmlname, $content, $width = '', $height = 200, $toolbarname = 'Basic', $toolbarlocation = 'In', $toolbarstartexpanded = false, $uselocalbrowser = 1, $okforextendededitor = true, $rows = 0, $cols = '', $readonly = 0, $poscursor = array())
73  {
74  global $conf;
75 
76  dol_syslog(get_class($this)."::DolEditor htmlname=".$htmlname." width=".$width." height=".$height." toolbarname=".$toolbarname);
77 
78  if (!$rows) {
79  $rows = round($height / 20);
80  }
81  if (!$cols) {
82  $cols = ($width ? round($width / 6) : 80);
83  }
84  $shorttoolbarname = preg_replace('/_encoded$/', '', $toolbarname);
85 
86  // Name of extended editor to use (FCKEDITOR_EDITORNAME can be 'ckeditor' or 'fckeditor')
87  $defaulteditor = 'ckeditor';
88  $this->tool = !getDolGlobalString('FCKEDITOR_EDITORNAME') ? $defaulteditor : $conf->global->FCKEDITOR_EDITORNAME;
89  $this->uselocalbrowser = $uselocalbrowser;
90  $this->readonly = $readonly;
91 
92  // Check if extended editor is ok. If not we force textarea
93  if ((!isModEnabled('fckeditor') && $okforextendededitor !== 'ace') || empty($okforextendededitor)) {
94  $this->tool = 'textarea';
95  }
96  if ($okforextendededitor === 'ace') {
97  $this->tool = 'ace';
98  }
99  //if ($conf->dol_use_jmobile) $this->tool = 'textarea'; // ckeditor and ace seems ok with mobile
100 
101  if ( isset($poscursor['find']) ) {
102  $posy = 0;
103  $lines = explode("\n", $content);
104  $nblines = count($lines);
105  for ($i = 0 ; $i < $nblines ; $i++) {
106  if (preg_match('/'.$poscursor['find'].'/', $lines[$i])) {
107  $posy = $i;
108  break;
109  }
110  }
111  if ($posy != 0 ) $poscursor['y'] = $posy;
112  }
113 
114  // Define some properties
115  if (in_array($this->tool, array('textarea', 'ckeditor', 'ace'))) {
116  if ($this->tool == 'ckeditor' && !dol_textishtml($content)) { // We force content to be into HTML if we are using an advanced editor if content is not HTML.
117  $this->content = dol_nl2br($content);
118  } else {
119  $this->content = $content;
120  }
121  $this->htmlname = $htmlname;
122  $this->toolbarname = $shorttoolbarname;
123  $this->toolbarstartexpanded = $toolbarstartexpanded;
124  $this->rows = max(ROWS_3, $rows);
125  $this->cols = (preg_match('/%/', $cols) ? $cols : max(40, $cols)); // If $cols is a percent, we keep it, otherwise, we take max
126  $this->height = $height;
127  $this->width = $width;
128  $this->posx = empty($poscursor['x']) ? 0 : $poscursor['x'];
129  $this->posy = empty($poscursor['y']) ? 0 : $poscursor['y'];
130  }
131  }
132 
133  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
147  public function Create($noprint = 0, $morejs = '', $disallowAnyContent = true, $titlecontent = '', $option = '', $moreparam = '', $morecss = '')
148  {
149  // phpcs:enable
150  global $conf, $langs;
151 
152  $fullpage = false;
153  if (isset($conf->global->FCKEDITOR_ALLOW_ANY_CONTENT)) {
154  $disallowAnyContent = !getDolGlobalString('FCKEDITOR_ALLOW_ANY_CONTENT'); // Only predefined list of html tags are allowed or all
155  }
156 
157  $found = 0;
158  $out = '';
159 
160  if (in_array($this->tool, array('textarea', 'ckeditor'))) {
161  $found = 1;
162  //$out.= '<textarea id="'.$this->htmlname.'" name="'.$this->htmlname.'" '.($this->readonly?' disabled':'').' rows="'.$this->rows.'"'.(preg_match('/%/',$this->cols)?' style="margin-top: 5px; width: '.$this->cols.'"':' cols="'.$this->cols.'"').' class="flat">';
163  // TODO We do not put the 'disabled' tag because on a read form, it change style with grey.
164  //print $this->content;
165  $out .= '<textarea id="'.$this->htmlname.'" name="'.$this->htmlname.'" rows="'.$this->rows.'"'.(preg_match('/%/', $this->cols) ? ' style="margin-top: 5px; width: '.$this->cols.'"' : ' cols="'.$this->cols.'"').' '.($moreparam ? $moreparam : '').' class="flat '.$morecss.'">';
166  $out .= htmlspecialchars($this->content);
167  $out .= '</textarea>';
168 
169  if ($this->tool == 'ckeditor' && !empty($conf->use_javascript_ajax) && isModEnabled('fckeditor')) {
170  if (!defined('REQUIRE_CKEDITOR')) {
171  define('REQUIRE_CKEDITOR', '1');
172  }
173 
174  $skin = getDolGlobalString('FCKEDITOR_SKIN', 'moono-lisa'); // default with ckeditor 4.6 : moono-lisa
175 
176  $pluginstodisable = 'elementspath,save,flash,div,anchor';
177  if (!getDolGlobalString('FCKEDITOR_ENABLE_SPECIALCHAR')) {
178  $pluginstodisable .= ',specialchar';
179  }
180  if (!empty($conf->dol_optimize_smallscreen)) {
181  $pluginstodisable .= ',scayt,wsc,find,undo';
182  }
183  if (!getDolGlobalString('FCKEDITOR_ENABLE_WSC')) { // spellchecker has end of life december 2021
184  $pluginstodisable .= ',wsc';
185  }
186  if (!getDolGlobalString('FCKEDITOR_ENABLE_PDF')) {
187  $pluginstodisable .= ',exportpdf';
188  }
189  if (getDolGlobalInt('MAIN_DISALLOW_URL_INTO_DESCRIPTIONS') == 2) {
190  $this->uselocalbrowser = 0; // Can't use browser to navigate into files. Only links with "<img src=data:..." are allowed.
191  }
192  $scaytautostartup = '';
193  if (getDolGlobalString('FCKEDITOR_ENABLE_SCAYT_AUTOSTARTUP')) {
194  $scaytautostartup = 'scayt_autoStartup: true,';
195  $scaytautostartup .= 'scayt_sLang: \''.dol_escape_js($langs->getDefaultLang()).'\',';
196  } else {
197  $pluginstodisable .= ',scayt';
198  }
199 
200  $htmlencode_force = preg_match('/_encoded$/', $this->toolbarname) ? 'true' : 'false';
201 
202  $out .= '<!-- Output ckeditor $disallowAnyContent='.dol_escape_htmltag($disallowAnyContent).' toolbarname='.dol_escape_htmltag($this->toolbarname).' -->'."\n";
203  $out .= '<script nonce="'.getNonce().'" type="text/javascript">
204  $(document).ready(function () {
205  /* console.log("Run ckeditor"); */
206  /* if (CKEDITOR.loadFullCore) CKEDITOR.loadFullCore(); */
207  /* should be editor=CKEDITOR.replace but what if there is several editors ? */
208  tmpeditor = CKEDITOR.replace(\''.dol_escape_js($this->htmlname).'\',
209  {
210  /* property:xxx is same than CKEDITOR.config.property = xxx */
211  customConfig: ckeditorConfig,
212  removePlugins: \''.dol_escape_js($pluginstodisable).'\',
213  versionCheck: false,
214  readOnly: '.($this->readonly ? 'true' : 'false').',
215  htmlEncodeOutput: '.dol_escape_js($htmlencode_force).',
216  allowedContent: '.($disallowAnyContent ? 'false' : 'true').', /* Advanced Content Filter (ACF) is own when allowedContent is false */
217  extraAllowedContent: \'a[target];div{float,display}\', /* Add the style float and display into div to default other allowed tags */
218  disallowedContent: \'\', /* Tags that are not allowed */
219  fullPage: '.($fullpage ? 'true' : 'false').', /* if true, the html, header and body tags are kept */
220  toolbar: \''.dol_escape_js($this->toolbarname).'\',
221  toolbarStartupExpanded: '.($this->toolbarstartexpanded ? 'true' : 'false').',
222  width: '.($this->width ? '\''.dol_escape_js($this->width).'\'' : '\'\'').',
223  height: '.dol_escape_js($this->height).',
224  skin: \''.dol_escape_js($skin).'\',
225  '.$scaytautostartup.'
226  language: \''.dol_escape_js($langs->defaultlang).'\',
227  textDirection: \''.dol_escape_js($langs->trans("DIRECTION")).'\',
228  on : {
229  instanceReady : function(ev) {
230  console.log("ckeditor instanceReady");
231  // Output paragraphs as <p>Text</p>.
232  this.dataProcessor.writer.setRules( \'p\', {
233  indent : false,
234  breakBeforeOpen : true,
235  breakAfterOpen : false,
236  breakBeforeClose : false,
237  breakAfterClose : true
238  });
239  },
240  /* This is to remove the tab Link on image popup. Does not work, so commented */
241  /*
242  dialogDefinition: function (event) {
243  var dialogName = event.data.name;
244  var dialogDefinition = event.data.definition;
245  if (dialogName == \'image\') {
246  dialogDefinition.removeContents(\'Link\');
247  }
248  }
249  */
250  },
251  disableNativeSpellChecker: '.(!getDolGlobalString('CKEDITOR_NATIVE_SPELLCHECKER') ? 'true' : 'false');
252 
253  if ($this->uselocalbrowser) {
254  $out .= ','."\n";
255  // To use filemanager with old fckeditor (GPL)
256  // Note: ckeditorFilebrowserBrowseUrl and ckeditorFilebrowserImageBrowseUrl are defined in header by main.inc.php. They include url to browser with url of upload connector in parameter
257  $out .= ' filebrowserBrowseUrl : ckeditorFilebrowserBrowseUrl,';
258  $out .= ' filebrowserImageBrowseUrl : ckeditorFilebrowserImageBrowseUrl,';
259  //$out.= ' filebrowserUploadUrl : \''.DOL_URL_ROOT.'/includes/fckeditor/editor/filemanagerdol/connectors/php/upload.php?Type=File\',';
260  //$out.= ' filebrowserImageUploadUrl : \''.DOL_URL_ROOT.'/includes/fckeditor/editor/filemanagerdol/connectors/php/upload.php?Type=Image\',';
261  $out .= "\n";
262  // To use filemanager with ckfinder (Non free) and ckfinder directory is inside htdocs/includes
263  /* $out.= ' filebrowserBrowseUrl : \''.DOL_URL_ROOT.'/includes/ckfinder/ckfinder.html\',
264  filebrowserImageBrowseUrl : \''.DOL_URL_ROOT.'/includes/ckfinder/ckfinder.html?Type=Images\',
265  filebrowserFlashBrowseUrl : \''.DOL_URL_ROOT.'/includes/ckfinder/ckfinder.html?Type=Flash\',
266  filebrowserUploadUrl : \''.DOL_URL_ROOT.'/includes/ckfinder/core/connector/php/connector.php?command=QuickUpload&type=Files\',
267  filebrowserImageUploadUrl : \''.DOL_URL_ROOT.'/includes/ckfinder/core/connector/php/connector.php?command=QuickUpload&type=Images\',
268  filebrowserFlashUploadUrl : \''.DOL_URL_ROOT.'/includes/ckfinder/core/connector/php/connector.php?command=QuickUpload&type=Flash\','."\n";
269  */
270  $out .= ' filebrowserWindowWidth : \'900\',
271  filebrowserWindowHeight : \'500\',
272  filebrowserImageWindowWidth : \'900\',
273  filebrowserImageWindowHeight : \'500\'';
274  }
275  $out .= ' })'.$morejs; // end CKEditor.replace
276  // Show the CKEditor javascript object once loaded is ready 'For debug)
277  //$out .= '; CKEDITOR.on(\'instanceReady\', function(ck) { ck.editor.removeMenuItem(\'maximize\'); ck.editor.removeMenuItem(\'Undo\'); ck.editor.removeMenuItem(\'undo\'); console.log(ck.editor); console.log(ck.editor.toolbar[0]); }); ';
278  $out .= '});'."\n"; // end document.ready
279  $out .= '</script>'."\n";
280  }
281  }
282 
283  // Output editor ACE
284  // Warning: ace.js and ext-statusbar.js must be loaded by the parent page.
285  if (preg_match('/^ace/', $this->tool)) {
286  $found = 1;
287  $format = $option;
288 
289  $out .= "\n".'<!-- Output Ace editor -->'."\n";
290 
291  if ($titlecontent) {
292  $out .= '<div class="aceeditorstatusbar" id="statusBar'.$this->htmlname.'">'.$titlecontent;
293  $out .= ' &nbsp; - &nbsp; <span id="morelines" class="right classlink cursorpointer morelines'.$this->htmlname.'">'.dol_escape_htmltag($langs->trans("ShowMoreLines")).'</span> &nbsp; &nbsp; ';
294  $out .= '</div>';
295  $out .= '<script nonce="'.getNonce().'" type="text/javascript">'."\n";
296  $out .= 'jQuery(document).ready(function() {'."\n";
297  $out .= ' var aceEditor = window.ace.edit("'.$this->htmlname.'aceeditorid");
298  aceEditor.moveCursorTo('.($this->posy + 1).','.$this->posx.');
299  aceEditor.gotoLine('.($this->posy + 1).','.$this->posx.');
300  var StatusBar = window.ace.require("ace/ext/statusbar").StatusBar; // Init status bar. Need lib ext-statusbar
301  var statusBar = new StatusBar(aceEditor, document.getElementById("statusBar'.$this->htmlname.'")); // Init status bar. Need lib ext-statusbar
302 
303  var oldNbOfLines = 0;
304  jQuery(".morelines'.$this->htmlname.'").click(function() {
305  var aceEditorClicked = window.ace.edit("'.$this->htmlname.'aceeditorid");
306  currentline = aceEditorClicked.getOption("maxLines");
307  if (oldNbOfLines == 0)
308  {
309  oldNbOfLines = currentline;
310  }
311  console.log("We click on more lines, oldNbOfLines is "+oldNbOfLines+", we have currently "+currentline);
312  if (currentline < 500)
313  {
314  aceEditorClicked.setOptions({ maxLines: 500 });
315  }
316  else
317  {
318  aceEditorClicked.setOptions({ maxLines: oldNbOfLines });
319  }
320  });
321  })';
322  $out .= '</script>'."\n";
323  }
324 
325  $out .= '<pre id="'.$this->htmlname.'aceeditorid" style="'.($this->width ? 'width: '.$this->width.'px; ' : '');
326  $out .= ($this->height ? ' height: '.$this->height.'px; ' : '');
327  //$out.=" min-height: 100px;";
328  $out .= '">';
329  $out .= htmlspecialchars($this->content);
330  $out .= '</pre>';
331  $out .= '<input type="hidden" id="'.$this->htmlname.'_x" name="'.$this->htmlname.'_x">';
332  $out .= '<input type="hidden" id="'.$this->htmlname.'_y" name="'.$this->htmlname.'_y">';
333  $out .= '<textarea id="'.$this->htmlname.'" name="'.$this->htmlname.'" style="width:0px; height: 0px; display: none;">';
334  $out .= htmlspecialchars($this->content);
335  $out .= '</textarea>';
336 
337  $out .= '<script nonce="'.getNonce().'" type="text/javascript">'."\n";
338  $out .= 'var aceEditor = window.ace.edit("'.$this->htmlname.'aceeditorid");
339 
340  aceEditor.session.setMode("ace/mode/'.$format.'");
341  aceEditor.setOptions({
342  enableBasicAutocompletion: true, // the editor completes the statement when you hit Ctrl + Space. Need lib ext-language_tools.js
343  enableLiveAutocompletion: false, // the editor completes the statement while you are typing. Need lib ext-language_tools.js
344  showPrintMargin: false, // hides the vertical limiting strip
345  minLines: 10,
346  maxLines: '.(empty($this->height) ? '34' : (round($this->height / 10))).',
347  fontSize: "110%" // ensures that the editor fits in the environment
348  });
349 
350  // defines the style of the editor
351  aceEditor.setTheme("ace/theme/chrome");
352  // hides line numbers (widens the area occupied by error and warning messages)
353  //aceEditor.renderer.setOption("showLineNumbers", false);
354  // ensures proper autocomplete, validation and highlighting of JavaScript code
355  //aceEditor.getSession().setMode("ace/mode/javascript_expression");
356  '."\n";
357 
358  $out .= 'jQuery(document).ready(function() {
359  jQuery(".buttonforacesave").click(function() {
360  console.log("We click on savefile button for component '.dol_escape_js($this->htmlname).'");
361  var aceEditor = window.ace.edit("'.dol_escape_js($this->htmlname).'aceeditorid");
362  if (aceEditor) {
363  var cursorPos = aceEditor.getCursorPosition();
364  //console.log(cursorPos);
365  if (cursorPos) {
366  jQuery("#'.dol_escape_js($this->htmlname).'_x").val(cursorPos.column);
367  jQuery("#'.dol_escape_js($this->htmlname).'_y").val(cursorPos.row);
368  }
369  //console.log(aceEditor.getSession().getValue());
370  // Inject content of editor into the original HTML field.
371  jQuery("#'.dol_escape_js($this->htmlname).'").val(aceEditor.getSession().getValue());
372  /*if (jQuery("#'.dol_escape_js($this->htmlname).'").html().length > 0) return true;
373  else return false;*/
374  return true;
375  } else {
376  console.log("Failed to retrieve js object ACE from its name");
377  return false;
378  }
379  });
380  })';
381  $out .= '</script>'."\n";
382  }
383 
384  if (empty($found)) {
385  $out .= 'Error, unknown value for tool '.$this->tool.' in DolEditor Create function.';
386  }
387 
388  if ($noprint) {
389  return $out;
390  } else {
391  print $out;
392  }
393  }
394 }
Class to manage a WYSIWYG editor.
__construct($htmlname, $content, $width='', $height=200, $toolbarname='Basic', $toolbarlocation='In', $toolbarstartexpanded=false, $uselocalbrowser=1, $okforextendededitor=true, $rows=0, $cols='', $readonly=0, $poscursor=array())
Create an object to build an HTML area to edit a large string content.
Create($noprint=0, $morejs='', $disallowAnyContent=true, $titlecontent='', $option='', $moreparam='', $morecss='')
Output edit area inside the HTML stream.
dol_nl2br($stringtoencode, $nl2brmode=0, $forxml=false)
Replace CRLF in string with a HTML BR tag.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dol_escape_js($stringtoescape, $mode=0, $noescapebackslashn=0)
Returns text escaped for inclusion into javascript code.
dol_textishtml($msg, $option=0)
Return if a text is a html content.
getDolGlobalString($key, $default='')
Return dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input f...
if(preg_match('/crypted:/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
Definition: repair.php:123