Project

General

Profile

htmlarea.php

David Ragot, 11/18/2010 10:48 AM

Download (101 KB)

 
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 or (!empty($_SERVER['HTTPS']) and $_SERVER['HTTPS'] != 'off')) {
33
        $url = preg_replace('|https?://[^/]+|', '', $CFG->wwwroot).'/lib/editor/htmlarea/';
34
    } else {
35
        $url = $CFG->wwwroot.'/lib/editor/htmlarea/';
36
    }
37

    
38
    $strheading = get_string("heading", "editor");
39
    $strnormal = get_string("normal", "editor");
40
    $straddress = get_string("address", "editor");
41
    $strpreformatted = get_string("preformatted", "editor");
42
    $strlang = get_string('lang', 'editor');
43
    $strmulti = get_string('multi', 'editor');
44
?>
45

    
46
// htmlArea v3.0 - Copyright (c) 2002, 2003 interactivetools.com, inc.
47
// This copyright notice MUST stay intact for use (see license.txt).
48
//
49
// Portions (c) dynarch.com, 2003-2004
50
//
51
// A free WYSIWYG editor replacement for <textarea> fields.
52
// For full source code and docs, visit http://www.interactivetools.com/
53
//
54
// Version 3.0 developed by Mihai Bazon.
55
//   http://dynarch.com/mishoo
56
//
57
// $Id: htmlarea.php,v 1.24.2.8 2009/08/27 03:21:33 jerome Exp $
58

    
59
if (typeof _editor_url == "string") {
60
    // Leave exactly one backslash at the end of _editor_url
61
    _editor_url = _editor_url.replace(/\x2f*$/, '/');
62
} else {
63
    //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.");
64
    _editor_url = '<?php echo $url; ?>';// we need relative path to site root for editor in pages wit hrequired https
65
}
66

    
67
// make sure we have a language
68
if (typeof _editor_lang == "string") {
69
    _editor_lang = "en"; // should always be english in moodle.
70
} else {
71
    _editor_lang = "en";
72
}
73

    
74
// Creates a new HTMLArea object.  Tries to replace the textarea with the given
75
// ID with it.
76
function HTMLArea(textarea, config) {
77
    if (HTMLArea.checkSupportedBrowser()) {
78
        if (typeof config == "undefined") {
79
            this.config = new HTMLArea.Config();
80
        } else {
81
            this.config = config;
82
        }
83
        this._htmlArea = null;
84
        this._textArea = textarea;
85
        this._editMode = "wysiwyg";
86
        this.plugins = {};
87
        this._timerToolbar = null;
88
        this._timerUndo = null;
89
        this._undoQueue = new Array(this.config.undoSteps);
90
        this._undoPos = -1;
91
        this._customUndo = true;
92
        this._mdoc = document; // cache the document, we need it in plugins
93
        this.doctype = '';
94
        this.dropdowns = [];   // Array of select elements in the toolbar
95
    }
96
};
97

    
98
// load some scripts
99
(function() {
100
    var scripts = HTMLArea._scripts = [ _editor_url + "htmlarea.js",
101
                        _editor_url + "dialog.js",
102
                        _editor_url + "popupwin.js" ];
103
    var head = document.getElementsByTagName("head")[0];
104
    // start from 1, htmlarea.js is already loaded
105
    for (var i = 1; i < scripts.length; ++i) {
106
        var script = document.createElement("script");
107
        script.src = scripts[i];
108
        head.appendChild(script);
109
    }
110
})();
111

    
112
// cache some regexps
113
HTMLArea.RE_tagName = /(<\/|<)\s*([^ \t\n>]+)/ig;
114
HTMLArea.RE_doctype = /(<!doctype((.|\n)*?)>)\n?/i;
115
HTMLArea.RE_head    = /<head>((.|\n)*?)<\/head>/i;
116
HTMLArea.RE_body    = /<body>((.|\n)*?)<\/body>/i;
117
HTMLArea.RE_blocktag = /^(h1|h2|h3|h4|h5|h6|p|address|pre)$/i;
118
HTMLArea.RE_junktag = /^\/($|\/)/;
119
// Hopefully a complete list of tags that MSIEs parser will consider
120
// as possible content tags. Retrieved from
121
// http://www.echoecho.com/htmlreference.htm
122
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
123

    
124
HTMLArea.Config = function () {
125
    this.version = "3.0";
126

    
127
    this.width = "auto";
128
    this.height = "auto";
129

    
130
    // enable creation of a status bar?
131
    this.statusBar = true;
132

    
133
    // maximum size of the undo queue
134
    this.undoSteps = 20;
135

    
136
    // the time interval at which undo samples are taken
137
    this.undoTimeout = 500; // 1/2 sec.
138

    
139
    // the next parameter specifies whether the toolbar should be included
140
    // in the size or not.
141
    this.sizeIncludesToolbar = true;
142

    
143
    // if true then HTMLArea will retrieve the full HTML, starting with the
144
    // <HTML> tag.
145
    this.fullPage = false;
146

    
147
    // style included in the iframe document
148
    this.pageStyle = "body { background-color: #fff; font-family: 'Times New Roman', Times; } \n .lang { background-color: #dee; }";
149

    
150
    // set to true if you want Word code to be cleaned upon Paste
151
    this.killWordOnPaste = true;
152

    
153
    // BaseURL included in the iframe document
154
    this.baseURL = document.baseURI || document.URL;
155
    if (this.baseURL && this.baseURL.match(/(.*)\/([^\/]+)/))
156
        this.baseURL = RegExp.$1 + "/";
157

    
158
    // URL-s
159
    this.imgURL = "images/";
160
    this.popupURL = "popups/";
161

    
162
    this.toolbar = [
163
        [ "fontname", "space",
164
          "fontsize", "space",
165
          "formatblock", "space",
166
          "language", "space",
167
          "bold", "italic", "underline", "strikethrough", "separator",
168
          "subscript", "superscript", "separator",
169
          "clean", "separator", "undo", "redo" ],
170

    
171
        [ "justifyleft", "justifycenter", "justifyright", "justifyfull", "separator",
172
          "lefttoright", "righttoleft", "separator",
173
          "insertorderedlist", "insertunorderedlist", "outdent", "indent", "separator",
174
          "forecolor", "hilitecolor", "separator",
175
          "inserthorizontalrule", "createanchor", "createlink", "unlink", "nolink", "separator",
176
          "insertimage", "inserttable",
177
          "insertsmile", "insertchar", "search_replace",
178
          <?php if (!empty($CFG->aspellpath) && file_exists($CFG->aspellpath) && !empty($CFG->editorspelling)) {
179
              echo '"separator","spellcheck",';
180
            } ?>
181
          "separator", "htmlmode", "separator", "popupeditor"]
182
    ];
183

    
184
    this.fontname = {
185
        "Arial":       'arial,helvetica,sans-serif',
186
        "Courier New":     'courier new,courier,monospace',
187
        "Georgia":     'georgia,times new roman,times,serif',
188
        "Tahoma":      'tahoma,arial,helvetica,sans-serif',
189
        "Times New Roman": 'times new roman,times,serif',
190
        "Verdana":     'verdana,arial,helvetica,sans-serif',
191
        "Impact":           'impact',
192
        "WingDings":       'wingdings'
193
    };
194

    
195
    this.fontsize = {
196
        "1 (8 pt)":  "1",
197
        "2 (10 pt)": "2",
198
        "3 (12 pt)": "3",
199
        "4 (14 pt)": "4",
200
        "5 (18 pt)": "5",
201
        "6 (24 pt)": "6",
202
        "7 (36 pt)": "7"
203
    };
204

    
205
    this.formatblock = {
206
        "":"",
207
        "<?php echo $strheading ?> 1": "h1",
208
        "<?php echo $strheading ?> 2": "h2",
209
        "<?php echo $strheading ?> 3": "h3",
210
        "<?php echo $strheading ?> 4": "h4",
211
        "<?php echo $strheading ?> 5": "h5",
212
        "<?php echo $strheading ?> 6": "h6",
213
        "<?php echo $strnormal ?>": "p",
214
        "<?php echo $straddress ?>": "address",
215
        "<?php echo $strpreformatted ?>": "pre"
216
    };
217

    
218
    this.language = {
219
        "<?php echo $strlang; ?>":"",
220
        <?php
221
        $strlangarray = '';
222
        foreach ($LANGUAGES as $key => $name) {
223
            $key = str_replace('_', '-', $key);
224
            $strlangarray .= '"'.$key.'": "'.$key.'",';
225
        }
226
        $strlangarray .= '"'.$strmulti.'": "multi",';
227

    
228
        foreach ($LANGUAGES as $key => $name) {
229
            $key = str_replace('_', '-', $key);
230
            $strlangarray .= '"'.$key.' ": "'.$key.'_ML",';
231
        }
232
        $strlangarray = substr($strlangarray, 0, -1);
233
        echo $strlangarray;
234
        ?>
235
    };
236

    
237
    this.customSelects = {};
238

    
239
    function cut_copy_paste(e, cmd, obj) {
240
        e.execCommand(cmd);
241
    };
242

    
243
    this.btnList = {
244
        bold: [ "Bold", "ed_format_bold.gif", false, function(e) {e.execCommand("bold");} ],
245
        italic: [ "Italic", "ed_format_italic.gif", false, function(e) {e.execCommand("italic");} ],
246
        underline: [ "Underline", "ed_format_underline.gif", false, function(e) {e.execCommand("underline");} ],
247
        strikethrough: [ "Strikethrough", "ed_format_strike.gif", false, function(e) {e.execCommand("strikethrough");} ],
248
        subscript: [ "Subscript", "ed_format_sub.gif", false, function(e) {e.execCommand("subscript");} ],
249
        superscript: [ "Superscript", "ed_format_sup.gif", false, function(e) {e.execCommand("superscript");} ],
250
        justifyleft: [ "Justify Left", "ed_align_left.gif", false, function(e) {e.execCommand("justifyleft");} ],
251
        justifycenter: [ "Justify Center", "ed_align_center.gif", false, function(e) {e.execCommand("justifycenter");} ],
252
        justifyright: [ "Justify Right", "ed_align_right.gif", false, function(e) {e.execCommand("justifyright");} ],
253
        justifyfull: [ "Justify Full", "ed_align_justify.gif", false, function(e) {e.execCommand("justifyfull");} ],
254
        insertorderedlist: [ "Ordered List", "ed_list_num.gif", false, function(e) {e.execCommand("insertorderedlist");} ],
255
        insertunorderedlist: [ "Bulleted List", "ed_list_bullet.gif", false, function(e) {e.execCommand("insertunorderedlist");} ],
256
        outdent: [ "Decrease Indent", "ed_indent_less.gif", false, function(e) {e.execCommand("outdent");} ],
257
        indent: [ "Increase Indent", "ed_indent_more.gif", false, function(e) {e.execCommand("indent");} ],
258
        forecolor: [ "Font Color", "ed_color_fg.gif", false, function(e) {e.execCommand("forecolor");} ],
259
        hilitecolor: [ "Background Color", "ed_color_bg.gif", false, function(e) {e.execCommand("hilitecolor");} ],
260
        inserthorizontalrule: [ "Horizontal Rule", "ed_hr.gif", false, function(e) {e.execCommand("inserthorizontalrule");} ],
261
        createanchor: [ "Create anchor", "ed_anchor.gif", false, function(e) {e.execCommand("createanchor", true);} ],
262
        createlink: [ "Insert Web Link", "ed_link.gif", false, function(e) {e.execCommand("createlink", true);} ],
263
        unlink: [ "Remove Link", "ed_unlink.gif", false, function(e) {e.execCommand("unlink");} ],
264
        nolink: [ "No link", "ed_nolink.gif", false, function(e) {e.execCommand("nolink");} ],
265
        insertimage: [ "Insert/Modify Image", "ed_image.gif", false, function(e) {e.execCommand("insertimage");} ],
266
        inserttable: [ "Insert Table", "insert_table.gif", false, function(e) {e.execCommand("inserttable");} ],
267
        htmlmode: [ "Toggle HTML Source", "ed_html.gif", true, function(e) {e.execCommand("htmlmode");} ],
268
        popupeditor: [ "Enlarge Editor", "fullscreen_maximize.gif", true, function(e) {e.execCommand("popupeditor");} ],
269
        about: [ "About this editor", "ed_about.gif", true, function(e) {e.execCommand("about");} ],
270
        showhelp: [ "Help using editor", "ed_help.gif", true, function(e) {e.execCommand("showhelp");} ],
271
        undo: [ "Undoes your last action", "ed_undo.gif", false, function(e) {e.execCommand("undo");} ],
272
        redo: [ "Redoes your last action", "ed_redo.gif", false, function(e) {e.execCommand("redo");} ],
273
        clean: [ "Clean Word HTML", "ed_wordclean.gif", false, function(e) {e.execCommand("killword"); }],
274
        lefttoright: [ "Direction left to right", "ed_left_to_right.gif", false, function(e) {e.execCommand("lefttoright");} ],
275
        righttoleft: [ "Direction right to left", "ed_right_to_left.gif", false, function(e) {e.execCommand("righttoleft");} ],
276
        <?php if (!empty($CFG->aspellpath) && file_exists($CFG->aspellpath) && !empty($CFG->editorspelling)) {
277
            echo 'spellcheck: ["Spell-check", "spell-check.gif", false, spellClickHandler ],'."\n";
278
        }?>
279
        insertsmile: ["Insert Smiley", "em.icon.smile.gif", false, function(e) {e.execCommand("insertsmile");} ],
280
        insertchar: [ "Insert Char", "icon_ins_char.gif", false, function(e) {e.execCommand("insertchar");} ],
281
        search_replace: [ "Search and replace", "ed_replace.gif", false, function(e) {e.execCommand("searchandreplace");} ]
282
    };
283

    
284
    // initialize tooltips from the I18N module and generate correct image path
285
    for (var i in this.btnList) {
286
        var btn = this.btnList[i];
287
        btn[1] = _editor_url + this.imgURL + btn[1];
288
        if (typeof HTMLArea.I18N.tooltips[i] != "undefined") {
289
            btn[0] = HTMLArea.I18N.tooltips[i];
290
        }
291
    }
292
};
293

    
294
HTMLArea.Config.prototype.registerButton = function(id, tooltip, image, textMode, action, context) {
295
    var the_id;
296
    if (typeof id == "string") {
297
        the_id = id;
298
    } else if (typeof id == "object") {
299
        the_id = id.id;
300
    } else {
301
        alert("ERROR [HTMLArea.Config::registerButton]:\ninvalid arguments");
302
        return false;
303
    }
304
    // check for existing id
305
    if (typeof this.customSelects[the_id] != "undefined") {
306
        // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA dropdown with the same ID already exists.");
307
    }
308
    if (typeof this.btnList[the_id] != "undefined") {
309
        // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA button with the same ID already exists.");
310
    }
311
    switch (typeof id) {
312
        case "string": this.btnList[id] = [ tooltip, image, textMode, action, context ]; break;
313
        case "object": this.btnList[id.id] = [ id.tooltip, id.image, id.textMode, id.action, id.context ]; break;
314
    }
315
};
316

    
317
HTMLArea.Config.prototype.registerDropdown = function(object) {
318
    // check for existing id
319
    if (typeof this.customSelects[object.id] != "undefined") {
320
        // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA dropdown with the same ID already exists.");
321
    }
322
    if (typeof this.btnList[object.id] != "undefined") {
323
        // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA button with the same ID already exists.");
324
    }
325
    this.customSelects[object.id] = object;
326
};
327

    
328
HTMLArea.Config.prototype.hideSomeButtons = function(remove) {
329
    var toolbar = this.toolbar;
330
    for (var i in toolbar) {
331
        var line = toolbar[i];
332
        for (var j = line.length; --j >= 0; ) {
333
            if (remove.indexOf(" " + line[j] + " ") >= 0) {
334
                var len = 1;
335
                if (/separator|space/.test(line[j + 1])) {
336
                    len = 2;
337
                }
338
                line.splice(j, len);
339
            }
340
        }
341
    }
342
};
343

    
344
/** Helper function: replace all TEXTAREA-s in the document with HTMLArea-s. */
345
HTMLArea.replaceAll = function(config) {
346
    var tas = document.getElementsByTagName("textarea");
347
    for (var i = tas.length; i > 0; (new HTMLArea(tas[--i], config)).generate());
348
};
349

    
350
/** Helper function: replaces the TEXTAREA with the given ID with HTMLArea. */
351
HTMLArea.replace = function(id, config) {
352
    var ta = HTMLArea.getElementById("textarea", id);
353
    return ta ? (new HTMLArea(ta, config)).generate() : null;
354
};
355

    
356
// Creates the toolbar and appends it to the _htmlarea
357
HTMLArea.prototype._createToolbar = function () {
358
    var editor = this;  // to access this in nested functions
359

    
360
    var toolbar = document.createElement("div");
361
    this._toolbar = toolbar;
362
    toolbar.className = "toolbar";
363
    toolbar.unselectable = "1";
364
    var tb_row = null;
365
    var tb_objects = new Object();
366
    this._toolbarObjects = tb_objects;
367

    
368
    // creates a new line in the toolbar
369
    function newLine() {
370
        var table = document.createElement("table");
371
        table.border = "0px";
372
        table.cellSpacing = "0px";
373
        table.cellPadding = "0px";
374
        toolbar.appendChild(table);
375
        // TBODY is required for IE, otherwise you don't see anything
376
        // in the TABLE.
377
        var tb_body = document.createElement("tbody");
378
        table.appendChild(tb_body);
379
        tb_row = document.createElement("tr");
380
        tb_body.appendChild(tb_row);
381
    }; // END of function: newLine
382
    // init first line
383
    newLine();
384

    
385
    function setButtonStatus(id, newval) {
386
        var oldval = this[id];
387
        var el = this.element;
388
        if (oldval != newval) {
389
            switch (id) {
390
                case "enabled":
391
                if (newval) {
392
                    HTMLArea._removeClass(el, "buttonDisabled");
393
                    el.disabled = false;
394
                } else {
395
                    HTMLArea._addClass(el, "buttonDisabled");
396
                    el.disabled = true;
397
                }
398
                break;
399
                case "active":
400
                if (newval) {
401
                    HTMLArea._addClass(el, "buttonPressed");
402
                } else {
403
                    HTMLArea._removeClass(el, "buttonPressed");
404
                }
405
                break;
406
            }
407
            this[id] = newval;
408
        }
409
    }; // END of function: setButtonStatus
410

    
411
    function createSelect(txt) {
412
        var options = null;
413
        var el = null;
414
        var cmd = null;
415
        var customSelects = editor.config.customSelects;
416
        var context = null;
417
        switch (txt) {
418
            case "fontsize":
419
            case "fontname":
420
            case "formatblock":
421
            case "language":
422
            options = editor.config[txt];
423
            cmd = txt;
424
            break;
425
            default:
426
            // try to fetch it from the list of registered selects
427
            cmd = txt;
428
            var dropdown = customSelects[cmd];
429
            if (typeof dropdown != "undefined") {
430
                options = dropdown.options;
431
                context = dropdown.context;
432
            } else {
433
                alert("ERROR [createSelect]:\nCan't find the requested dropdown definition");
434
            }
435
            break;
436
        }
437
        if (options) {
438
            el = document.createElement("select");
439
            var obj = {
440
                name    : txt, // field name
441
                element : el,   // the UI element (SELECT)
442
                enabled : true, // is it enabled?
443
                text    : false, // enabled in text mode?
444
                cmd : cmd, // command ID
445
                state   : setButtonStatus, // for changing state
446
                context : context
447
            };
448
            tb_objects[txt] = obj;
449
            for (var i in options) {
450
                var op = document.createElement("option");
451
                op.appendChild(document.createTextNode(i));
452
                op.value = options[i];
453
                el.appendChild(op);
454
            }
455
            HTMLArea._addEvent(el, "change", function () {
456
                editor._comboSelected(el, txt);
457
            });
458
        }
459
        editor.dropdowns[txt] = el;  // Keep track of the element for keyboard
460
                                     // access later.
461
        return el;
462
    }; // END of function: createSelect
463

    
464
    // appends a new button to toolbar
465
    function createButton(txt) {
466
        // the element that will be created
467
        var el = null;
468
        var btn = null;
469
        switch (txt) {
470
            case "separator":
471
            el = document.createElement("div");
472
            el.className = "separator";
473
            break;
474
            case "space":
475
            el = document.createElement("div");
476
            el.className = "space";
477
            break;
478
            case "linebreak":
479
            newLine();
480
            return false;
481
            case "textindicator":
482
            el = document.createElement("div");
483
            el.appendChild(document.createTextNode("A"));
484
            el.className = "indicator";
485
            el.title = HTMLArea.I18N.tooltips.textindicator;
486
            var obj = {
487
                name    : txt, // the button name (i.e. 'bold')
488
                element : el, // the UI element (DIV)
489
                enabled : true, // is it enabled?
490
                active  : false, // is it pressed?
491
                text    : false, // enabled in text mode?
492
                cmd : "textindicator", // the command ID
493
                state   : setButtonStatus // for changing state
494
            };
495
            tb_objects[txt] = obj;
496
            break;
497
            default:
498
            btn = editor.config.btnList[txt];
499
        }
500
        if (!el && btn) {
501
            el = document.createElement("div");
502
            el.title = btn[0];
503
            el.className = "button";
504
            // let's just pretend we have a button object, and
505
            // assign all the needed information to it.
506
            var obj = {
507
                name    : txt, // the button name (i.e. 'bold')
508
                element : el, // the UI element (DIV)
509
                enabled : true, // is it enabled?
510
                active  : false, // is it pressed?
511
                text    : btn[2], // enabled in text mode?
512
                cmd : btn[3], // the command ID
513
                state   : setButtonStatus, // for changing state
514
                context : btn[4] || null // enabled in a certain context?
515
            };
516
            tb_objects[txt] = obj;
517
            // handlers to emulate nice flat toolbar buttons
518
            HTMLArea._addEvent(el, "mouseover", function () {
519
                if (obj.enabled) {
520
                    HTMLArea._addClass(el, "buttonHover");
521
                }
522
            });
523
            HTMLArea._addEvent(el, "mouseout", function () {
524
                if (obj.enabled) with (HTMLArea) {
525
                    _removeClass(el, "buttonHover");
526
                    _removeClass(el, "buttonActive");
527
                    (obj.active) && _addClass(el, "buttonPressed");
528
                }
529
            });
530
            HTMLArea._addEvent(el, "mousedown", function (ev) {
531
                if (obj.enabled) with (HTMLArea) {
532
                    _addClass(el, "buttonActive");
533
                    _removeClass(el, "buttonPressed");
534
                    _stopEvent(is_ie ? window.event : ev);
535
                }
536
            });
537
            // when clicked, do the following:
538
            HTMLArea._addEvent(el, "click", function (ev) {
539
                if (obj.enabled) with (HTMLArea) {
540
                    _removeClass(el, "buttonActive");
541
                    _removeClass(el, "buttonHover");
542
                    obj.cmd(editor, obj.name, obj);
543
                    _stopEvent(is_ie ? window.event : ev);
544
                }
545
            });
546
            var img = document.createElement("img");
547
            img.src = btn[1];
548
            img.style.width = "18px";
549
            img.style.height = "18px";
550
            el.appendChild(img);
551
        } else if (!el) {
552
            el = createSelect(txt);
553
        }
554
        if (el) {
555
            var tb_cell = document.createElement("td");
556
            tb_row.appendChild(tb_cell);
557
            tb_cell.appendChild(el);
558
        } else {
559
            alert("FIXME: Unknown toolbar item: " + txt);
560
        }
561
        return el;
562
    };
563

    
564
    var first = true;
565
    for (var i in this.config.toolbar) {
566
        if (this.config.toolbar.propertyIsEnumerable(i)) { // fix for prototype.js compatibility
567
        if (!first) {
568
            createButton("linebreak");
569
        } else {
570
            first = false;
571
        }
572
        var group = this.config.toolbar[i];
573
        for (var j in group) {
574
                if (group.propertyIsEnumerable(j)) { // fix for prototype.js compatibility
575
            var code = group[j];
576
            if (/^([IT])\[(.*?)\]/.test(code)) {
577
                // special case, create text label
578
                var l7ed = RegExp.$1 == "I"; // localized?
579
                var label = RegExp.$2;
580
                if (l7ed) {
581
                    label = HTMLArea.I18N.custom[label];
582
                }
583
                var tb_cell = document.createElement("td");
584
                tb_row.appendChild(tb_cell);
585
                tb_cell.className = "label";
586
                tb_cell.innerHTML = label;
587
            } else {
588
                createButton(code);
589
                    }
590
                }
591
            }
592
        }
593
    }
594

    
595
    this._htmlArea.appendChild(toolbar);
596
};
597

    
598
HTMLArea.prototype._createStatusBar = function() {
599
    var statusbar = document.createElement("div");
600
    statusbar.className = "statusBar";
601
    this._htmlArea.appendChild(statusbar);
602
    this._statusBar = statusbar;
603
    // statusbar.appendChild(document.createTextNode(HTMLArea.I18N.msg["Path"] + ": "));
604
    // creates a holder for the path view
605
    div = document.createElement("span");
606
    div.className = "statusBarTree";
607
    div.innerHTML = HTMLArea.I18N.msg["Path"] + ": ";
608
    this._statusBarTree = div;
609
    this._statusBar.appendChild(div);
610
    if (!this.config.statusBar) {
611
        // disable it...
612
        statusbar.style.display = "none";
613
    }
614
};
615

    
616
// Creates the HTMLArea object and replaces the textarea with it.
617
HTMLArea.prototype.generate = function () {
618
    var editor = this;  // we'll need "this" in some nested functions
619

    
620
    // get the textarea
621
    var textarea = this._textArea;
622
    if (typeof textarea == "string") {
623
        // it's not element but ID
624
        this._textArea = textarea = HTMLArea.getElementById("textarea", textarea);
625
    }
626
    // Fix for IE's sticky bug. Editor doesn't load
627
    // editing area.
628
    var height;
629
    if ( textarea.offsetHeight && textarea.offsetHeight > 0 ) {
630
        height = textarea.offsetHeight;
631
    } else {
632
        height = 300;
633
    }
634
    this._ta_size = {
635
        w: textarea.offsetWidth,
636
        h: height
637
    };
638
    textarea.style.display = "none";
639

    
640
    // create the editor framework
641
    var htmlarea = document.createElement("div");
642
    htmlarea.className = "htmlarea";
643
    this._htmlArea = htmlarea;
644

    
645
    // insert the editor before the textarea.
646
    //Bug fix - unless the textarea is nested within its label, in which case insert editor before label.
647
    if (textarea.parentNode.nodeName.toLowerCase()=='label') {
648
        textarea.parentNode.parentNode.insertBefore(htmlarea,textarea.parentNode);
649
    }
650
    else {
651
        textarea.parentNode.insertBefore(htmlarea, textarea);
652
    }
653

    
654
    if (textarea.form) {
655
        // we have a form, on submit get the HTMLArea content and
656
        // update original textarea.
657
        var f = textarea.form;
658
        if (typeof f.onsubmit == "function") {
659
            var funcref = f.onsubmit;
660
            if (typeof f.__msh_prevOnSubmit == "undefined") {
661
                f.__msh_prevOnSubmit = [];
662
            }
663
            f.__msh_prevOnSubmit.push(funcref);
664
        }
665
        f.onsubmit = function() {
666
            // Moodle hack. Bug fix #2736
667
            var test = editor.getHTML();
668
            test = test.replace(/<br \/>/gi, '');
669
            test = test.replace(/\&nbsp\;/gi, '');
670
            test = test.trim();
671
            //alert(test + test.length);
672
            if (test.length < 1) {
673
                editor._textArea.value = test.trim();
674
            } else {
675
                editor._textArea.value = editor.getHTML();
676
            }
677
            // Moodle hack end.
678
            var a = this.__msh_prevOnSubmit;
679
            var ret = true;
680
            // call previous submit methods if they were there.
681
            if (typeof a != "undefined") {
682
                for (var i = a.length; --i >= 0;) {
683
                    ret = a[i]() && ret;
684
                }
685
            }
686
            return ret;
687
        };
688
        if (typeof f.onreset == "function") {
689
            var funcref = f.onreset;
690
            if (typeof f.__msh_prevOnReset == "undefined") {
691
                f.__msh_prevOnReset = [];
692
            }
693
            f.__msh_prevOnReset.push(funcref);
694
        }
695
        f.onreset = function() {
696
            editor.setHTML(editor._textArea.value);
697
            editor.updateToolbar();
698
            var a = this.__msh_prevOnReset;
699
            // call previous reset methods if they were there.
700
            if (typeof a != "undefined") {
701
                for (var i = a.length; --i >= 0;) {
702
                    a[i]();
703
                }
704
            }
705
        };
706
    }
707

    
708
    // add a handler for the "back/forward" case -- on body.unload we save
709
    // the HTML content into the original textarea.
710
    try {
711
    window.onunload = function() {
712
        editor._textArea.value = editor.getHTML();
713
    };
714
    } catch(e) {};
715

    
716
    // creates & appends the toolbar
717
    this._createToolbar();
718

    
719
    // create the IFRAME
720
    var iframe = document.createElement("iframe");
721

    
722
    iframe.src = "<?php echo $url; ?>blank.html";
723

    
724
    iframe.className = "iframe";
725

    
726
    htmlarea.appendChild(iframe);
727

    
728
    var editor = this
729
    editor._iframe = iframe;
730
    var doc = editor._iframe.contentWindow.document;
731
    editor._doc = doc;
732

    
733
    // Generate iframe content
734
    var html = ""
735
    if (!editor.config.fullPage) {
736
        html = "<html>\n";
737
        html += "<head>\n";
738
        html += '<meta http-equiv="content-type" content="text/html; charset=utf-8" />\n';
739
        if (editor.config.baseURL)
740
            html += '<base href="' + editor.config.baseURL + '" />';
741
        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
742
        html += "</head>\n";
743
        html += '<body>\n';
744
        html += editor._textArea.value;
745
        html = html.replace(/<nolink>/gi, '<span class="nolink">').
746
                    replace(/<\/nolink>/gi, '</span>');
747
        html += "</body>\n";
748
        html += "</html>";
749
    } else {
750
        html = editor._textArea.value;
751
        if (html.match(HTMLArea.RE_doctype)) {
752
            editor.setDoctype(RegExp.$1);
753
            html = html.replace(HTMLArea.RE_doctype, "");
754
        }
755
    }
756

    
757
    // Write content to iframe
758
    doc.open();
759
    doc.write(html);
760
    doc.close();
761

    
762
    // The magic: onClick the designMode is set to 'on'
763
    // This one is for click on HTMLarea toolbar and else
764
    if(HTMLArea.is_gecko) {
765
        HTMLArea._addEvents(
766
          this._htmlArea,
767
          ["mousedown"],
768
          function(event) {
769
            if(editor.designModeIsOn != true)
770
            {
771
                editor.designModeIsOn = true;
772
                try {
773
                  doc.designMode = "on";
774
                } catch (e) {
775
                  alert(e)
776
                };
777
            }
778
          }
779
        );
780

    
781
        // This one is for click in iframe
782
        HTMLArea._addEvents(
783
          editor._iframe.contentWindow,
784
          ["mousedown"],
785
          function(event) {
786
            if(editor.designModeIsOn != true)
787
            {
788
                editor.designModeIsOn = true;
789
                try {
790
                  doc.designMode = "on";
791
                } catch (e) {
792
                  alert(e)
793
                };
794
            }
795
          }
796
        );
797
    }
798
    // creates & appends the status bar, if the case
799
    this._createStatusBar();
800

    
801
    // remove the default border as it keeps us from computing correctly
802
    // the sizes.  (somebody tell me why doesn't this work in IE)
803

    
804
    if (!HTMLArea.is_ie) {
805
        iframe.style.borderWidth = "1px";
806
    }
807

    
808
    // size the IFRAME according to user's prefs or initial textarea
809
    var height = (this.config.height == "auto" ? (this._ta_size.h) : this.config.height);
810
    height = parseInt(height);
811
    var width = (this.config.width == "auto" ? (this._toolbar.offsetWidth) : this.config.width);
812
    width = (width == 0 ? 598 : width);
813
    //width = Math.max(parseInt(width), 598);
814

    
815
    width = String(width);
816
    if (width.match(/^\d+$/)) { // is this a pure int? if so, let it be in px, and remove 2px
817
        height -= 2;
818
        width  -= 2;
819
        width=width+"px";
820
    }
821

    
822
    iframe.style.width = width;
823

    
824
    if (this.config.sizeIncludesToolbar) {
825
        // substract toolbar height
826
        height -= this._toolbar.offsetHeight;
827
        height -= this._statusBar.offsetHeight;
828
    }
829
    if (height < 0) {
830
        height = 0;
831
    }
832
    iframe.style.height = height + "px";
833

    
834
    // the editor including the toolbar now have the same size as the
835
    // original textarea.. which means that we need to reduce that a bit.
836
    textarea.style.width = iframe.style.width;
837
    textarea.style.height = iframe.style.height;
838

    
839
    if (HTMLArea.is_ie) {
840
        doc.body.contentEditable = true;
841
    }
842

    
843
    // intercept some events; for updating the toolbar & keyboard handlers
844
    HTMLArea._addEvents
845
          (doc, ["keydown", "keypress", "mousedown", "mouseup", "drag"],
846
          function (event) {
847
              return editor._editorEvent(HTMLArea.is_ie ? editor._iframe.contentWindow.event : event);
848
          });
849

    
850
    // check if any plugins have registered refresh handlers
851
    for (var i in editor.plugins) {
852
        var plugin = editor.plugins[i].instance;
853
        if (typeof plugin.onGenerate == "function") {
854
            plugin.onGenerate();
855
        }
856
        if (typeof plugin.onGenerateOnce == "function") {
857
            plugin.onGenerateOnce();
858
            plugin.onGenerateOnce = null;
859
        }
860
    }
861

    
862
    // Moodle fix for bug Bug #2521 Too long statusbar line in IE
863
    //
864
    //setTimeout(function() {
865
    //    editor.updateToolbar();
866
    //}, 250);
867

    
868
    if (typeof editor.onGenerate == "function") {
869
        editor.onGenerate();
870
    }
871
};
872

    
873

    
874
// Switches editor mode; parameter can be "textmode" or "wysiwyg".  If no
875
// parameter was passed this function toggles between modes.
876
HTMLArea.prototype.setMode = function(mode) {
877
    if (typeof mode == "undefined") {
878
        mode = ((this._editMode == "textmode") ? "wysiwyg" : "textmode");
879
    }
880
    switch (mode) {
881
        case "textmode":
882
        this._textArea.value = this.getHTML();
883
        this._iframe.style.display = "none";
884
        this._textArea.style.display = "block";
885
        if (this.config.statusBar) {
886
            while(this._statusBar.childNodes.length>0) {
887
                this._statusBar.removeChild(this._statusBar.childNodes[0]);
888
            }
889

    
890
            this._statusBar.appendChild(document.createTextNode(HTMLArea.I18N.msg["TEXT_MODE"]));
891
        }
892
        break;
893
        case "wysiwyg":
894
        if (HTMLArea.is_gecko) {
895
            // disable design mode before changing innerHTML
896
            try {
897
            this._doc.designMode = "off";
898
            } catch(e) {};
899
        }
900
        if (!this.config.fullPage)
901
            this._doc.body.innerHTML = this.getHTML();
902
        else
903
            this.setFullHTML(this.getHTML());
904
        this._iframe.style.display = "block";
905
        this._textArea.style.display = "none";
906
        if (HTMLArea.is_gecko) {
907
            // we need to refresh that info for Moz-1.3a
908
            try {
909
            this._doc.designMode = "on";
910
            //this._doc.focus();
911
            } catch(e) {};
912
        }
913
        if (this.config.statusBar) {
914
            this._statusBar.innerHTML = '';
915
            this._statusBar.appendChild(this._statusBarTree);
916
        }
917
        break;
918
        default:
919
        alert("Mode <" + mode + "> not defined!");
920
        return false;
921
    }
922
    this._editMode = mode;
923
    this.focusEditor();
924
};
925

    
926
HTMLArea.prototype.setFullHTML = function(html) {
927
    var save_multiline = RegExp.multiline;
928
    RegExp.multiline = true;
929
    if (html.match(HTMLArea.RE_doctype)) {
930
        this.setDoctype(RegExp.$1);
931
        html = html.replace(HTMLArea.RE_doctype, "");
932
    }
933
    RegExp.multiline = save_multiline;
934
    if (!HTMLArea.is_ie) {
935
        if (html.match(HTMLArea.RE_head))
936
            this._doc.getElementsByTagName("head")[0].innerHTML = RegExp.$1;
937
        if (html.match(HTMLArea.RE_body))
938
            this._doc.getElementsByTagName("body")[0].innerHTML = RegExp.$1;
939
    } else {
940
        var html_re = /<html>((.|\n)*?)<\/html>/i;
941
        html = html.replace(html_re, "$1");
942
        this._doc.open();
943
        this._doc.write(html);
944
        this._doc.close();
945
        this._doc.body.contentEditable = true;
946
        return true;
947
    }
948
};
949

    
950
// Category: PLUGINS
951

    
952
HTMLArea.prototype.registerPlugin2 = function(plugin, args) {
953
    if (typeof plugin == "string")
954
        plugin = eval(plugin);
955
    var obj = new plugin(this, args);
956
    if (obj) {
957
        var clone = {};
958
        var info = plugin._pluginInfo;
959
        for (var i in info)
960
            clone[i] = info[i];
961
        clone.instance = obj;
962
        clone.args = args;
963
        this.plugins[plugin._pluginInfo.name] = clone;
964
    } else
965
        alert("Can't register plugin " + plugin.toString() + ".");
966
};
967

    
968
// Create the specified plugin and register it with this HTMLArea
969
HTMLArea.prototype.registerPlugin = function() {
970
    var plugin = arguments[0];
971
    var args = [];
972
    for (var i = 1; i < arguments.length; ++i)
973
        args.push(arguments[i]);
974
    this.registerPlugin2(plugin, args);
975
};
976

    
977
HTMLArea.loadPlugin = function(pluginName) {
978
    var dir = _editor_url + "plugins/" + pluginName;
979
    var plugin = pluginName.replace(/([a-z])([A-Z])([a-z])/g,
980
                    function (str, l1, l2, l3) {
981
                        return l1 + "-" + l2.toLowerCase() + l3;
982
                    }).toLowerCase() + ".js";
983
    var plugin_file = dir + "/" + plugin;
984
    var plugin_lang = dir + "/lang/" + HTMLArea.I18N.lang + ".js";
985
    HTMLArea._scripts.push(plugin_file, plugin_lang);
986
    document.write("<script type='text/javascript' src='" + plugin_file + "'></script>");
987
    document.write("<script type='text/javascript' src='" + plugin_lang + "'></script>");
988
};
989

    
990
HTMLArea.loadStyle = function(style, plugin) {
991
    var url = _editor_url || '';
992
    if (typeof plugin != "undefined") {
993
        url += "plugins/" + plugin + "/";
994
    }
995
    url += style;
996
    document.write("<style type='text/css'>@import url(" + url + ");</style>");
997
};
998
HTMLArea.loadStyle("htmlarea.css");
999

    
1000
// Category: EDITOR UTILITIES
1001

    
1002
// The following function is a slight variation of the word cleaner code posted
1003
// by Weeezl (user @ InteractiveTools forums).
1004
HTMLArea.prototype._wordClean = function() {
1005
    this._unnestBlocks();
1006

    
1007
    var D = this.getInnerHTML();
1008
    if (/[Mm]so/.test(D)) {
1009

    
1010
        // make one line
1011
        D = D.replace(/\r\n/g, '\[br\]').
1012
            replace(/\n/g, '').
1013
            replace(/\r/g, '').
1014
            replace(/\&nbsp\;/g,' ');
1015

    
1016
        // keep tags, strip attributes
1017
        D = D.replace(/ class=[^\s|>]*/gi,'').
1018
            //replace(/<p [^>]*TEXT-ALIGN: justify[^>]*>/gi,'<p align="justify">').
1019
            replace(/ style=\"[^>]*\"/gi,'').
1020
            replace(/ align=[^\s|>]*/gi,'');
1021

    
1022
        //clean up tags
1023
        D = D.replace(/<b [^>]*>/gi,'<b>').
1024
            replace(/<i [^>]*>/gi,'<i>').
1025
            replace(/<li [^>]*>/gi,'<li>').
1026
            replace(/<ul [^>]*>/gi,'<ul>');
1027

    
1028
        // replace outdated tags
1029
        D = D.replace(/<b>/gi,'<strong>').
1030
            replace(/<\/b>/gi,'</strong>');
1031

    
1032
        // mozilla doesn't like <em> tags
1033
        D = D.replace(/<em>/gi,'<i>').
1034
            replace(/<\/em>/gi,'</i>');
1035

    
1036
        // kill unwanted tags
1037
        D = D.replace(/<\?xml:[^>]*>/g, '').       // Word xml
1038
            replace(/<\/?st1:[^>]*>/g,'').     // Word SmartTags
1039
            replace(/<\/?[a-z]\:[^>]*>/g,'').  // All other funny Word non-HTML stuff
1040
            replace(/<\/?personname[^>]*>/gi,'').
1041
            replace(/<\/?font[^>]*>/gi,'').    // Disable if you want to keep font formatting
1042
            replace(/<\/?span[^>]*>/gi,' ').
1043
            replace(/<\/?div[^>]*>/gi,' ').
1044
            replace(/<\/?pre[^>]*>/gi,' ').
1045
            replace(/<(\/?)(h[1-6]+)[^>]*>/gi,'<$1$2>');
1046

    
1047
        // Lorenzo Nicola's addition
1048
        // to get rid off silly word generated tags.
1049
        D = D.replace(/<!--\[[^\]]*\]-->/gi,' ');
1050

    
1051
        //remove empty tags
1052
        //D = D.replace(/<strong><\/strong>/gi,'').
1053
        //replace(/<i><\/i>/gi,'').
1054
        //replace(/<P[^>]*><\/P>/gi,'');
1055
        D = D.replace(/<h[1-6]+>\s?<\/h[1-6]+>/gi, ''); // Remove empty headings
1056

    
1057
        // nuke double tags
1058
        oldlen = D.length + 1;
1059
        while(oldlen > D.length) {
1060
            oldlen = D.length;
1061
            // join us now and free the tags, we'll be free hackers, we'll be free... ;-)
1062
            D = D.replace(/<([a-z][a-z]*)> *<\/\1>/gi,' ').
1063
                replace(/<([a-z][a-z]*)> *<([a-z][^>]*)> *<\/\1>/gi,'<$2>');
1064
        }
1065
        D = D.replace(/<([a-z][a-z]*)><\1>/gi,'<$1>').
1066
            replace(/<\/([a-z][a-z]*)><\/\1>/gi,'<\/$1>');
1067

    
1068
        // nuke double spaces
1069
        D = D.replace(/  */gi,' ');
1070

    
1071
        // Split into lines and remove
1072
        // empty lines and add carriage returns back
1073
        var splitter  = /\[br\]/g;
1074
        var emptyLine = /^\s+\s+$/g;
1075
        var strHTML   = '';
1076
        var toLines   = D.split(splitter);
1077
        for (var i = 0; i < toLines.length; i++) {
1078
            var line = toLines[i];
1079
            if (line.length < 1) {
1080
                continue;
1081
            }
1082

    
1083
            if (emptyLine.test(line)) {
1084
                continue;
1085
            }
1086

    
1087
            line = line.replace(/^\s+\s+$/g, '');
1088
            strHTML += line + '\n';
1089
        }
1090
        D = strHTML;
1091
        strHTML = '';
1092

    
1093
        this.setHTML(D);
1094
        this.updateToolbar();
1095
    }
1096
};
1097

    
1098
HTMLArea.prototype._unnestBlockWalk = function(node, unnestingParent) {
1099
    if (HTMLArea.RE_blocktag.test(node.nodeName)) {
1100
        if (unnestingParent) {
1101
            if (node.nextSibling) {
1102
                var splitNode = this._doc.createElement(unnestingParent.nodeName.toLowerCase());
1103
                while (node.nextSibling) {
1104
                    splitNode.appendChild(node.nextSibling);
1105
                }
1106
                unnestingParent.parentNode.insertBefore(splitNode, unnestingParent.nextSibling);
1107
            }
1108
            unnestingParent.parentNode.insertBefore(node, unnestingParent.nextSibling);
1109
            return;
1110
        }
1111
        else if (node.firstChild) {
1112
            this._unnestBlockWalk(node.firstChild, node);
1113
        }
1114
    } else {
1115
        if (node.firstChild) {
1116
            this._unnestBlockWalk(node.firstChild, null);
1117
        }
1118
    }
1119
    if (node.nextSibling) {
1120
        this._unnestBlockWalk(node.nextSibling, unnestingParent);
1121
    }
1122
}
1123

    
1124
HTMLArea.prototype._unnestBlocks = function() {
1125
    this._unnestBlockWalk(this._doc.documentElement, null);
1126
}
1127

    
1128
HTMLArea.prototype.forceRedraw = function() {
1129
    this._doc.body.style.visibility = "hidden";
1130
    this._doc.body.style.visibility = "visible";
1131
    // this._doc.body.innerHTML = this.getInnerHTML();
1132
};
1133

    
1134
// focuses the iframe window.  returns a reference to the editor document.
1135
HTMLArea.prototype.focusEditor = function() {
1136
    switch (this._editMode) {
1137
        case "wysiwyg" : this._iframe.contentWindow.focus(); break;
1138
        case "textmode": this._textArea.focus(); break;
1139
        default    : alert("ERROR: mode " + this._editMode + " is not defined");
1140
    }
1141
    return this._doc;
1142
};
1143

    
1144
// takes a snapshot of the current text (for undo)
1145
HTMLArea.prototype._undoTakeSnapshot = function() {
1146
    ++this._undoPos;
1147
    if (this._undoPos >= this.config.undoSteps) {
1148
        // remove the first element
1149
        this._undoQueue.shift();
1150
        --this._undoPos;
1151
    }
1152
    // use the fasted method (getInnerHTML);
1153
    var take = true;
1154
    var txt = this.getInnerHTML();
1155
    if (this._undoPos > 0)
1156
        take = (this._undoQueue[this._undoPos - 1] != txt);
1157
    if (take) {
1158
        this._undoQueue[this._undoPos] = txt;
1159
    } else {
1160
        this._undoPos--;
1161
    }
1162
};
1163

    
1164
HTMLArea.prototype.undo = function() {
1165
    if (this._undoPos > 0) {
1166
        var txt = this._undoQueue[--this._undoPos];
1167
        if (txt) this.setHTML(txt);
1168
        else ++this._undoPos;
1169
    }
1170
};
1171

    
1172
HTMLArea.prototype.redo = function() {
1173
    if (this._undoPos < this._undoQueue.length - 1) {
1174
        var txt = this._undoQueue[++this._undoPos];
1175
        if (txt) this.setHTML(txt);
1176
        else --this._undoPos;
1177
    }
1178
};
1179

    
1180
// updates enabled/disable/active state of the toolbar elements
1181
HTMLArea.prototype.updateToolbar = function(noStatus) {
1182
    var doc = this._doc;
1183
    var text = (this._editMode == "textmode");
1184
    var ancestors = null;
1185
    if (!text) {
1186
        ancestors = this.getAllAncestors();
1187
        if (this.config.statusBar && !noStatus) {
1188

    
1189
            while(this._statusBarTree.childNodes.length>0) {
1190
                this._statusBarTree.removeChild(this._statusBarTree.childNodes[0]);
1191
            }
1192

    
1193
            this._statusBarTree.appendChild(document.createTextNode(HTMLArea.I18N.msg["Path"] + ": "));
1194

    
1195
            for (var i = ancestors.length; --i >= 0;) {
1196
                var el = ancestors[i];
1197
                if (!el) {
1198
                    // hell knows why we get here; this
1199
                    // could be a classic example of why
1200
                    // it's good to check for conditions
1201
                    // that are impossible to happen ;-)
1202
                    continue;
1203
                }
1204
                var a = document.createElement("a");
1205
                a.href = "#";
1206
                a.el = el;
1207
                a.editor = this;
1208
                a.onclick = function() {
1209
                    this.blur();
1210
                    this.editor.selectNodeContents(this.el);
1211
                    this.editor.updateToolbar(true);
1212
                    return false;
1213
                };
1214
                a.oncontextmenu = function() {
1215
                    // TODO: add context menu here
1216
                    this.blur();
1217
                    var info = "Inline style:\n\n";
1218
                    info += this.el.style.cssText.split(/;\s*/).join(";\n");
1219
                    alert(info);
1220
                    return false;
1221
                };
1222
                var txt = el.tagName.toLowerCase();
1223
                a.title = el.style.cssText;
1224
                if (el.id) {
1225
                    txt += "#" + el.id;
1226
                }
1227
                if (el.className) {
1228
                    txt += "." + el.className;
1229
                }
1230
                a.appendChild(document.createTextNode(txt));
1231
                this._statusBarTree.appendChild(a);
1232
                if (i != 0) {
1233
                    this._statusBarTree.appendChild(document.createTextNode(String.fromCharCode(0xbb)));
1234
                }
1235
            }
1236
        }
1237
    }
1238
    for (var i in this._toolbarObjects) {
1239
        var btn = this._toolbarObjects[i];
1240
        var cmd = i;
1241
        var inContext = true;
1242
        if (btn.context && !text) {
1243
            inContext = false;
1244
            var context = btn.context;
1245
            var attrs = [];
1246
            if (/(.*)\[(.*?)\]/.test(context)) {
1247
                context = RegExp.$1;
1248
                attrs = RegExp.$2.split(",");
1249
            }
1250
            context = context.toLowerCase();
1251
            var match = (context == "*");
1252
            for (var k in ancestors) {
1253
                if (!ancestors[k]) {
1254
                    // the impossible really happens.
1255
                    continue;
1256
                }
1257
                if (match || (ancestors[k].tagName.toLowerCase() == context)) {
1258
                    inContext = true;
1259
                    for (var ka in attrs) {
1260
                        if (!eval("ancestors[k]." + attrs[ka])) {
1261
                            inContext = false;
1262
                            break;
1263
                        }
1264
                    }
1265
                    if (inContext) {
1266
                        break;
1267
                    }
1268
                }
1269
            }
1270
        }
1271
        btn.state("enabled", (!text || btn.text) && inContext);
1272
        if (typeof cmd == "function") {
1273
            continue;
1274
        }
1275
        // look-it-up in the custom dropdown boxes
1276
        var dropdown = this.config.customSelects[cmd];
1277
        if ((!text || btn.text) && (typeof dropdown != "undefined")) {
1278
            dropdown.refresh(this);
1279
            continue;
1280
        }
1281
        switch (cmd) {
1282
            case "fontname":
1283
            case "fontsize":
1284
            case "formatblock":
1285
                if (!text) try {
1286
                    var value = ("" + doc.queryCommandValue(cmd)).toLowerCase();
1287
                    if (!value) {
1288
                        // FIXME: what do we do here?
1289
                        break;
1290
                    }
1291
                    var options = this.config[cmd];
1292
                    var k = 0;
1293
                    // btn.element.selectedIndex = 0;
1294
                    for (var j in options) {
1295
                        // FIXME: the following line is scary.
1296
                        if ((j.toLowerCase() == value) ||
1297
                            (options[j].substr(0, value.length).toLowerCase() == value)) {
1298
                            btn.element.selectedIndex = k;
1299
                            break;
1300
                        }
1301
                        ++k;
1302
                    }
1303
                } catch(e) {};
1304
                break;
1305
            case "language":
1306
                if (!text) try {
1307
                    var value;
1308
                    parentEl = this.getParentElement();
1309
                    if (parentEl.getAttribute('lang')) {
1310
                        // A language was previously defined for the block.
1311
                        if (parentEl.getAttribute('class') == 'multilang') {
1312
                            value = parentEl.getAttribute('lang')+'_ML';
1313
                        } else {
1314
                            value = parentEl.getAttribute('lang');
1315
                        }
1316
                    } else {
1317
                        value = '';
1318
                    }
1319
                    var options = this.config[cmd];
1320
                    var k = 0;
1321
                    for (var j in options) {
1322
                        // FIXME: the following line is scary.
1323
                        if ((j.toLowerCase() == value) ||
1324
                            (options[j].substr(0, value.length).toLowerCase() == value)) {
1325
                            btn.element.selectedIndex = k;
1326
                            break;
1327
                        }
1328
                        ++k;
1329
                    }
1330
                } catch(e) {};
1331
                break;
1332
            case "textindicator":
1333
                if (!text) {
1334
                    try {with (btn.element.style) {
1335
                        backgroundColor = HTMLArea._makeColor(
1336
                            doc.queryCommandValue(HTMLArea.is_ie ? "backcolor" : "hilitecolor"));
1337
                        if (/transparent/i.test(backgroundColor)) {
1338
                            // Mozilla
1339
                            backgroundColor = HTMLArea._makeColor(doc.queryCommandValue("backcolor"));
1340
                        }
1341
                        color = HTMLArea._makeColor(doc.queryCommandValue("forecolor"));
1342
                        fontFamily = doc.queryCommandValue("fontname");
1343
                        fontWeight = doc.queryCommandState("bold") ? "bold" : "normal";
1344
                        fontStyle = doc.queryCommandState("italic") ? "italic" : "normal";
1345
                    }} catch (e) {
1346
                        // alert(e + "\n\n" + cmd);
1347
                    }
1348
                }
1349
                break;
1350
            case "htmlmode": btn.state("active", text); break;
1351
            case "lefttoright":
1352
            case "righttoleft":
1353
                var el = this.getParentElement();
1354
                while (el && !HTMLArea.isBlockElement(el))
1355
                    el = el.parentNode;
1356
                if (el)
1357
                    btn.state("active", (el.style.direction == ((cmd == "righttoleft") ? "rtl" : "ltr")));
1358
                break;
1359
            default:
1360
                try {
1361
                    btn.state("active", (!text && doc.queryCommandState(cmd)));
1362
                } catch (e) {}
1363
        }
1364
    }
1365
    // take undo snapshots
1366
    if (this._customUndo && !this._timerUndo) {
1367
        this._undoTakeSnapshot();
1368
        var editor = this;
1369
        this._timerUndo = setTimeout(function() {
1370
            editor._timerUndo = null;
1371
        }, this.config.undoTimeout);
1372
    }
1373
    // check if any plugins have registered refresh handlers
1374
    for (var i in this.plugins) {
1375
        var plugin = this.plugins[i].instance;
1376
        if (typeof plugin.onUpdateToolbar == "function")
1377
            plugin.onUpdateToolbar();
1378
    }
1379
};
1380

    
1381
/** Returns a node after which we can insert other nodes, in the current
1382
 * selection.  The selection is removed.  It splits a text node, if needed.
1383
 */
