[ Index ]

PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008]

title

Body

[close]

/lib/editor/htmlarea/ -> htmlarea.php (source)

   1  <?php
   2      include("../../../config.php");
   3      require_once($CFG->dirroot.'/lib/languages.php');
   4  
   5      $id            = optional_param('id', SITEID, PARAM_INT);
   6      $httpsrequired = optional_param('httpsrequired', 0, PARAM_BOOL); //flag indicating editor on page with required https
   7  
   8      require_course_login($id);
   9  
  10      $lastmodified = filemtime("htmlarea.php");
  11      $lifetime = 1800;
  12  
  13      // Commenting this out since it's creating problems
  14      // where solution seem to be hard to find...
  15      // http://moodle.org/mod/forum/discuss.php?d=34376
  16      //if ( function_exists('ob_gzhandler') ) {
  17      //    ob_start("ob_gzhandler");
  18      //}
  19  
  20      header("Content-type: application/x-javascript; charset: utf-8");  // Correct MIME type
  21      header("Last-Modified: " . gmdate("D, d M Y H:i:s", $lastmodified) . " GMT");
  22      header("Expires: " . gmdate("D, d M Y H:i:s", time() + $lifetime) . " GMT");
  23      header("Cache-control: max_age = $lifetime");
  24      header("Pragma: ");
  25  
  26      $lang = current_language();
  27  
  28      if (empty($lang)) {
  29          $lang = "en";
  30      }
  31  
  32      if ($httpsrequired) {
  33          // this is an ugly hack to allow partial operation of editor on pages that require https when loginhttps enabled
  34          // please note that some popups still show nonsecurre items and fullscreen may not function properly in IE
  35          $url = preg_replace('|https?://[^/]+|', '', $CFG->wwwroot).'/lib/editor/htmlarea/';
  36      } else {
  37          $url = $CFG->wwwroot.'/lib/editor/htmlarea/';
  38      }
  39  
  40      $strheading = get_string("heading", "editor");
  41      $strnormal = get_string("normal", "editor");
  42      $straddress = get_string("address", "editor");
  43      $strpreformatted = get_string("preformatted", "editor");
  44      $strlang = get_string('lang', 'editor');
  45      $strmulti = get_string('multi', 'editor');
  46  ?>
  47  
  48  // htmlArea v3.0 - Copyright (c) 2002, 2003 interactivetools.com, inc.
  49  // This copyright notice MUST stay intact for use (see license.txt).
  50  //
  51  // Portions (c) dynarch.com, 2003-2004
  52  //
  53  // A free WYSIWYG editor replacement for <textarea> fields.
  54  // For full source code and docs, visit http://www.interactivetools.com/
  55  //
  56  // Version 3.0 developed by Mihai Bazon.
  57  //   http://dynarch.com/mishoo
  58  //
  59  // $Id: htmlarea.php,v 1.24.2.5 2008/07/10 04:11:34 scyrma Exp $
  60  
  61  if (typeof _editor_url == "string") {
  62      // Leave exactly one backslash at the end of _editor_url
  63      _editor_url = _editor_url.replace(/\x2f*$/, '/');
  64  } else {
  65      //alert("WARNING: _editor_url is not set!  You should set this variable to the editor files path; it should preferably be an absolute path, like in '/htmlarea', but it can be relative if you prefer.  Further we will try to load the editor files correctly but we'll probably fail.");
  66      _editor_url = '<?php echo $url; ?>';// we need relative path to site root for editor in pages wit hrequired https
  67  }
  68  
  69  // make sure we have a language
  70  if (typeof _editor_lang == "string") {
  71      _editor_lang = "en"; // should always be english in moodle.
  72  } else {
  73      _editor_lang = "en";
  74  }
  75  
  76  // Creates a new HTMLArea object.  Tries to replace the textarea with the given
  77  // ID with it.
  78  function HTMLArea(textarea, config) {
  79      if (HTMLArea.checkSupportedBrowser()) {
  80          if (typeof config == "undefined") {
  81              this.config = new HTMLArea.Config();
  82          } else {
  83              this.config = config;
  84          }
  85          this._htmlArea = null;
  86          this._textArea = textarea;
  87          this._editMode = "wysiwyg";
  88          this.plugins = {};
  89          this._timerToolbar = null;
  90          this._timerUndo = null;
  91          this._undoQueue = new Array(this.config.undoSteps);
  92          this._undoPos = -1;
  93          this._customUndo = true;
  94          this._mdoc = document; // cache the document, we need it in plugins
  95          this.doctype = '';
  96          this.dropdowns = [];   // Array of select elements in the toolbar
  97      }
  98  };
  99  
 100  // load some scripts
 101  (function() {
 102      var scripts = HTMLArea._scripts = [ _editor_url + "htmlarea.js",
 103                          _editor_url + "dialog.js",
 104                          _editor_url + "popupwin.js" ];
 105      var head = document.getElementsByTagName("head")[0];
 106      // start from 1, htmlarea.js is already loaded
 107      for (var i = 1; i < scripts.length; ++i) {
 108          var script = document.createElement("script");
 109          script.src = scripts[i];
 110          head.appendChild(script);
 111      }
 112  })();
 113  
 114  // cache some regexps
 115  HTMLArea.RE_tagName = /(<\/|<)\s*([^ \t\n>]+)/ig;
 116  HTMLArea.RE_doctype = /(<!doctype((.|\n)*?)>)\n?/i;
 117  HTMLArea.RE_head    = /<head>((.|\n)*?)<\/head>/i;
 118  HTMLArea.RE_body    = /<body>((.|\n)*?)<\/body>/i;
 119  HTMLArea.RE_blocktag = /^(h1|h2|h3|h4|h5|h6|p|address|pre)$/i;
 120  HTMLArea.RE_junktag = /^\/($|\/)/;
 121  // Hopefully a complete list of tags that MSIEs parser will consider
 122  // as possible content tags. Retrieved from
 123  // http://www.echoecho.com/htmlreference.htm
 124  HTMLArea.RE_msietag  = /^\/?(a|abbr|acronym|address|applet|area|b|base|basefont|bdo|bgsound|big|blink|blockquote|body|br|button|caption|center|cite|code|col|colgroup|comment|dd|del|dfn|dir|div|dl|dt|em|embed|fieldset|font|form|frame|frameset|h1|h2|h3|h4|h5|h6|head|hr|html|i|iframe|ilayer|img|input|ins|isindex|kbd|keygen|label|layer|legend|li|link|map|marquee|menu|meta|multicol|nobr|noembed|noframes|nolayer|noscript|object|ol|optgroup|option|p|param|plaintext|pre|q|s|samp|script|select|server|small|spacer|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|title|tr|tt|u|ul|var)$/i
 125  
 126  HTMLArea.Config = function () {
 127      this.version = "3.0";
 128  
 129      this.width = "auto";
 130      this.height = "auto";
 131  
 132      // enable creation of a status bar?
 133      this.statusBar = true;
 134  
 135      // maximum size of the undo queue
 136      this.undoSteps = 20;
 137  
 138      // the time interval at which undo samples are taken
 139      this.undoTimeout = 500; // 1/2 sec.
 140  
 141      // the next parameter specifies whether the toolbar should be included
 142      // in the size or not.
 143      this.sizeIncludesToolbar = true;
 144  
 145      // if true then HTMLArea will retrieve the full HTML, starting with the
 146      // <HTML> tag.
 147      this.fullPage = false;
 148  
 149      // style included in the iframe document
 150      this.pageStyle = "body { background-color: #fff; font-family: 'Times New Roman', Times; } \n .lang { background-color: #dee; }";
 151  
 152      // set to true if you want Word code to be cleaned upon Paste
 153      this.killWordOnPaste = true;
 154  
 155      // BaseURL included in the iframe document
 156      this.baseURL = document.baseURI || document.URL;
 157      if (this.baseURL && this.baseURL.match(/(.*)\/([^\/]+)/))
 158          this.baseURL = RegExp.$1 + "/";
 159  
 160      // URL-s
 161      this.imgURL = "images/";
 162      this.popupURL = "popups/";
 163  
 164      this.toolbar = [
 165          [ "fontname", "space",
 166            "fontsize", "space",
 167            "formatblock", "space",
 168            "language", "space",
 169            "bold", "italic", "underline", "strikethrough", "separator",
 170            "subscript", "superscript", "separator",
 171            "clean", "separator", "undo", "redo" ],
 172  
 173          [ "justifyleft", "justifycenter", "justifyright", "justifyfull", "separator",
 174            "lefttoright", "righttoleft", "separator",
 175            "insertorderedlist", "insertunorderedlist", "outdent", "indent", "separator",
 176            "forecolor", "hilitecolor", "separator",
 177            "inserthorizontalrule", "createanchor", "createlink", "unlink", "nolink", "separator",
 178            "insertimage", "inserttable",
 179            "insertsmile", "insertchar", "search_replace",
 180            <?php if (!empty($CFG->aspellpath) && file_exists($CFG->aspellpath) && !empty($CFG->editorspelling)) {
 181                echo '"separator","spellcheck",';
 182              } ?>
 183            "separator", "htmlmode", "separator", "popupeditor"]
 184      ];
 185  
 186      this.fontname = {
 187          "Arial":       'arial,helvetica,sans-serif',
 188          "Courier New":     'courier new,courier,monospace',
 189          "Georgia":     'georgia,times new roman,times,serif',
 190          "Tahoma":      'tahoma,arial,helvetica,sans-serif',
 191          "Times New Roman": 'times new roman,times,serif',
 192          "Verdana":     'verdana,arial,helvetica,sans-serif',
 193          "Impact":           'impact',
 194          "WingDings":       'wingdings'
 195      };
 196  
 197      this.fontsize = {
 198          "1 (8 pt)":  "1",
 199          "2 (10 pt)": "2",
 200          "3 (12 pt)": "3",
 201          "4 (14 pt)": "4",
 202          "5 (18 pt)": "5",
 203          "6 (24 pt)": "6",
 204          "7 (36 pt)": "7"
 205      };
 206  
 207      this.formatblock = {
 208          "":"",
 209          "<?php echo $strheading ?> 1": "h1",
 210          "<?php echo $strheading ?> 2": "h2",
 211          "<?php echo $strheading ?> 3": "h3",
 212          "<?php echo $strheading ?> 4": "h4",
 213          "<?php echo $strheading ?> 5": "h5",
 214          "<?php echo $strheading ?> 6": "h6",
 215          "<?php echo $strnormal ?>": "p",
 216          "<?php echo $straddress ?>": "address",
 217          "<?php echo $strpreformatted ?>": "pre"
 218      };
 219  
 220      this.language = {
 221          "<?php echo $strlang; ?>":"",
 222          <?php
 223          $strlangarray = '';
 224          foreach ($LANGUAGES as $key => $name) {
 225              $key = str_replace('_', '-', $key);
 226              $strlangarray .= '"'.$key.'": "'.$key.'",';
 227          }
 228          $strlangarray .= '"'.$strmulti.'": "multi",';
 229  
 230          foreach ($LANGUAGES as $key => $name) {
 231              $key = str_replace('_', '-', $key);
 232              $strlangarray .= '"'.$key.' ": "'.$key.'_ML",';
 233          }
 234          $strlangarray = substr($strlangarray, 0, -1);
 235          echo $strlangarray;
 236          ?>
 237      };
 238  
 239      this.customSelects = {};
 240  
 241      function cut_copy_paste(e, cmd, obj) {
 242          e.execCommand(cmd);
 243      };
 244  
 245      this.btnList = {
 246          bold: [ "Bold", "ed_format_bold.gif", false, function(e) {e.execCommand("bold");} ],
 247          italic: [ "Italic", "ed_format_italic.gif", false, function(e) {e.execCommand("italic");} ],
 248          underline: [ "Underline", "ed_format_underline.gif", false, function(e) {e.execCommand("underline");} ],
 249          strikethrough: [ "Strikethrough", "ed_format_strike.gif", false, function(e) {e.execCommand("strikethrough");} ],
 250          subscript: [ "Subscript", "ed_format_sub.gif", false, function(e) {e.execCommand("subscript");} ],
 251          superscript: [ "Superscript", "ed_format_sup.gif", false, function(e) {e.execCommand("superscript");} ],
 252          justifyleft: [ "Justify Left", "ed_align_left.gif", false, function(e) {e.execCommand("justifyleft");} ],
 253          justifycenter: [ "Justify Center", "ed_align_center.gif", false, function(e) {e.execCommand("justifycenter");} ],
 254          justifyright: [ "Justify Right", "ed_align_right.gif", false, function(e) {e.execCommand("justifyright");} ],
 255          justifyfull: [ "Justify Full", "ed_align_justify.gif", false, function(e) {e.execCommand("justifyfull");} ],
 256          insertorderedlist: [ "Ordered List", "ed_list_num.gif", false, function(e) {e.execCommand("insertorderedlist");} ],
 257          insertunorderedlist: [ "Bulleted List", "ed_list_bullet.gif", false, function(e) {e.execCommand("insertunorderedlist");} ],
 258          outdent: [ "Decrease Indent", "ed_indent_less.gif", false, function(e) {e.execCommand("outdent");} ],
 259          indent: [ "Increase Indent", "ed_indent_more.gif", false, function(e) {e.execCommand("indent");} ],
 260          forecolor: [ "Font Color", "ed_color_fg.gif", false, function(e) {e.execCommand("forecolor");} ],
 261          hilitecolor: [ "Background Color", "ed_color_bg.gif", false, function(e) {e.execCommand("hilitecolor");} ],
 262          inserthorizontalrule: [ "Horizontal Rule", "ed_hr.gif", false, function(e) {e.execCommand("inserthorizontalrule");} ],
 263          createanchor: [ "Create anchor", "ed_anchor.gif", false, function(e) {e.execCommand("createanchor", true);} ],
 264          createlink: [ "Insert Web Link", "ed_link.gif", false, function(e) {e.execCommand("createlink", true);} ],
 265          unlink: [ "Remove Link", "ed_unlink.gif", false, function(e) {e.execCommand("unlink");} ],
 266          nolink: [ "No link", "ed_nolink.gif", false, function(e) {e.execCommand("nolink");} ],
 267          insertimage: [ "Insert/Modify Image", "ed_image.gif", false, function(e) {e.execCommand("insertimage");} ],
 268          inserttable: [ "Insert Table", "insert_table.gif", false, function(e) {e.execCommand("inserttable");} ],
 269          htmlmode: [ "Toggle HTML Source", "ed_html.gif", true, function(e) {e.execCommand("htmlmode");} ],
 270          popupeditor: [ "Enlarge Editor", "fullscreen_maximize.gif", true, function(e) {e.execCommand("popupeditor");} ],
 271          about: [ "About this editor", "ed_about.gif", true, function(e) {e.execCommand("about");} ],
 272          showhelp: [ "Help using editor", "ed_help.gif", true, function(e) {e.execCommand("showhelp");} ],
 273          undo: [ "Undoes your last action", "ed_undo.gif", false, function(e) {e.execCommand("undo");} ],
 274          redo: [ "Redoes your last action", "ed_redo.gif", false, function(e) {e.execCommand("redo");} ],
 275          clean: [ "Clean Word HTML", "ed_wordclean.gif", false, function(e) {e.execCommand("killword"); }],
 276          lefttoright: [ "Direction left to right", "ed_left_to_right.gif", false, function(e) {e.execCommand("lefttoright");} ],
 277          righttoleft: [ "Direction right to left", "ed_right_to_left.gif", false, function(e) {e.execCommand("righttoleft");} ],
 278          <?php if (!empty($CFG->aspellpath) && file_exists($CFG->aspellpath) && !empty($CFG->editorspelling)) {
 279              echo 'spellcheck: ["Spell-check", "spell-check.gif", false, spellClickHandler ],'."\n";
 280          }?>
 281          insertsmile: ["Insert Smiley", "em.icon.smile.gif", false, function(e) {e.execCommand("insertsmile");} ],
 282          insertchar: [ "Insert Char", "icon_ins_char.gif", false, function(e) {e.execCommand("insertchar");} ],
 283          search_replace: [ "Search and replace", "ed_replace.gif", false, function(e) {e.execCommand("searchandreplace");} ]
 284      };
 285  
 286      // initialize tooltips from the I18N module and generate correct image path
 287      for (var i in this.btnList) {
 288          var btn = this.btnList[i];
 289          btn[1] = _editor_url + this.imgURL + btn[1];
 290          if (typeof HTMLArea.I18N.tooltips[i] != "undefined") {
 291              btn[0] = HTMLArea.I18N.tooltips[i];
 292          }
 293      }
 294  };
 295  
 296  HTMLArea.Config.prototype.registerButton = function(id, tooltip, image, textMode, action, context) {
 297      var the_id;
 298      if (typeof id == "string") {
 299          the_id = id;
 300      } else if (typeof id == "object") {
 301          the_id = id.id;
 302      } else {
 303          alert("ERROR [HTMLArea.Config::registerButton]:\ninvalid arguments");
 304          return false;
 305      }
 306      // check for existing id
 307      if (typeof this.customSelects[the_id] != "undefined") {
 308          // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA dropdown with the same ID already exists.");
 309      }
 310      if (typeof this.btnList[the_id] != "undefined") {
 311          // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA button with the same ID already exists.");
 312      }
 313      switch (typeof id) {
 314          case "string": this.btnList[id] = [ tooltip, image, textMode, action, context ]; break;
 315          case "object": this.btnList[id.id] = [ id.tooltip, id.image, id.textMode, id.action, id.context ]; break;
 316      }
 317  };
 318  
 319  HTMLArea.Config.prototype.registerDropdown = function(object) {
 320      // check for existing id
 321      if (typeof this.customSelects[object.id] != "undefined") {
 322          // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA dropdown with the same ID already exists.");
 323      }
 324      if (typeof this.btnList[object.id] != "undefined") {
 325          // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA button with the same ID already exists.");
 326      }
 327      this.customSelects[object.id] = object;
 328  };
 329  
 330  HTMLArea.Config.prototype.hideSomeButtons = function(remove) {
 331      var toolbar = this.toolbar;
 332      for (var i in toolbar) {
 333          var line = toolbar[i];
 334          for (var j = line.length; --j >= 0; ) {
 335              if (remove.indexOf(" " + line[j] + " ") >= 0) {
 336                  var len = 1;
 337                  if (/separator|space/.test(line[j + 1])) {
 338                      len = 2;
 339                  }
 340                  line.splice(j, len);
 341              }
 342          }
 343      }
 344  };
 345  
 346  /** Helper function: replace all TEXTAREA-s in the document with HTMLArea-s. */
 347  HTMLArea.replaceAll = function(config) {
 348      var tas = document.getElementsByTagName("textarea");
 349      for (var i = tas.length; i > 0; (new HTMLArea(tas[--i], config)).generate());
 350  };
 351  
 352  /** Helper function: replaces the TEXTAREA with the given ID with HTMLArea. */
 353  HTMLArea.replace = function(id, config) {
 354      var ta = HTMLArea.getElementById("textarea", id);
 355      return ta ? (new HTMLArea(ta, config)).generate() : null;
 356  };
 357  
 358  // Creates the toolbar and appends it to the _htmlarea
 359  HTMLArea.prototype._createToolbar = function () {
 360      var editor = this;  // to access this in nested functions
 361  
 362      var toolbar = document.createElement("div");
 363      this._toolbar = toolbar;
 364      toolbar.className = "toolbar";
 365      toolbar.unselectable = "1";
 366      var tb_row = null;
 367      var tb_objects = new Object();
 368      this._toolbarObjects = tb_objects;
 369  
 370      // creates a new line in the toolbar
 371      function newLine() {
 372          var table = document.createElement("table");
 373          table.border = "0px";
 374          table.cellSpacing = "0px";
 375          table.cellPadding = "0px";
 376          toolbar.appendChild(table);
 377          // TBODY is required for IE, otherwise you don't see anything
 378          // in the TABLE.
 379          var tb_body = document.createElement("tbody");
 380          table.appendChild(tb_body);
 381          tb_row = document.createElement("tr");
 382          tb_body.appendChild(tb_row);
 383      }; // END of function: newLine
 384      // init first line
 385      newLine();
 386  
 387      function setButtonStatus(id, newval) {
 388          var oldval = this[id];
 389          var el = this.element;
 390          if (oldval != newval) {
 391              switch (id) {
 392                  case "enabled":
 393                  if (newval) {
 394                      HTMLArea._removeClass(el, "buttonDisabled");
 395                      el.disabled = false;
 396                  } else {
 397                      HTMLArea._addClass(el, "buttonDisabled");
 398                      el.disabled = true;
 399                  }
 400                  break;
 401                  case "active":
 402                  if (newval) {
 403                      HTMLArea._addClass(el, "buttonPressed");
 404                  } else {
 405                      HTMLArea._removeClass(el, "buttonPressed");
 406                  }
 407                  break;
 408              }
 409              this[id] = newval;
 410          }
 411      }; // END of function: setButtonStatus
 412  
 413      function createSelect(txt) {
 414          var options = null;
 415          var el = null;
 416          var cmd = null;
 417          var customSelects = editor.config.customSelects;
 418          var context = null;
 419          switch (txt) {
 420              case "fontsize":
 421              case "fontname":
 422              case "formatblock":
 423              case "language":
 424              options = editor.config[txt];
 425              cmd = txt;
 426              break;
 427              default:
 428              // try to fetch it from the list of registered selects
 429              cmd = txt;
 430              var dropdown = customSelects[cmd];
 431              if (typeof dropdown != "undefined") {
 432                  options = dropdown.options;
 433                  context = dropdown.context;
 434              } else {
 435                  alert("ERROR [createSelect]:\nCan't find the requested dropdown definition");
 436              }
 437              break;
 438          }
 439          if (options) {
 440              el = document.createElement("select");
 441              var obj = {
 442                  name    : txt, // field name
 443                  element : el,   // the UI element (SELECT)
 444                  enabled : true, // is it enabled?
 445                  text    : false, // enabled in text mode?
 446                  cmd : cmd, // command ID
 447                  state   : setButtonStatus, // for changing state
 448                  context : context
 449              };
 450              tb_objects[txt] = obj;
 451              for (var i in options) {
 452                  var op = document.createElement("option");
 453                  op.appendChild(document.createTextNode(i));
 454                  op.value = options[i];
 455                  el.appendChild(op);
 456              }
 457              HTMLArea._addEvent(el, "change", function () {
 458                  editor._comboSelected(el, txt);
 459              });
 460          }
 461          editor.dropdowns[txt] = el;  // Keep track of the element for keyboard
 462                                       // access later.
 463          return el;
 464      }; // END of function: createSelect
 465  
 466      // appends a new button to toolbar
 467      function createButton(txt) {
 468          // the element that will be created
 469          var el = null;
 470          var btn = null;
 471          switch (txt) {
 472              case "separator":
 473              el = document.createElement("div");
 474              el.className = "separator";
 475              break;
 476              case "space":
 477              el = document.createElement("div");
 478              el.className = "space";
 479              break;
 480              case "linebreak":
 481              newLine();
 482              return false;
 483              case "textindicator":
 484              el = document.createElement("div");
 485              el.appendChild(document.createTextNode("A"));
 486              el.className = "indicator";
 487              el.title = HTMLArea.I18N.tooltips.textindicator;
 488              var obj = {
 489                  name    : txt, // the button name (i.e. 'bold')
 490                  element : el, // the UI element (DIV)
 491                  enabled : true, // is it enabled?
 492                  active  : false, // is it pressed?
 493                  text    : false, // enabled in text mode?
 494                  cmd : "textindicator", // the command ID
 495                  state   : setButtonStatus // for changing state
 496              };
 497              tb_objects[txt] = obj;
 498              break;
 499              default:
 500              btn = editor.config.btnList[txt];
 501          }
 502          if (!el && btn) {
 503              el = document.createElement("div");
 504              el.title = btn[0];
 505              el.className = "button";
 506              // let's just pretend we have a button object, and
 507              // assign all the needed information to it.
 508              var obj = {
 509                  name    : txt, // the button name (i.e. 'bold')
 510                  element : el, // the UI element (DIV)
 511                  enabled : true, // is it enabled?
 512                  active  : false, // is it pressed?
 513                  text    : btn[2], // enabled in text mode?
 514                  cmd : btn[3], // the command ID
 515                  state   : setButtonStatus, // for changing state
 516                  context : btn[4] || null // enabled in a certain context?
 517              };
 518              tb_objects[txt] = obj;
 519              // handlers to emulate nice flat toolbar buttons
 520              HTMLArea._addEvent(el, "mouseover", function () {
 521                  if (obj.enabled) {
 522                      HTMLArea._addClass(el, "buttonHover");
 523                  }
 524              });
 525              HTMLArea._addEvent(el, "mouseout", function () {
 526                  if (obj.enabled) with (HTMLArea) {
 527                      _removeClass(el, "buttonHover");
 528                      _removeClass(el, "buttonActive");
 529                      (obj.active) && _addClass(el, "buttonPressed");
 530                  }
 531              });
 532              HTMLArea._addEvent(el, "mousedown", function (ev) {
 533                  if (obj.enabled) with (HTMLArea) {
 534                      _addClass(el, "buttonActive");
 535                      _removeClass(el, "buttonPressed");
 536                      _stopEvent(is_ie ? window.event : ev);
 537                  }
 538              });
 539              // when clicked, do the following:
 540              HTMLArea._addEvent(el, "click", function (ev) {
 541                  if (obj.enabled) with (HTMLArea) {
 542                      _removeClass(el, "buttonActive");
 543                      _removeClass(el, "buttonHover");
 544                      obj.cmd(editor, obj.name, obj);
 545                      _stopEvent(is_ie ? window.event : ev);
 546                  }
 547              });
 548              var img = document.createElement("img");
 549              img.src = btn[1];
 550              img.style.width = "18px";
 551              img.style.height = "18px";
 552              el.appendChild(img);
 553          } else if (!el) {
 554              el = createSelect(txt);
 555          }
 556          if (el) {
 557              var tb_cell = document.createElement("td");
 558              tb_row.appendChild(tb_cell);
 559              tb_cell.appendChild(el);
 560          } else {
 561              alert("FIXME: Unknown toolbar item: " + txt);
 562          }
 563          return el;
 564      };
 565  
 566      var first = true;
 567      for (var i in this.config.toolbar) {
 568          if (this.config.toolbar.propertyIsEnumerable(i)) { // fix for prototype.js compatibility
 569          if (!first) {
 570              createButton("linebreak");
 571          } else {
 572              first = false;
 573          }
 574          var group = this.config.toolbar[i];
 575          for (var j in group) {
 576                  if (group.propertyIsEnumerable(j)) { // fix for prototype.js compatibility
 577              var code = group[j];
 578              if (/^([IT])\[(.*?)\]/.test(code)) {
 579                  // special case, create text label
 580                  var l7ed = RegExp.$1 == "I"; // localized?
 581                  var label = RegExp.$2;
 582                  if (l7ed) {
 583                      label = HTMLArea.I18N.custom[label];
 584                  }
 585                  var tb_cell = document.createElement("td");
 586                  tb_row.appendChild(tb_cell);
 587                  tb_cell.className = "label";
 588                  tb_cell.innerHTML = label;
 589              } else {
 590                  createButton(code);
 591                      }
 592                  }
 593              }
 594          }
 595      }
 596  
 597      this._htmlArea.appendChild(toolbar);
 598  };
 599  
 600  HTMLArea.prototype._createStatusBar = function() {
 601      var statusbar = document.createElement("div");
 602      statusbar.className = "statusBar";
 603      this._htmlArea.appendChild(statusbar);
 604      this._statusBar = statusbar;
 605      // statusbar.appendChild(document.createTextNode(HTMLArea.I18N.msg["Path"] + ": "));
 606      // creates a holder for the path view
 607      div = document.createElement("span");
 608      div.className = "statusBarTree";
 609      div.innerHTML = HTMLArea.I18N.msg["Path"] + ": ";
 610      this._statusBarTree = div;
 611      this._statusBar.appendChild(div);
 612      if (!this.config.statusBar) {
 613          // disable it...
 614          statusbar.style.display = "none";
 615      }
 616  };
 617  
 618  // Creates the HTMLArea object and replaces the textarea with it.
 619  HTMLArea.prototype.generate = function () {
 620      var editor = this;  // we'll need "this" in some nested functions
 621  
 622      // get the textarea
 623      var textarea = this._textArea;
 624      if (typeof textarea == "string") {
 625          // it's not element but ID
 626          this._textArea = textarea = HTMLArea.getElementById("textarea", textarea);
 627      }
 628      // Fix for IE's sticky bug. Editor doesn't load
 629      // editing area.
 630      var height;
 631      if ( textarea.offsetHeight && textarea.offsetHeight > 0 ) {
 632          height = textarea.offsetHeight;
 633      } else {
 634          height = 300;
 635      }
 636      this._ta_size = {
 637          w: textarea.offsetWidth,
 638          h: height
 639      };
 640      textarea.style.display = "none";
 641  
 642      // create the editor framework
 643      var htmlarea = document.createElement("div");
 644      htmlarea.className = "htmlarea";
 645      this._htmlArea = htmlarea;
 646  
 647      // insert the editor before the textarea.
 648      //Bug fix - unless the textarea is nested within its label, in which case insert editor before label.
 649      if (textarea.parentNode.nodeName.toLowerCase()=='label') {
 650          textarea.parentNode.parentNode.insertBefore(htmlarea,textarea.parentNode);
 651      }
 652      else {
 653          textarea.parentNode.insertBefore(htmlarea, textarea);
 654      }
 655  
 656      if (textarea.form) {
 657          // we have a form, on submit get the HTMLArea content and
 658          // update original textarea.
 659          var f = textarea.form;
 660          if (typeof f.onsubmit == "function") {
 661              var funcref = f.onsubmit;
 662              if (typeof f.__msh_prevOnSubmit == "undefined") {
 663                  f.__msh_prevOnSubmit = [];
 664              }
 665              f.__msh_prevOnSubmit.push(funcref);
 666          }
 667          f.onsubmit = function() {
 668              // Moodle hack. Bug fix #2736
 669              var test = editor.getHTML();
 670              test = test.replace(/<br \/>/gi, '');
 671              test = test.replace(/\&nbsp\;/gi, '');
 672              test = test.trim();
 673              //alert(test + test.length);
 674              if (test.length < 1) {
 675                  editor._textArea.value = test.trim();
 676              } else {
 677                  editor._textArea.value = editor.getHTML();
 678              }
 679              // Moodle hack end.
 680              var a = this.__msh_prevOnSubmit;
 681              var ret = true;
 682              // call previous submit methods if they were there.
 683              if (typeof a != "undefined") {
 684                  for (var i = a.length; --i >= 0;) {
 685                      ret = a[i]() && ret;
 686                  }
 687              }
 688              return ret;
 689          };
 690          if (typeof f.onreset == "function") {
 691              var funcref = f.onreset;
 692              if (typeof f.__msh_prevOnReset == "undefined") {
 693                  f.__msh_prevOnReset = [];
 694              }
 695              f.__msh_prevOnReset.push(funcref);
 696          }
 697          f.onreset = function() {
 698              editor.setHTML(editor._textArea.value);
 699              editor.updateToolbar();
 700              var a = this.__msh_prevOnReset;
 701              // call previous reset methods if they were there.
 702              if (typeof a != "undefined") {
 703                  for (var i = a.length; --i >= 0;) {
 704                      a[i]();
 705                  }
 706              }
 707          };
 708      }
 709  
 710      // add a handler for the "back/forward" case -- on body.unload we save
 711      // the HTML content into the original textarea.
 712      try {
 713      window.onunload = function() {
 714          editor._textArea.value = editor.getHTML();
 715      };
 716      } catch(e) {};
 717  
 718      // creates & appends the toolbar
 719      this._createToolbar();
 720  
 721      // create the IFRAME
 722      var iframe = document.createElement("iframe");
 723  
 724      iframe.src = "about:blank";
 725  
 726      iframe.className = "iframe";
 727  
 728      htmlarea.appendChild(iframe);
 729  
 730      var editor = this
 731      editor._iframe = iframe;
 732      var doc = editor._iframe.contentWindow.document;
 733      editor._doc = doc;
 734  
 735      // Generate iframe content
 736      var html = ""
 737      if (!editor.config.fullPage) {
 738          html = "<html>\n";
 739          html += "<head>\n";
 740          html += '<meta http-equiv="content-type" content="text/html; charset=utf-8" />\n';
 741          if (editor.config.baseURL)
 742              html += '<base href="' + editor.config.baseURL + '" />';
 743          html += '<style type="text/css">\n' + editor.config.pageStyle + "td { border: 1px dotted gray; } body { direction: <?php echo get_string('thisdirection')?>; } </style>\n"; // RTL support: direction added for RTL support
 744          html += "</head>\n";
 745          html += '<body>\n';
 746          html += editor._textArea.value;
 747          html = html.replace(/<nolink>/gi, '<span class="nolink">').
 748                      replace(/<\/nolink>/gi, '</span>');
 749          html += "</body>\n";
 750          html += "</html>";
 751      } else {
 752          html = editor._textArea.value;
 753          if (html.match(HTMLArea.RE_doctype)) {
 754              editor.setDoctype(RegExp.$1);
 755              html = html.replace(HTMLArea.RE_doctype, "");
 756          }
 757      }
 758  
 759      // Write content to iframe
 760      doc.open();
 761      doc.write(html);
 762      doc.close();
 763  
 764      // The magic: onClick the designMode is set to 'on'
 765      // This one is for click on HTMLarea toolbar and else
 766      if(HTMLArea.is_gecko) {
 767          HTMLArea._addEvents(
 768            this._htmlArea,
 769            ["mousedown"],
 770            function(event) {
 771              if(editor.designModeIsOn != true)
 772              {
 773                  editor.designModeIsOn = true;
 774                  try {
 775                    doc.designMode = "on";
 776                  } catch (e) {
 777                    alert(e)
 778                  };
 779              }
 780            }
 781          );
 782  
 783          // This one is for click in iframe
 784          HTMLArea._addEvents(
 785            editor._iframe.contentWindow,
 786            ["mousedown"],
 787            function(event) {
 788              if(editor.designModeIsOn != true)
 789              {
 790                  editor.designModeIsOn = true;
 791                  try {
 792                    doc.designMode = "on";
 793                  } catch (e) {
 794                    alert(e)
 795                  };
 796              }
 797            }
 798          );
 799      }
 800      // creates & appends the status bar, if the case
 801      this._createStatusBar();
 802  
 803      // remove the default border as it keeps us from computing correctly
 804      // the sizes.  (somebody tell me why doesn't this work in IE)
 805  
 806      if (!HTMLArea.is_ie) {
 807          iframe.style.borderWidth = "1px";
 808      }
 809  
 810      // size the IFRAME according to user's prefs or initial textarea
 811      var height = (this.config.height == "auto" ? (this._ta_size.h) : this.config.height);
 812      height = parseInt(height);
 813      var width = (this.config.width == "auto" ? (this._toolbar.offsetWidth) : this.config.width);
 814      width = (width == 0 ? 598 : width);
 815      //width = Math.max(parseInt(width), 598);
 816  
 817      width = String(width);
 818      if (width.match(/^\d+$/)) { // is this a pure int? if so, let it be in px, and remove 2px
 819          height -= 2;
 820          width  -= 2;
 821          width=width+"px";
 822      }
 823  
 824      iframe.style.width = width;
 825  
 826      if (this.config.sizeIncludesToolbar) {
 827          // substract toolbar height
 828          height -= this._toolbar.offsetHeight;
 829          height -= this._statusBar.offsetHeight;
 830      }
 831      if (height < 0) {
 832          height = 0;
 833      }
 834      iframe.style.height = height + "px";
 835  
 836      // the editor including the toolbar now have the same size as the
 837      // original textarea.. which means that we need to reduce that a bit.
 838      textarea.style.width = iframe.style.width;
 839      textarea.style.height = iframe.style.height;
 840  
 841      if (HTMLArea.is_ie) {
 842          doc.body.contentEditable = true;
 843      }
 844  
 845      // intercept some events; for updating the toolbar & keyboard handlers
 846      HTMLArea._addEvents
 847            (doc, ["keydown", "keypress", "mousedown", "mouseup", "drag"],
 848            function (event) {
 849                return editor._editorEvent(HTMLArea.is_ie ? editor._iframe.contentWindow.event : event);
 850            });
 851  
 852      // check if any plugins have registered refresh handlers
 853      for (var i in editor.plugins) {
 854          var plugin = editor.plugins[i].instance;
 855          if (typeof plugin.onGenerate == "function") {
 856              plugin.onGenerate();
 857          }
 858          if (typeof plugin.onGenerateOnce == "function") {
 859              plugin.onGenerateOnce();
 860              plugin.onGenerateOnce = null;
 861          }
 862      }
 863  
 864      // Moodle fix for bug Bug #2521 Too long statusbar line in IE
 865      //
 866      //setTimeout(function() {
 867      //    editor.updateToolbar();
 868      //}, 250);
 869  
 870      if (typeof editor.onGenerate == "function") {
 871          editor.onGenerate();
 872      }
 873  };
 874  
 875  
 876  // Switches editor mode; parameter can be "textmode" or "wysiwyg".  If no
 877  // parameter was passed this function toggles between modes.
 878  HTMLArea.prototype.setMode = function(mode) {
 879      if (typeof mode == "undefined") {
 880          mode = ((this._editMode == "textmode") ? "wysiwyg" : "textmode");
 881      }
 882      switch (mode) {
 883          case "textmode":
 884          this._textArea.value = this.getHTML();
 885          this._iframe.style.display = "none";
 886          this._textArea.style.display = "block";
 887          if (this.config.statusBar) {
 888              while(this._statusBar.childNodes.length>0) {
 889                  this._statusBar.removeChild(this._statusBar.childNodes[0]);
 890              }
 891  
 892              this._statusBar.appendChild(document.createTextNode(HTMLArea.I18N.msg["TEXT_MODE"]));
 893          }
 894          break;
 895          case "wysiwyg":
 896          if (HTMLArea.is_gecko) {
 897              // disable design mode before changing innerHTML
 898              try {
 899              this._doc.designMode = "off";
 900              } catch(e) {};
 901          }
 902          if (!this.config.fullPage)
 903              this._doc.body.innerHTML = this.getHTML();
 904          else
 905              this.setFullHTML(this.getHTML());
 906          this._iframe.style.display = "block";
 907          this._textArea.style.display = "none";
 908          if (HTMLArea.is_gecko) {
 909              // we need to refresh that info for Moz-1.3a
 910              try {
 911              this._doc.designMode = "on";
 912              //this._doc.focus();
 913              } catch(e) {};
 914          }
 915          if (this.config.statusBar) {
 916              this._statusBar.innerHTML = '';
 917              this._statusBar.appendChild(this._statusBarTree);
 918          }
 919          break;
 920          default:
 921          alert("Mode <" + mode + "> not defined!");
 922          return false;
 923      }
 924      this._editMode = mode;
 925      this.focusEditor();
 926  };
 927  
 928  HTMLArea.prototype.setFullHTML = function(html) {
 929      var save_multiline = RegExp.multiline;
 930      RegExp.multiline = true;
 931      if (html.match(HTMLArea.RE_doctype)) {
 932          this.setDoctype(RegExp.$1);
 933          html = html.replace(HTMLArea.RE_doctype, "");
 934      }
 935      RegExp.multiline = save_multiline;
 936      if (!HTMLArea.is_ie) {
 937          if (html.match(HTMLArea.RE_head))
 938              this._doc.getElementsByTagName("head")[0].innerHTML = RegExp.$1;
 939          if (html.match(HTMLArea.RE_body))
 940              this._doc.getElementsByTagName("body")[0].innerHTML = RegExp.$1;
 941      } else {
 942          var html_re = /<html>((.|\n)*?)<\/html>/i;
 943          html = html.replace(html_re, "$1");
 944          this._doc.open();
 945          this._doc.write(html);
 946          this._doc.close();
 947          this._doc.body.contentEditable = true;
 948          return true;
 949      }
 950  };
 951  
 952  // Category: PLUGINS
 953  
 954  HTMLArea.prototype.registerPlugin2 = function(plugin, args) {
 955      if (typeof plugin == "string")
 956          plugin = eval(plugin);
 957      var obj = new plugin(this, args);
 958      if (obj) {
 959          var clone = {};
 960          var info = plugin._pluginInfo;
 961          for (var i in info)
 962              clone[i] = info[i];
 963          clone.instance = obj;
 964          clone.args = args;
 965          this.plugins[plugin._pluginInfo.name] = clone;
 966      } else
 967          alert("Can't register plugin " + plugin.toString() + ".");
 968  };
 969  
 970  // Create the specified plugin and register it with this HTMLArea
 971  HTMLArea.prototype.registerPlugin = function() {
 972      var plugin = arguments[0];
 973      var args = [];
 974      for (var i = 1; i < arguments.length; ++i)
 975          args.push(arguments[i]);
 976      this.registerPlugin2(plugin, args);
 977  };
 978  
 979  HTMLArea.loadPlugin = function(pluginName) {
 980      var dir = _editor_url + "plugins/" + pluginName;
 981      var plugin = pluginName.replace(/([a-z])([A-Z])([a-z])/g,
 982                      function (str, l1, l2, l3) {
 983                          return l1 + "-" + l2.toLowerCase() + l3;
 984                      }).toLowerCase() + ".js";
 985      var plugin_file = dir + "/" + plugin;
 986      var plugin_lang = dir + "/lang/" + HTMLArea.I18N.lang + ".js";
 987      HTMLArea._scripts.push(plugin_file, plugin_lang);
 988      document.write("<script type='text/javascript' src='" + plugin_file + "'></script>");
 989      document.write("<script type='text/javascript' src='" + plugin_lang + "'></script>");
 990  };
 991  
 992  HTMLArea.loadStyle = function(style, plugin) {
 993      var url = _editor_url || '';
 994      if (typeof plugin != "undefined") {
 995          url += "plugins/" + plugin + "/";
 996      }
 997      url += style;
 998      document.write("<style type='text/css'>@import url(" + url + ");</style>");
 999  };