1384
HTMLArea.prototype.insertNodeAtSelection = function(toBeInserted) {
1385
    if (!HTMLArea.is_ie) {
1386
        var sel = this._getSelection();
1387
        var range = this._createRange(sel);
1388
        // remove the current selection
1389
        sel.removeAllRanges();
1390
        range.deleteContents();
1391
        var node = range.startContainer;
1392
        var pos = range.startOffset;
1393
        switch (node.nodeType) {
1394
            case 3: // Node.TEXT_NODE
1395
            // we have to split it at the caret position.
1396
            if (toBeInserted.nodeType == 3) {
1397
                // do optimized insertion
1398
                node.insertData(pos, toBeInserted.data);
1399
                range = this._createRange();
1400
                range.setEnd(node, pos + toBeInserted.length);
1401
                range.setStart(node, pos + toBeInserted.length);
1402
                sel.addRange(range);
1403
            } else {
1404
                node = node.splitText(pos);
1405
                var selnode = toBeInserted;
1406
                if (toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */) {
1407
                    selnode = selnode.firstChild;
1408
                }
1409
                node.parentNode.insertBefore(toBeInserted, node);
1410
                this.selectNodeContents(selnode);
1411
                this.updateToolbar();
1412
            }
1413
            break;
1414
            case 1: // Node.ELEMENT_NODE
1415
            var selnode = toBeInserted;
1416
            if (toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */) {
1417
                selnode = selnode.firstChild;
1418
            }
1419
            node.insertBefore(toBeInserted, node.childNodes[pos]);
1420
            this.selectNodeContents(selnode);
1421
            this.updateToolbar();
1422
            break;
1423
        }
1424
    } else {
1425
        return null;    // this function not yet used for IE <FIXME>
1426
    }
1427
};
1428

    
1429
// Returns the deepest node that contains both endpoints of the selection.
1430
HTMLArea.prototype.getParentElement = function() {
1431
    var sel = this._getSelection();
1432
    var range = this._createRange(sel);
1433
    if (HTMLArea.is_ie) {
1434
        switch (sel.type) {
1435
            case "Text":
1436
            case "None":
1437
            return range.parentElement();
1438
            case "Control":
1439
            return range.item(0);
1440
            default:
1441
            return this._doc.body;
1442
        }
1443
    } else try {
1444
        var p = range.commonAncestorContainer;
1445
        if (!range.collapsed && range.startContainer == range.endContainer &&
1446
            range.startOffset - range.endOffset <= 1 && range.startContainer.hasChildNodes())
1447
            p = range.startContainer.childNodes[range.startOffset];
1448
        /*
1449
        alert(range.startContainer + ":" + range.startOffset + "\n" +
1450
              range.endContainer + ":" + range.endOffset);
1451
        */
1452
        while (p.nodeType == 3) {
1453
            p = p.parentNode;
1454
        }
1455
        return p;
1456
    } catch (e) {
1457
        return null;
1458
    }
1459
};
1460

    
1461
// Returns an array with all the ancestor nodes of the selection.
1462
HTMLArea.prototype.getAllAncestors = function() {
1463
    var p = this.getParentElement();
1464
    var a = [];
1465
    while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
1466
        a.push(p);
1467
        p = p.parentNode;
1468
    }
1469
    a.push(this._doc.body);
1470
    return a;
1471
};
1472

    
1473
// Selects the contents inside the given node
1474
HTMLArea.prototype.selectNodeContents = function(node, pos) {
1475
    this.focusEditor();
1476
    this.forceRedraw();
1477
    var range;
1478
    var collapsed = (typeof pos != "undefined");
1479
    if (HTMLArea.is_ie) {
1480
        range = this._doc.body.createTextRange();
1481
        range.moveToElementText(node);
1482
        (collapsed) && range.collapse(pos);
1483
        range.select();
1484
    } else {
1485
        var sel = this._getSelection();
1486
        range = this._doc.createRange();
1487
        range.selectNodeContents(node);
1488
        (collapsed) && range.collapse(pos);
1489
        sel.removeAllRanges();
1490
        sel.addRange(range);
1491
    }
1492
};
1493

    
1494
// Call this function to insert HTML code at the current position.  It deletes
1495
// the selection, if any.
1496
HTMLArea.prototype.insertHTML = function(html) {
1497
    var sel = this._getSelection();
1498
    var range = this._createRange(sel);
1499
    if (HTMLArea.is_ie) {
1500
        range.pasteHTML(html);
1501
    } else {
1502
        // construct a new document fragment with the given HTML
1503
        var fragment = this._doc.createDocumentFragment();
1504
        var div = this._doc.createElement("div");
1505
        div.innerHTML = html;
1506
        while (div.firstChild) {
1507
            // the following call also removes the node from div
1508
            fragment.appendChild(div.firstChild);
1509
        }
1510
        // this also removes the selection
1511
        var node = this.insertNodeAtSelection(fragment);
1512
    }
1513
};
1514

    
1515
// Call this function to surround the existing HTML code in the selection with
1516
// your tags.  FIXME: buggy!  This function will be deprecated "soon".
1517
HTMLArea.prototype.surroundHTML = function(startTag, endTag) {
1518
    var html = this.getSelectedHTML();
1519
    // the following also deletes the selection
1520
    this.insertHTML(startTag + html + endTag);
1521
};
1522

    
1523
/// Retrieve the selected block
1524
HTMLArea.prototype.getSelectedHTML = function() {
1525
    var sel = this._getSelection();
1526
    var range = this._createRange(sel);
1527
    var existing = null;
1528
    if (HTMLArea.is_ie) {
1529
        existing = range.htmlText;
1530
    } else {
1531
        existing = HTMLArea.getHTML(range.cloneContents(), false, this);
1532
    }
1533
    return existing;
1534
};
1535

    
1536
/// Return true if we have some selection
1537
HTMLArea.prototype.hasSelectedText = function() {
1538
    // FIXME: come _on_ mishoo, you can do better than this ;-)
1539
    return this.getSelectedHTML() != '';
1540
};
1541

    
1542
HTMLArea.prototype._createLink = function(link) {
1543
    var editor = this;
1544
    var allinks = editor._doc.getElementsByTagName('A');
1545
    var anchors = new Array();
1546
    for(var i = 0; i < allinks.length; i++) {
1547
        var attrname = allinks[i].getAttribute('name');
1548
        if((HTMLArea.is_ie ? attrname.length > 0 : attrname != null)) {
1549
            anchors[i] = allinks[i].getAttribute('name');
1550
        }
1551
    }
1552
    var outparam = null;
1553
    if (typeof link == "undefined") {
1554
        link = this.getParentElement();
1555
        if (link && !/^a$/i.test(link.tagName)) {
1556
            if(link.tagName.toLowerCase() != 'img') {
1557
                link = null;
1558
                var sel = this._getSelection();
1559
                var rng = this._createRange(sel);
1560
                var len = HTMLArea.is_ie ? rng.text.toString().length : sel.toString().length;
1561
                if(len < 1) {
1562
                    alert("<?php print_string("alertnoselectedtext","editor");?>");
1563
                    return false;
1564
                }
1565
            }
1566
            link = null;
1567
        }
1568
    }
1569
    if (link) {
1570
        outparam = {
1571
        f_href   : HTMLArea.is_ie ? editor.stripBaseURL(link.href) : link.getAttribute("href"),
1572
        f_title  : link.title,
1573
        f_target : link.target,
1574
        f_anchors: anchors
1575
    };
1576
    } else {
1577
        outparam = {
1578
        f_anchors:anchors };
1579
    }
1580
    this._popupDialog("link_std.php?id=<?php echo $id; ?>", function(param) {
1581
        if (!param) {
1582
            return false;
1583
        }
1584
        var a = link;
1585
        if (!a) {
1586
            // Create a temporary unique link, insert it then find it and set the correct parameters
1587
            var tmpLink = 'http://www.moodle.org/'+Math.random();
1588
            var elm = editor._doc.execCommand("createlink",false,tmpLink);
1589
            var links=editor._doc.getElementsByTagName("a");
1590
            for(var i=0;i<links.length;i++){
1591
                var link=links[i];
1592
                if(link.href==tmpLink) {
1593
                    link.href=param.f_href.trim();
1594
                    if(param.f_target){
1595
                        link.target=param.f_target.trim();
1596
                    }
1597
                    if(param.f_title){
1598
                        link.title=param.f_title.trim();
1599
                    }
1600
                    break;
1601
                }
1602
            }
1603
        } else {
1604
            var href = param.f_href.trim();
1605
            editor.selectNodeContents(a);
1606
            if (href == "") {
1607
                editor._doc.execCommand("unlink", false, null);
1608
                editor.updateToolbar();
1609
                return false;
1610
            } else {
1611
                a.href = href;
1612
            }
1613
        }
1614
        if (!(a && /^a$/i.test(a.tagName))) {
1615
            return false;
1616
        }
1617
        a.target = param.f_target.trim();
1618
        a.title = param.f_title.trim();
1619
        editor.selectNodeContents(a);
1620
        editor.updateToolbar();
1621
    }, outparam);
1622
};
1623

    
1624
// Called when the user clicks on "InsertImage" button.  If an image is already
1625
// there, it will just modify it's properties.
1626
HTMLArea.prototype._insertImage = function(image) {
1627

    
1628
    // Make sure that editor has focus
1629
    this.focusEditor();
1630
    var editor = this;  // for nested functions
1631
    var outparam = null;
1632
    if (typeof image == "undefined") {
1633
        image = this.getParentElement();
1634
        if (image && !/^img$/i.test(image.tagName))
1635
            image = null;
1636
    }
1637
    if (image) outparam = {
1638
        f_url    : HTMLArea.is_ie ? editor.stripBaseURL(image.src) : image.getAttribute("src"),
1639
        f_alt    : image.alt,
1640
        f_border : image.border,
1641
        f_align  : image.align,
1642
        f_vert   : image.vspace,
1643
        f_horiz  : image.hspace,
1644
        f_width  : image.width,
1645
        f_height : image.height
1646
    };
1647
    this._popupDialog("<?php
1648
    if(!empty($id) and has_capability('moodle/course:managefiles', get_context_instance(CONTEXT_COURSE, $id))) {
1649
        echo "insert_image.php?id=$id";
1650
    } else {
1651
        echo "insert_image_std.php?id=$id";
1652
    }?>", function(param) {
1653
        if (!param) {   // user must have pressed Cancel
1654
            return false;
1655
        }
1656
        var img = image;
1657
        if (!img) {
1658
            var sel = editor._getSelection();
1659
            var range = editor._createRange(sel);
1660
                if (HTMLArea.is_ie) {
1661
                editor._doc.execCommand("insertimage", false, param.f_url);
1662
                }
1663
            if (HTMLArea.is_ie) {
1664
                img = range.parentElement();
1665
                // wonder if this works...
1666
                if (img.tagName.toLowerCase() != "img") {
1667
                    img = img.previousSibling;
1668
                }
1669
            } else {
1670
                // MOODLE HACK: startContainer.perviousSibling
1671
                // Doesn't work so we'll use createElement and
1672
                // insertNodeAtSelection
1673
                //img = range.startContainer.previousSibling;
1674
                var img = editor._doc.createElement("img");
1675
                img.setAttribute("src",""+ param.f_url +"");
1676
                img.setAttribute("alt",""+ param.f_alt +"");
1677
                editor.insertNodeAtSelection(img);
1678
            }
1679
        } else {
1680
            img.src = param.f_url;
1681
        }
1682
        for (field in param) {
1683
            var value = param[field];
1684
            switch (field) {
1685
                case "f_alt"    : img.alt    = value; img.title = value; break;
1686
                case "f_border" : img.border = parseInt(value || "0"); break;
1687
                case "f_align"  : img.align  = value; break;
1688
                case "f_vert"   : img.vspace = parseInt(value || "0"); break;
1689
                case "f_horiz"  : img.hspace = parseInt(value || "0"); break;
1690
                case "f_width"  :
1691
                    if(value != 0) {
1692
                        img.width = parseInt(value);
1693
                    } else {
1694
                        break;
1695
                    }
1696
                    break;
1697
                case "f_height"  :
1698
                    if(value != 0) {
1699
                        img.height = parseInt(value);
1700
                    } else {
1701
                        break;
1702
                    }
1703
                    break;
1704
            }
1705
        }
1706
    }, outparam);