1000  HTMLArea.loadStyle("htmlarea.css");
1001  
1002  // Category: EDITOR UTILITIES
1003  
1004  // The following function is a slight variation of the word cleaner code posted
1005  // by Weeezl (user @ InteractiveTools forums).
1006  HTMLArea.prototype._wordClean = function() {
1007      this._unnestBlocks();
1008  
1009      var D = this.getInnerHTML();
1010      if (/[Mm]so/.test(D)) {
1011  
1012          // make one line
1013          D = D.replace(/\r\n/g, '\[br\]').
1014              replace(/\n/g, '').
1015              replace(/\r/g, '').
1016              replace(/\&nbsp\;/g,' ');
1017  
1018          // keep tags, strip attributes
1019          D = D.replace(/ class=[^\s|>]*/gi,'').
1020              //replace(/<p [^>]*TEXT-ALIGN: justify[^>]*>/gi,'<p align="justify">').
1021              replace(/ style=\"[^>]*\"/gi,'').
1022              replace(/ align=[^\s|>]*/gi,'');
1023  
1024          //clean up tags
1025          D = D.replace(/<b [^>]*>/gi,'<b>').
1026              replace(/<i [^>]*>/gi,'<i>').
1027              replace(/<li [^>]*>/gi,'<li>').
1028              replace(/<ul [^>]*>/gi,'<ul>');
1029  
1030          // replace outdated tags
1031          D = D.replace(/<b>/gi,'<strong>').
1032              replace(/<\/b>/gi,'</strong>');
1033  
1034          // mozilla doesn't like <em> tags
1035          D = D.replace(/<em>/gi,'<i>').
1036              replace(/<\/em>/gi,'</i>');
1037  
1038          // kill unwanted tags
1039          D = D.replace(/<\?xml:[^>]*>/g, '').       // Word xml
1040              replace(/<\/?st1:[^>]*>/g,'').     // Word SmartTags
1041              replace(/<\/?[a-z]\:[^>]*>/g,'').  // All other funny Word non-HTML stuff
1042              replace(/<\/?personname[^>]*>/gi,'').
1043              replace(/<\/?font[^>]*>/gi,'').    // Disable if you want to keep font formatting
1044              replace(/<\/?span[^>]*>/gi,' ').
1045              replace(/<\/?div[^>]*>/gi,' ').
1046              replace(/<\/?pre[^>]*>/gi,' ').
1047              replace(/<(\/?)(h[1-6]+)[^>]*>/gi,'<$1$2>');
1048  
1049          // Lorenzo Nicola's addition
1050          // to get rid off silly word generated tags.
1051          D = D.replace(/<!--\[[^\]]*\]-->/gi,' ');
1052  
1053          //remove empty tags
1054          //D = D.replace(/<strong><\/strong>/gi,'').
1055          //replace(/<i><\/i>/gi,'').
1056          //replace(/<P[^>]*><\/P>/gi,'');
1057          D = D.replace(/<h[1-6]+>\s?<\/h[1-6]+>/gi, ''); // Remove empty headings
1058  
1059          // nuke double tags
1060          oldlen = D.length + 1;
1061          while(oldlen > D.length) {
1062              oldlen = D.length;
1063              // join us now and free the tags, we'll be free hackers, we'll be free... ;-)
1064              D = D.replace(/<([a-z][a-z]*)> *<\/\1>/gi,' ').
1065                  replace(/<([a-z][a-z]*)> *<([a-z][^>]*)> *<\/\1>/gi,'<$2>');
1066          }
1067          D = D.replace(/<([a-z][a-z]*)><\1>/gi,'<$1>').
1068              replace(/<\/([a-z][a-z]*)><\/\1>/gi,'<\/$1>');
1069  
1070          // nuke double spaces
1071          D = D.replace(/  */gi,' ');
1072  
1073          // Split into lines and remove
1074          // empty lines and add carriage returns back
1075          var splitter  = /\[br\]/g;
1076          var emptyLine = /^\s+\s+$/g;
1077          var strHTML   = '';
1078          var toLines   = D.split(splitter);
1079          for (var i = 0; i < toLines.length; i++) {
1080              var line = toLines[i];
1081              if (line.length < 1) {
1082                  continue;
1083              }
1084  
1085              if (emptyLine.test(line)) {
1086                  continue;
1087              }
1088  
1089              line = line.replace(/^\s+\s+$/g, '');
1090              strHTML += line + '\n';
1091          }
1092          D = strHTML;
1093          strHTML = '';
1094  
1095          this.setHTML(D);
1096          this.updateToolbar();
1097      }
1098  };
1099  
1100  HTMLArea.prototype._unnestBlockWalk = function(node, unnestingParent) {
1101      if (HTMLArea.RE_blocktag.test(node.nodeName)) {
1102      if (unnestingParent) {
1103          if (node.nextSibling) {
1104          var splitNode = this._doc.createElement(unnestingParent.nodeName.toLowerCase());
1105          while (node.nextSibling) {
1106              splitNode.appendChild(node.nextSibling);
1107          }
1108          unnestingParent.parentNode.insertBefore(splitNode, unnestingParent.nextSibling);
1109          }
1110          unnestingParent.parentNode.insertBefore(node, unnestingParent.nextSibling);
1111          return;
1112      }
1113      else if (node.firstChild) {
1114          this._unnestBlockWalk(node.firstChild, node);
1115      }
1116      } else {
1117      if (node.firstChild) {
1118          this._unnestBlockWalk(node.firstChild, null);
1119      }
1120      }
1121      if (node.nextSibling) {
1122      this._unnestBlockWalk(node.nextSibling, unnestingParent);
1123      }
1124  }
1125  
1126  HTMLArea.prototype._unnestBlocks = function() {
1127      this._unnestBlockWalk(this._doc.documentElement, null);
1128  }
1129  
1130  HTMLArea.prototype.forceRedraw = function() {
1131      this._doc.body.style.visibility = "hidden";
1132      this._doc.body.style.visibility = "visible";
1133      // this._doc.body.innerHTML = this.getInnerHTML();
1134  };
1135  
1136  // focuses the iframe window.  returns a reference to the editor document.
1137  HTMLArea.prototype.focusEditor = function() {
1138      switch (this._editMode) {
1139          case "wysiwyg" : this._iframe.contentWindow.focus(); break;
1140          case "textmode": this._textArea.focus(); break;
1141          default    : alert("ERROR: mode " + this._editMode + " is not defined");
1142      }
1143      return this._doc;
1144  };
1145  
1146  // takes a snapshot of the current text (for undo)
1147  HTMLArea.prototype._undoTakeSnapshot = function() {
1148      ++this._undoPos;
1149      if (this._undoPos >= this.config.undoSteps) {
1150          // remove the first element
1151          this._undoQueue.shift();
1152          --this._undoPos;
1153      }
1154      // use the fasted method (getInnerHTML);
1155      var take = true;
1156      var txt = this.getInnerHTML();
1157      if (this._undoPos > 0)
1158          take = (this._undoQueue[this._undoPos - 1] != txt);
1159      if (take) {
1160          this._undoQueue[this._undoPos] = txt;
1161      } else {
1162          this._undoPos--;
1163      }
1164  };
1165  
1166  HTMLArea.prototype.undo = function() {
1167      if (this._undoPos > 0) {
1168          var txt = this._undoQueue[--this._undoPos];
1169          if (txt) this.setHTML(txt);
1170          else ++this._undoPos;
1171      }
1172  };
1173  
1174  HTMLArea.prototype.redo = function() {
1175      if (this._undoPos < this._undoQueue.length - 1) {
1176          var txt = this._undoQueue[++this._undoPos];
1177          if (txt) this.setHTML(txt);
1178          else --this._undoPos;
1179      }
1180  };
1181  
1182  // updates enabled/disable/active state of the toolbar elements
1183  HTMLArea.prototype.updateToolbar = function(noStatus) {
1184      var doc = this._doc;
1185      var text = (this._editMode == "textmode");
1186      var ancestors = null;
1187      if (!text) {
1188          ancestors = this.getAllAncestors();
1189          if (this.config.statusBar && !noStatus) {
1190  
1191              while(this._statusBarTree.childNodes.length>0) {
1192                  this._statusBarTree.removeChild(this._statusBarTree.childNodes[0]);
1193              }
1194  
1195              this._statusBarTree.appendChild(document.createTextNode(HTMLArea.I18N.msg["Path"] + ": "));
1196  
1197              for (var i = ancestors.length; --i >= 0;) {
1198                  var el = ancestors[i];
1199                  if (!el) {
1200                      // hell knows why we get here; this
1201                      // could be a classic example of why
1202                      // it's good to check for conditions
1203                      // that are impossible to happen ;-)
1204                      continue;
1205                  }
1206                  var a = document.createElement("a");
1207                  a.href = "#";
1208                  a.el = el;
1209                  a.editor = this;
1210                  a.onclick = function() {
1211                      this.blur();
1212                      this.editor.selectNodeContents(this.el);
1213                      this.editor.updateToolbar(true);
1214                      return false;
1215                  };
1216                  a.oncontextmenu = function() {
1217                      // TODO: add context menu here
1218                      this.blur();
1219                      var info = "Inline style:\n\n";
1220                      info += this.el.style.cssText.split(/;\s*/).join(";\n");
1221                      alert(info);
1222                      return false;
1223                  };
1224                  var txt = el.tagName.toLowerCase();
1225                  a.title = el.style.cssText;
1226                  if (el.id) {
1227                      txt += "#" + el.id;
1228                  }
1229                  if (el.className) {
1230                      txt += "." + el.className;
1231                  }
1232                  a.appendChild(document.createTextNode(txt));
1233                  this._statusBarTree.appendChild(a);
1234                  if (i != 0) {
1235                      this._statusBarTree.appendChild(document.createTextNode(String.fromCharCode(0xbb)));
1236                  }
1237              }
1238          }
1239      }
1240      for (var i in this._toolbarObjects) {
1241          var btn = this._toolbarObjects[i];
1242          var cmd = i;
1243          var inContext = true;
1244          if (btn.context && !text) {
1245              inContext = false;
1246              var context = btn.context;
1247              var attrs = [];
1248              if (/(.*)\[(.*?)\]/.test(context)) {
1249                  context = RegExp.$1;
1250                  attrs = RegExp.$2.split(",");
1251              }
1252              context = context.toLowerCase();
1253              var match = (context == "*");
1254              for (var k in ancestors) {
1255                  if (!ancestors[k]) {
1256                      // the impossible really happens.
1257                      continue;
1258                  }
1259                  if (match || (ancestors[k].tagName.toLowerCase() == context)) {
1260                      inContext = true;
1261                      for (var ka in attrs) {
1262                          if (!eval("ancestors[k]." + attrs[ka])) {
1263                              inContext = false;
1264                              break;
1265                          }
1266                      }
1267                      if (inContext) {
1268                          break;
1269                      }
1270                  }
1271              }
1272          }
1273          btn.state("enabled", (!text || btn.text) && inContext);
1274          if (typeof cmd == "function") {
1275              continue;
1276          }
1277          // look-it-up in the custom dropdown boxes
1278          var dropdown = this.config.customSelects[cmd];
1279          if ((!text || btn.text) && (typeof dropdown != "undefined")) {
1280              dropdown.refresh(this);
1281              continue;
1282          }
1283          switch (cmd) {
1284              case "fontname":
1285              case "fontsize":
1286              case "formatblock":
1287                  if (!text) try {
1288                      var value = ("" + doc.queryCommandValue(cmd)).toLowerCase();
1289                      if (!value) {
1290                          // FIXME: what do we do here?
1291                          break;
1292                      }
1293                      var options = this.config[cmd];
1294                      var k = 0;
1295                      // btn.element.selectedIndex = 0;
1296                      for (var j in options) {
1297                          // FIXME: the following line is scary.
1298                          if ((j.toLowerCase() == value) ||
1299                              (options[j].substr(0, value.length).toLowerCase() == value)) {
1300                              btn.element.selectedIndex = k;
1301                              break;
1302                          }
1303                          ++k;
1304                      }
1305                  } catch(e) {};
1306                  break;
1307              case "language":
1308                  if (!text) try {
1309                      var value;
1310                      parentEl = this.getParentElement();
1311                      if (parentEl.getAttribute('lang')) {
1312                          // A language was previously defined for the block.
1313                          if (parentEl.getAttribute('class') == 'multilang') {
1314                              value = parentEl.getAttribute('lang')+'_ML';
1315                          } else {
1316                              value = parentEl.getAttribute('lang');
1317                          }
1318                      } else {
1319                          value = '';
1320                      }
1321                      var options = this.config[cmd];
1322                      var k = 0;
1323                      for (var j in options) {
1324                          // FIXME: the following line is scary.
1325                          if ((j.toLowerCase() == value) ||
1326                              (options[j].substr(0, value.length).toLowerCase() == value)) {
1327                              btn.element.selectedIndex = k;
1328                              break;
1329                          }
1330                          ++k;
1331                      }
1332                  } catch(e) {};
1333                  break;
1334              case "textindicator":
1335                  if (!text) {
1336                      try {with (btn.element.style) {
1337                          backgroundColor = HTMLArea._makeColor(
1338                              doc.queryCommandValue(HTMLArea.is_ie ? "backcolor" : "hilitecolor"));
1339                          if (/transparent/i.test(backgroundColor)) {
1340                              // Mozilla
1341                              backgroundColor = HTMLArea._makeColor(doc.queryCommandValue("backcolor"));
1342                          }
1343                          color = HTMLArea._makeColor(doc.queryCommandValue("forecolor"));
1344                          fontFamily = doc.queryCommandValue("fontname");
1345                          fontWeight = doc.queryCommandState("bold") ? "bold" : "normal";
1346                          fontStyle = doc.queryCommandState("italic") ? "italic" : "normal";
1347                      }} catch (e) {
1348                          // alert(e + "\n\n" + cmd);
1349                      }
1350                  }
1351                  break;
1352              case "htmlmode": btn.state("active", text); break;
1353              case "lefttoright":
1354              case "righttoleft":
1355                  var el = this.getParentElement();
1356                  while (el && !HTMLArea.isBlockElement(el))
1357                      el = el.parentNode;
1358                  if (el)
1359                      btn.state("active", (el.style.direction == ((cmd == "righttoleft") ? "rtl" : "ltr")));
1360                  break;
1361              default:
1362                  try {
1363                      btn.state("active", (!text && doc.queryCommandState(cmd)));
1364                  } catch (e) {}
1365          }
1366      }
1367      // take undo snapshots
1368      if (this._customUndo && !this._timerUndo) {
1369          this._undoTakeSnapshot();
1370          var editor = this;
1371          this._timerUndo = setTimeout(function() {
1372              editor._timerUndo = null;
1373          }, this.config.undoTimeout);
1374      }
1375      // check if any plugins have registered refresh handlers
1376      for (var i in this.plugins) {
1377          var plugin = this.plugins[i].instance;
1378          if (typeof plugin.onUpdateToolbar == "function")
1379              plugin.onUpdateToolbar();
1380      }
1381  };
1382  
1383  /** Returns a node after which we can insert other nodes, in the current
1384   * selection.  The selection is removed.  It splits a text node, if needed.
1385   */
1386  HTMLArea.prototype.insertNodeAtSelection = function(toBeInserted) {
1387      if (!HTMLArea.is_ie) {
1388          var sel = this._getSelection();
1389          var range = this._createRange(sel);
1390          // remove the current selection
1391          sel.removeAllRanges();
1392          range.deleteContents();
1393          var node = range.startContainer;
1394          var pos = range.startOffset;
1395          switch (node.nodeType) {
1396              case 3: // Node.TEXT_NODE
1397              // we have to split it at the caret position.
1398              if (toBeInserted.nodeType == 3) {
1399                  // do optimized insertion
1400                  node.insertData(pos, toBeInserted.data);
1401                  range = this._createRange();
1402                  range.setEnd(node, pos + toBeInserted.length);
1403                  range.setStart(node, pos + toBeInserted.length);
1404                  sel.addRange(range);
1405              } else {
1406                  node = node.splitText(pos);
1407                  var selnode = toBeInserted;
1408                  if (toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */) {
1409                      selnode = selnode.firstChild;
1410                  }
1411                  node.parentNode.insertBefore(toBeInserted, node);
1412                  this.selectNodeContents(selnode);
1413                  this.updateToolbar();
1414              }
1415              break;
1416              case 1: // Node.ELEMENT_NODE
1417              var selnode = toBeInserted;
1418              if (toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */) {
1419                  selnode = selnode.firstChild;
1420              }
1421              node.insertBefore(toBeInserted, node.childNodes[pos]);
1422              this.selectNodeContents(selnode);
1423              this.updateToolbar();
1424              break;
1425          }
1426      } else {
1427          return null;    // this function not yet used for IE <FIXME>
1428      }
1429  };
1430  
1431  // Returns the deepest node that contains both endpoints of the selection.
1432  HTMLArea.prototype.getParentElement = function() {
1433      var sel = this._getSelection();
1434      var range = this._createRange(sel);
1435      if (HTMLArea.is_ie) {
1436          switch (sel.type) {
1437              case "Text":
1438              case "None":
1439              return range.parentElement();
1440              case "Control":
1441              return range.item(0);
1442              default:
1443              return this._doc.body;
1444          }
1445      } else try {
1446          var p = range.commonAncestorContainer;
1447          if (!range.collapsed && range.startContainer == range.endContainer &&
1448              range.startOffset - range.endOffset <= 1 && range.startContainer.hasChildNodes())
1449              p = range.startContainer.childNodes[range.startOffset];
1450          /*
1451          alert(range.startContainer + ":" + range.startOffset + "\n" +
1452                range.endContainer + ":" + range.endOffset);
1453          */
1454          while (p.nodeType == 3) {
1455              p = p.parentNode;
1456          }
1457          return p;
1458      } catch (e) {
1459          return null;
1460      }
1461  };
1462  
1463  // Returns an array with all the ancestor nodes of the selection.
1464  HTMLArea.prototype.getAllAncestors = function() {
1465      var p = this.getParentElement();
1466      var a = [];
1467      while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
1468          a.push(p);
1469          p = p.parentNode;
1470      }
1471      a.push(this._doc.body);
1472      return a;
1473  };
1474  
1475  // Selects the contents inside the given node
1476  HTMLArea.prototype.selectNodeContents = function(node, pos) {
1477      this.focusEditor();
1478      this.forceRedraw();
1479      var range;
1480      var collapsed = (typeof pos != "undefined");
1481      if (HTMLArea.is_ie) {
1482          range = this._doc.body.createTextRange();
1483          range.moveToElementText(node);
1484          (collapsed) && range.collapse(pos);
1485          range.select();
1486      } else {
1487          var sel = this._getSelection();
1488          range = this._doc.createRange();
1489          range.selectNodeContents(node);
1490          (collapsed) && range.collapse(pos);
1491          sel.removeAllRanges();
1492          sel.addRange(range);
1493      }
1494  };
1495  
1496  // Call this function to insert HTML code at the current position.  It deletes
1497  // the selection, if any.
1498  HTMLArea.prototype.insertHTML = function(html) {
1499      var sel = this._getSelection();
1500      var range = this._createRange(sel);
1501      if (HTMLArea.is_ie) {
1502          range.pasteHTML(html);
1503      } else {
1504          // construct a new document fragment with the given HTML
1505          var fragment = this._doc.createDocumentFragment();
1506          var div = this._doc.createElement("div");
1507          div.innerHTML = html;
1508          while (div.firstChild) {
1509              // the following call also removes the node from div
1510              fragment.appendChild(div.firstChild);
1511          }
1512          // this also removes the selection
1513          var node = this.insertNodeAtSelection(fragment);
1514      }
1515  };
1516  
1517  // Call this function to surround the existing HTML code in the selection with
1518  // your tags.  FIXME: buggy!  This function will be deprecated "soon".
1519  HTMLArea.prototype.surroundHTML = function(startTag, endTag) {
1520      var html = this.getSelectedHTML();
1521      // the following also deletes the selection
1522      this.insertHTML(startTag + html + endTag);
1523  };
1524  
1525  /// Retrieve the selected block
1526  HTMLArea.prototype.getSelectedHTML = function() {
1527      var sel = this._getSelection();
1528      var range = this._createRange(sel);
1529      var existing = null;
1530      if (HTMLArea.is_ie) {
1531          existing = range.htmlText;
1532      } else {
1533          existing = HTMLArea.getHTML(range.cloneContents(), false, this);
1534      }
1535      return existing;
1536  };
1537  
1538  /// Return true if we have some selection
1539  HTMLArea.prototype.hasSelectedText = function() {
1540      // FIXME: come _on_ mishoo, you can do better than this ;-)
1541      return this.getSelectedHTML() != '';
1542  };
1543  
1544  HTMLArea.prototype._createLink = function(link) {
1545      var editor = this;
1546      var allinks = editor._doc.getElementsByTagName('A');
1547      var anchors = new Array();
1548      for(var i = 0; i < allinks.length; i++) {
1549          var attrname = allinks[i].getAttribute('name');
1550          if((HTMLArea.is_ie ? attrname.length > 0 : attrname != null)) {
1551              anchors[i] = allinks[i].getAttribute('name');
1552          }
1553      }
1554      var outparam = null;
1555      if (typeof link == "undefined") {
1556          link = this.getParentElement();
1557          if (link && !/^a$/i.test(link.tagName)) {
1558              if(link.tagName.toLowerCase() != 'img') {
1559                  link = null;
1560                  var sel = this._getSelection();
1561                  var rng = this._createRange(sel);
1562                  var len = HTMLArea.is_ie ? rng.text.toString().length : sel.toString().length;
1563                  if(len < 1) {
1564                      alert("<?php print_string("alertnoselectedtext","editor");?>");
1565                      return false;
1566                  }
1567              }
1568              link = null;
1569          }
1570      }
1571      if (link) {
1572          outparam = {
1573          f_href   : HTMLArea.is_ie ? editor.stripBaseURL(link.href) : link.getAttribute("href"),
1574          f_title  : link.title,
1575          f_target : link.target,
1576          f_anchors: anchors
1577      };
1578      } else {
1579          outparam = {
1580          f_anchors:anchors };
1581      }
1582      this._popupDialog("link_std.php?id=<?php echo $id; ?>", function(param) {
1583          if (!param) {
1584              return false;
1585          }
1586          var a = link;
1587          if (!a) {
1588              // Create a temporary unique link, insert it then find it and set the correct parameters
1589              var tmpLink = 'http://www.moodle.org/'+Math.random();
1590              var elm = editor._doc.execCommand("createlink",false,tmpLink);
1591              var links=editor._doc.getElementsByTagName("a");
1592              for(var i=0;i<links.length;i++){
1593                  var link=links[i];
1594                  if(link.href==tmpLink) {
1595                      link.href=param.f_href.trim();
1596                      if(param.f_target){
1597                          link.target=param.f_target.trim();
1598                      }
1599                      if(param.f_title){
1600                          link.title=param.f_title.trim();
1601                      }
1602                      break;
1603                  }
1604              }
1605          } else {
1606              var href = param.f_href.trim();
1607              editor.selectNodeContents(a);
1608              if (href == "") {
1609                  editor._doc.execCommand("unlink", false, null);
1610                  editor.updateToolbar();
1611                  return false;
1612              } else {
1613                  a.href = href;
1614              }
1615          }
1616          if (!(a && /^a$/i.test(a.tagName))) {
1617              return false;
1618          }
1619          a.target = param.f_target.trim();
1620          a.title = param.f_title.trim();
1621          editor.selectNodeContents(a);
1622          editor.updateToolbar();
1623      }, outparam);
1624  };
1625  
1626  // Called when the user clicks on "InsertImage" button.  If an image is already
1627  // there, it will just modify it's properties.
1628  HTMLArea.prototype._insertImage = function(image) {
1629  
1630      // Make sure that editor has focus
1631      this.focusEditor();
1632      var editor = this;  // for nested functions
1633      var outparam = null;
1634      if (typeof image == "undefined") {
1635          image = this.getParentElement();
1636          if (image && !/^img$/i.test(image.tagName))
1637              image = null;
1638      }
1639      if (image) outparam = {
1640          f_url    : HTMLArea.is_ie ? editor.stripBaseURL(image.src) : image.getAttribute("src"),
1641          f_alt    : image.alt,
1642          f_border : image.border,
1643          f_align  : image.align,
1644          f_vert   : image.vspace,
1645          f_horiz  : image.hspace,
1646          f_width  : image.width,
1647          f_height : image.height
1648      };
1649      this._popupDialog("<?php
1650      if(!empty($id) and has_capability('moodle/course:managefiles', get_context_instance(CONTEXT_COURSE, $id))) {
1651          echo "insert_image.php?id=$id";
1652      } else {
1653          echo "insert_image_std.php?id=$id";
1654      }?>", function(param) {
1655          if (!param) {   // user must have pressed Cancel
1656              return false;
1657          }
1658          var img = image;
1659          if (!img) {
1660              var sel = editor._getSelection();
1661              var range = editor._createRange(sel);
1662                  if (HTMLArea.is_ie) {
1663                  editor._doc.execCommand("insertimage", false, param.f_url);
1664                  }
1665              if (HTMLArea.is_ie) {
1666                  img = range.parentElement();
1667                  // wonder if this works...
1668                  if (img.tagName.toLowerCase() != "img") {
1669                      img = img.previousSibling;
1670                  }
1671              } else {
1672                  // MOODLE HACK: startContainer.perviousSibling
1673                  // Doesn't work so we'll use createElement and
1674                  // insertNodeAtSelection
1675                  //img = range.startContainer.previousSibling;
1676                  var img = editor._doc.createElement("img");
1677                  img.setAttribute("src",""+ param.f_url +"");
1678                  img.setAttribute("alt",""+ param.f_alt +"");
1679                  editor.insertNodeAtSelection(img);
1680              }
1681          } else {
1682              img.src = param.f_url;
1683          }
1684          for (field in param) {
1685              var value = param[field];
1686              switch (field) {
1687                  case "f_alt"    : img.alt    = value; img.title = value; break;
1688                  case "f_border" : img.border = parseInt(value || "0"); break;
1689                  case "f_align"  : img.align  = value; break;
1690                  case "f_vert"   : img.vspace = parseInt(value || "0"); break;
1691                  case "f_horiz"  : img.hspace = parseInt(value || "0"); break;
1692                  case "f_width"  :
1693                      if(value != 0) {
1694                          img.width = parseInt(value);
1695                      } else {
1696                          break;
1697                      }
1698                      break;
1699                  case "f_height"  :
1700                      if(value != 0) {
1701                          img.height = parseInt(value);
1702                      } else {
1703                          break;
1704                      }
1705                      break;
1706              }
1707          }
1708      }, outparam);
1709  };
1710  
1711  // Called when the user clicks the Insert Table button
1712  HTMLArea.prototype._insertTable = function() {
1713      var sel = this._getSelection();
1714      var range = this._createRange(sel);
1715      var editor = this;  // for nested functions
1716      this._popupDialog("insert_table.php?id=<?php echo $id; ?>", function(param) {
1717          if (!param) {   // user must have pressed Cancel
1718              return false;
1719          }
1720          var doc = editor._doc;
1721          // create the table element
1722          var table = doc.createElement("table");
1723          // assign the given arguments
1724          for (var field in param) {
1725              var value = param[field];
1726              if (!value) {
1727                  continue;
1728              }
1729              switch (field) {
1730                  case "f_width"   : table.width = value + param["f_unit"]; break;
1731                  case "f_align"   : table.align   = value; break;
1732                  case "f_border"  : table.border  = parseInt(value); break;
1733                  case "f_spacing" : table.cellspacing = parseInt(value); break;
1734                  case "f_padding" : table.cellpadding = parseInt(value); break;
1735              }
1736          }
1737          var tbody = doc.createElement("tbody");
1738          table.appendChild(tbody);
1739          for (var i = 0; i < param["f_rows"]; ++i) {
1740              var tr = doc.createElement("tr");
1741              tbody.appendChild(tr);
1742              for (var j = 0; j < param["f_cols"]; ++j) {
1743                  var td = doc.createElement("td");
1744                  /// Moodle hack
1745                  if(param["f_unit"] == "px") {
1746                      tdwidth = Math.round(table.width / param["f_cols"]);
1747                  } else {
1748                      tdwidth = Math.round(100 / param["f_cols"]);
1749                  }
1750                  td.setAttribute("width",tdwidth + param["f_unit"]);
1751                  td.setAttribute("valign","top");
1752                  /// Moodle hack -ends
1753                  tr.appendChild(td);
1754                  // Mozilla likes to see something inside the cell.
1755                  (HTMLArea.is_gecko) && td.appendChild(doc.createElement("br"));
1756              }
1757          }
1758          if (HTMLArea.is_ie) {
1759              range.pasteHTML(table.outerHTML);
1760          } else {
1761              // insert the table
1762              editor.insertNodeAtSelection(table);
1763          }
1764          return true;
1765      }, null);
1766  };
1767  
1768  /// Moodle hack - insertSmile
1769  HTMLArea.prototype._insertSmile = function() {
1770      // Make sure that editor has focus
1771      this.focusEditor();
1772      var sel = this._getSelection();
1773      var range = this._createRange(sel);
1774      var editor = this;  // for nested functions
1775      this._popupDialog("dlg_ins_smile.php?id=<?php echo $id; ?>", function(imgString) {
1776          if(!imgString) {
1777              return false;
1778          }
1779          if (HTMLArea.is_ie) {
1780              range.pasteHTML(imgString);
1781          } else {
1782              editor.insertHTML(imgString);
1783          }
1784          return true;
1785      }, null);
1786  };
1787  
1788  HTMLArea.prototype._insertChar = function() {
1789      var sel = this._getSelection();
1790      var range = this._createRange(sel);
1791      var editor = this;  // for nested functions
1792      this._popupDialog("dlg_ins_char.php?id=<?php echo $id; ?>", function(sChar) {
1793          if(!sChar) {
1794              return false;
1795          }
1796          if (HTMLArea.is_ie) {
1797              range.pasteHTML(sChar);
1798          } else {
1799              // insert the table
1800              editor.insertHTML(sChar);
1801          }
1802          return true;
1803      }, null);
1804  };
1805  
1806  HTMLArea.prototype._removelink = function() {
1807      var editor = this;
1808      link = this.getParentElement();
1809      editor.selectNodeContents(link);
1810  
1811      this._doc.execCommand("unlink", false, null);
1812      this.focusEditor();
1813  };
1814  
1815  HTMLArea.prototype._createanchor = function () {
1816      var editor = this;
1817      var sel = this._getSelection();
1818      var rng = this._createRange(sel);
1819      var len = HTMLArea.is_ie ? rng.text.toString().length : sel.toString().length;
1820      if(len < 1) {
1821          alert("<?php print_string("alertnoselectedtext","editor");?>");
1822          return false;
1823      }
1824      this._popupDialog("createanchor.php?id=<?php echo $id; ?>", function(objAn) {
1825          if(!objAn) {
1826              return false;
1827          }
1828          var str = '<a name="'+ objAn.anchor+'">';
1829          str += HTMLArea.is_ie ? rng.text : sel ;
1830          str += '</a>';
1831          editor.insertHTML(str);
1832      },null);
1833  };
1834  
1835  HTMLArea.prototype._nolinktag = function () {
1836  
1837      var editor = this;
1838      var sel = this._getSelection();
1839      var rng = this._createRange(sel);
1840      var len = HTMLArea.is_ie ? rng.text.toString().length : sel.toString().length;
1841  
1842      if (len < 1) {
1843          alert("<?php print_string("alertnoselectedtext","editor");?>");
1844          return false;
1845      }
1846      var str = '<span class="nolink">';
1847      str += HTMLArea.is_ie ? rng.text : sel;
1848      str += '</span>';
1849      editor.insertHTML(str);
1850      this.focusEditor();
1851  
1852  };
1853  
1854  HTMLArea.prototype._searchReplace = function() {
1855  
1856      var editor = this;
1857      var selectedtxt = "";
1858      <?php
1859      $strreplaced = addslashes(get_string('itemsreplaced','editor'));
1860      $strnotfound = addslashes(get_string('searchnotfound','editor'));
1861      ?>
1862      var strReplaced = '<?php echo $strreplaced ?>';
1863      var strNotfound = '<?php echo $strnotfound ?>';
1864      var ile;
1865  
1866      //in source mode mozilla show errors, try diffrent method
1867      if (editor._editMode == "wysiwyg") {
1868          selectedtxt = editor.getSelectedHTML();
1869      } else {
1870          if (HTMLArea.is_ie) {
1871              selectedtxt = document.selection.createRange().text;
1872          } else {
1873              selectedtxt = getMozSelection(editor._textArea);
1874          }
1875      }
1876  
1877      outparam = {
1878          f_search : selectedtxt
1879      };
1880  
1881      //Call Search And Replace popup window
1882      editor._popupDialog( "searchandreplace.php?id=<?php echo $id; ?>", function( entity ) {
1883          if ( !entity ) {
1884              //user must have pressed Cancel
1885              return false;
1886          }
1887          var text = editor.getHTML();
1888          var search = entity[0];
1889          var replace = entity[1];
1890          var delim = entity[2];
1891          var regularx = entity[3];
1892          var closesar = entity[4];
1893          ile = 0;
1894          if (search.length < 1) {
1895              alert ("Enter a search word! \n search for: " + entity[0]);
1896          } else {
1897              if (regularx) {
1898              var regX = new RegExp (search, delim) ;
1899              var text = text.replace ( regX,
1900              function (str, n) {
1901                  // Increment our counter variable.
1902                  ile++ ;
1903                  //return replace ;
1904                  return str.replace( regX, replace) ;
1905                  }
1906              )
1907  
1908              } else {
1909                  while (text.indexOf(search)>-1) {
1910                      pos = text.indexOf(search);
1911                      text = "" + (text.substring(0, pos) + replace + text.substring((pos + search.length), text.length));
1912                      ile++;
1913                  }
1914              }
1915  
1916              editor.setHTML(text);
1917              editor.forceRedraw();
1918              if (ile > 0) {
1919                  alert(ile + ' ' + strReplaced);
1920              } else {
1921                  alert (strNotfound + "\n");
1922              }
1923          }
1924      }, outparam);
1925  
1926      function getMozSelection(txtarea) {
1927          var selLength = txtarea.textLength;
1928          var selStart = txtarea.selectionStart;
1929          var selEnd = txtarea.selectionEnd;
1930          if (selEnd==1 || selEnd==2) selEnd=selLength;
1931          return (txtarea.value).substring(selStart, selEnd);
1932      }
1933  };
1934  
1935  /// Moodle hack's ends
1936  //
1937  // Category: EVENT HANDLERS
1938  
1939  // el is reference to the SELECT object
1940  // txt is the name of the select field, as in config.toolbar
1941  HTMLArea.prototype._comboSelected = function(el, txt) {
1942      this.focusEditor();
1943      var value = el.options[el.selectedIndex].value;
1944      switch (txt) {
1945          case "fontname":
1946          case "fontsize": this.execCommand(txt, false, value); break;
1947          case "language":
1948              this.setLang(value);
1949              break;
1950          case "formatblock":
1951              (HTMLArea.is_ie) && (value = "<" + value + ">");
1952              this.execCommand(txt, false, value);
1953              break;
1954          default:
1955          // try to look it up in the registered dropdowns
1956          var dropdown = this.config.customSelects[txt];
1957          if (typeof dropdown != "undefined") {
1958              dropdown.action(this);
1959          } else {
1960              alert("FIXME: combo box " + txt + " not implemented");
1961          }
1962      }
1963  };
1964  
1965  
1966  /**
1967   * Used to set the language for the selected content.
1968   * We use the <span lang="en" class="multilang">content</span> format for
1969   * content that should be marked for multilang filter use, and
1970   * <span lang="en">content</span> for normal content for which we want to
1971   * set the language (for screen reader usage, for example).
1972   */
1973  HTMLArea.prototype.setLang = function(lang) {
1974  
1975      if (lang == 'multi') {
1976          // This is just the separator in the dropdown. Does nothing.
1977          return;
1978      }
1979  
1980      var editor = this;
1981      var selectedHTML = editor.getSelectedHTML();
1982      var multiLang = false;
1983  
1984      var re = new RegExp('_ML', 'g');
1985      if (lang.match(re)) {
1986          multiLang = true;
1987          lang = lang.replace(re, '');
1988      }
1989  
1990      // Remove all lang attributes from span tags in selected html.
1991      selectedHTML = selectedHTML.replace(/(<span[^>]*)lang="[^"]*"([^>]*>)/, "$1$2");
1992      selectedHTML = selectedHTML.replace(/(<span[^>]*)class="multilang"([^>]*>)/, "$1$2");
1993  
1994      // If a span tag is now empty, delete it.
1995      selectedHTML = selectedHTML.replace(/<span\s*>(.*?)<\/span>/, "$1");
1996  
1997  
1998      var parentEl = this.getParentElement();
1999      var insertNewSpan = false;
2000  
2001      if (parentEl.nodeName == 'SPAN' && parentEl.getAttribute('lang')) {
2002          // A language was previously defined for the current block.
2003          // Check whether the selected text makes up the whole of the block
2004          // contents.
2005          var re = new RegExp(parentEl.innerHTML);
2006  
2007          if (selectedHTML.match(re)) {
2008              // The selected text makes up the whole of the span block.
2009              if (lang != '') {
2010                  parentEl.setAttribute('lang', lang);
2011                  if (multiLang) {
2012                      parentEl.setAttribute('class', 'multilang');
2013                  }
2014              } else {
2015                  parentEl.removeAttribute('lang');
2016  
2017                  var classAttr = parentEl.getAttribute('class');
2018                  if (classAttr) {
2019                      classAttr = classAttr.replace(/multilang/, '').trim();
2020                  }
2021                  if (classAttr == '') {
2022                      parentEl.removeAttribute('class');
2023                  }
2024                  if (parentEl.attributes.length == 0) {
2025                      // The span is no longer needed.
2026                      for (i=0; i<parentEl.childNodes.length; i++) {
2027                          parentEl.parentNode.insertBefore(parentEl.childNodes[i], parentEl);
2028                      }
2029                      parentEl.parentNode.removeChild(parentEl);
2030                  }
2031              }
2032          } else {
2033              insertNewSpan = true;
2034          }
2035      } else {
2036          insertNewSpan = true;
2037      }
2038  
2039      if (insertNewSpan && lang != '') {
2040          var str  = '<span lang="'+lang.trim()+'"';
2041              str += ' class="multilang"';
2042          str += '>';
2043          str += selectedHTML;
2044          str += '</span>';
2045          editor.insertHTML(str);
2046      }
2047  }
2048  
2049  
2050  // the execCommand function (intercepts some commands and replaces them with
2051  // our own implementation)
2052  HTMLArea.prototype.execCommand = function(cmdID, UI, param) {
2053      var editor = this;  // for nested functions
2054      this.focusEditor();
2055      cmdID = cmdID.toLowerCase();
2056      switch (cmdID) {
2057          case "htmlmode" : this.setMode(); break;
2058          case "hilitecolor":
2059          (HTMLArea.is_ie) && (cmdID = "backcolor");
2060          case "forecolor":
2061              this._popupDialog("select_color.php?id=<?php echo $id; ?>", function(color) {
2062                  if (color) { // selection not canceled
2063                      editor._doc.execCommand(cmdID, false, "#" + color);
2064                  }
2065              }, HTMLArea._colorToRgb(this._doc.queryCommandValue(cmdID)));
2066              break;
2067          case "createanchor": this._createanchor(); break;
2068          case "createlink":
2069          this._createLink();
2070          break;
2071          case "unlink": this._removelink(); break;
2072          case "nolink": this._nolinktag(); break;
2073          case "popupeditor":
2074          // this object will be passed to the newly opened window
2075          HTMLArea._object = this;
2076          if (HTMLArea.is_ie) {
2077              {
2078                  window.open(this.popupURL("fullscreen.php?id=<?php echo $id;?>"), "ha_fullscreen",
2079                      "toolbar=no,location=no,directories=no,status=no,menubar=no," +
2080                          "scrollbars=no,resizable=yes,width=800,height=600");
2081              }
2082          } else {
2083              window.open(this.popupURL("fullscreen.php?id=<?php echo $id;?>"), "ha_fullscreen",
2084                      "toolbar=no,menubar=no,personalbar=no,width=800,height=600," +
2085                      "scrollbars=no,resizable=yes");
2086          }
2087          break;
2088          case "undo":
2089          case "redo":
2090          if (this._customUndo)
2091              this[cmdID]();
2092          else
2093              this._doc.execCommand(cmdID, UI, param);
2094          break;
2095          case "inserttable": this._insertTable(); break;
2096          case "insertimage": this._insertImage(); break;
2097          case "insertsmile": this._insertSmile(); break;
2098          case "insertchar": this._insertChar(); break;
2099          case "searchandreplace": this._searchReplace(); break;
2100          case "about"    : this._popupDialog("about.html", null, this); break;
2101          case "showhelp" : window.open(_editor_url + "reference.html", "ha_help"); break;
2102  
2103          case "killword": this._wordClean(); break;
2104  
2105          case "cut":
2106          case "copy":
2107          case "paste":
2108          try {
2109              // Paste first then clean
2110              this._doc.execCommand(cmdID, UI, param);
2111              if (this.config.killWordOnPaste) {
2112                  this._wordClean();
2113              }
2114          } catch (e) {
2115              if (HTMLArea.is_gecko) {
2116                  if (confirm("<?php
2117                      $strmoz = get_string('cutpastemozilla','editor');
2118                      $strmoz = preg_replace("/[\n|\r]+/", "", $strmoz);
2119                      $strmoz = str_replace('<br />', '\\n', $strmoz);
2120  
2121                      echo addslashes($strmoz);
2122  
2123                      ?>"))
2124                      window.open("http://moodle.org/mozillahelp");
2125              }
2126          }
2127          break;
2128          case "lefttoright":
2129          case "righttoleft":
2130          var dir = (cmdID == "righttoleft") ? "rtl" : "ltr";
2131          var el = this.getParentElement();
2132          while (el && !HTMLArea.isBlockElement(el))
2133              el = el.parentNode;
2134          if (el) {
2135              if (el.style.direction == dir)
2136                  el.style.direction = "";
2137              else
2138                  el.style.direction = dir;
2139          }
2140          break;
2141          default: this._doc.execCommand(cmdID, UI, param);
2142      }
2143      this.updateToolbar();
2144      return false;
2145  };
2146  
2147  
2148  /**
2149   * A generic event handler for things that happen in the IFRAME's document.
2150   * This function also handles key bindings.
2151   */
2152  HTMLArea.prototype._editorEvent = function(ev) {
2153  
2154      var editor = this;
2155      var keyEvent = (HTMLArea.is_ie && ev.type == "keydown") || (ev.type == "keypress");
2156  
2157      if (keyEvent) {
2158  
2159          for (var i in editor.plugins) {
2160              var plugin = editor.plugins[i].instance;
2161              if (typeof plugin.onKeyPress == "function") plugin.onKeyPress(ev);
2162          }
2163  
2164          var sel = null;
2165          var range = null;
2166          var key = String.fromCharCode(HTMLArea.is_ie ? ev.keyCode : ev.charCode).toLowerCase();
2167          var cmd = null;
2168          var value = null;
2169  
2170          if (ev.ctrlKey && !ev.altKey) {
2171              /**
2172               * Ctrl modifier only.
2173               * We use these for shortcuts that change existing content,
2174               * e.g. make text bold.
2175               */
2176              switch (key) {
2177  
2178                  case 'a':
2179                      // Select all.
2180                      if (!HTMLArea.is_ie) {
2181                          // KEY select all
2182                          sel = this._getSelection();
2183                          sel.removeAllRanges();
2184                          range = this._createRange();
2185                          range.selectNodeContents(this._doc.body);
2186                          sel.addRange(range);
2187                          HTMLArea._stopEvent(ev);
2188                      }
2189                      break;
2190  
2191                  // For the dropdowns, we assign focus to them so that they are
2192                  // keyboard accessible.
2193                  case 'o':
2194                      editor.dropdowns['fontname'].focus();
2195                      break;
2196                  case 'p':
2197                      editor.dropdowns['fontsize'].focus();
2198                      break;
2199                  case 'h':
2200                      editor.dropdowns['formatblock'].focus();
2201                      break;
2202                  case '=':
2203                      editor.dropdowns['language'].focus();
2204                      break;
2205  
2206                  case 'b': cmd = "bold"; break;
2207                  case 'i': cmd = "italic"; break;
2208                  case 'u': cmd = "underline"; break;
2209                  case 's': cmd = "strikethrough"; break;
2210                  case ',': cmd = "subscript"; break;
2211                  case '.': cmd = "superscript"; break;
2212  
2213                  case 'v':
2214                      if (! HTMLArea.is_gecko ) {
2215                          cmd = "paste";
2216                      }
2217                      break;
2218  
2219                  case '0': cmd = "killword"; break;
2220                  case 'z': cmd = "undo"; break;
2221                  case 'y': cmd = "redo"; break;
2222                  case 'l': cmd = "justifyleft"; break;
2223                  case 'e': cmd = "justifycenter"; break;
2224                  case 'r': cmd = "justifyright"; break;
2225                  case 'j': cmd = "justifyfull"; break;
2226                  case '/': cmd = "lefttoright"; break;
2227                  case '|': cmd = "righttoleft"; break;
2228                  case ';': cmd = "outdent"; break;
2229                  case "'": cmd = "indent"; break;
2230                  case 'g': cmd = "forecolor"; break;
2231                  case 'k': cmd = "hilitecolor"; break;
2232                  case 'f': cmd = "searchandreplace"; break;
2233                  case '`': cmd = "htmlmode"; break;  // FIXME: can't toggle from source code to wysiwyg
2234  
2235                  case 'm':
2236                      // Toggle fullscreen on or off.
2237                      if (this.config.btnList['popupeditor'][0] == 'Enlarge Editor') {
2238                          cmd = 'popupeditor';
2239                      } else {
2240                          window.close();
2241                      }
2242                      break;
2243  
2244                  // Headings.
2245                  case '1':
2246                  case '2':
2247                  case '3':
2248                  case '4':
2249                  case '5':
2250                  case '6':
2251                  cmd = "formatblock";
2252                  value = "h" + key;
2253                  if (HTMLArea.is_ie) {
2254                      value = "<" + value + ">";
2255                  }
2256                  break;
2257  
2258              } // End switch (key)
2259  
2260  
2261          } else if (ev.ctrlKey && ev.altKey) {
2262              /**
2263               * Ctrl + Alt modifiers.
2264               * We use these for shortcuts that insert stuff, e.g. images.
2265               */
2266              switch (key) {
2267                  case 'o': cmd = "insertorderedlist"; break;
2268                  case 'u': cmd = "insertunorderedlist"; break;
2269                  case 'r': cmd = "inserthorizontalrule"; break;
2270                  case 'a': cmd = "createanchor"; break;
2271                  case 'l': cmd = "createlink"; break;
2272                  case 'd': cmd = "unlink"; break;
2273                  case 'n': cmd = "nolink"; break;
2274                  case 'i': cmd = 'insertimage'; break;
2275                  case 't': cmd = 'inserttable'; break;
2276                  case 's': cmd = 'insertsmile'; break;
2277                  case 'c': cmd = 'insertchar'; break;
2278              }
2279          }
2280  
2281          if (cmd) {
2282              // execute simple command
2283              this.execCommand(cmd, false, value);
2284              HTMLArea._stopEvent(ev);
2285          }
2286      } // End if (keyEvent)
2287  
2288      /*
2289      else if (keyEvent) {
2290          // other keys here
2291          switch (ev.keyCode) {
2292              case 13: // KEY enter
2293              // if (HTMLArea.is_ie) {
2294              this.insertHTML("<br />");
2295              HTMLArea._stopEvent(ev);
2296              // }
2297              break;
2298          }
2299      }
2300      */
2301  
2302      // Update the toolbar state after some time.
2303      if (editor._timerToolbar) {
2304          clearTimeout(editor._timerToolbar);
2305      }
2306      editor._timerToolbar = setTimeout(function() {
2307          editor.updateToolbar();
2308          editor._timerToolbar = null;
2309      }, 50);
2310  };
2311  
2312  
2313  // retrieve the HTML
2314  HTMLArea.prototype.getHTML = function() {
2315      switch (this._editMode) {
2316          case "wysiwyg"  :
2317          if (!this.config.fullPage) {
2318              return HTMLArea.getHTML(this._doc.body, false, this);
2319          } else
2320              return this.doctype + "\n" + HTMLArea.getHTML(this._doc.documentElement, true, this);
2321          case "textmode" : return this._textArea.value;
2322          default     : alert("Mode <" + mode + "> not defined!");
2323      }
2324      return false;
2325  };
2326  
2327  // retrieve the HTML (fastest version, but uses innerHTML)
2328  HTMLArea.prototype.getInnerHTML = function() {
2329      switch (this._editMode) {
2330          case "wysiwyg"  :
2331          if (!this.config.fullPage)
2332              return this._doc.body.innerHTML;
2333          else
2334              return this.doctype + "\n" + this._doc.documentElement.innerHTML;
2335          case "textmode" : return this._textArea.value;
2336          default     : alert("Mode <" + mode + "> not defined!");
2337      }
2338      return false;
2339  };
2340  
2341  // completely change the HTML inside
2342  HTMLArea.prototype.setHTML = function(html) {
2343      switch (this._editMode) {
2344          case "wysiwyg"  :
2345          if (!this.config.fullPage)
2346              this._doc.body.innerHTML = html;
2347          else
2348              // this._doc.documentElement.innerHTML = html;
2349              this._doc.body.innerHTML = html;
2350          break;
2351          case "textmode" : this._textArea.value = html; break;
2352          default     : alert("Mode <" + mode + "> not defined!");
2353      }
2354      return false;
2355  };
2356  
2357  // sets the given doctype (useful when config.fullPage is true)
2358  HTMLArea.prototype.setDoctype = function(doctype) {
2359      this.doctype = doctype;
2360  };
2361  
2362  /***************************************************
2363   *  Category: UTILITY FUNCTIONS
2364   ***************************************************/
2365  
2366  // browser identification
2367  
2368  HTMLArea.agt = navigator.userAgent.toLowerCase();
2369  HTMLArea.is_ie     = ((HTMLArea.agt.indexOf("msie") != -1) && (HTMLArea.agt.indexOf("opera") == -1));
2370  HTMLArea.is_opera  = (HTMLArea.agt.indexOf("opera") != -1);
2371  HTMLArea.is_mac    = (HTMLArea.agt.indexOf("mac") != -1);
2372  HTMLArea.is_mac_ie = (HTMLArea.is_ie && HTMLArea.is_mac);
2373  HTMLArea.is_win_ie = (HTMLArea.is_ie && !HTMLArea.is_mac);
2374  HTMLArea.is_gecko  = (navigator.product == "Gecko");
2375  HTMLArea.is_safari = (HTMLArea.agt.indexOf("safari") != -1);
2376  
2377  // variable used to pass the object to the popup editor window.
2378  HTMLArea._object = null;
2379  
2380  // function that returns a clone of the given object
2381  HTMLArea.cloneObject = function(obj) {
2382      var newObj = new Object;
2383  
2384      // check for array objects
2385      if (obj.constructor.toString().indexOf("function Array(") >= 0) {
2386          newObj = obj.constructor();
2387      }
2388  
2389      // check for function objects (as usual, IE is phucked up)
2390      if (obj.constructor.toString().indexOf("function Function(") >= 0) {
2391          newObj = obj; // just copy reference to it
2392      } else for (var n in obj) {
2393          var node = obj[n];
2394          if (typeof node == 'object') { newObj[n] = HTMLArea.cloneObject(node); }
2395          else                         { newObj[n] = node; }
2396      }
2397  
2398      return newObj;
2399  };
2400  
2401  // FIXME!!! this should return false for IE < 5.5
2402  HTMLArea.checkSupportedBrowser = function() {
2403      if (HTMLArea.is_gecko) {
2404          if (navigator.productSub < 20021201) {
2405              alert("You need at least Mozilla-1.3 Alpha.\n" +
2406                    "Sorry, your Gecko is not supported.");
2407              return false;
2408          }
2409          if (navigator.productSub < 20030210) {
2410              alert("Mozilla < 1.3 Beta is not supported!\n" +
2411                    "I'll try, though, but it might not work.");
2412          }
2413      }
2414      if(HTMLArea.is_safari) {
2415          return false;
2416      }
2417      return HTMLArea.is_gecko || HTMLArea.is_ie;
2418  };
2419  
2420  // selection & ranges
2421  
2422  // returns the current selection object
2423  HTMLArea.prototype._getSelection = function() {
2424      if (HTMLArea.is_ie) {
2425          return this._doc.selection;
2426      } else {
2427          return this._iframe.contentWindow.getSelection();
2428      }
2429  };
2430  
2431  // returns a range for the current selection
2432  HTMLArea.prototype._createRange = function(sel) {
2433      if (HTMLArea.is_ie) {
2434          return sel.createRange();
2435      } else {
2436          // Commented out because we need the dropdowns to be able to keep
2437          // focus for keyboard accessibility. Comment by Vy-Shane Sin Fat.
2438          //this.focusEditor();
2439          if (typeof sel != "undefined") {
2440              try {
2441              return sel.getRangeAt(0);
2442              } catch(e) {
2443                  return this._doc.createRange();
2444              }
2445          } else {
2446              return this._doc.createRange();
2447          }
2448      }
2449  };
2450  
2451  // event handling
2452  
2453  HTMLArea._addEvent = function(el, evname, func) {
2454      if (HTMLArea.is_ie) {
2455          el.attachEvent("on" + evname, func);
2456      } else {
2457          el.addEventListener(evname, func, true);
2458      }
2459  };
2460  
2461  HTMLArea._addEvents = function(el, evs, func) {
2462      for (var i in evs) {
2463          HTMLArea._addEvent(el, evs[i], func);
2464      }
2465  };
2466  
2467  HTMLArea._removeEvent = function(el, evname, func) {
2468      if (HTMLArea.is_ie) {
2469          el.detachEvent("on" + evname, func);
2470      } else {
2471          el.removeEventListener(evname, func, true);
2472      }
2473  };
2474  
2475  HTMLArea._removeEvents = function(el, evs, func) {
2476      for (var i in evs) {
2477          HTMLArea._removeEvent(el, evs[i], func);
2478      }
2479  };
2480  
2481  HTMLArea._stopEvent = function(ev) {
2482      if (HTMLArea.is_ie) {
2483          ev.cancelBubble = true;
2484          ev.returnValue = false;
2485      } else {
2486          ev.preventDefault();
2487          ev.stopPropagation();
2488      }
2489  };
2490  
2491  HTMLArea._removeClass = function(el, className) {
2492      if (!(el && el.className)) {
2493          return;
2494      }
2495      var cls = el.className.split(" ");
2496      var ar = new Array();
2497      for (var i = cls.length; i > 0;) {
2498          if (cls[--i] != className) {
2499              ar[ar.length] = cls[i];
2500          }
2501      }
2502      el.className = ar.join(" ");
2503  };
2504  
2505  HTMLArea._addClass = function(el, className) {
2506      // remove the class first, if already there
2507      HTMLArea._removeClass(el, className);
2508      el.className += " " + className;
2509  };
2510  
2511  HTMLArea._hasClass = function(el, className) {
2512      if (!(el && el.className)) {
2513          return false;
2514      }
2515      var cls = el.className.split(" ");
2516      for (var i = cls.length; i > 0;) {
2517          if (cls[--i] == className) {
2518              return true;
2519          }
2520      }
2521      return false;
2522  };
2523  
2524  HTMLArea.isBlockElement = function(el) {
2525  
2526      var blockTags = " body form textarea fieldset ul ol dl li div " +
2527          "p h1 h2 h3 h4 h5 h6 quote pre table thead " +
2528          "tbody tfoot tr td iframe address ";
2529      try {
2530      return (blockTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1);
2531      } catch (e) {}
2532  
2533  };
2534  
2535  HTMLArea.needsClosingTag = function(el) {
2536      var closingTags = " head script style div span tr td tbody table em strong font a title iframe object applet ";
2537      return (closingTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1);
2538  };
2539  
2540  // performs HTML encoding of some given string
2541  HTMLArea.htmlEncode = function(str) {
2542      // we don't need regexp for that, but.. so be it for now.
2543      str = str.replace(/&/ig, "&amp;");
2544      str = str.replace(/</ig, "&lt;");
2545      str = str.replace(/>/ig, "&gt;");
2546      str = str.replace(/\x22/ig, "&quot;");
2547      // \x22 means '"' -- we use hex reprezentation so that we don't disturb
2548      // JS compressors (well, at least mine fails.. ;)
2549      return str;
2550  };
2551  
2552  HTMLArea.isStandardTag = function (el) {
2553      return HTMLArea.RE_msietag.test(el.tagName);
2554  };
2555  HTMLArea.isSingleTag = function (el) {
2556      var re = /^(br|hr|img|input|link|meta|param|embed|area)$/i;
2557      return re.test(el.tagName.toLowerCase());
2558  };
2559  // Retrieves the HTML code from the given node.  This is a replacement for
2560  // getting innerHTML, using standard DOM calls.
2561  HTMLArea.getHTML = function(root, outputRoot, editor) {
2562      var html = "";
2563      switch (root.nodeType) {
2564          case 1: // Node.ELEMENT_NODE
2565          case 11: // Node.DOCUMENT_FRAGMENT_NODE
2566          var closed;
2567          var i;
2568          var root_tag = (root.nodeType == 1) ? root.tagName.toLowerCase() : '';
2569      if (HTMLArea.RE_junktag.test(root_tag)) {
2570          return '';
2571      }
2572          if (HTMLArea.is_ie && root_tag == "head") {
2573              if (outputRoot)
2574                  html += "<head>";
2575              // lowercasize
2576              var save_multiline = RegExp.multiline;
2577              RegExp.multiline = true;
2578              var txt = root.innerHTML.replace(HTMLArea.RE_tagName, function(str, p1, p2) {
2579                  return p1 + p2.toLowerCase();
2580              });
2581              RegExp.multiline = save_multiline;
2582              html += txt;
2583              if (outputRoot)
2584                  html += "</head>";
2585              break;
2586          } else if (outputRoot) {
2587              closed = (!(root.hasChildNodes() || !HTMLArea.isSingleTag(root)));
2588              html = "<" + root.tagName.toLowerCase();
2589              var attrs = root.attributes;
2590              for (i = 0; i < attrs.length; ++i) {
2591                  var a = attrs.item(i);
2592                  if (!a.specified) {
2593                      continue;
2594                  }
2595                  var name = a.nodeName.toLowerCase();
2596                  if (/_moz|contenteditable|_msh/.test(name)) {
2597                      // avoid certain attributes
2598                      continue;
2599                  }
2600                  var value;
2601                  if (name != "style") {
2602                      //
2603                      // Using Gecko the values of href and src are converted to absolute links
2604                      // unless we get them using nodeValue()
2605                      if (typeof root[a.nodeName] != "undefined" && name != "href" && name != "src") {
2606                          value = root[a.nodeName];
2607                      } else {
2608                          // This seems to be working, but if it does cause
2609                          // problems later on return the old value...
2610                          if (name.toLowerCase() == "href" && name.toLowerCase() == "src") {
2611                              value = root[a.nodeName];
2612                          } else {
2613                          value = a.nodeValue;
2614                          }
2615                          if (HTMLArea.is_ie && (name == "href" || name == "src")) {
2616                              value = editor.stripBaseURL(value);
2617                          }
2618                      }
2619                  } else { // IE fails to put style in attributes list
2620                      // FIXME: cssText reported by IE is UPPERCASE
2621                      value = root.style.cssText.toLowerCase();
2622                  }
2623                  if (/(_moz|^$)/.test(value)) {
2624                      // Mozilla reports some special tags
2625                      // here; we don't need them.
2626                      continue;
2627                  }
2628                  html += " " + name + '="' + value + '"';
2629              }
2630              html += closed ? " />" : ">";
2631          }
2632          for (i = root.firstChild; i; i = i.nextSibling) {
2633              html += HTMLArea.getHTML(i, true, editor);
2634          }
2635          if (outputRoot && !closed) {
2636              if ( HTMLArea.is_ie && !HTMLArea.isStandardTag(root) ) {
2637                  html += '';
2638              } else {
2639                  html += "</" + root.tagName.toLowerCase() + ">";
2640              }
2641          }
2642          break;
2643          case 3: // Node.TEXT_NODE
2644          // If a text node is alone in an element and all spaces, replace it with an non breaking one
2645          // This partially undoes the damage done by moz, which translates '&nbsp;'s into spaces in the data element
2646          if ( !root.previousSibling && !root.nextSibling && root.data.match(/^\s*$/i) && root.data.length > 1 ) html = '&nbsp;';
2647          else html = HTMLArea.htmlEncode(root.data);
2648          break;
2649          case 8: // Node.COMMENT_NODE
2650          html = "<!--" + root.data + "-->";
2651          break;      // skip comments, for now.
2652      }
2653  
2654      return HTMLArea.indent(html);
2655  };
2656  
2657  HTMLArea.prototype.stripBaseURL = function(string) {
2658      var baseurl = this.config.baseURL;
2659  
2660      // IE adds the path to an anchor, converting #anchor
2661      // to path/#anchor which of course needs to be fixed
2662      var index = string.indexOf("/#")+1;
2663      if ((index > 0) && (string.indexOf(baseurl) > -1)) {
2664          return string.substr(index);
2665      }
2666      return string; // Moodle doesn't use the code below because
2667                     // Moodle likes to keep absolute links
2668  
2669      // strip to last directory in case baseurl points to a file
2670      baseurl = baseurl.replace(/[^\/]+$/, '');
2671      var basere = new RegExp(baseurl);
2672      string = string.replace(basere, "");
2673  
2674      // strip host-part of URL which is added by MSIE to links relative to server root
2675      baseurl = baseurl.replace(/^(https?:\/\/[^\/]+)(.*)$/, '$1');
2676      basere = new RegExp(baseurl);
2677      return string.replace(basere, "");
2678  };
2679  
2680  String.prototype.trim = function() {
2681      a = this.replace(/^\s+/, '');
2682      return a.replace(/\s+$/, '');
2683  };
2684  
2685  // creates a rgb-style color from a number
2686  HTMLArea._makeColor = function(v) {
2687      if (typeof v != "number") {
2688          // already in rgb (hopefully); IE doesn't get here.
2689          return v;
2690      }
2691      // IE sends number; convert to rgb.
2692      var r = v & 0xFF;
2693      var g = (v >> 8) & 0xFF;
2694      var b = (v >> 16) & 0xFF;
2695      return "rgb(" + r + "," + g + "," + b + ")";
2696  };
2697  
2698  // returns hexadecimal color representation from a number or a rgb-style color.
2699  HTMLArea._colorToRgb = function(v) {
2700      if (!v)
2701          return '';
2702  
2703      // returns the hex representation of one byte (2 digits)
2704      function hex(d) {
2705          return (d < 16) ? ("0" + d.toString(16)) : d.toString(16);
2706      };
2707  
2708      if (typeof v == "number") {
2709          // we're talking to IE here
2710          var r = v & 0xFF;
2711          var g = (v >> 8) & 0xFF;
2712          var b = (v >> 16) & 0xFF;
2713          return "#" + hex(r) + hex(g) + hex(b);
2714      }
2715  
2716      if (v.substr(0, 3) == "rgb") {
2717          // in rgb(...) form -- Mozilla
2718          var re = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/;
2719          if (v.match(re)) {
2720              var r = parseInt(RegExp.$1);
2721              var g = parseInt(RegExp.$2);
2722              var b = parseInt(RegExp.$3);
2723              return "#" + hex(r) + hex(g) + hex(b);
2724          }
2725          // doesn't match RE?!  maybe uses percentages or float numbers
2726          // -- FIXME: not yet implemented.
2727          return null;
2728      }
2729  
2730      if (v.substr(0, 1) == "#") {
2731          // already hex rgb (hopefully :D )
2732          return v;
2733      }
2734  
2735      // if everything else fails ;)
2736      return null;
2737  };
2738  
2739  HTMLArea.prototype._popupDialog = function(url, action, init) {
2740      Dialog(this.popupURL(url), action, init);
2741  };
2742  
2743  // paths
2744  
2745  HTMLArea.prototype.imgURL = function(file, plugin) {
2746      if (typeof plugin == "undefined")
2747          return _editor_url + file;
2748      else
2749          return _editor_url + "plugins/" + plugin + "/img/" + file;
2750  };
2751  
2752  HTMLArea.prototype.popupURL = function(file) {
2753      var url = "";
2754      if (file.match(/^plugin:\/\/(.*?)\/(.*)/)) {
2755          var plugin = RegExp.$1;
2756          var popup = RegExp.$2;
2757          if (!/\.html$/.test(popup))
2758              popup += ".html";
2759          url = _editor_url + "plugins/" + plugin + "/popups/" + popup;
2760      } else
2761          url = _editor_url + this.config.popupURL + file;
2762      return url;
2763  };
2764  
2765  /**
2766   * FIX: Internet Explorer returns an item having the _name_ equal to the given
2767   * id, even if it's not having any id.  This way it can return a different form
2768   * field even if it's not a textarea.  This workarounds the problem by
2769   * specifically looking to search only elements having a certain tag name.
2770   */
2771  HTMLArea.getElementById = function(tag, id) {
2772      var el, i, objs = document.getElementsByTagName(tag);
2773      for (i = objs.length; --i >= 0 && (el = objs[i]);)
2774          if (el.id == id)
2775              return el;
2776      return null;
2777  };
2778  // Modified version of GetHtml plugin's indent.
2779  HTMLArea.indent = function(s, sindentChar) {
2780      var c = [
2781      /*0*/  new RegExp().compile(/<\/?(div|p|h[1-6]|table|tr|td|th|ul|ol|li|blockquote|object|br|hr|img|embed|param|pre|script|html|head|body|meta|link|title|area)[^>]*>/g),
2782      /*1*/  new RegExp().compile(/<\/(div|p|h[1-6]|table|tr|td|th|ul|ol|li|blockquote|object|html|head|body|script)( [^>]*)?>/g),//blocklevel closing tag
2783      /*2*/  new RegExp().compile(/<(div|p|h[1-6]|table|tr|td|th|ul|ol|li|blockquote|object|html|head|body|script)( [^>]*)?>/g),//blocklevel opening tag
2784      /*3*/  new RegExp().compile(/<(br|hr|img|embed|param|pre|meta|link|title|area)[^>]*>/g),//singlet tag
2785      /*4*/  new RegExp().compile(/(^|<\/(pre|script)>)(\s|[^\s])*?(<(pre|script)[^>]*>|$)/g),//find content NOT inside pre and script tags
2786      /*5*/  new RegExp().compile(/(<pre[^>]*>)(\s|[^\s])*?(<\/pre>)/g),//find content inside pre tags
2787      /*6*/  new RegExp().compile(/(^|<!--(\s|\S)*?-->)((\s|\S)*?)(?=<!--(\s|\S)*?-->|$)/g),//find content NOT inside comments
2788      /*7*/  new RegExp().compile(/<\/(table|tbody|tr|td|th|ul|ol|object|html|head|body)( [^>]*)?>/g),//blocklevel closing tag
2789      ];
2790      HTMLArea.__nindent = 0;
2791      HTMLArea.__sindent = "";
2792      HTMLArea.__sindentChar = (typeof sindentChar == "undefined") ? "  " : sindentChar;
2793  
2794      if(HTMLArea.is_gecko) { //moz changes returns into <br> inside <pre> tags
2795          s = s.replace(c[5], function(str){return str.replace(/<br \/>/g,"\n")});
2796      }
2797      s = s.replace(c[4], function(strn) { //skip pre and script tags
2798        strn = strn.replace(c[6], function(st,$1,$2,$3) { //exclude comments
2799          string = $3.replace(/[\n\r]/gi, " ").replace(/\s+/gi," ").replace(c[0], function(str) {
2800              if (str.match(c[2])) {
2801                  var s = "\n" + HTMLArea.__sindent + str;
2802                  // blocklevel openingtag - increase indent
2803                  HTMLArea.__sindent += HTMLArea.__sindentChar;
2804                  ++HTMLArea.__nindent;
2805                  return s;
2806              } else if (str.match(c[1])) {
2807                  // blocklevel closingtag - decrease indent
2808                  --HTMLArea.__nindent;
2809                  HTMLArea.__sindent = "";
2810                  for (var i=HTMLArea.__nindent;i>0;--i) {
2811                      HTMLArea.__sindent += HTMLArea.__sindentChar;
2812                  }
2813                  return (str.match(c[7]) ? "\n" + HTMLArea.__sindent : "") + str;
2814              }
2815              return str; // this won't actually happen
2816          });
2817          return $1 + string;
2818        });return strn;
2819      });
2820      if (s.charAt(0) == "\n") {
2821          return s.substring(1, s.length);
2822      }
2823      s = s.replace(/ *\n/g,'\n');//strip spaces at end of lines
2824      return s;
2825  };


Generated: Wed Jan 14 11:33:29 2009 Cross-referenced by PHPXref 0.7