1707
};
1708

    
1709
// Called when the user clicks the Insert Table button
1710
HTMLArea.prototype._insertTable = function() {
1711
    var sel = this._getSelection();
1712
    var range = this._createRange(sel);
1713
    var editor = this;  // for nested functions
1714
    this._popupDialog("insert_table.php?id=<?php echo $id; ?>", function(param) {
1715
        if (!param) {   // user must have pressed Cancel
1716
            return false;
1717
        }
1718
        var doc = editor._doc;
1719
        // create the table element
1720
        var table = doc.createElement("table");
1721
        // assign the given arguments
1722
        for (var field in param) {
1723
            var value = param[field];
1724
            if (!value) {
1725
                continue;
1726
            }
1727
            switch (field) {
1728
                case "f_width"   : table.width = value + param["f_unit"]; break;
1729
                case "f_align"   : table.align   = value; break;
1730
                case "f_border"  : table.border  = parseInt(value); break;
1731
                case "f_spacing" : table.cellspacing = parseInt(value); break;
1732
                case "f_padding" : table.cellpadding = parseInt(value); break;
1733
            }
1734
        }
1735
        var tbody = doc.createElement("tbody");
1736
        table.appendChild(tbody);
1737
        for (var i = 0; i < param["f_rows"]; ++i) {
1738
            var tr = doc.createElement("tr");
1739
            tbody.appendChild(tr);
1740
            for (var j = 0; j < param["f_cols"]; ++j) {
1741
                var td = doc.createElement("td");
1742
                /// Moodle hack
1743
                if(param["f_unit"] == "px") {
1744
                    tdwidth = Math.round(table.width / param["f_cols"]);
1745
                } else {
1746
                    tdwidth = Math.round(100 / param["f_cols"]);
1747
                }
1748
                td.setAttribute("width",tdwidth + param["f_unit"]);
1749
                td.setAttribute("valign","top");
1750
                /// Moodle hack -ends
1751
                tr.appendChild(td);
1752
                // Mozilla likes to see something inside the cell.
1753
                (HTMLArea.is_gecko) && td.appendChild(doc.createElement("br"));
1754
            }
1755
        }
1756
        if (HTMLArea.is_ie) {
1757
            range.pasteHTML(table.outerHTML);
1758
        } else {
1759
            // insert the table
1760
            editor.insertNodeAtSelection(table);
1761
        }
1762
        return true;
1763
    }, null);
1764
};
1765

    
1766
/// Moodle hack - insertSmile
1767
HTMLArea.prototype._insertSmile = function() {
1768
    // Make sure that editor has focus
1769
    this.focusEditor();
1770
    var sel = this._getSelection();
1771
    var range = this._createRange(sel);
1772
    var editor = this;  // for nested functions
1773
    this._popupDialog("dlg_ins_smile.php?id=<?php echo $id; ?>", function(imgString) {
1774
        if(!imgString) {
1775
            return false;
1776
        }
1777
        if (HTMLArea.is_ie) {
1778
            range.pasteHTML(imgString);
1779
        } else {
1780
            editor.insertHTML(imgString);
1781
        }
1782
        return true;
1783
    }, null);
1784
};
1785

1786
HTMLArea.prototype._insertChar = function() {
1787
    var sel = this._getSelection();
1788
    var range = this._createRange(sel);
1789
    var editor = this;  // for nested functions
1790
    this._popupDialog("dlg_ins_char.php?id=<?php echo $id; ?>", function(sChar) {
1791
        if(!sChar) {
1792
            return false;
1793
        }
1794
        if (HTMLArea.is_ie) {
1795
            range.pasteHTML(sChar);
1796
        } else {
1797
            // insert the table
1798
            editor.insertHTML(sChar);
1799
        }
1800
        return true;
1801
    }, null);
1802
};
1803

1804
HTMLArea.prototype._removelink = function() {
1805
    var editor = this;
1806
    link = this.getParentElement();
1807
    editor.selectNodeContents(link);
1808

1809
    this._doc.execCommand("unlink", false, null);
1810
    this.focusEditor();
1811
};
1812

    
1813
HTMLArea.prototype._createanchor = function () {
1814
    var editor = this;
1815
    var sel = this._getSelection();
1816
    var rng = this._createRange(sel);
1817
    var len = HTMLArea.is_ie ? rng.text.toString().length : sel.toString().length;
1818
    if(len < 1) {
1819
        alert("<?php print_string("alertnoselectedtext","editor");?>");
1820
        return false;
1821
    }
1822
    this._popupDialog("createanchor.php?id=<?php echo $id; ?>", function(objAn) {
1823
        if(!objAn) {
1824
            return false;
1825
        }
1826
        var str = '<a name="'+ objAn.anchor+'">';
1827
        str += HTMLArea.is_ie ? rng.text : sel ;
1828
        str += '</a>';
1829
        editor.insertHTML(str);
1830
    },null);
1831
};
1832

    
1833
HTMLArea.prototype._nolinktag = function () {
1834

    
1835
    var editor = this;
1836
    var sel = this._getSelection();
1837
    var rng = this._createRange(sel);
1838
    var len = HTMLArea.is_ie ? rng.text.toString().length : sel.toString().length;
1839

    
1840
    if (len < 1) {
1841
        alert("<?php print_string("alertnoselectedtext","editor");?>");
1842
        return false;
1843
    }
1844
    var str = '<span class="nolink">';
1845
    str += HTMLArea.is_ie ? rng.text : sel;
1846
    str += '</span>';
1847
    editor.insertHTML(str);
1848
    this.focusEditor();
1849

    
1850
};
1851

    
1852
HTMLArea.prototype._searchReplace = function() {
1853

    
1854
    var editor = this;
1855
    var selectedtxt = "";
1856
    <?php
1857
    $strreplaced = addslashes(get_string('itemsreplaced','editor'));
1858
    $strnotfound = addslashes(get_string('searchnotfound','editor'));
1859
    ?>
1860
    var strReplaced = '<?php echo $strreplaced ?>';
1861
    var strNotfound = '<?php echo $strnotfound ?>';
1862
    var ile;
1863

    
1864
    //in source mode mozilla show errors, try diffrent method
1865
    if (editor._editMode == "wysiwyg") {
1866
        selectedtxt = editor.getSelectedHTML();
1867
    } else {
1868
        if (HTMLArea.is_ie) {
1869
            selectedtxt = document.selection.createRange().text;
1870
        } else {
1871
            selectedtxt = getMozSelection(editor._textArea);
1872
        }
1873
    }
1874

    
1875
    outparam = {
1876
        f_search : selectedtxt
1877
    };
1878

    
1879
    //Call Search And Replace popup window
1880
    editor._popupDialog( "searchandreplace.php?id=<?php echo $id; ?>", function( entity ) {
1881
        if ( !entity ) {
1882
            //user must have pressed Cancel
1883
            return false;
1884
        }
1885
        var text = editor.getHTML();
1886
        var search = entity[0];
1887
        var replace = entity[1];
1888
        var delim = entity[2];
1889
        var regularx = entity[3];
1890
        var closesar = entity[4];
1891
        ile = 0;
1892
        if (search.length < 1) {
1893
            alert ("Enter a search word! \n search for: " + entity[0]);
1894
        } else {
1895
            if (regularx) {
1896
            var regX = new RegExp (search, delim) ;
1897
            var text = text.replace ( regX,
1898
            function (str, n) {
1899
                // Increment our counter variable.
1900
                ile++ ;
1901
                //return replace ;
1902
                return str.replace( regX, replace) ;
1903
                }
1904
            )
1905

    
1906
            } else {
1907
                while (text.indexOf(search)>-1) {
1908
                    pos = text.indexOf(search);
1909
                    text = "" + (text.substring(0, pos) + replace + text.substring((pos + search.length), text.length));
1910
                    ile++;
1911
                }
1912
            }
1913

    
1914
            editor.setHTML(text);
1915
            editor.forceRedraw();
1916
            if (ile > 0) {
1917
                alert(ile + ' ' + strReplaced);
1918
            } else {
1919
                alert (strNotfound + "\n");
1920
            }
1921
        }
1922
    }, outparam);
1923

    
1924
    function getMozSelection(txtarea) {
1925
        var selLength = txtarea.textLength;
1926
        var selStart = txtarea.selectionStart;
1927
        var selEnd = txtarea.selectionEnd;
1928
        if (selEnd==1 || selEnd==2) selEnd=selLength;
1929
        return (txtarea.value).substring(selStart, selEnd);
1930
    }
1931
};
1932

    
1933
/// Moodle hack's ends
1934
//
1935
// Category: EVENT HANDLERS
1936

    
1937
// el is reference to the SELECT object
1938
// txt is the name of the select field, as in config.toolbar
1939
HTMLArea.prototype._comboSelected = function(el, txt) {
1940
    this.focusEditor();
1941
    var value = el.options[el.selectedIndex].value;
1942
    switch (txt) {
1943
        case "fontname":
1944
        case "fontsize": this.execCommand(txt, false, value); break;
1945
        case "language":
1946
            this.setLang(value);
1947
            break;
1948
        case "formatblock":
1949
            (HTMLArea.is_ie) && (value = "<" + value + ">");
1950
            this.execCommand(txt, false, value);
1951
            break;
1952
        default:
1953
        // try to look it up in the registered dropdowns
1954
        var dropdown = this.config.customSelects[txt];
1955
        if (typeof dropdown != "undefined") {
1956
            dropdown.action(this);
1957
        } else {
1958
            alert("FIXME: combo box " + txt + " not implemented");
1959
        }
1960
    }
1961
};
1962

    
1963

    
1964
/**
1965
 * Used to set the language for the selected content.
1966
 * We use the <span lang="en" class="multilang">content</span> format for
1967
 * content that should be marked for multilang filter use, and
1968
 * <span lang="en">content</span> for normal content for which we want to
1969
 * set the language (for screen reader usage, for example).
1970
 */
1971
HTMLArea.prototype.setLang = function(lang) {
1972

    
1973
    if (lang == 'multi') {
1974
        // This is just the separator in the dropdown. Does nothing.
1975
        return;
1976
    }
1977

    
1978
    var editor = this;
1979
    var selectedHTML = editor.getSelectedHTML();
1980
    var multiLang = false;
1981

    
1982
    var re = new RegExp('_ML', 'g');
1983
    if (lang.match(re)) {
1984
        multiLang = true;
1985
        lang = lang.replace(re, '');
1986
    }
1987

    
1988
    // Remove all lang attributes from span tags in selected html.
1989
    selectedHTML = selectedHTML.replace(/(<span[^>]*)lang="[^"]*"([^>]*>)/, "$1$2");
1990
    selectedHTML = selectedHTML.replace(/(<span[^>]*)class="multilang"([^>]*>)/, "$1$2");
1991

    
1992
    // If a span tag is now empty, delete it.
1993
    selectedHTML = selectedHTML.replace(/<span\s*>(.*?)<\/span>/, "$1");
1994

    
1995

    
1996
    var parentEl = this.getParentElement();
1997
    var insertNewSpan = false;
1998

    
1999
    if (parentEl.nodeName == 'SPAN' && parentEl.getAttribute('lang')) {
2000
        // A language was previously defined for the current block.
2001
        // Check whether the selected text makes up the whole of the block
2002
        // contents.
2003
        var re = new RegExp(parentEl.innerHTML);
2004

    
2005
        if (selectedHTML.match(re)) {
2006
            // The selected text makes up the whole of the span block.
2007
            if (lang != '') {
2008
                parentEl.setAttribute('lang', lang);
2009
                if (multiLang) {
2010
                    parentEl.setAttribute('class', 'multilang');
2011
                }
2012
            } else {
2013
                parentEl.removeAttribute('lang');
2014

    
2015
                var classAttr = parentEl.getAttribute('class');
2016
                if (classAttr) {
2017
                    classAttr = classAttr.replace(/multilang/, '').trim();
2018
                }
2019
                if (classAttr == '') {
2020
                    parentEl.removeAttribute('class');
2021
                }
2022
                if (parentEl.attributes.length == 0) {
2023
                    // The span is no longer needed.
2024
                    for (i=0; i<parentEl.childNodes.length; i++) {
2025
                        parentEl.parentNode.insertBefore(parentEl.childNodes[i], parentEl);
2026
                    }
2027
                    parentEl.parentNode.removeChild(parentEl);
2028
                }
2029
            }
2030
        } else {
2031
            insertNewSpan = true;
2032
        }
2033
    } else {
2034
        insertNewSpan = true;
2035
    }
2036

    
2037
    if (insertNewSpan && lang != '') {
2038
        var str  = '<span lang="'+lang.trim()+'"';
2039
            str += ' class="multilang"';
2040
        str += '>';
2041
        str += selectedHTML;
2042
        str += '</span>';
2043
        editor.insertHTML(str);
2044
    }
2045
}
2046

    
2047

    
2048
// the execCommand function (intercepts some commands and replaces them with
2049
// our own implementation)
2050
HTMLArea.prototype.execCommand = function(cmdID, UI, param) {
2051
    var editor = this;  // for nested functions
2052
    this.focusEditor();
2053
    cmdID = cmdID.toLowerCase();
2054
    switch (cmdID) {
2055
        case "htmlmode" : this.setMode(); break;
2056
        case "hilitecolor":
2057
        (HTMLArea.is_ie) && (cmdID = "backcolor");
2058
        case "forecolor":
2059
            this._popupDialog("select_color.php?id=<?php echo $id; ?>", function(color) {
2060
                if (color) { // selection not canceled
2061
                    editor._doc.execCommand(cmdID, false, "#" + color);
2062
                }
2063
            }, HTMLArea._colorToRgb(this._doc.queryCommandValue(cmdID)));
2064
            break;
2065
        case "createanchor": this._createanchor(); break;
2066
        case "createlink":
2067
        this._createLink();
2068
        break;
2069
        case "unlink": this._removelink(); break;
2070
        case "nolink": this._nolinktag(); break;
2071
        case "popupeditor":
2072
        // this object will be passed to the newly opened window
2073
        HTMLArea._object = this;
2074
        if (HTMLArea.is_ie) {
2075
            {
2076
                window.open(this.popupURL("fullscreen.php?id=<?php echo $id;?>"), "ha_fullscreen",
2077
                    "toolbar=no,location=no,directories=no,status=no,menubar=no," +
2078
                        "scrollbars=no,resizable=yes,width=800,height=600");
2079
            }
2080
        } else {
2081
            window.open(this.popupURL("fullscreen.php?id=<?php echo $id;?>"), "ha_fullscreen",
2082
                    "toolbar=no,menubar=no,personalbar=no,width=800,height=600," +
2083
                    "scrollbars=no,resizable=yes");
2084
        }
2085
        break;
2086
        case "undo":
2087
        case "redo":
2088
        if (this._customUndo)
2089
            this[cmdID]();
2090
        else
2091
            this._doc.execCommand(cmdID, UI, param);
2092
        break;
2093
        case "inserttable": this._insertTable(); break;
2094
        case "insertimage": this._insertImage(); break;
2095
        case "insertsmile": this._insertSmile(); break;
2096
        case "insertchar": this._insertChar(); break;
2097
        case "searchandreplace": this._searchReplace(); break;
2098
        case "about"    : this._popupDialog("about.html", null, this); break;
2099
        case "showhelp" : window.open(_editor_url + "reference.html", "ha_help"); break;
2100

    
2101
        case "killword": this._wordClean(); break;
2102

    
2103
        case "cut":
2104
        case "copy":
2105
        case "paste":
2106
        try {
2107
            // Paste first then clean
2108
            this._doc.execCommand(cmdID, UI, param);
2109
            if (this.config.killWordOnPaste) {
2110
                this._wordClean();
2111
            }
2112
        } catch (e) {
2113
            if (HTMLArea.is_gecko) {
2114
                if (confirm("<?php
2115
                    $strmoz = get_string('cutpastemozilla','editor');
2116
                    $strmoz = preg_replace("/[\n|\r]+/", "", $strmoz);
2117
                    $strmoz = str_replace('<br />', '\\n', $strmoz);
2118

    
2119
                    echo addslashes($strmoz);
2120

    
2121
                    ?>"))
2122
                    window.open("http://moodle.org/mozillahelp");
2123
            }
2124
        }
2125
        break;
2126
        case "lefttoright":
2127
        case "righttoleft":
2128
        var dir = (cmdID == "righttoleft") ? "rtl" : "ltr";
2129
        var el = this.getParentElement();
2130
        while (el && !HTMLArea.isBlockElement(el))
2131
            el = el.parentNode;
2132
        if (el) {
2133
            if (el.style.direction == dir)
2134
                el.style.direction = "";
2135
            else
2136
                el.style.direction = dir;
2137
        }
2138
        break;
2139
        default: this._doc.execCommand(cmdID, UI, param);
2140
    }
2141
    this.updateToolbar();
2142
    return false;
2143
};
2144

    
2145

    
2146
/**
2147
 * A generic event handler for things that happen in the IFRAME's document.
2148
 * This function also handles key bindings.
2149
 */
2150
HTMLArea.prototype._editorEvent = function(ev) {
2151

    
2152
    var editor = this;
2153
    var keyEvent = (HTMLArea.is_ie && ev.type == "keydown") || (ev.type == "keypress");
2154

    
2155
    if (keyEvent) {
2156

    
2157
        for (var i in editor.plugins) {
2158
            var plugin = editor.plugins[i].instance;
2159
            if (typeof plugin.onKeyPress == "function") plugin.onKeyPress(ev);
2160
        }
2161

    
2162
        var sel = null;
2163
        var range = null;
2164
        var key = String.fromCharCode(HTMLArea.is_ie ? ev.keyCode : ev.charCode).toLowerCase();
2165
        var cmd = null;
2166
        var value = null;
2167

    
2168
        if (ev.ctrlKey && !ev.altKey) {
2169
            /**
2170
             * Ctrl modifier only.
2171
             * We use these for shortcuts that change existing content,
2172
             * e.g. make text bold.
2173
             */
2174
            switch (key) {
2175

    
2176
                case 'a':
2177
                    // Select all.
2178
                    if (!HTMLArea.is_ie) {
2179
                        // KEY select all
2180
                        sel = this._getSelection();
2181
                        sel.removeAllRanges();
2182
                        range = this._createRange();
2183
                        range.selectNodeContents(this._doc.body);
2184
                        sel.addRange(range);
2185
                        HTMLArea._stopEvent(ev);
2186
                    }
2187
                    break;
2188

    
2189
                // For the dropdowns, we assign focus to them so that they are
2190
                // keyboard accessible.
2191
                case 'o':
2192
                    editor.dropdowns['fontname'].focus();
2193
                    break;
2194
                case 'p':
2195
                    editor.dropdowns['fontsize'].focus();
2196
                    break;
2197
                case 'h':
2198
                    editor.dropdowns['formatblock'].focus();
2199
                    break;
2200
                case '=':
2201
                    editor.dropdowns['language'].focus();
2202
                    break;
2203

    
2204
                case 'b': cmd = "bold"; break;
2205
                case 'i': cmd = "italic"; break;
2206
                case 'u': cmd = "underline"; break;
2207
                case 's': cmd = "strikethrough"; break;
2208
                case ',': cmd = "subscript"; break;
2209
                case '.': cmd = "superscript"; break;
2210

    
2211
                case 'v':
2212
                    if (! HTMLArea.is_gecko ) {
2213
                        cmd = "paste";
2214
                    }
2215
                    break;
2216

    
2217
                case '0': cmd = "killword"; break;
2218
                case 'z': cmd = "undo"; break;
2219
                case 'y': cmd = "redo"; break;
2220
                case 'l': cmd = "justifyleft"; break;
2221
                case 'e': cmd = "justifycenter"; break;
2222
                case 'r': cmd = "justifyright"; break;
2223
                case 'j': cmd = "justifyfull"; break;
2224
                case '/': cmd = "lefttoright"; break;
2225
                case '|': cmd = "righttoleft"; break;
2226
                case ';': cmd = "outdent"; break;
2227
                case "'": cmd = "indent"; break;
2228
                case 'g': cmd = "forecolor"; break;
2229
                case 'k': cmd = "hilitecolor"; break;
2230
                case 'f': cmd = "searchandreplace"; break;
2231
                case '`': cmd = "htmlmode"; break;  // FIXME: can't toggle from source code to wysiwyg
2232

    
2233
                case 'm':
2234
                    // Toggle fullscreen on or off.
2235
                    if (this.config.btnList['popupeditor'][0] == 'Enlarge Editor') {
2236
                        cmd = 'popupeditor';
2237
                    } else {
2238
                        window.close();
2239
                    }
2240
                    break;
2241

    
2242
                // Headings.
2243
                case '1':
2244
                case '2':
2245
                case '3':
2246
                case '4':
2247
                case '5':
2248
                case '6':
2249
                cmd = "formatblock";
2250
                value = "h" + key;
2251
                if (HTMLArea.is_ie) {
2252
                    value = "<" + value + ">";
2253
                }
2254
                break;
2255

    
2256
            } // End switch (key)
2257

    
2258

    
2259
        } else if (ev.ctrlKey && ev.altKey) {
2260
            /**
2261
             * Ctrl + Alt modifiers.
2262
             * We use these for shortcuts that insert stuff, e.g. images.
2263
             */
2264
            switch (key) {
2265
                case 'o': cmd = "insertorderedlist"; break;
2266
                case 'u': cmd = "insertunorderedlist"; break;
2267
                case 'r': cmd = "inserthorizontalrule"; break;
2268
                case 'a': cmd = "createanchor"; break;
2269
                case 'l': cmd = "createlink"; break;
2270
                case 'd': cmd = "unlink"; break;
2271
                case 'n': cmd = "nolink"; break;
2272
                case 'i': cmd = 'insertimage'; break;
2273
                case 't': cmd = 'inserttable'; break;
2274
                case 's': cmd = 'insertsmile'; break;
2275
                case 'c': cmd = 'insertchar'; break;
2276
            }
2277
        }
2278

    
2279
        if (cmd) {
2280
            // execute simple command
2281
            this.execCommand(cmd, false, value);
2282
            HTMLArea._stopEvent(ev);
2283
        }
2284
    } // End if (keyEvent)
2285

    
2286
    /*
2287
    else if (keyEvent) {
2288
        // other keys here
2289
        switch (ev.keyCode) {
2290
            case 13: // KEY enter
2291
            // if (HTMLArea.is_ie) {
2292
            this.insertHTML("<br />");
2293
            HTMLArea._stopEvent(ev);
2294
            // }
2295
            break;
2296
        }
2297
    }
2298
    */
2299

    
2300
    // Update the toolbar state after some time.
2301
    if (editor._timerToolbar) {
2302
        clearTimeout(editor._timerToolbar);
2303
    }
2304
    editor._timerToolbar = setTimeout(function() {
2305
        editor.updateToolbar();
2306
        editor._timerToolbar = null;
2307
    }, 50);
2308
};
2309

    
2310

    
2311
// retrieve the HTML
2312
HTMLArea.prototype.getHTML = function() {
2313
    switch (this._editMode) {
2314
        case "wysiwyg"  :
2315
        if (!this.config.fullPage) {
2316
            return HTMLArea.getHTML(this._doc.body, false, this);
2317
        } else
2318
            return this.doctype + "\n" + HTMLArea.getHTML(this._doc.documentElement, true, this);
2319
        case "textmode" : return this._textArea.value;
2320
        default     : alert("Mode <" + mode + "> not defined!");
2321
    }
2322
    return false;
2323
};
2324

    
2325
// retrieve the HTML (fastest version, but uses innerHTML)
2326
HTMLArea.prototype.getInnerHTML = function() {
2327
    switch (this._editMode) {
2328
        case "wysiwyg"  :
2329
        if (!this.config.fullPage)
2330
            return this._doc.body.innerHTML;
2331
        else
2332
            return this.doctype + "\n" + this._doc.documentElement.innerHTML;
2333
        case "textmode" : return this._textArea.value;
2334
        default     : alert("Mode <" + mode + "> not defined!");
2335
    }
2336
    return false;
2337
};
2338

    
2339
// completely change the HTML inside
2340
HTMLArea.prototype.setHTML = function(html) {
2341
    switch (this._editMode) {
2342
        case "wysiwyg"  :
2343
        if (!this.config.fullPage)
2344
            this._doc.body.innerHTML = html;
2345
        else
2346
            // this._doc.documentElement.innerHTML = html;
2347
            this._doc.body.innerHTML = html;
2348
        break;
2349
        case "textmode" : this._textArea.value = html; break;
2350
        default     : alert("Mode <" + mode + "> not defined!");
2351
    }
2352
    return false;
2353
};
2354

    
2355
// sets the given doctype (useful when config.fullPage is true)
2356
HTMLArea.prototype.setDoctype = function(doctype) {
2357
    this.doctype = doctype;
2358
};
2359

    
2360
/***************************************************
2361
 *  Category: UTILITY FUNCTIONS
2362
 ***************************************************/
2363

    
2364
// browser identification
2365

    
2366
HTMLArea.agt = navigator.userAgent.toLowerCase();
2367
HTMLArea.is_ie     = ((HTMLArea.agt.indexOf("msie") != -1) && (HTMLArea.agt.indexOf("opera") == -1));
2368
HTMLArea.is_opera  = (HTMLArea.agt.indexOf("opera") != -1);
2369
HTMLArea.is_mac    = (HTMLArea.agt.indexOf("mac") != -1);
2370
HTMLArea.is_mac_ie = (HTMLArea.is_ie && HTMLArea.is_mac);
2371
HTMLArea.is_win_ie = (HTMLArea.is_ie && !HTMLArea.is_mac);
2372
HTMLArea.is_gecko  = (navigator.product == "Gecko");
2373
HTMLArea.is_safari = (HTMLArea.agt.indexOf("safari") != -1);
2374

    
2375
// variable used to pass the object to the popup editor window.
2376
HTMLArea._object = null;
2377

    
2378
// function that returns a clone of the given object
2379
HTMLArea.cloneObject = function(obj) {
2380
    var newObj = new Object;
2381

    
2382
    // check for array objects
2383
    if (obj.constructor.toString().indexOf("function Array(") >= 0) {
2384
        newObj = obj.constructor();
2385
    }
2386

    
2387
    // check for function objects (as usual, IE is phucked up)
2388
    if (obj.constructor.toString().indexOf("function Function(") >= 0) {
2389
        newObj = obj; // just copy reference to it
2390
    } else for (var n in obj) {
2391
        var node = obj[n];
2392
        if (typeof node == 'object') { newObj[n] = HTMLArea.cloneObject(node); }
2393
        else                         { newObj[n] = node; }
2394
    }
2395

    
2396
    return newObj;
2397
};
2398

    
2399
// FIXME!!! this should return false for IE < 5.5
2400
HTMLArea.checkSupportedBrowser = function() {
2401
    if (HTMLArea.is_gecko) {
2402
        if (navigator.productSub < 20021201) {
2403
            alert("You need at least Mozilla-1.3 Alpha.\n" +
2404
                  "Sorry, your Gecko is not supported.");
2405
            return false;
2406
        }
2407
      //  if (navigator.productSub < 20030210) {
2408
      //      alert("Mozilla < 1.3 Beta is not supported!\n" +
2409
      //            "I'll try, though, but it might not work.");
2410
//        }
2411
    }
2412
// modifications
2413
        if(HTMLArea.is_chrome) {
2414
                return true;
2415
    }
2416

    
2417

    
2418
    return HTMLArea.is_gecko || HTMLArea.is_ie || HTMLArea.is_chrome;
2419
};
2420

    
2421
// selection & ranges
2422

    
2423
// returns the current selection object
2424
HTMLArea.prototype._getSelection = function() {
2425
    if (HTMLArea.is_ie) {
2426
        return this._doc.selection;
2427
    } else {
2428
        return this._iframe.contentWindow.getSelection();
2429
    }
2430
};
2431

    
2432
// returns a range for the current selection
2433
HTMLArea.prototype._createRange = function(sel) {
2434
    if (HTMLArea.is_ie) {
2435
        return sel.createRange();
2436
    } else {
2437
        // Commented out because we need the dropdowns to be able to keep
2438
        // focus for keyboard accessibility. Comment by Vy-Shane Sin Fat.
2439
        //this.focusEditor();
2440
        if (typeof sel != "undefined") {
2441
            try {
2442
            return sel.getRangeAt(0);
2443
            } catch(e) {
2444
                return this._doc.createRange();
2445
            }
2446
        } else {
2447
            return this._doc.createRange();
2448
        }
2449
    }
2450
};
2451

    
2452
// event handling
2453

    
2454
HTMLArea._addEvent = function(el, evname, func) {
2455
    if (HTMLArea.is_ie) {
2456
        el.attachEvent("on" + evname, func);
2457
    } else {
2458
        el.addEventListener(evname, func, true);
2459
    }
2460
};
2461

    
2462
HTMLArea._addEvents = function(el, evs, func) {
2463
    for (var i in evs) {
2464
        HTMLArea._addEvent(el, evs[i], func);
2465
    }
2466
};
2467

    
2468
HTMLArea._removeEvent = function(el, evname, func) {
2469
    if (HTMLArea.is_ie) {
2470
        el.detachEvent("on" + evname, func);
2471
    } else {
2472
        el.removeEventListener(evname, func, true);
2473
    }
2474
};
2475

    
2476
HTMLArea._removeEvents = function(el, evs, func) {
2477
    for (var i in evs) {
2478
        HTMLArea._removeEvent(el, evs[i], func);
2479
    }
2480
};
2481

    
2482
HTMLArea._stopEvent = function(ev) {
2483
    if (HTMLArea.is_ie) {
2484
        ev.cancelBubble = true;
2485
        ev.returnValue = false;
2486
    } else {
2487
        ev.preventDefault();
2488
        ev.stopPropagation();
2489
    }
2490
};
2491

    
2492
HTMLArea._removeClass = function(el, className) {
2493
    if (!(el && el.className)) {
2494
        return;
2495
    }
2496
    var cls = el.className.split(" ");
2497
    var ar = new Array();
2498
    for (var i = cls.length; i > 0;) {
2499
        if (cls[--i] != className) {
2500
            ar[ar.length] = cls[i];
2501
        }
2502
    }
2503
    el.className = ar.join(" ");
2504
};
2505

    
2506
HTMLArea._addClass = function(el, className) {
2507
    // remove the class first, if already there
2508
    HTMLArea._removeClass(el, className);
2509
    el.className += " " + className;
2510
};
2511

    
2512
HTMLArea._hasClass = function(el, className) {
2513
    if (!(el && el.className)) {
2514
        return false;
2515
    }
2516
    var cls = el.className.split(" ");
2517
    for (var i = cls.length; i > 0;) {
2518
        if (cls[--i] == className) {
2519
            return true;
2520
        }
2521
    }
2522
    return false;
2523
};
2524

    
2525
HTMLArea.isBlockElement = function(el) {
2526

    
2527
    var blockTags = " body form textarea fieldset ul ol dl li div " +
2528
        "p h1 h2 h3 h4 h5 h6 quote pre table thead " +
2529
        "tbody tfoot tr td iframe address ";
2530
    try {
2531
    return (blockTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1);
2532
    } catch (e) {}
2533

    
2534
};
2535

    
2536
HTMLArea.needsClosingTag = function(el) {
2537
    var closingTags = " head script style div span tr td tbody table em strong font a title iframe object applet ";
2538
    return (closingTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1);
2539
};
2540

    
2541
// performs HTML encoding of some given string
2542
HTMLArea.htmlEncode = function(str) {
2543
    // we don't need regexp for that, but.. so be it for now.
2544
    str = str.replace(/&/ig, "&amp;");
2545
    str = str.replace(/</ig, "&lt;");
2546
    str = str.replace(/>/ig, "&gt;");
2547
    str = str.replace(/\x22/ig, "&quot;");
2548
    // \x22 means '"' -- we use hex reprezentation so that we don't disturb
2549
    // JS compressors (well, at least mine fails.. ;)
2550
    return str;
2551
};
2552

    
2553
HTMLArea.isStandardTag = function (el) {
2554
    return HTMLArea.RE_msietag.test(el.tagName);
2555
};
2556
HTMLArea.isSingleTag = function (el) {
2557
    var re = /^(br|hr|img|input|link|meta|param|embed|area)$/i;
2558
    return re.test(el.tagName.toLowerCase());
2559
};
2560
// Retrieves the HTML code from the given node.  This is a replacement for
2561
// getting innerHTML, using standard DOM calls.
2562
HTMLArea.getHTML = function(root, outputRoot, editor) {
2563
    var html = "";
2564
    switch (root.nodeType) {
2565
        case 1: // Node.ELEMENT_NODE
2566
        case 11: // Node.DOCUMENT_FRAGMENT_NODE
2567
        var closed;
2568
        var i;
2569
        var root_tag = (root.nodeType == 1) ? root.tagName.toLowerCase() : '';
2570
        if (HTMLArea.RE_junktag.test(root_tag)) {
2571
            return '';
2572
        }
2573
        if (HTMLArea.is_ie && root_tag == "head") {
2574
            if (outputRoot)
2575
                html += "<head>";
2576
            // lowercasize
2577
            var save_multiline = RegExp.multiline;
2578
            RegExp.multiline = true;
2579
            var txt = root.innerHTML.replace(HTMLArea.RE_tagName, function(str, p1, p2) {
2580
                return p1 + p2.toLowerCase();
2581
            });
2582
            RegExp.multiline = save_multiline;
2583
            html += txt;
2584
            if (outputRoot)
2585
                html += "</head>";
2586
            break;
2587
        } else if (outputRoot) {
2588
            closed = (!(root.hasChildNodes() || !HTMLArea.isSingleTag(root)));
2589
            html = "<" + root.tagName.toLowerCase();
2590
            var attrs = root.attributes;
2591
            for (i = 0; i < attrs.length; ++i) {
2592
                var a = attrs.item(i);
2593
                if (!a.specified) {
2594
                    continue;
2595
                }
2596
                var name = a.nodeName.toLowerCase();
2597
                if (/_moz|contenteditable|_msh/.test(name)) {
2598
                    // avoid certain attributes
2599
                    continue;
2600
                }
2601
                var value;
2602
                if (name != "style") {
2603
                    //
2604
                    // Using Gecko the values of href and src are converted to absolute links
2605
                    // unless we get them using nodeValue()
2606
                    if (typeof root[a.nodeName] != "undefined" && name != "href" && name != "src") {
2607
                        value = root[a.nodeName];
2608
                    } else {
2609
                        // This seems to be working, but if it does cause
2610
                        // problems later on return the old value...
2611
                        if (name.toLowerCase() == "href" && name.toLowerCase() == "src") {
2612
                            value = root[a.nodeName];
2613
                        } else {
2614
                        value = a.nodeValue;
2615
                        }
2616
                        if (HTMLArea.is_ie && (name == "href" || name == "src")) {
2617
                            value = editor.stripBaseURL(value);
2618
                        }
2619
                    }
2620
                } else { // IE fails to put style in attributes list
2621
                    // FIXME: cssText reported by IE is UPPERCASE
2622
                    value = root.style.cssText.toLowerCase();
2623
                }
2624
                if (/(_moz|^$)/.test(value)) {
2625
                    // Mozilla reports some special tags
2626
                    // here; we don't need them.
2627
                    continue;
2628
                }
2629
                html += " " + name + '="' + value + '"';
2630
            }
2631
            html += closed ? " />" : ">";
2632
        }
2633
        for (i = root.firstChild; i; i = i.nextSibling) {
2634
            html += HTMLArea.getHTML(i, true, editor);
2635
        }
2636
        if (outputRoot && !closed) {
2637
            if ( HTMLArea.is_ie && !HTMLArea.isStandardTag(root) ) {
2638
                html += '';
2639
            } else {
2640
                html += "</" + root.tagName.toLowerCase() + ">";
2641
            }
2642
        }
2643
        break;
2644
        case 3: // Node.TEXT_NODE
2645
        // If a text node is alone in an element and all spaces, replace it with an non breaking one
2646
        // This partially undoes the damage done by moz, which translates '&nbsp;'s into spaces in the data element
2647
        if ( !root.previousSibling && !root.nextSibling && root.data.match(/^\s*$/i) && root.data.length > 1 ) html = '&nbsp;';
2648
        else html = HTMLArea.htmlEncode(root.data);
2649
        break;
2650
        case 8: // Node.COMMENT_NODE
2651
        html = "<!--" + root.data + "-->";
2652
        break;      // skip comments, for now.
2653
    }
2654

    
2655
    return HTMLArea.indent(html);
2656
};
2657

    
2658
HTMLArea.prototype.stripBaseURL = function(string) {
2659
    var baseurl = this.config.baseURL;
2660

    
2661
    // IE adds the path to an anchor, converting #anchor
2662
    // to path/#anchor which of course needs to be fixed
2663
    var index = string.indexOf("/#")+1;
2664
    if ((index > 0) && (string.indexOf(baseurl) > -1)) {
2665
        return string.substr(index);
2666
    }
2667
    return string; // Moodle doesn't use the code below because
2668
                   // Moodle likes to keep absolute links
2669

    
2670
    // strip to last directory in case baseurl points to a file
2671
    baseurl = baseurl.replace(/[^\/]+$/, '');
2672
    var basere = new RegExp(baseurl);
2673
    string = string.replace(basere, "");
2674

    
2675
    // strip host-part of URL which is added by MSIE to links relative to server root
2676
    baseurl = baseurl.replace(/^(https?:\/\/[^\/]+)(.*)$/, '$1');
2677
    basere = new RegExp(baseurl);
2678
    return string.replace(basere, "");
2679
};
2680

    
2681
String.prototype.trim = function() {
2682
    a = this.replace(/^\s+/, '');
2683
    return a.replace(/\s+$/, '');
2684
};
2685

    
2686
// creates a rgb-style color from a number
2687
HTMLArea._makeColor = function(v) {
2688
    if (typeof v != "number") {
2689
        // already in rgb (hopefully); IE doesn't get here.
2690
        return v;
2691
    }
2692
    // IE sends number; convert to rgb.
2693
    var r = v & 0xFF;
2694
    var g = (v >> 8) & 0xFF;
2695
    var b = (v >> 16) & 0xFF;
2696
    return "rgb(" + r + "," + g + "," + b + ")";
2697
};
2698

    
2699
// returns hexadecimal color representation from a number or a rgb-style color.
2700
HTMLArea._colorToRgb = function(v) {
2701
    if (!v)
2702
        return '';
2703

    
2704
    // returns the hex representation of one byte (2 digits)
2705
    function hex(d) {
2706
        return (d < 16) ? ("0" + d.toString(16)) : d.toString(16);
2707
    };
2708

    
2709
    if (typeof v == "number") {
2710
        // we're talking to IE here
2711
        var r = v & 0xFF;
2712
        var g = (v >> 8) & 0xFF;
2713
        var b = (v >> 16) & 0xFF;
2714
        return "#" + hex(r) + hex(g) + hex(b);
2715
    }
2716

    
2717
    if (v.substr(0, 3) == "rgb") {
2718
        // in rgb(...) form -- Mozilla
2719
        var re = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/;
2720
        if (v.match(re)) {
2721
            var r = parseInt(RegExp.$1);
2722
            var g = parseInt(RegExp.$2);
2723
            var b = parseInt(RegExp.$3);
2724
            return "#" + hex(r) + hex(g) + hex(b);
2725
        }
2726
        // doesn't match RE?!  maybe uses percentages or float numbers
2727
        // -- FIXME: not yet implemented.
2728
        return null;
2729
    }
2730

    
2731
    if (v.substr(0, 1) == "#") {
2732
        // already hex rgb (hopefully :D )
2733
        return v;
2734
    }
2735

    
2736
    // if everything else fails ;)
2737
    return null;
2738
};
2739

    
2740
HTMLArea.prototype._popupDialog = function(url, action, init) {
2741
    Dialog(this.popupURL(url), action, init);
2742
};
2743

    
2744
// paths
2745

    
2746
HTMLArea.prototype.imgURL = function(file, plugin) {
2747
    if (typeof plugin == "undefined")
2748
        return _editor_url + file;
2749
    else
2750
        return _editor_url + "plugins/" + plugin + "/img/" + file;
2751
};
2752

    
2753
HTMLArea.prototype.popupURL = function(file) {
2754
    var url = "";
2755
    if (file.match(/^plugin:\/\/(.*?)\/(.*)/)) {
2756
        var plugin = RegExp.$1;
2757
        var popup = RegExp.$2;
2758
        if (!/\.html$/.test(popup))
2759
            popup += ".html";
2760
        url = _editor_url + "plugins/" + plugin + "/popups/" + popup;
2761
    } else
2762
        url = _editor_url + this.config.popupURL + file;
2763
    return url;
2764
};
2765

    
2766
/**
2767
 * FIX: Internet Explorer returns an item having the _name_ equal to the given
2768
 * id, even if it's not having any id.  This way it can return a different form
2769
 * field even if it's not a textarea.  This workarounds the problem by
2770
 * specifically looking to search only elements having a certain tag name.
2771
 */
2772
HTMLArea.getElementById = function(tag, id) {
2773
    var el, i, objs = document.getElementsByTagName(tag);
2774
    for (i = objs.length; --i >= 0 && (el = objs[i]);)
2775
        if (el.id == id)
2776
            return el;
2777
    return null;
2778
};
2779
// Modified version of GetHtml plugin's indent.
2780
HTMLArea.indent = function(s, sindentChar) {
2781
    var c = [
2782
    /*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),
2783
    /*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
2784
    /*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
2785
    /*3*/  new RegExp().compile(/<(br|hr|img|embed|param|pre|meta|link|title|area)[^>]*>/g),//singlet tag
2786
    /*4*/  new RegExp().compile(/(^|<\/(pre|script)>)(\s|[^\s])*?(<(pre|script)[^>]*>|$)/g),//find content NOT inside pre and script tags
2787
    /*5*/  new RegExp().compile(/(<pre[^>]*>)(\s|[^\s])*?(<\/pre>)/g),//find content inside pre tags
2788
    /*6*/  new RegExp().compile(/(^|<!--(\s|\S)*?-->)((\s|\S)*?)(?=<!--(\s|\S)*?-->|$)/g),//find content NOT inside comments
2789
    /*7*/  new RegExp().compile(/<\/(table|tbody|tr|td|th|ul|ol|object|html|head|body)( [^>]*)?>/g),//blocklevel closing tag
2790
    ];
2791
    HTMLArea.__nindent = 0;
2792
    HTMLArea.__sindent = "";
2793
    HTMLArea.__sindentChar = (typeof sindentChar == "undefined") ? "  " : sindentChar;
2794

    
2795
    if(HTMLArea.is_gecko) { //moz changes returns into <br> inside <pre> tags
2796
        s = s.replace(c[5], function(str){return str.replace(/<br \/>/g,"\n")});
2797
    }
2798
    s = s.replace(c[4], function(strn) { //skip pre and script tags
2799
      strn = strn.replace(c[6], function(st,$1,$2,$3) { //exclude comments
2800
        string = $3.replace(/[\n\r]/gi, " ").replace(/\s+/gi," ").replace(c[0], function(str) {
2801
            if (str.match(c[2])) {
2802
                var s = "\n" + HTMLArea.__sindent + str;
2803
                // blocklevel openingtag - increase indent
2804
                HTMLArea.__sindent += HTMLArea.__sindentChar;
2805
                ++HTMLArea.__nindent;
2806
                return s;
2807
            } else if (str.match(c[1])) {
2808
                // blocklevel closingtag - decrease indent
2809
                --HTMLArea.__nindent;
2810
                HTMLArea.__sindent = "";
2811
                for (var i=HTMLArea.__nindent;i>0;--i) {
2812
                    HTMLArea.__sindent += HTMLArea.__sindentChar;
2813
                }
2814
                return (str.match(c[7]) ? "\n" + HTMLArea.__sindent : "") + str;
2815
            }
2816
            return str; // this won't actually happen
2817
        });
2818
        return $1 + string;
2819
      });return strn;
2820
    });
2821
    if (s.charAt(0) == "\n") {
2822
        return s.substring(1, s.length);
2823
    }
2824
    s = s.replace(/ *\n/g,'\n');//strip spaces at end of lines
2825
    return s;
2826
};