From 0a28f3812eb7f62fbb1cbde00c6c7b0f60b11840 Mon Sep 17 00:00:00 2001 From: viniciuscb Date: Sat, 18 Jun 2005 20:43:14 +0000 Subject: [PATCH] Some changes: - Inclusion of the following javascript directories: * Connector: javascript object to interface xmlhttprequest object. This object allows asynchronous posts and support for messages while this post is being done, such as "wait, contacting server", etc. * JsAPI: general javascript functions and methods * jsolait: performs conversion from a xmlrpc message to a javascript object * xmlRpcMsgCreator: performs conversion from a javascript object to a xmlrpc message * dynapi: cross-browser class to draw layers - Update in setup version: now is 1.0.1.008; Update your versions. There was made a change in phpgw_vfs2_files table in handling of modified files. - Upgrade of vfs2 classes and PclZip class - Changes in javascript object and common object to allow the javascript backend to applications to work (now just filescenter will use it...) --- phpgwapi/inc/class.PclZip.inc.php | 939 ++- phpgwapi/inc/class.common.inc.php | 23 + phpgwapi/inc/class.javascript.inc.php | 327 +- phpgwapi/inc/class.vfs_sql2.inc.php | 179 +- phpgwapi/inc/class.vfs_versionsystem.inc.php | 14 +- phpgwapi/js/dJSWin/dJSWin.js | 235 +- phpgwapi/js/dTabs/dTabs.js | 183 +- phpgwapi/js/htmlarea/examples/core.html | 1 + phpgwapi/js/htmlarea/examples/full-page.html | 6 +- .../js/htmlarea/examples/fully-loaded.html | 20 +- phpgwapi/js/htmlarea/examples/index.html | 2 +- phpgwapi/js/htmlarea/htmlarea.css | 68 +- phpgwapi/js/htmlarea/htmlarea.js | 5690 +++++++++++------ phpgwapi/js/htmlarea/index.html | 6 +- phpgwapi/js/htmlarea/lang/de.js | 3 +- phpgwapi/js/htmlarea/lang/en.js | 77 +- phpgwapi/js/htmlarea/lang/es.js | 86 +- phpgwapi/js/htmlarea/lang/nl.js | 7 +- phpgwapi/js/htmlarea/lang/vn.js | 112 +- .../plugins/ContextMenu/context-menu.js | 81 +- .../htmlarea/plugins/ContextMenu/lang/en.js | 6 +- .../plugins/ContextMenu/lang/lang.php | 1 - .../js/htmlarea/plugins/ContextMenu/menu.css | 1 + .../EnterParagraphs/enter-paragraphs.js | 272 +- .../js/htmlarea/plugins/FullPage/full-page.js | 38 +- .../htmlarea/plugins/FullPage/lang/lang.php | 1 - .../plugins/FullPage/popups/docprop.html | 14 +- .../js/htmlarea/plugins/HtmlTidy/html-tidy.js | 2 +- .../htmlarea/plugins/HtmlTidy/lang/lang.php | 1 - .../htmlarea/plugins/ListType/lang/lang.php | 1 - .../htmlarea/plugins/SpellChecker/.htaccess | 2 +- .../plugins/SpellChecker/lang/lang.php | 1 - .../plugins/SpellChecker/spell-check-ui.js | 8 +- .../plugins/SpellChecker/spell-checker.js | 4 +- .../plugins/TableOperations/lang/lang.php | 1 - .../plugins/TableOperations/lang/no.js | 2 +- .../TableOperations/table-operations.js | 20 +- .../plugins/UploadImage/lang/lang.php | 1 - .../popups/ImageManager/config.inc.php | 192 +- phpgwapi/js/htmlarea/popupdiv.js | 2 +- phpgwapi/js/htmlarea/popups/fullscreen.html | 24 +- phpgwapi/js/htmlarea/popups/insert_table.html | 23 +- phpgwapi/js/htmlarea/popups/link.html | 19 +- phpgwapi/js/htmlarea/popups/popup.js | 13 +- phpgwapi/js/htmlarea/popupwin.js | 4 +- phpgwapi/js/jscalendar/ChangeLog | 369 +- phpgwapi/js/jscalendar/README | 2 +- phpgwapi/js/jscalendar/calendar-blue.css | 5 +- phpgwapi/js/jscalendar/calendar-blue2.css | 1 + phpgwapi/js/jscalendar/calendar-brown.css | 1 + phpgwapi/js/jscalendar/calendar-green.css | 1 + phpgwapi/js/jscalendar/calendar-setup.js | 56 +- .../js/jscalendar/calendar-setup_stripped.js | 2 +- phpgwapi/js/jscalendar/calendar-system.css | 1 + phpgwapi/js/jscalendar/calendar-tas.css | 1 + phpgwapi/js/jscalendar/calendar-win2k-1.css | 1 + phpgwapi/js/jscalendar/calendar-win2k-2.css | 1 + .../js/jscalendar/calendar-win2k-cold-1.css | 1 + .../js/jscalendar/calendar-win2k-cold-2.css | 1 + phpgwapi/js/jscalendar/calendar.js | 746 ++- phpgwapi/js/jscalendar/calendar_stripped.js | 12 +- .../js/jscalendar/doc/html/reference-Z-S.css | 193 + .../js/jscalendar/doc/html/reference.html | 904 ++- phpgwapi/js/jscalendar/doc/reference.pdf | Bin 214982 -> 281155 bytes phpgwapi/js/jscalendar/index.html | 69 +- phpgwapi/js/jscalendar/lang/calendar-br.js | 95 +- phpgwapi/js/jscalendar/lang/calendar-ca.js | 104 +- .../js/jscalendar/lang/calendar-cs-win.js | 43 +- phpgwapi/js/jscalendar/lang/calendar-da.js | 146 +- phpgwapi/js/jscalendar/lang/calendar-de.js | 106 +- phpgwapi/js/jscalendar/lang/calendar-el.js | 4 +- phpgwapi/js/jscalendar/lang/calendar-en.js | 10 +- phpgwapi/js/jscalendar/lang/calendar-es.js | 61 +- phpgwapi/js/jscalendar/lang/calendar-fi.js | 4 +- phpgwapi/js/jscalendar/lang/calendar-fr.js | 85 +- phpgwapi/js/jscalendar/lang/calendar-hu.js | 89 +- phpgwapi/js/jscalendar/lang/calendar-it.js | 105 +- .../js/jscalendar/lang/calendar-ko-utf8.js | 6 +- phpgwapi/js/jscalendar/lang/calendar-ko.js | 6 +- .../js/jscalendar/lang/calendar-lt-utf8.js | 4 +- phpgwapi/js/jscalendar/lang/calendar-lt.js | 4 +- phpgwapi/js/jscalendar/lang/calendar-nl.js | 40 +- phpgwapi/js/jscalendar/lang/calendar-no.js | 107 +- .../js/jscalendar/lang/calendar-pl-utf8.js | 4 +- phpgwapi/js/jscalendar/lang/calendar-pl.js | 4 +- phpgwapi/js/jscalendar/lang/calendar-pt.js | 106 +- phpgwapi/js/jscalendar/lang/calendar-ro.js | 4 +- phpgwapi/js/jscalendar/lang/calendar-ru.js | 150 +- phpgwapi/js/jscalendar/lang/calendar-si.js | 4 +- phpgwapi/js/jscalendar/lang/calendar-sk.js | 4 +- phpgwapi/js/jscalendar/lang/calendar-sp.js | 61 +- phpgwapi/js/jscalendar/lang/calendar-sv.js | 6 +- phpgwapi/js/jscalendar/lang/calendar-zh.js | 88 +- phpgwapi/js/jscalendar/release-notes.html | 103 +- phpgwapi/js/wz_dragdrop/wz_dragdrop.js | 8 +- phpgwapi/js/wz_tooltip/wz_tooltip.js | 1 + phpgwapi/setup/setup.inc.php | 2 +- phpgwapi/setup/tables_current.inc.php | 2 + phpgwapi/setup/tables_update.inc.php | 33 + 99 files changed, 8711 insertions(+), 3967 deletions(-) diff --git a/phpgwapi/inc/class.PclZip.inc.php b/phpgwapi/inc/class.PclZip.inc.php index adb97f57cb..ac6af2f1ff 100644 --- a/phpgwapi/inc/class.PclZip.inc.php +++ b/phpgwapi/inc/class.PclZip.inc.php @@ -1,8 +1,8 @@ 'optional', PCLZIP_CB_PRE_ADD => 'optional', PCLZIP_CB_POST_ADD => 'optional', - PCLZIP_OPT_NO_COMPRESSION => 'optional' )); + PCLZIP_OPT_NO_COMPRESSION => 'optional', + PCLZIP_OPT_COMMENT => 'optional' + //, PCLZIP_OPT_CRYPT => 'optional' + )); if ($v_result != 1) { //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, 0); return 0; @@ -287,7 +304,8 @@ } else if ($v_size > 2) { // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid number / type of arguments"); + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, + "Invalid number / type of arguments"); // ----- Return //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, PclZip::errorCode(), PclZip::errorInfo()); @@ -363,6 +381,9 @@ // PCLZIP_OPT_ADD_PATH : // PCLZIP_OPT_REMOVE_PATH : // PCLZIP_OPT_REMOVE_ALL_PATH : + // PCLZIP_OPT_COMMENT : + // PCLZIP_OPT_ADD_COMMENT : + // PCLZIP_OPT_PREPEND_COMMENT : // PCLZIP_CB_PRE_ADD : // PCLZIP_CB_POST_ADD : // Return Values : @@ -410,7 +431,12 @@ PCLZIP_OPT_ADD_PATH => 'optional', PCLZIP_CB_PRE_ADD => 'optional', PCLZIP_CB_POST_ADD => 'optional', - PCLZIP_OPT_NO_COMPRESSION => 'optional' )); + PCLZIP_OPT_NO_COMPRESSION => 'optional', + PCLZIP_OPT_COMMENT => 'optional', + PCLZIP_OPT_ADD_COMMENT => 'optional', + PCLZIP_OPT_PREPEND_COMMENT => 'optional' + //, PCLZIP_OPT_CRYPT => 'optional' + )); if ($v_result != 1) { //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, 0); return 0; @@ -646,7 +672,11 @@ PCLZIP_OPT_BY_EREG => 'optional', PCLZIP_OPT_BY_PREG => 'optional', PCLZIP_OPT_BY_INDEX => 'optional', - PCLZIP_OPT_EXTRACT_AS_STRING => 'optional' )); + PCLZIP_OPT_EXTRACT_AS_STRING => 'optional', + PCLZIP_OPT_EXTRACT_IN_OUTPUT => 'optional', + PCLZIP_OPT_REPLACE_NEWER => 'optional' + ,PCLZIP_OPT_STOP_ON_ERROR => 'optional' + )); if ($v_result != 1) { //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, 0); return 0; @@ -700,8 +730,9 @@ // ----- Call the extracting fct $p_list = array(); - if (($v_result = $this->privExtractByRule($p_list, $v_path, $v_remove_path, $v_remove_all_path, $v_options)) != 1) - { + $v_result = $this->privExtractByRule($p_list, $v_path, $v_remove_path, + $v_remove_all_path, $v_options); + if ($v_result < 1) { unset($p_list); //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, 0, PclZip::errorInfo()); return(0); @@ -799,7 +830,10 @@ PCLZIP_OPT_ADD_PATH => 'optional', PCLZIP_CB_PRE_EXTRACT => 'optional', PCLZIP_CB_POST_EXTRACT => 'optional', - PCLZIP_OPT_SET_CHMOD => 'optional' )); + PCLZIP_OPT_SET_CHMOD => 'optional', + PCLZIP_OPT_REPLACE_NEWER => 'optional' + ,PCLZIP_OPT_STOP_ON_ERROR => 'optional' + )); if ($v_result != 1) { //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, 0); return 0; @@ -872,7 +906,7 @@ $v_options[PCLZIP_OPT_BY_INDEX] = $v_options_trick[PCLZIP_OPT_BY_INDEX]; // ----- Call the extracting fct - if (($v_result = $this->privExtractByRule($p_list, $v_path, $v_remove_path, $v_remove_all_path, $v_options)) != 1) { + if (($v_result = $this->privExtractByRule($p_list, $v_path, $v_remove_path, $v_remove_all_path, $v_options)) < 1) { //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, 0, PclZip::errorInfo()); return(0); } @@ -1223,7 +1257,9 @@ PCLZIP_ERR_BAD_CHECKSUM => 'PCLZIP_ERR_BAD_CHECKSUM', PCLZIP_ERR_INVALID_ARCHIVE_ZIP => 'PCLZIP_ERR_INVALID_ARCHIVE_ZIP', PCLZIP_ERR_MISSING_OPTION_VALUE => 'PCLZIP_ERR_MISSING_OPTION_VALUE', - PCLZIP_ERR_INVALID_OPTION_VALUE => 'PCLZIP_ERR_INVALID_OPTION_VALUE' ); + PCLZIP_ERR_INVALID_OPTION_VALUE => 'PCLZIP_ERR_INVALID_OPTION_VALUE', + PCLZIP_ERR_UNSUPPORTED_COMPRESSION => 'PCLZIP_ERR_UNSUPPORTED_COMPRESSION', + PCLZIP_ERR_UNSUPPORTED_ENCRYPTION => 'PCLZIP_ERR_UNSUPPORTED_ENCRYPTION' ); if (isset($v_name[$this->error_code])) { $v_value = $v_name[$this->error_code]; @@ -1290,6 +1326,9 @@ //--(MAGIC-PclTrace)--//PclTraceFctStart(__FILE__, __LINE__, "PclZip::privCheckFormat", ""); $v_result = true; + // ----- Reset the file system cache + clearstatcache(); + // ----- Reset the error handler $this->privErrorReset(); @@ -1339,7 +1378,7 @@ // 1 on success. // 0 on failure. // -------------------------------------------------------------------------------- - function privParseOptions(&$p_options_list, $p_size, &$v_result_list, $v_requested_options) + function privParseOptions(&$p_options_list, $p_size, &$v_result_list, $v_requested_options=false) { //--(MAGIC-PclTrace)--//PclTraceFctStart(__FILE__, __LINE__, "PclZip::privParseOptions", ""); $v_result=1; @@ -1415,6 +1454,7 @@ // ----- Look for options that request an EREG or PREG expression case PCLZIP_OPT_BY_EREG : case PCLZIP_OPT_BY_PREG : + //case PCLZIP_OPT_CRYPT : // ----- Check the number of parameters if (($i+1) >= $p_size) { // ----- Error log @@ -1441,6 +1481,42 @@ $i++; break; + // ----- Look for options that takes a string + case PCLZIP_OPT_COMMENT : + case PCLZIP_OPT_ADD_COMMENT : + case PCLZIP_OPT_PREPEND_COMMENT : + // ----- Check the number of parameters + if (($i+1) >= $p_size) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, + "Missing parameter value for option '" + .PclZipUtilOptionText($p_options_list[$i]) + ."'"); + + // ----- Return + //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, PclZip::errorCode(), PclZip::errorInfo()); + return PclZip::errorCode(); + } + + // ----- Get the value + if (is_string($p_options_list[$i+1])) { + $v_result_list[$p_options_list[$i]] = $p_options_list[$i+1]; + } + else { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, + "Wrong parameter value for option '" + .PclZipUtilOptionText($p_options_list[$i]) + ."'"); + + // ----- Return + //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, PclZip::errorCode(), PclZip::errorInfo()); + return PclZip::errorCode(); + } + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "".PclZipUtilOptionText($p_options_list[$i])." = '".$v_result_list[$p_options_list[$i]]."'"); + $i++; + break; + // ----- Look for options that request an array of index case PCLZIP_OPT_BY_INDEX : // ----- Check the number of parameters @@ -1547,6 +1623,9 @@ case PCLZIP_OPT_REMOVE_ALL_PATH : case PCLZIP_OPT_EXTRACT_AS_STRING : case PCLZIP_OPT_NO_COMPRESSION : + case PCLZIP_OPT_EXTRACT_IN_OUTPUT : + case PCLZIP_OPT_REPLACE_NEWER : + case PCLZIP_OPT_STOP_ON_ERROR : $v_result_list[$p_options_list[$i]] = true; //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "".PclZipUtilOptionText($p_options_list[$i])." = '".$v_result_list[$p_options_list[$i]]."'"); break; @@ -1611,7 +1690,9 @@ default : // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Unknown optional parameter '".$p_options_list[$i]."'"); + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, + "Unknown parameter '" + .$p_options_list[$i]."'"); // ----- Return //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, PclZip::errorCode(), PclZip::errorInfo()); @@ -1623,18 +1704,20 @@ } // ----- Look for mandatory options - for ($key=reset($v_requested_options); $key=key($v_requested_options); $key=next($v_requested_options)) { - // ----- Look for mandatory option - if ($v_requested_options[$key] == 'mandatory') { - //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 4, "Detect a mandatory option : ".PclZipUtilOptionText($key)."(".$key.")"); - // ----- Look if present - if (!isset($v_result_list[$key])) { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Missing mandatory parameter ".PclZipUtilOptionText($key)."(".$key.")"); + if ($v_requested_options !== false) { + for ($key=reset($v_requested_options); $key=key($v_requested_options); $key=next($v_requested_options)) { + // ----- Look for mandatory option + if ($v_requested_options[$key] == 'mandatory') { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 4, "Detect a mandatory option : ".PclZipUtilOptionText($key)."(".$key.")"); + // ----- Look if present + if (!isset($v_result_list[$key])) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Missing mandatory parameter ".PclZipUtilOptionText($key)."(".$key.")"); - // ----- Return - //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, PclZip::errorCode(), PclZip::errorInfo()); - return PclZip::errorCode(); + // ----- Return + //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, PclZip::errorCode(), PclZip::errorInfo()); + return PclZip::errorCode(); + } } } } @@ -1810,7 +1893,16 @@ } // ----- Zip file comment - $v_comment = ''; + $v_comment = $v_central_dir['comment']; + if (isset($p_options[PCLZIP_OPT_COMMENT])) { + $v_comment = $p_options[PCLZIP_OPT_COMMENT]; + } + if (isset($p_options[PCLZIP_OPT_ADD_COMMENT])) { + $v_comment = $v_comment.$p_options[PCLZIP_OPT_ADD_COMMENT]; + } + if (isset($p_options[PCLZIP_OPT_PREPEND_COMMENT])) { + $v_comment = $p_options[PCLZIP_OPT_PREPEND_COMMENT].$v_comment; + } // ----- Calculate the size of the central header $v_size = @ftell($this->zip_fd)-$v_offset; @@ -1960,6 +2052,9 @@ // ----- Zip file comment $v_comment = ''; + if (isset($p_options[PCLZIP_OPT_COMMENT])) { + $v_comment = $p_options[PCLZIP_OPT_COMMENT]; + } // ----- Calculate the size of the central header $v_size = @ftell($this->zip_fd)-$v_offset; @@ -2059,7 +2154,7 @@ } // ----- Look for directory - if (is_dir($p_filename)) + if (@is_dir($p_filename)) { //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "$p_filename is a directory"); @@ -2070,41 +2165,38 @@ $v_path = ""; // ----- Read the directory for files and sub-directories - $p_hdir = opendir($p_filename); - $p_hitem = readdir($p_hdir); // '.' directory - $p_hitem = readdir($p_hdir); // '..' directory - while ($p_hitem = readdir($p_hdir)) - { - //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "Looking for $p_hitem in the directory"); + if ($p_hdir = @opendir($p_filename)) { + $p_hitem = @readdir($p_hdir); // '.' directory + $p_hitem = @readdir($p_hdir); // '..' directory + while (($p_hitem = @readdir($p_hdir)) !== false) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "Looking for $p_hitem in the directory"); - // ----- Look for a file - if (is_file($v_path.$p_hitem)) - { - //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 4, "Add the file '".$v_path.$p_hitem."'"); + // ----- Look for a file + if (is_file($v_path.$p_hitem)) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 4, "Add the file '".$v_path.$p_hitem."'"); - // ----- Add the file - if (($v_result = $this->privAddFile($v_path.$p_hitem, $v_header, $p_add_dir, $p_remove_dir, $p_remove_all_dir, $p_options)) != 1) - { - // ----- Return status - //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, $v_result); - return $v_result; + // ----- Add the file + if (($v_result = $this->privAddFile($v_path.$p_hitem, $v_header, $p_add_dir, $p_remove_dir, $p_remove_all_dir, $p_options)) != 1) { + // ----- Return status + //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, $v_result); + return $v_result; + } + + // ----- Store the file infos + $p_result_list[$v_nb++] = $v_header; } - // ----- Store the file infos - $p_result_list[$v_nb++] = $v_header; - } + // ----- Recursive call to privAddFileList() + else { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 4, "Add the directory '".$v_path.$p_hitem."'"); - // ----- Recursive call to privAddFileList() - else - { - //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 4, "Add the directory '".$v_path.$p_hitem."'"); + // ----- Need an array as parameter + $p_temp_list[0] = $v_path.$p_hitem; + $v_result = $this->privAddFileList($p_temp_list, $p_result_list, $p_add_dir, $p_remove_dir, $p_remove_all_dir, $p_options); - // ----- Need an array as parameter - $p_temp_list[0] = $v_path.$p_hitem; - $v_result = $this->privAddFileList($p_temp_list, $p_result_list, $p_add_dir, $p_remove_dir, $p_remove_all_dir, $p_options); - - // ----- Update the number of elements of the list - $v_nb = sizeof($p_result_list); + // ----- Update the number of elements of the list + $v_nb = sizeof($p_result_list); + } } } @@ -2195,19 +2287,6 @@ //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "Filename (reduced) '$v_stored_filename', strlen ".strlen($v_stored_filename)); - /* filename length moved after call-back in release 1.3 - // ----- Check the path length - if (strlen($v_stored_filename) > 0xFF) - { - // ----- Error log - PclZip::privErrorLog(-5, "Stored file name is too long (max. 255) : '$v_stored_filename'"); - - // ----- Return - //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, PclZip::errorCode(), PclZip::errorInfo()); - return PclZip::errorCode(); - } - */ - // ----- Set the file properties clearstatcache(); $p_header['version'] = 20; @@ -2223,7 +2302,9 @@ $p_header['comment_len'] = 0; $p_header['disk'] = 0; $p_header['internal'] = 0; - $p_header['external'] = (is_file($p_filename)?0xFE49FFE0:0x41FF0010); +// $p_header['external'] = (is_file($p_filename)?0xFE49FFE0:0x41FF0010); + $p_header['external'] = (is_file($p_filename)?0x00000000:0x00000010); + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 5, "Header external extension '".sprintf("0x%X",$p_header['external'])."'"); $p_header['offset'] = 0; $p_header['filename'] = $p_filename; $p_header['stored_filename'] = $v_stored_filename; @@ -2232,6 +2313,7 @@ $p_header['status'] = 'ok'; $p_header['index'] = -1; + // ----- Look for pre-add callback if (isset($p_options[PCLZIP_CB_PRE_ADD])) { //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "A pre-callback '".$p_options[PCLZIP_CB_PRE_ADD]."()') is defined for the extraction"); @@ -2274,6 +2356,7 @@ // ----- Look for a file if (is_file($p_filename)) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "'".$p_filename."' is a file"); // ----- Open the source file if (($v_file = @fopen($p_filename, "rb")) == 0) { PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, "Unable to open file '$p_filename' in binary read mode"); @@ -2282,26 +2365,55 @@ } if ($p_options[PCLZIP_OPT_NO_COMPRESSION]) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "File will not be compressed"); // ----- Read the file content $v_content_compressed = @fread($v_file, $p_header['size']); // ----- Calculate the CRC - $p_header['crc'] = crc32($v_content_compressed); + $p_header['crc'] = @crc32($v_content_compressed); + + // ----- Set header parameters + $p_header['compressed_size'] = $p_header['size']; + $p_header['compression'] = 0; } else { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "File will be compressed"); // ----- Read the file content $v_content = @fread($v_file, $p_header['size']); // ----- Calculate the CRC - $p_header['crc'] = crc32($v_content); + $p_header['crc'] = @crc32($v_content); // ----- Compress the file - $v_content_compressed = gzdeflate($v_content); - } + $v_content_compressed = @gzdeflate($v_content); - // ----- Set header parameters - $p_header['compressed_size'] = strlen($v_content_compressed); - $p_header['compression'] = 8; + // ----- Set header parameters + $p_header['compressed_size'] = strlen($v_content_compressed); + $p_header['compression'] = 8; + } + + // ----- Look for encryption + /* + if ((isset($p_options[PCLZIP_OPT_CRYPT])) + && ($p_options[PCLZIP_OPT_CRYPT] != "")) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "File need to be crypted ...."); + + // Should be a random header + $v_header = 'xxxxxxxxxxxx'; + $v_content_compressed = PclZipUtilZipEncrypt($v_content_compressed, + $p_header['compressed_size'], + $v_header, + $p_header['crc'], + "test"); + + $p_header['compressed_size'] += 12; + $p_header['flag'] = 1; + + // ----- Add the header to the data + $v_content_compressed = $v_header.$v_content_compressed; + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "Size after header : ".strlen($v_content_compressed).""); + } + */ // ----- Call the header generation if (($v_result = $this->privWriteFileHeader($p_header)) != 1) { @@ -2310,8 +2422,9 @@ return $v_result; } - // ----- Write the compressed content - $v_binary_data = pack('a'.$p_header['compressed_size'], $v_content_compressed); + // ----- Write the compressed (or not) content + $v_binary_data = pack('a'.$p_header['compressed_size'], + $v_content_compressed); @fwrite($this->zip_fd, $v_binary_data, $p_header['compressed_size']); // ----- Close the file @@ -2319,13 +2432,17 @@ } // ----- Look for a directory - else - { + else { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "'".$p_filename."' is a folder"); + // ----- Look for directory last '/' + if (@substr($p_header['stored_filename'], -1) != '/') { + $p_header['stored_filename'] .= '/'; + } + // ----- Set the file properties - $p_header['filename'] .= '/'; - $p_header['filename_len']++; $p_header['size'] = 0; - $p_header['external'] = 0x41FF0010; // Value for a folder : to be checked + //$p_header['external'] = 0x41FF0010; // Value for a folder : to be checked + $p_header['external'] = 0x00000010; // Value for a folder : to be checked // ----- Call the header generation if (($v_result = $this->privWriteFileHeader($p_header)) != 1) @@ -2390,10 +2507,13 @@ $v_mdate = (($v_date['year']-1980)<<9) + ($v_date['mon']<<5) + $v_date['mday']; // ----- Packed data - $v_binary_data = pack("VvvvvvVVVvv", 0x04034b50, $p_header['version'], $p_header['flag'], + $v_binary_data = pack("VvvvvvVVVvv", 0x04034b50, + $p_header['version_extracted'], $p_header['flag'], $p_header['compression'], $v_mtime, $v_mdate, - $p_header['crc'], $p_header['compressed_size'], $p_header['size'], - strlen($p_header['stored_filename']), $p_header['extra_len']); + $p_header['crc'], $p_header['compressed_size'], + $p_header['size'], + strlen($p_header['stored_filename']), + $p_header['extra_len']); // ----- Write the first 148 bytes of the header in the archive fputs($this->zip_fd, $v_binary_data, 30); @@ -2437,11 +2557,15 @@ $v_mdate = (($v_date['year']-1980)<<9) + ($v_date['mon']<<5) + $v_date['mday']; // ----- Packed data - $v_binary_data = pack("VvvvvvvVVVvvvvvVV", 0x02014b50, $p_header['version'], $p_header['version_extracted'], - $p_header['flag'], $p_header['compression'], $v_mtime, $v_mdate, $p_header['crc'], + $v_binary_data = pack("VvvvvvvVVVvvvvvVV", 0x02014b50, + $p_header['version'], $p_header['version_extracted'], + $p_header['flag'], $p_header['compression'], + $v_mtime, $v_mdate, $p_header['crc'], $p_header['compressed_size'], $p_header['size'], - strlen($p_header['stored_filename']), $p_header['extra_len'], $p_header['comment_len'], - $p_header['disk'], $p_header['internal'], $p_header['external'], $p_header['offset']); + strlen($p_header['stored_filename']), + $p_header['extra_len'], $p_header['comment_len'], + $p_header['disk'], $p_header['internal'], + $p_header['external'], $p_header['offset']); // ----- Write the 42 bytes of the header in the zip file fputs($this->zip_fd, $v_binary_data, 46); @@ -2478,7 +2602,9 @@ $v_result=1; // ----- Packed data - $v_binary_data = pack("VvvvvVVv", 0x06054b50, 0, 0, $p_nb_entries, $p_nb_entries, $p_size, $p_offset, strlen($p_comment)); + $v_binary_data = pack("VvvvvVVv", 0x06054b50, 0, 0, $p_nb_entries, + $p_nb_entries, $p_size, + $p_offset, strlen($p_comment)); // ----- Write the 22 bytes of the header in the zip file fputs($this->zip_fd, $v_binary_data, 22); @@ -2797,8 +2923,63 @@ //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 3, "Extract with no rule (extract all)"); $v_extract = true; } - + // ----- Check compression method + if ( ($v_extract) + && ( ($v_header['compression'] != 8) + && ($v_header['compression'] != 0))) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "Unsupported compression method (".$v_header['compression'].")"); + $v_header['status'] = 'unsupported_compression'; + + // ----- Look for PCLZIP_OPT_STOP_ON_ERROR + if ( (isset($p_options[PCLZIP_OPT_STOP_ON_ERROR])) + && ($p_options[PCLZIP_OPT_STOP_ON_ERROR]===true)) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "PCLZIP_OPT_STOP_ON_ERROR is selected, extraction will be stopped"); + + PclZip::privErrorLog(PCLZIP_ERR_UNSUPPORTED_COMPRESSION, + "Filename '".$v_header['stored_filename']."' is " + ."compressed by an unsupported compression " + ."method (".$v_header['compression'].") "); + + //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, PclZip::errorCode(), PclZip::errorInfo()); + return PclZip::errorCode(); + } + } + + // ----- Check encrypted files + if (($v_extract) && (($v_header['flag'] & 1) == 1)) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "Unsupported file encryption"); + $v_header['status'] = 'unsupported_encryption'; + + // ----- Look for PCLZIP_OPT_STOP_ON_ERROR + if ( (isset($p_options[PCLZIP_OPT_STOP_ON_ERROR])) + && ($p_options[PCLZIP_OPT_STOP_ON_ERROR]===true)) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "PCLZIP_OPT_STOP_ON_ERROR is selected, extraction will be stopped"); + + PclZip::privErrorLog(PCLZIP_ERR_UNSUPPORTED_ENCRYPTION, + "Unsupported encryption for " + ." filename '".$v_header['stored_filename'] + ."'"); + + //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, PclZip::errorCode(), PclZip::errorInfo()); + return PclZip::errorCode(); + } + } + + // ----- Look for real extraction + if (($v_extract) && ($v_header['status'] != 'ok')) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "No need for extract"); + $v_result = $this->privConvertHeader2FileInfo($v_header, + $p_file_list[$v_nb_extracted++]); + if ($v_result != 1) { + $this->privCloseFd(); + //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, $v_result); + return $v_result; + } + + $v_extract = false; + } + // ----- Look for real extraction if ($v_extract) { @@ -2826,13 +3007,11 @@ if ($p_options[PCLZIP_OPT_EXTRACT_AS_STRING]) { // ----- Extracting the file - if (($v_result = $this->privExtractFileAsString($v_header, $v_string)) != 1) - { - // ----- Close the zip file + $v_result1 = $this->privExtractFileAsString($v_header, $v_string); + if ($v_result1 < 1) { $this->privCloseFd(); - - //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, $v_result); - return $v_result; + //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, $v_result1); + return $v_result1; } // ----- Get the only interesting attributes @@ -2850,18 +3029,48 @@ // ----- Next extracted file $v_nb_extracted++; + + // ----- Look for user callback abort + if ($v_result1 == 2) { + break; + } } - else { - // ----- Extracting the file - if (($v_result = $this->privExtractFile($v_header, $p_path, $p_remove_path, $p_remove_all_path, $p_options)) != 1) - { - // ----- Close the zip file + // ----- Look for extraction in standard output + elseif ( (isset($p_options[PCLZIP_OPT_EXTRACT_IN_OUTPUT])) + && ($p_options[PCLZIP_OPT_EXTRACT_IN_OUTPUT])) { + // ----- Extracting the file in standard output + $v_result1 = $this->privExtractFileInOutput($v_header, $p_options); + if ($v_result1 < 1) { $this->privCloseFd(); + //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, $v_result1); + return $v_result1; + } + // ----- Get the only interesting attributes + if (($v_result = $this->privConvertHeader2FileInfo($v_header, $p_file_list[$v_nb_extracted++])) != 1) { + $this->privCloseFd(); //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, $v_result); return $v_result; } + // ----- Look for user callback abort + if ($v_result1 == 2) { + break; + } + } + // ----- Look for normal extraction + else { + // ----- Extracting the file + $v_result1 = $this->privExtractFile($v_header, + $p_path, $p_remove_path, + $p_remove_all_path, + $p_options); + if ($v_result1 < 1) { + $this->privCloseFd(); + //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, $v_result1); + return $v_result1; + } + // ----- Get the only interesting attributes if (($v_result = $this->privConvertHeader2FileInfo($v_header, $p_file_list[$v_nb_extracted++])) != 1) { @@ -2871,6 +3080,11 @@ //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, $v_result); return $v_result; } + + // ----- Look for user callback abort + if ($v_result1 == 2) { + break; + } } } } @@ -2889,6 +3103,9 @@ // Description : // Parameters : // Return Values : + // + // 1 : ... ? + // PCLZIP_ERR_USER_ABORTED(2) : User ask for extraction stop in callback // -------------------------------------------------------------------------------- function privExtractFile(&$p_entry, $p_path, $p_remove_path, $p_remove_all_path, &$p_options) { @@ -2906,10 +3123,13 @@ //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "Found file '".$v_header['filename']."', size '".$v_header['size']."'"); // ----- Check that the file header is coherent with $p_entry info - // TBC + if ($this->privCheckFileHeaders($v_header, $p_entry) != 1) { + // TBC + } // ----- Look for all path to remove if ($p_remove_all_path == true) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 3, "All path is removed"); // ----- Get the basename of the path $p_entry['filename'] = basename($p_entry['filename']); } @@ -2917,7 +3137,7 @@ // ----- Look for path to remove else if ($p_remove_path != "") { - //if (strcmp($p_remove_path, $p_entry['filename'])==0) + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 3, "Look for some path to remove"); if (PclZipUtilPathInclusion($p_remove_path, $p_entry['filename']) == 2) { //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "The folder is the same as the removed path '".$p_entry['filename']."'"); @@ -2965,6 +3185,14 @@ $p_entry['status'] = "skipped"; $v_result = 1; } + + // ----- Look for abort result + if ($v_result == 2) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "User callback abort the extraction"); + // ----- This status is internal and will be changed in 'skipped' + $p_entry['status'] = "aborted"; + $v_result = PCLZIP_ERR_USER_ABORTED; + } // ----- Update the informations // Only some fields can be modified @@ -2972,7 +3200,6 @@ //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "New filename is '".$p_entry['filename']."'"); } - // ----- Trace //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "Extracting file (with path) '".$p_entry['filename']."', size '$v_header[size]'"); // ----- Look if extraction should be done @@ -2990,10 +3217,21 @@ // ----- Change the file status $p_entry['status'] = "already_a_directory"; + + // ----- Look for PCLZIP_OPT_STOP_ON_ERROR + // For historical reason first PclZip implementation does not stop + // when this kind of error occurs. + if ( (isset($p_options[PCLZIP_OPT_STOP_ON_ERROR])) + && ($p_options[PCLZIP_OPT_STOP_ON_ERROR]===true)) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "PCLZIP_OPT_STOP_ON_ERROR is selected, extraction will be stopped"); - // ----- Return - ////--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, $v_result); - //return $v_result; + PclZip::privErrorLog(PCLZIP_ERR_ALREADY_A_DIRECTORY, + "Filename '".$p_entry['filename']."' is " + ."already used by an existing directory"); + + //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, PclZip::errorCode(), PclZip::errorInfo()); + return PclZip::errorCode(); + } } // ----- Look if file is write protected else if (!is_writeable($p_entry['filename'])) @@ -3003,22 +3241,53 @@ // ----- Change the file status $p_entry['status'] = "write_protected"; - // ----- Return - ////--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, $v_result); - //return $v_result; + // ----- Look for PCLZIP_OPT_STOP_ON_ERROR + // For historical reason first PclZip implementation does not stop + // when this kind of error occurs. + if ( (isset($p_options[PCLZIP_OPT_STOP_ON_ERROR])) + && ($p_options[PCLZIP_OPT_STOP_ON_ERROR]===true)) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "PCLZIP_OPT_STOP_ON_ERROR is selected, extraction will be stopped"); + + PclZip::privErrorLog(PCLZIP_ERR_WRITE_OPEN_FAIL, + "Filename '".$p_entry['filename']."' exists " + ."and is write protected"); + + //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, PclZip::errorCode(), PclZip::errorInfo()); + return PclZip::errorCode(); + } } // ----- Look if the extracted file is older else if (filemtime($p_entry['filename']) > $p_entry['mtime']) { //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "Existing file '".$p_entry['filename']."' is newer (".date("l dS of F Y h:i:s A", filemtime($p_entry['filename'])).") than the extracted file (".date("l dS of F Y h:i:s A", $p_entry['mtime']).")"); - // ----- Change the file status - $p_entry['status'] = "newer_exist"; + if ( (isset($p_options[PCLZIP_OPT_REPLACE_NEWER])) + && ($p_options[PCLZIP_OPT_REPLACE_NEWER]===true)) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "PCLZIP_OPT_REPLACE_NEWER is selected, file will be replaced"); + } + else { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "File will not be replaced"); + $p_entry['status'] = "newer_exist"; - // ----- Return - ////--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, $v_result); - //return $v_result; + // ----- Look for PCLZIP_OPT_STOP_ON_ERROR + // For historical reason first PclZip implementation does not stop + // when this kind of error occurs. + if ( (isset($p_options[PCLZIP_OPT_STOP_ON_ERROR])) + && ($p_options[PCLZIP_OPT_STOP_ON_ERROR]===true)) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "PCLZIP_OPT_STOP_ON_ERROR is selected, extraction will be stopped"); + + PclZip::privErrorLog(PCLZIP_ERR_WRITE_OPEN_FAIL, + "Newer version of '".$p_entry['filename']."' exists " + ."and option PCLZIP_OPT_REPLACE_NEWER is not selected"); + + //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, PclZip::errorCode(), PclZip::errorInfo()); + return PclZip::errorCode(); + } + } + } + else { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "Existing file '".$p_entry['filename']."' is older than the extrated one - will be replaced by the extracted one (".date("l dS of F Y h:i:s A", filemtime($p_entry['filename'])).") than the extracted file (".date("l dS of F Y h:i:s A", $p_entry['mtime']).")"); } } @@ -3051,13 +3320,11 @@ // ----- Do the extraction (if not a folder) if (!(($p_entry['external']&0x00000010)==0x00000010)) { - // ----- Look for not compressed file - if ($p_entry['compressed_size'] == $p_entry['size']) - { + if ($p_entry['compression'] == 0) { //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "Extracting an un-compressed file"); - // ----- Opening destination file + // ----- Opening destination file if (($v_dest_file = @fopen($p_entry['filename'], 'wb')) == 0) { //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "Error while opening '".$p_entry['filename']."' in write binary mode"); @@ -3070,7 +3337,7 @@ return $v_result; } - //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "Reading '".$p_entry['size']."' bytes"); + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "Read '".$p_entry['size']."' bytes"); // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks $v_size = $p_entry['compressed_size']; @@ -3089,12 +3356,50 @@ // ----- Change the file mtime touch($p_entry['filename'], $p_entry['mtime']); - } - else - { - // ----- Trace - //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "Extracting a compressed file"); + + } + else { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "Extracting a compressed file (Compression method ".$p_entry['compression'].")"); + // ----- TBC + // Need to be finished + if (($p_entry['flag'] & 1) == 1) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "File is encrypted"); + /* + // ----- Read the encryption header + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 5, "Read 12 encryption header bytes"); + $v_encryption_header = @fread($this->zip_fd, 12); + + // ----- Read the encrypted & compressed file in a buffer + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 5, "Read '".($p_entry['compressed_size']-12)."' compressed & encrypted bytes"); + $v_buffer = @fread($this->zip_fd, $p_entry['compressed_size']-12); + + // ----- Decrypt the buffer + $this->privDecrypt($v_encryption_header, $v_buffer, + $p_entry['compressed_size']-12, $p_entry['crc']); + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 5, "Buffer is '".$v_buffer."'"); + */ + } + else { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 5, "Read '".$p_entry['compressed_size']."' compressed bytes"); + // ----- Read the compressed file in a buffer (one shot) + $v_buffer = @fread($this->zip_fd, $p_entry['compressed_size']); + } + + // ----- Decompress the file + $v_file_content = @gzinflate($v_buffer); + unset($v_buffer); + if ($v_file_content === FALSE) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "Unable to inflate compressed file"); + + // ----- Change the file status + // TBC + $p_entry['status'] = "error"; + + //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, $v_result); + return $v_result; + } + // ----- Opening destination file if (($v_dest_file = @fopen($p_entry['filename'], 'wb')) == 0) { //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "Error while opening '".$p_entry['filename']."' in write binary mode"); @@ -3106,15 +3411,6 @@ return $v_result; } - //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 5, "Reading '".$p_entry['size']."' bytes"); - - // ----- Read the compressed file in a buffer (one shot) - $v_buffer = @fread($this->zip_fd, $p_entry['compressed_size']); - - // ----- Decompress the file - $v_file_content = gzinflate($v_buffer); - unset($v_buffer); - // ----- Write the uncompressed data @fwrite($v_dest_file, $v_file_content, $p_entry['size']); unset($v_file_content); @@ -3123,7 +3419,7 @@ @fclose($v_dest_file); // ----- Change the file mtime - touch($p_entry['filename'], $p_entry['mtime']); + @touch($p_entry['filename'], $p_entry['mtime']); } // ----- Look for chmod option @@ -3131,15 +3427,20 @@ //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "chmod option activated '".$p_options[PCLZIP_OPT_SET_CHMOD]."'"); // ----- Change the mode of the file - chmod($p_entry['filename'], $p_options[PCLZIP_OPT_SET_CHMOD]); + @chmod($p_entry['filename'], $p_options[PCLZIP_OPT_SET_CHMOD]); } //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "Extraction done"); } } + // ----- Change abort status + if ($p_entry['status'] == "aborted") { + $p_entry['status'] = "skipped"; + } + // ----- Look for post-extract callback - if (isset($p_options[PCLZIP_CB_POST_EXTRACT])) { + elseif (isset($p_options[PCLZIP_CB_POST_EXTRACT])) { //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "A post-callback '".$p_options[PCLZIP_CB_POST_EXTRACT]."()') is defined for the extraction"); // ----- Generate a local information @@ -3150,6 +3451,12 @@ // Here I do not use call_user_func() because I need to send a reference to the // header. eval('$v_result = '.$p_options[PCLZIP_CB_POST_EXTRACT].'(PCLZIP_CB_POST_EXTRACT, $v_local_header);'); + + // ----- Look for abort result + if ($v_result == 2) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "User callback abort the extraction"); + $v_result = PCLZIP_ERR_USER_ABORTED; + } } // ----- Return @@ -3158,6 +3465,131 @@ } // -------------------------------------------------------------------------------- + // -------------------------------------------------------------------------------- + // Function : privExtractFileInOutput() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + function privExtractFileInOutput(&$p_entry, &$p_options) + { + //--(MAGIC-PclTrace)--//PclTraceFctStart(__FILE__, __LINE__, 'PclZip::privExtractFileInOutput', ""); + $v_result=1; + + // ----- Read the file header + if (($v_result = $this->privReadFileHeader($v_header)) != 1) { + //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, $v_result); + return $v_result; + } + + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "Found file '".$v_header['filename']."', size '".$v_header['size']."'"); + + // ----- Check that the file header is coherent with $p_entry info + if ($this->privCheckFileHeaders($v_header, $p_entry) != 1) { + // TBC + } + + // ----- Look for pre-extract callback + if (isset($p_options[PCLZIP_CB_PRE_EXTRACT])) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "A pre-callback '".$p_options[PCLZIP_CB_PRE_EXTRACT]."()') is defined for the extraction"); + + // ----- Generate a local information + $v_local_header = array(); + $this->privConvertHeader2FileInfo($p_entry, $v_local_header); + + // ----- Call the callback + // Here I do not use call_user_func() because I need to send a reference to the + // header. + eval('$v_result = '.$p_options[PCLZIP_CB_PRE_EXTRACT].'(PCLZIP_CB_PRE_EXTRACT, $v_local_header);'); + if ($v_result == 0) { + // ----- Change the file status + $p_entry['status'] = "skipped"; + $v_result = 1; + } + + // ----- Look for abort result + if ($v_result == 2) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "User callback abort the extraction"); + // ----- This status is internal and will be changed in 'skipped' + $p_entry['status'] = "aborted"; + $v_result = PCLZIP_ERR_USER_ABORTED; + } + + // ----- Update the informations + // Only some fields can be modified + $p_entry['filename'] = $v_local_header['filename']; + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "New filename is '".$p_entry['filename']."'"); + } + + // ----- Trace + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "Extracting file (with path) '".$p_entry['filename']."', size '$v_header[size]'"); + + // ----- Look if extraction should be done + if ($p_entry['status'] == 'ok') { + + // ----- Do the extraction (if not a folder) + if (!(($p_entry['external']&0x00000010)==0x00000010)) { + // ----- Look for not compressed file + if ($p_entry['compressed_size'] == $p_entry['size']) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "Extracting an un-compressed file"); + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "Reading '".$p_entry['size']."' bytes"); + + // ----- Read the file in a buffer (one shot) + $v_buffer = @fread($this->zip_fd, $p_entry['compressed_size']); + + // ----- Send the file to the output + echo $v_buffer; + unset($v_buffer); + } + else { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "Extracting a compressed file"); + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 5, "Reading '".$p_entry['size']."' bytes"); + + // ----- Read the compressed file in a buffer (one shot) + $v_buffer = @fread($this->zip_fd, $p_entry['compressed_size']); + + // ----- Decompress the file + $v_file_content = gzinflate($v_buffer); + unset($v_buffer); + + // ----- Send the file to the output + echo $v_file_content; + unset($v_file_content); + } + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "Extraction done"); + } + } + + // ----- Change abort status + if ($p_entry['status'] == "aborted") { + $p_entry['status'] = "skipped"; + } + + // ----- Look for post-extract callback + elseif (isset($p_options[PCLZIP_CB_POST_EXTRACT])) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "A post-callback '".$p_options[PCLZIP_CB_POST_EXTRACT]."()') is defined for the extraction"); + + // ----- Generate a local information + $v_local_header = array(); + $this->privConvertHeader2FileInfo($p_entry, $v_local_header); + + // ----- Call the callback + // Here I do not use call_user_func() because I need to send a reference to the + // header. + eval('$v_result = '.$p_options[PCLZIP_CB_POST_EXTRACT].'(PCLZIP_CB_POST_EXTRACT, $v_local_header);'); + + // ----- Look for abort result + if ($v_result == 2) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "User callback abort the extraction"); + $v_result = PCLZIP_ERR_USER_ABORTED; + } + } + + //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, $v_result); + return $v_result; + } + // -------------------------------------------------------------------------------- + // -------------------------------------------------------------------------------- // Function : privExtractFileAsString() // Description : @@ -3181,9 +3613,10 @@ //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "Found file '".$v_header['filename']."', size '".$v_header['size']."'"); // ----- Check that the file header is coherent with $p_entry info - // TBC + if ($this->privCheckFileHeaders($v_header, $p_entry) != 1) { + // TBC + } - // ----- Trace //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "Extracting file in string (with path) '".$p_entry['filename']."', size '$v_header[size]'"); // ----- Do the extraction (if not a folder) @@ -3192,12 +3625,11 @@ // ----- Look for not compressed file if ($p_entry['compressed_size'] == $p_entry['size']) { - // ----- Trace //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "Extracting an un-compressed file"); //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "Reading '".$p_entry['size']."' bytes"); // ----- Reading the file - $p_string = fread($this->zip_fd, $p_entry['compressed_size']); + $p_string = @fread($this->zip_fd, $p_entry['compressed_size']); } else { @@ -3205,10 +3637,12 @@ //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "Extracting a compressed file"); // ----- Reading the file - $v_data = fread($this->zip_fd, $p_entry['compressed_size']); + $v_data = @fread($this->zip_fd, $p_entry['compressed_size']); // ----- Decompress the file - $p_string = gzinflate($v_data); + if (($p_string = @gzinflate($v_data)) === FALSE) { + // TBC + } } // ----- Trace @@ -3293,14 +3727,16 @@ //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 3, 'Extra field : \''.bin2hex($p_header['extra']).'\''); // ----- Extract properties + $p_header['version_extracted'] = $v_data['version']; + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 4, 'Version need to extract : ('.$p_header['version_extracted'].') \''.($p_header['version_extracted']/10).'.'.($p_header['version_extracted']%10).'\''); $p_header['compression'] = $v_data['compression']; - //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 3, 'Compression method : \''.bin2hex($p_header['compression']).'\''); + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 3, 'Compression method : \''.$p_header['compression'].'\''); $p_header['size'] = $v_data['size']; //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 3, 'Size : \''.$p_header['size'].'\''); $p_header['compressed_size'] = $v_data['compressed_size']; //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 3, 'Compressed Size : \''.$p_header['compressed_size'].'\''); $p_header['crc'] = $v_data['crc']; - //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 3, 'CRC : \''.$p_header['crc'].'\''); + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 3, 'CRC : \''.sprintf("0x%X", $p_header['crc']).'\''); $p_header['flag'] = $v_data['flag']; //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 3, 'Flag : \''.$p_header['flag'].'\''); @@ -3330,10 +3766,6 @@ //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 3, 'Date is actual : \''.date("d/m/y H:i:s", $p_header['mtime']).'\''); } - // ----- Other informations - //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 3, "Compression type : ".$v_data['compression']); - //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 3, "Version : ".$v_data['version']); - // TBC //for(reset($v_data); $key = key($v_data); next($v_data)) { // //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 3, "Attribut[$key] = ".$v_data[$key]); @@ -3433,7 +3865,7 @@ //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 4, 'Version need to extract : \''.($p_header['version_extracted']/10).'.'.($p_header['version_extracted']%10).'\''); //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 4, 'Size : \''.$p_header['size'].'\''); //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 4, 'Compressed Size : \''.$p_header['compressed_size'].'\''); - //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 4, 'CRC : \''.$p_header['crc'].'\''); + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 4, 'CRC : \''.sprintf("0x%X", $p_header['crc']).'\''); //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 4, 'Flag : \''.$p_header['flag'].'\''); //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 4, 'Offset : \''.$p_header['offset'].'\''); @@ -3470,10 +3902,10 @@ // ----- Look if it is a directory //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 5, "Internal (Hex) : '".sprintf("Ox%04X", $p_header['internal'])."'"); //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 4, "External (Hex) : '".sprintf("Ox%04X", $p_header['external'])."' (".(($p_header['external']&0x00000010)==0x00000010?'is a folder':'is a file').')'); - if (substr($p_header['filename'], -1) == '/') - { - $p_header['external'] = 0x41FF0010; - //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 4, 'Force folder external : \''.$p_header['external'].'\''); + if (substr($p_header['filename'], -1) == '/') { + //$p_header['external'] = 0x41FF0010; + $p_header['external'] = 0x00000010; + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 4, 'Force folder external : \''.sprintf("Ox%04X", $p_header['external']).'\''); } //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 3, 'Header of filename : \''.$p_header['filename'].'\''); @@ -3484,6 +3916,58 @@ } // -------------------------------------------------------------------------------- + // -------------------------------------------------------------------------------- + // Function : privCheckFileHeaders() + // Description : + // Parameters : + // Return Values : + // 1 on success, + // 0 on error; + // -------------------------------------------------------------------------------- + function privCheckFileHeaders(&$p_local_header, &$p_central_header) + { + //--(MAGIC-PclTrace)--//PclTraceFctStart(__FILE__, __LINE__, "PclZip::privCheckFileHeaders", ""); + $v_result=1; + + // ----- Check the static values + // TBC + if ($p_local_header['filename'] != $p_central_header['filename']) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 3, 'Bad check "filename" : TBC To Be Completed'); + } + if ($p_local_header['version_extracted'] != $p_central_header['version_extracted']) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 3, 'Bad check "version_extracted" : TBC To Be Completed'); + } + if ($p_local_header['flag'] != $p_central_header['flag']) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 3, 'Bad check "flag" : TBC To Be Completed'); + } + if ($p_local_header['compression'] != $p_central_header['compression']) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 3, 'Bad check "compression" : TBC To Be Completed'); + } + if ($p_local_header['mtime'] != $p_central_header['mtime']) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 3, 'Bad check "mtime" : TBC To Be Completed'); + } + if ($p_local_header['filename_len'] != $p_central_header['filename_len']) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 3, 'Bad check "filename_len" : TBC To Be Completed'); + } + + // ----- Look for flag bit 3 + if (($p_local_header['flag'] & 8) == 8) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 3, 'Purpose bit flag bit 3 set !'); + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 3, 'File size, compression size and crc found in central header'); + $p_local_header['size'] = $p_central_header['size']; + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 3, 'Size : \''.$p_local_header['size'].'\''); + $p_local_header['compressed_size'] = $p_central_header['compressed_size']; + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 3, 'Compressed Size : \''.$p_local_header['compressed_size'].'\''); + $p_local_header['crc'] = $p_central_header['crc']; + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 3, 'CRC : \''.sprintf("0x%X", $p_local_header['crc']).'\''); + } + + // ----- Return + //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, $v_result); + return $v_result; + } + // -------------------------------------------------------------------------------- + // -------------------------------------------------------------------------------- // Function : privReadEndCentralDir() // Description : @@ -3530,7 +4014,7 @@ // ----- Read for bytes $v_binary_data = @fread($this->zip_fd, 4); //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 5, "Binary data is : '".sprintf("%08x", $v_binary_data)."'"); - $v_data = unpack('Vid', $v_binary_data); + $v_data = @unpack('Vid', $v_binary_data); //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 3, "Binary signature is : '".sprintf("0x%08x", $v_data['id'])."'"); // ----- Check signature @@ -3619,16 +4103,23 @@ // ----- Check the global size //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 3, "Comment length : ".$v_data['comment_size']); - if (($v_pos + $v_data['comment_size'] + 18) != $v_size) - { - //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "Fail to find the right signature"); + if (($v_pos + $v_data['comment_size'] + 18) != $v_size) { + //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 2, "The central dir is not at the end of the archive. Some trailing bytes exists after the archive."); + // ----- Removed in release 2.2 see readme file + // The check of the file size is a little too strict. + // Some bugs where found when a zip is encrypted/decrypted with 'crypt'. + // While decrypted, zip has training 0 bytes + if (0) { // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, "Fail to find the right signature"); + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, + 'The central dir is not at the end of the archive.' + .' Some trailing bytes exists after the archive.'); // ----- Return //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, PclZip::errorCode(), PclZip::errorInfo()); return PclZip::errorCode(); + } } // ----- Get comment @@ -3880,7 +4371,8 @@ //--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 5, "Position after fseek : ".ftell($this->zip_fd)."'"); // ----- Read the file header - if (($v_result = $this->privReadFileHeader($v_header_list[$i])) != 1) { + $v_local_header = array(); + if (($v_result = $this->privReadFileHeader($v_local_header)) != 1) { // ----- Close the zip file $this->privCloseFd(); $v_temp_zip->privCloseFd(); @@ -3890,6 +4382,13 @@ //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, $v_result); return $v_result; } + + // ----- Check that local file header is same as central file header + if ($this->privCheckFileHeaders($v_local_header, + $v_header_list[$i]) != 1) { + // TBC + } + unset($v_local_header); // ----- Write the file header if (($v_result = $v_temp_zip->privWriteFileHeader($v_header_list[$i])) != 1) { @@ -3944,6 +4443,9 @@ // ----- Zip file comment $v_comment = ''; + if (isset($p_options[PCLZIP_OPT_COMMENT])) { + $v_comment = $p_options[PCLZIP_OPT_COMMENT]; + } // ----- Calculate the size of the central header $v_size = @ftell($v_temp_zip->zip_fd)-$v_offset; @@ -3977,6 +4479,23 @@ // ----- Destroy the temporary archive unset($v_temp_zip); } + + // ----- Remove every files : reset the file + else if ($v_central_dir['entries'] != 0) { + $this->privCloseFd(); + + if (($v_result = $this->privOpenFd('wb')) != 1) { + //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, $v_result); + return $v_result; + } + + if (($v_result = $this->privWriteCentralHeader(0, 0, 0, '')) != 1) { + //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, $v_result); + return $v_result; + } + + $this->privCloseFd(); + } // ----- Return //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, $v_result); @@ -4205,9 +4724,8 @@ $v_size -= $v_read_size; } - // ----- Zip file comment - // TBC : I should merge the two comments - $v_comment = ''; + // ----- Merge the file comments + $v_comment = $v_central_dir['comment'].' '.$v_central_dir_to_add['comment']; // ----- Calculate the size of the (new) central header $v_size = @ftell($v_zip_temp_fd)-$v_offset; @@ -4360,12 +4878,35 @@ PclErrorReset(); } else { - $this->error_code = 1; + $this->error_code = 0; $this->error_string = ''; } } // -------------------------------------------------------------------------------- + // -------------------------------------------------------------------------------- + // Function : privDecrypt() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + function privDecrypt($p_encryption_header, &$p_buffer, $p_size, $p_crc) + { + //--(MAGIC-PclTrace)--//PclTraceFctStart(__FILE__, __LINE__, 'PclZip::privDecrypt', "size=".$p_size.""); + $v_result=1; + + // ----- To Be Modified ;-) + $v_pwd = "test"; + + $p_buffer = PclZipUtilZipDecrypt($p_buffer, $p_size, $p_encryption_header, + $p_crc, $v_pwd); + + // ----- Return + //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, $v_result); + return $v_result; + } + // -------------------------------------------------------------------------------- + } // End of class // -------------------------------------------------------------------------------- @@ -4617,58 +5158,19 @@ function PclZipUtilOptionText($p_option) { //--(MAGIC-PclTrace)--//PclTraceFctStart(__FILE__, __LINE__, "PclZipUtilOptionText", "option='".$p_option."'"); - - switch ($p_option) { - case PCLZIP_OPT_PATH : - $v_result = 'PCLZIP_OPT_PATH'; - break; - case PCLZIP_OPT_ADD_PATH : - $v_result = 'PCLZIP_OPT_ADD_PATH'; - break; - case PCLZIP_OPT_REMOVE_PATH : - $v_result = 'PCLZIP_OPT_REMOVE_PATH'; - break; - case PCLZIP_OPT_REMOVE_ALL_PATH : - $v_result = 'PCLZIP_OPT_REMOVE_ALL_PATH'; - break; - case PCLZIP_OPT_EXTRACT_AS_STRING : - $v_result = 'PCLZIP_OPT_EXTRACT_AS_STRING'; - break; - case PCLZIP_OPT_SET_CHMOD : - $v_result = 'PCLZIP_OPT_SET_CHMOD'; - break; - case PCLZIP_OPT_BY_NAME : - $v_result = 'PCLZIP_OPT_BY_NAME'; - break; - case PCLZIP_OPT_BY_INDEX : - $v_result = 'PCLZIP_OPT_BY_INDEX'; - break; - case PCLZIP_OPT_BY_EREG : - $v_result = 'PCLZIP_OPT_BY_EREG'; - break; - case PCLZIP_OPT_BY_PREG : - $v_result = 'PCLZIP_OPT_BY_PREG'; - break; - - - case PCLZIP_CB_PRE_EXTRACT : - $v_result = 'PCLZIP_CB_PRE_EXTRACT'; - break; - case PCLZIP_CB_POST_EXTRACT : - $v_result = 'PCLZIP_CB_POST_EXTRACT'; - break; - case PCLZIP_CB_PRE_ADD : - $v_result = 'PCLZIP_CB_PRE_ADD'; - break; - case PCLZIP_CB_POST_ADD : - $v_result = 'PCLZIP_CB_POST_ADD'; - break; - - default : - $v_result = 'Unknown'; + + $v_list = get_defined_constants(); + for (reset($v_list); $v_key = key($v_list); next($v_list)) { + $v_prefix = substr($v_key, 0, 10); + if ((($v_prefix == 'PCLZIP_OPT') || ($v_prefix == 'PCLZIP_CB_')) + && ($v_list[$v_key] == $p_option)) { + //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, $v_key); + return $v_key; + } } + + $v_result = 'Unknown'; - // ----- Return //--(MAGIC-PclTrace)--//PclTraceFctEnd(__FILE__, __LINE__, $v_result); return $v_result; } @@ -4701,4 +5203,5 @@ } // -------------------------------------------------------------------------------- + ?> diff --git a/phpgwapi/inc/class.common.inc.php b/phpgwapi/inc/class.common.inc.php index 6f81953817..d8bd40f7e7 100644 --- a/phpgwapi/inc/class.common.inc.php +++ b/phpgwapi/inc/class.common.inc.php @@ -1313,6 +1313,29 @@ { $java_script = ''; + //viniciuscb: in Concisus this condition is inexistent, and in all + //pages the javascript globals are inserted. Today, because + //filescenter needs these javascript globals, this + //include_jsbackend is a must to the javascript globals be + //included. + if ($GLOBALS['egw_info']['flags']['include_jsbackend']) + { + if (!$GLOBALS['egw_info']['flags']['nojsapi']) + { + if(!@is_object($GLOBALS['egw']->js)) + { + $GLOBALS['egw']->js =& CreateObject('phpgwapi.javascript'); + } + + $GLOBALS['egw']->js->validate_jsapi(); + } + + if(@is_object($GLOBALS['egw']->js)) + { + $java_script .= $GLOBALS['egw']->js->get_javascript_globals(); + } + } + /* this flag is for all javascript code that has to be put before other jscode. Think of conf vars etc... (pim@lingewoud.nl) */ if (isset($GLOBALS['egw_info']['flags']['java_script_thirst'])) diff --git a/phpgwapi/inc/class.javascript.inc.php b/phpgwapi/inc/class.javascript.inc.php index 8bdc283d2a..11576ad8c8 100644 --- a/phpgwapi/inc/class.javascript.inc.php +++ b/phpgwapi/inc/class.javascript.inc.php @@ -1,44 +1,51 @@ - * if(!@is_object($GLOBALS['phpgw']->js)) - * { - * $GLOBALS['phpgw']->js = CreateObject('phpgwapi.javascript'); - * } - * - * - * This way a theme can see if this is a defined object and include the data, - * while the is_object() wrapper prevents whiping out existing data held in - * this instance variables, primarily the $files variable. - * - * Note: The package arguement is the subdirectory of js - all js should live in subdirectories - * - * @package phpgwapi - * @subpackage sessions - * @abstract - * @author Dave Hall - * @copyright © 2003 Free Software Foundation - * @license GPL - * @uses template - */ + // On production sites, remove this lines below! \\ + if (file_exists(EGW_INCLUDE_ROOT . '/uncompressed_thyapi')) define(EGW_UNCOMPRESSED_THYAPI, true); + else define(EGW_UNCOMPRESSED_THYAPI, false); + + $GLOBALS['concisus_system']['javascript'] = array(); + + /** + * eGroupWare javascript support class + * + * Only instanstiate this class using: + * + * if(!@is_object($GLOBALS['egw']->js)) + * { + * $GLOBALS['egw']->js =& CreateObject('phpgwapi.javascript'); + * } + * + * + * This way a theme can see if this is a defined object and include the data, + * while the is_object() wrapper prevents whiping out existing data held in + * this instance variables, primarily the $files variable. + * + * Note: The package argument is the subdirectory of js - all js should live in subdirectories + * + * @package phpgwapi + * @subpackage sessions + * @abstract + * @author Dave Hall + * @copyright © 2003 Free Software Foundation + * @license GPL + * @uses template + * + */ class javascript { /** @@ -55,6 +62,11 @@ * @var object used for holding an instance of the Template class */ var $t; + + /** + * @var boolean Load JS API ? + */ + var $js_api; /** * Constructor @@ -63,8 +75,9 @@ */ function javascript() { - //$this->t = CreateObject('phpgwapi.Template', ExecMethod('phpgwapi.phpgw.common.get_tpl_dir','phpgwapi')); + //$this->t =& CreateObject('phpgwapi.Template', ExecMethod('phpgwapi.phpgw.common.get_tpl_dir','phpgwapi')); //not currently used, but will be soon - I hope :) + $this->included_files =& $GLOBALS['concisus_system']['javascript']['included_files']; } @@ -76,7 +89,7 @@ */ function get_alert($msg) { - return 'return alert("'.lang($msg).'");'; + return 'return alert("'.lang($msg).'");'; } /** @@ -119,9 +132,12 @@ function get_script_links() { $links = ''; + $links_api = ''; + $links_api_main = ''; + if(!empty($this->files) && is_array($this->files)) { - $links = "\n"; + $links_head = "\n"; foreach($this->files as $app => $packages) { if(!empty($packages) && is_array($packages)) @@ -132,17 +148,58 @@ { foreach($files as $file => $browser) { - $links .= '\n"; + if ($app === 'phpgwapi' && $pkg === 'jsapi' && $file === 'jsapi') + { + $links_api_main = '\n"; + + if ($browser !== '.') + { + $links_api_main = '\n"; + } + } + else if ($app === 'phpgwapi') + { + if ($browser !== '.') + { + $links_api .= '\n"; + } + + $links_api .= '\n"; + } + else + { + if ($browser !== '.') + { + $links .= '\n"; + } + + $links .= '\n"; + } } } } } } } - return $links; + return $links_head.$links_api_main.$links_api.$links; } /** @@ -204,7 +261,7 @@ * @param string $file file to be included - no ".js" on the end * @param string $app application directory to search - default = phpgwapi * @param bool $browser insert specific browser javascript. - * + * * @discuss The browser specific option loads the file which is in the correct * browser folder. Supported folder are those supported by class.browser.inc.php * @@ -221,30 +278,196 @@ $browser_folder = '.'; } - if(is_readable(PHPGW_INCLUDE_ROOT .SEP .$app .SEP .'js' .SEP . $package . SEP . $browser_folder . SEP . $file . '.js')) + if ($this->included_files[$app][$package][$file]) return true; + + if(is_readable(EGW_INCLUDE_ROOT .SEP .$app .SEP .'js' .SEP . $package . SEP . $browser_folder . SEP . $file . '.js')) { $this->files[$app][$package][$file] = $browser_folder; + $this->included_files[$app][$package][$file] = $browser_folder; return True; } - elseif (is_readable(PHPGW_INCLUDE_ROOT .SEP .$app .SEP .'js' .SEP . $package . SEP . $file . '.js')) + elseif (is_readable(EGW_INCLUDE_ROOT .SEP .$app .SEP .'js' .SEP . $package . SEP . $file . '.js')) { $this->files[$app][$package][$file] = '.'; + $this->included_files[$app][$package][$file] = '.'; return True; } elseif($app != 'phpgwapi') { - if(is_readable(PHPGW_INCLUDE_ROOT .SEP .'phpgwapi' .SEP .'js' .SEP . $package .SEP . $browser_folder . SEP . $file . '.js')) + if(is_readable(EGW_INCLUDE_ROOT .SEP .'phpgwapi' .SEP .'js' .SEP . $package .SEP . $browser_folder . SEP . $file . '.js')) { $this->files['phpgwapi'][$package][$file] = $browser_folder; + $this->included_files['phpgwapi'][$package][$file] = $browser_folder; return True; } - elseif(is_readable(PHPGW_INCLUDE_ROOT .SEP .'phpgwapi' .SEP .'js' .SEP . $package .SEP . $file . '.js')) + elseif(is_readable(EGW_INCLUDE_ROOT .SEP .'phpgwapi' .SEP .'js' .SEP . $package .SEP . $file . '.js')) { $this->files['phpgwapi'][$package][$file] = '.'; + $this->included_files['phpgwapi'][$package][$file] = '.'; return True; } return False; } } + + function validate_jsapi() + { + if (EGW_UNCOMPRESSED_THYAPI) + { + $this->validate_file('plugins', 'thyPlugins'); + } + + /* This was included together with javascript globals to garantee prior load of dynapi. But it doesn't seems + * right to me... maybe on class common, it should load dynapi before everything... */ + $this->validate_file('dynapi','dynapi'); + + // Initialize DynAPI + $GLOBALS['egw_info']['flags']['java_script'] .= '\n";/**/ + + //FIXME: These files are temporary! They should be included inside DynAPI or substituted by + // other ones + $this->validate_file('jsapi', 'jsapi'); + $this->validate_file('wz_dragdrop', 'wz_dragdrop'); + $this->validate_file('dJSWin', 'dJSWin'); + $this->validate_file('dTabs', 'dTabs'); + $this->validate_file('connector', 'connector'); + $this->validate_file('xmlrpcMsgCreator','xmlrpc'); + $this->validate_file('jsolait','init'); + return true; + } + + function get_javascript_globals() + { + /* Default Global Messages */ + $GLOBALS['egw_info']['flags']['java_script_globals']['messages']['jsapi']['parseError'] = lang('Failed to Contact Server or Invalid Response from Server. Try to relogin. Contact Admin in case of faliure.'); + $GLOBALS['egw_info']['flags']['java_script_globals']['messages']['jsapi']['serverTimeout'] = lang('Could not contact server. Operation Timed Out!'); + $GLOBALS['egw_info']['flags']['java_script_globals']['messages']['jsapi']['dataSourceStartup'] = lang('Starting Up...'); + + $GLOBALS['egw_info']['flags']['java_script_globals']['messages']['jsapi']['connector_1'] = lang('Contacting Server...'); + $GLOBALS['egw_info']['flags']['java_script_globals']['messages']['jsapi']['connector_2'] = lang('Server Contacted. Waiting for response...'); + $GLOBALS['egw_info']['flags']['java_script_globals']['messages']['jsapi']['connector_3'] = lang('Server answered. Processing response...'); + $GLOBALS['egw_info']['flags']['java_script_globals']['preferences']['common'] =& $GLOBALS['egw_info']['user']['preferences']['common']; + + /* Default Global API Variables */ + $browser = strtolower(ExecMethod('phpgwapi.browser.get_agent')); + switch ($browser) + { + case 'ie': + case 'opera': + $thyapi_comp = 'thyapi_comp_'.$browser.'.js'; + break; + default: + $thyapi_comp = 'thyapi_comp_gecko.js'; + } + + $GLOBALS['egw_info']['flags']['java_script_globals']['jsapi']['imgDir'] = $GLOBALS['egw_info']['server']['webserver_url'].'/'.'phpgwapi/images'; + if (EGW_UNCOMPRESSED_THYAPI) + { + $jsCode = "\n" . + ''."\n". + ''."\n". + ''."\n"; + + return $jsCode; + } + + function convert_phparray_jsarray($name, $array, $new=true) + { + if (!is_array($array)) + { + return ''; + } + + if ($new) + { + $jsCode = "$name = new Object();\n"; + } + else + { + $jsCode = ''; + } + + foreach ($array as $index => $value) + { + if (is_array($value)) + { + $jsCode .= $name."['".$index."'] = new Object();\n"; + $jsCode .= $this->convert_phparray_jsarray($name."['".$index."']", $value,false); + continue; + } + + switch(gettype($value)) + { + case 'string': + $value = "'".str_replace(array("\n","\r"),'\n',addslashes($value))."'"; + break; + + case 'boolean': + if ($value) + { + $value = 'true'; + } + else + { + $value = 'false'; + } + break; + + default: + $value = 'null'; + } + + $jsCode .= $name."['".$index."'] = ".$value.";\n"; + } + + return $jsCode; + } } ?> diff --git a/phpgwapi/inc/class.vfs_sql2.inc.php b/phpgwapi/inc/class.vfs_sql2.inc.php index 06c3fafc46..e3b366e0f0 100644 --- a/phpgwapi/inc/class.vfs_sql2.inc.php +++ b/phpgwapi/inc/class.vfs_sql2.inc.php @@ -67,9 +67,9 @@ 'file_id' => 'phpgw_vfs2_files', 'owner_id' => 'phpgw_vfs2_files', 'createdby_id' => 'phpgw_vfs2_files', - 'modifiedby_id' => 'phpgw_vfs2_versioning', + 'modifiedby_id' => 'phpgw_vfs2_files', 'created' => 'phpgw_vfs2_files', - 'modified' => 'phpgw_vfs2_versioning', + 'modified' => 'phpgw_vfs2_files', 'size' => 'phpgw_vfs2_files', 'mime_type' => 'phpgw_vfs2_mimetypes', 'comment' => 'phpgw_vfs2_files', @@ -90,6 +90,9 @@ //searching in files for a particular value in a particular property. var $search_support = 1; + var $compress_support = 1; + var $extract_support = 1; + /*! @function vfs @@ -1088,12 +1091,6 @@ */ function mv ($data) { - //FIXME unknown bug tricky solving (temp) - if (!is_object($this->vfs_versionsystem)) - { - $this->vfs_versionsystem =& $GLOBALS['object_keeper']->GetObject('phpgwapi.vfs_versionsystem'); - } - if (!is_array ($data)) { $data = array (); @@ -1251,6 +1248,7 @@ 'relatives' => array ($t->mask) ) ); + $query = $GLOBALS['phpgw']->db->query ("UPDATE phpgw_vfs2_files SET size=$size WHERE directory='". $GLOBALS['phpgw']->db->db_addslashes($t->fake_leading_dirs_clean)."' AND name='". $GLOBALS['phpgw']->db->db_addslashes($t->fake_name_clean)."'", __LINE__, __FILE__); @@ -1269,7 +1267,7 @@ /* $this->set_attributes(array( 'string' => $t->fake_full_path, 'relatives' => array ($t->mask), - 'attributes' => array ( + 'attributes' => array ( 'modifiedby_id' => $account_id, 'modified' => $this->now ) @@ -1284,7 +1282,31 @@ if ($this->file_actions) { - $rr = rename ($f->real_full_path, $t->real_full_path); + if(file_exists($t->real_full_path)) + { + unlink($t->real_full_path); + $ok = rename($f->real_full_path, $t->real_full_path); + } + else + { + $ok = rename($f->real_full_path, $t->real_full_path); + } + + if (!$ok) + { + return false; + } + + if (is_dir($t->real_full_path) && $f->outside) + { + $this->update_real(array( + 'string' => $t->fake_full_path, + 'relatives' => array($t->mask) + )); + + } + + //$rr = rename ($f->real_full_path, $t->real_full_path); } /* @@ -1295,7 +1317,7 @@ { $this->rm (array( 'string' => $f->fake_full_path, - 'relatives' => $f->mask + 'relatives' => array($f->mask) ) ); } @@ -1578,6 +1600,10 @@ { if (!@is_dir($p->real_leading_dirs_clean)) // eg. /home or /group does not exist { + if (!ereg_replace('^/','',$p->fake_leading_dirs)) + { + return false; + } if (!@$this->mkdir(array( 'string' => $p->fake_leading_dirs, 'relatives' => array(RELATIVE_NONE) ))) // ==> create it @@ -1588,7 +1614,9 @@ if (@is_dir($p->real_full_path)) // directory already exists { - $this->update_real($data,True); // update its contents + //WITH Serious BUG when registrys are in database, but not + //in filesystem. Correct this ASAP. + //$this->update_real($data,True); // update its contents } elseif (!@mkdir ($p->real_full_path, 0770)) { @@ -2297,7 +2325,8 @@ 'orderby' => 'directory,name', 'backups' => false, /* show or hide backups */ 'files_specified' => array(), - 'allow_outside' => true + 'allow_outside' => true, + 'modifiedby_information' => false ); //check if orderby is a valid field (or is composed by valid fields) @@ -2367,6 +2396,7 @@ $sql .= "directory='".$GLOBALS['phpgw']->db->db_addslashes($p->fake_leading_dirs_clean). "' AND name='".$GLOBALS['phpgw']->db->db_addslashes($p->fake_name_clean)."'".$sql_backups; } + // echo " select1: dir=".$p->fake_leading_dirs_clean." name=".$p->fake_name_clean."
\n"; $query = $GLOBALS['phpgw']->db->query ($sql, __LINE__, __FILE__); @@ -2382,37 +2412,44 @@ $rarray = array (); foreach($this->attributes as $attribute) { - if ($attribute == 'mime_type') + switch ($attribute) { - if (!is_numeric($record['mime_id'])) - { - //no mime type registered for file, must find one and if not exist add one. - $extension = $this->get_file_extension($record['name']); - if (!$res = $this->vfs_mimetypes->get_type(array('extension' => $extension))) + case 'mime_type': + if (!is_numeric($record['mime_id'])) { - $res = $this->vfs_mimetypes->add_filetype(array('extension' => $extension)); + //no mime type registered for file, must find one and if not exist add one. + $extension = $this->get_file_extension($record['name']); + + if (!$res = $this->vfs_mimetypes->get_type(array('extension' => $extension))) + { + $res = $this->vfs_mimetypes->add_filetype(array('extension' => $extension)); + } + + if ($res) + { + $this->db->update('phpgw_vfs2_files', + array('mime_id' => $res['mime_id']), + array('directory' => $p->fake_leading_dirs_clean, + 'name' => $p->fake_name_clean + ),__LINE__,__FILE__); + + } + } + else + { + $res = $this->vfs_mimetypes->get_type(array( + 'mime_id' => $record['mime_id'] + )); } - if ($res) - { - $this->db->update('phpgw_vfs2_files', - array('mime_id' => $res['mime_id']), - array('directory' => $p->fake_leading_dirs_clean, - 'name' => $p->fake_name_clean - ),__LINE__,__FILE__); - - } - } - else - { - $res = $this->vfs_mimetypes->get_type(array( - 'mime_id' => $record['mime_id'] - )); - } - - $record['mime_type'] = $res['mime']; - $record['mime_friendly'] = $res['friendly']; + $record['mime_type'] = $res['mime']; + $record['mime_friendly'] = $res['friendly']; + break; + case 'created': + case 'modified': + $record[$attribute] = $this->db->from_timestamp($record[$attribute]); + break; } @@ -2589,38 +2626,44 @@ foreach($this->attributes as $attribute) { - if ($attribute == 'mime_type') + switch($attribute) { - if (!is_numeric($record['mime_id'])) - { - $extension = $this->get_file_extension($record['name']); - if(!$res = $this->vfs_mimetypes->get_type(array( - 'extension' => $extension)) ) + case 'mime_type': + if (!is_numeric($record['mime_id'])) { - $res = $this->vfs_mimetypes->add_filetype(array( - 'extension' - )); - - if ($res) + $extension = $this->get_file_extension($record['name']); + if(!$res = $this->vfs_mimetypes->get_type(array( + 'extension' => $extension)) ) { - $this->db->update('phpgw_vfs2_files', - array('mime_id' => $res['mime_id']), - array('directory' => $p->fake_leading_dirs_clean, - 'name' => $p->fake_name_clean - ),__LINE__,__FILE__); + $res = $this->vfs_mimetypes->add_filetype(array( + 'extension' + )); + + if ($res) + { + $this->db->update('phpgw_vfs2_files', + array('mime_id' => $res['mime_id']), + array('directory' => $p->fake_leading_dirs_clean, + 'name' => $p->fake_name_clean + ),__LINE__,__FILE__); + } + } - } - } - else - { - $res = $this->vfs_mimetypes->get_type(array( - 'mime_id' => $record['mime_id'] - )); - } + else + { + $res = $this->vfs_mimetypes->get_type(array( + 'mime_id' => $record['mime_id'] + )); + } - $record['mime_type'] = $res['mime']; - $rarray[$i]['mime_friendly'] = $res['friendly']; + $record['mime_type'] = $res['mime']; + $rarray[$i]['mime_friendly'] = $res['friendly']; + break; + case 'created': + case 'modified': + $record[$attribute] = $this->db->from_timestamp($record[$attribute]); + break; } $rarray[$i][$attribute] = $record[$attribute]; @@ -2650,7 +2693,7 @@ //FIXME this method does not work when there are registrys in //database, but not in filesystem. It starts corromping the //database by putting wrong things that are in the partition root. - return false; + //return false; if (!is_array ($data)) { $data = array (); @@ -3351,9 +3394,7 @@ foreach ($filelist as $file) { - - - $this->mv(array( + $res = $this->mv(array( 'from' => $file['directory'].'/'.$file['name'], 'to' => $dest->fake_full_path.'/'.$file['name'], 'relatives' => array(RELATIVE_NONE|VFS_REAL,$dest->mask) diff --git a/phpgwapi/inc/class.vfs_versionsystem.inc.php b/phpgwapi/inc/class.vfs_versionsystem.inc.php index 2ef0dbdc43..a36ce15fea 100644 --- a/phpgwapi/inc/class.vfs_versionsystem.inc.php +++ b/phpgwapi/inc/class.vfs_versionsystem.inc.php @@ -156,6 +156,13 @@ $res = $this->db->insert('phpgw_vfs2_versioning',$insert_data,null, __LINE__,__FILE__); + +/* $this->db->update('phpgw_vfs2_files',array( + 'modified' => $insert_data['modified'], + 'modifiedby_id' => $insert_data['modifiedby_id'] + ), + array('file_id' => $insert_data['file_id']).__LINE__,__FILE__ + );*/ @@ -188,7 +195,6 @@ */ function save_snapshot($file_id,$operation,$comment='',$other='') { - //Prevent recursive reentrant when working in vfs->copy, f.inst if ($GLOBALS['phpgw']->banish_journal) { @@ -467,7 +473,11 @@ { $this->db->update('phpgw_vfs2_files', - array('version' => $this->inc($file_data['insert_data']['version'])), + array( + 'version' => $this->inc($file_data['insert_data']['version']), + 'modified' => $file_data['insert_data']['modified'], + 'modifiedby_id' => $file_data['insert_data']['modifiedby_id'] + ), array('file_id' => $file_data['insert_data']['file_id']), __LINE__, __FILE__ ); diff --git a/phpgwapi/js/dJSWin/dJSWin.js b/phpgwapi/js/dJSWin/dJSWin.js index 0498c7295e..24813efc1d 100644 --- a/phpgwapi/js/dJSWin/dJSWin.js +++ b/phpgwapi/js/dJSWin/dJSWin.js @@ -9,42 +9,18 @@ * Free Software Foundation; either version 2 of the License, or (at your * * option) any later version. * \****************************************************************************/ - -if (!window.__DEFINE_DJSWIN__) -{ - __DEFINE_DJSWIN__ = true; - if (document.all) - { - navigator.userAgent.toLowerCase().indexOf('msie 5') != -1 ? is_ie5 = true : is_ie5 = false; - is_ie = true; - is_moz1_6 = false; - is_mozilla = false; - is_ns4 = false; - } - else if (document.getElementById) - { - navigator.userAgent.toLowerCase().match('mozilla.*rv[:]1\.6.*gecko') ? is_moz1_6 = true : is_moz1_6 = false; - is_ie = false; - is_ie5 = false; - is_mozilla = true; - is_ns4 = false; - } - else if (document.layers) - { - is_ie = false; - is_ie5 = false - is_moz1_6 = false; - is_mozilla = false; - is_ns4 = true; - } - if (!window.dd) { throw("wz_dragdrop lib must be loaded!"); } function dJSWin(params) + { + this.init(params); + } + + dJSWin.prototype.init = function(params) { if (!params || typeof(params) != 'object' || !params.id || !params.width || !params.height || !params.content_id) { @@ -52,19 +28,22 @@ if (!window.__DEFINE_DJSWIN__) } /* Internal Variables */ - if (is_ie) - { - this.winContainer = document.createElement('iframe'); - } this.clientArea = document.createElement('div'); this.title = document.createElement('div'); this.title_text = document.createElement('span'); this.buttons = new Array(); this.shadows = new Array(); this.border = new Array(); - this.content = _dJSWinElement(params.content_id); + this.content = Element(params.content_id); this.includedContents = params['include_contents']; var _this = this; + var style; + + style = document.createElement('link'); + style.href = GLOBALS['serverRoot'] + "phpgwapi/js/dJSWin/dJSWin.css"; + style.rel = "stylesheet"; + style.type = "text/css"; + document.body.appendChild(style); if (is_moz1_6) { @@ -103,36 +82,18 @@ if (!window.__DEFINE_DJSWIN__) this.title.style.visibility = 'hidden'; this.title.style.width = parseInt(params['width']) + 2 + 'px'; this.title.style.height = params['title_height'] ? params['title_height'] : '18px'; - this.title.style.backgroundColor = params['title_color']; + this.title.style.backgroundColor = '#3978d6'; + // this.title.className = 'dJSWin_title'; this.title.style.top = '0px'; this.title.style.left = '0px'; this.title.style.zIndex = '1'; this.title_text.style.position = 'relative'; - this.title_text.className = params['title_class']; - this.title_text.style.fontWeight = 'bold'; - this.title_text.style.color = params['title_text_color'] ? params['title_text_color'] : 'black'; + this.title_text.className = 'dJSWin_title_text'; // this.title_text.style.cursor = 'move'; this.title_text.innerHTML = params['title']; this.title_text.style.zIndex = '1'; - if (is_ie) - { - this.winContainer.id = params['id']+'_winContainer'; - this.winContainer.style.position = 'absolute'; - this.winContainer.style.visibility = 'hidden'; - this.winContainer.style.width = params['width']; - //this.winContainer.style.height = parseInt(params['height']) + parseInt(this.title.style.height) + 'px'; - this.winContainer.style.height = params['height']; - // this.winContainer.style.top = '0px'; - this.winContainer.style.top = this.title.style.height; - this.winContainer.style.left = '0px'; - this.winContainer.style.zIndex = '-1'; - // this.winContainer.style.backgroundColor = params['bg_color']; - // this.winContainer.className = params['win_class']; - this.winContainer.src = ''; - } - this.clientArea.id = params['id']+'_clientArea'; this.clientArea.style.position = 'absolute'; this.clientArea.style.visibility = 'hidden'; @@ -140,9 +101,9 @@ if (!window.__DEFINE_DJSWIN__) this.clientArea.style.height = params['height']; this.clientArea.style.top = parseInt(this.title.style.height) + 'px'; this.clientArea.style.left = '0px'; - this.clientArea.style.backgroundColor = params['bg_color']; + // this.clientArea.style.backgroundColor = params['bg_color']; // this.clientArea.style.overflow = 'auto'; - this.clientArea.className = params['win_class']; + this.clientArea.className = 'dJSWin_main'; this.buttons.xDIV.id = params['id']+'_button'; this.buttons.xDIV.style.position = 'absolute'; @@ -191,16 +152,11 @@ if (!window.__DEFINE_DJSWIN__) this.border.l.style.visibility = 'hidden'; this.border.r.style.visibility = 'hidden'; - this.border.t.style.backgroundColor = params['title_color']; - this.border.b.style.backgroundColor = params['title_color']; - this.border.l.style.backgroundColor = params['title_color']; - this.border.r.style.backgroundColor = params['title_color']; + this.border.t.className = 'dJSWin_title'; + this.border.b.className = 'dJSWin_title'; + this.border.l.className = 'dJSWin_title'; + this.border.r.className = 'dJSWin_title'; - this.border.t.className = params['title_class']; - this.border.b.className = params['title_class']; - this.border.l.className = params['title_class']; - this.border.r.className = params['title_class']; - this.border.t.style.border = '0px'; this.border.b.style.border = '0px'; this.border.l.style.border = '0px'; @@ -213,54 +169,25 @@ if (!window.__DEFINE_DJSWIN__) this.border.l.style.width = '2px'; this.border.r.style.width = '2px'; - if (is_ie) - { - this.border.t.style.height = '0'; - this.border.b.style.height = '0'; - this.border.l.style.height = parseInt(params['height']) + parseInt(this.title.style.height) + 2 + 'px'; - this.border.r.style.height = parseInt(params['height']) + parseInt(this.title.style.height) + 2 + 'px'; + this.border.t.style.height = '2px'; + this.border.b.style.height = '2px'; + this.border.l.style.height = parseInt(params['height']) + parseInt(this.title.style.height) + 4 + 'px'; + this.border.r.style.height = parseInt(params['height']) + parseInt(this.title.style.height) + 4 + 'px'; - this.border.t.style.top = '0'; - this.border.b.style.top = parseInt(params['height']) + parseInt(this.title.style.height) + 'px'; - this.border.l.style.top = '0'; - this.border.r.style.top = '0'; - } - else - { - this.border.t.style.height = '2px'; - this.border.b.style.height = '2px'; - this.border.l.style.height = parseInt(params['height']) + parseInt(this.title.style.height) + 4 + 'px'; - this.border.r.style.height = parseInt(params['height']) + parseInt(this.title.style.height) + 4 + 'px'; - - this.border.t.style.top = '-2px'; - this.border.b.style.top = parseInt(params['height']) + parseInt(this.title.style.height) + 'px'; - this.border.l.style.top = '-2px'; - this.border.r.style.top = '-2px'; - } + this.border.t.style.top = '-2px'; + this.border.b.style.top = parseInt(params['height']) + parseInt(this.title.style.height) + 'px'; + this.border.l.style.top = '-2px'; + this.border.r.style.top = '-2px'; this.border.t.style.left = '-2px'; this.border.b.style.left = '-2px'; this.border.l.style.left = '-2px'; this.border.r.style.left = params['width']; - //this.winContainer.style.width = parseInt(this.winContainer.style.width) + 4 + 'px'; - //this.winContainer.style.height = parseInt(this.winContainer.style.height) + 4 + 'px'; - //this.clientArea.style.width = parseInt(this.clientArea.style.width) + 4 + 'px'; - //this.clientArea.style.height = parseInt(this.clientArea.style.height) + 4 + 'px'; - - //this.title.style.top = parseInt(this.title.style.top) + 2 + 'px'; - //this.clientArea.style.top = parseInt(this.clientArea.style.top) + 2 + 'px'; - //this.buttons.xDIV.style.top = parseInt(this.buttons.xDIV.style.top) + 2 + 'px'; this.shadows.b.style.top = parseInt(this.shadows.b.style.top) + 2 + 'px'; this.shadows.r.style.top = parseInt(this.shadows.r.style.top) + 2 + 'px'; - //this.content.style.top = parseInt(this.content.style.top) + 2 + 'px'; - - //this.title.style.left = parseInt(this.title.style.left) + 2 + 'px'; - //this.clientArea.style.left = parseInt(this.clientArea.style.left) + 2 + 'px'; - //this.buttons.xDIV.style.left = parseInt(this.buttons.xDIV.style.left) + 2 + 'px'; this.shadows.b.style.left = parseInt(this.shadows.b.style.left) + 2 + 'px'; this.shadows.r.style.left = parseInt(this.shadows.r.style.left) + 2 + 'px'; - //this.content.style.left = '2px'; } else { @@ -274,28 +201,11 @@ if (!window.__DEFINE_DJSWIN__) { this.content.parentNode.removeChild(this.content); } -/* - this.winContainer.appendChild(this.title); - this.winContainer.appendChild(this.buttons.xDIV); - this.winContainer.appendChild(this.border.t); - this.winContainer.appendChild(this.border.b); - this.winContainer.appendChild(this.border.l); - this.winContainer.appendChild(this.border.r); - this.winContainer.appendChild(this.shadows.r); - this.winContainer.appendChild(this.shadows.b); - this.winContainer.appendChild(this.content); -*/ - if (is_ie) - { - this.title.appendChild(this.winContainer); - } - else - { - this.title.appendChild(this.border.t); - } + this.title.appendChild(this.title_text); this.title.appendChild(this.clientArea); this.title.appendChild(this.buttons.xDIV); + this.title.appendChild(this.border.t); this.title.appendChild(this.border.b); this.title.appendChild(this.border.l); this.title.appendChild(this.border.r); @@ -304,7 +214,7 @@ if (!window.__DEFINE_DJSWIN__) this.clientArea.appendChild(this.content); document.body.appendChild(this.title); - + this.draw(); } dJSWin.prototype.close = function() @@ -314,27 +224,8 @@ if (!window.__DEFINE_DJSWIN__) dJSWin.prototype.open = function() { -/* if (is_ie) - { - this.moveTo(document.body.offsetWidth/2 - dd.elements[this.title.id].w/2, - document.body.offsetHeight/2 - dd.elements[this.winContainer.id].h/2); - } - else - { - this.moveTo(window.innerWidth/2 - dd.elements[this.title.id].w/2, - window.innerHeight/2 - dd.elements[this.clientArea.id].h/2); - } -*/ - if (is_ie) - { - this.moveTo(document.body.offsetWidth/2 + document.body.scrollLeft - dd.elements[this.title.id].w/2, - document.body.offsetHeight/2 + document.body.scrollTop - dd.elements[this.winContainer.id].h/2); - } - else - { - this.moveTo(window.innerWidth/2 + window.pageXOffset - dd.elements[this.title.id].w/2, - window.innerHeight/2 + window.pageYOffset - dd.elements[this.clientArea.id].h/2); - } + this.moveTo(window.innerWidth/2 + window.pageXOffset - dd.elements[this.title.id].w/2, + window.innerHeight/2 + window.pageYOffset - dd.elements[this.clientArea.id].h/2); dd.elements[this.title.id].maximizeZ(); dd.elements[this.title.id].show(); @@ -372,34 +263,25 @@ if (!window.__DEFINE_DJSWIN__) return; } - if (is_ie) + if (this.drawn) { - ADD_DHTML(this.winContainer.id+NO_DRAG); - } - else - { - ADD_DHTML(this.border.t.id+NO_DRAG); + return; } + + this.drawn = true; + ADD_DHTML(this.title.id+CURSOR_MOVE); ADD_DHTML(this.clientArea.id+NO_DRAG); ADD_DHTML(this.buttons.xDIV.id+NO_DRAG); ADD_DHTML(this.content.id+NO_DRAG); ADD_DHTML(this.shadows.r.id+NO_DRAG); ADD_DHTML(this.shadows.b.id+NO_DRAG); + ADD_DHTML(this.border.t.id+NO_DRAG); ADD_DHTML(this.border.b.id+NO_DRAG); ADD_DHTML(this.border.l.id+NO_DRAG); ADD_DHTML(this.border.r.id+NO_DRAG); - if (is_ie) - { - dd.elements[this.title.id].addChild(dd.elements[this.winContainer.id]); - } - else - { - dd.elements[this.title.id].addChild(dd.elements[this.border.t.id]); - } - //dd.elements[this.title.id].setZ(dd.elements[this.border.t.id].z+1); - //dd.elements[this.title.id].maximizeZ(); + dd.elements[this.title.id].addChild(dd.elements[this.border.t.id]); dd.elements[this.title.id].addChild(dd.elements[this.clientArea.id]); dd.elements[this.title.id].addChild(dd.elements[this.buttons.xDIV.id]); @@ -420,36 +302,10 @@ if (!window.__DEFINE_DJSWIN__) } } -// dd.elements[this.title.id].setZ('-1'); - - if (is_ie) - { - dd.elements[this.title.id].moveTo(document.body.offsetWidth/2 - dd.elements[this.winContainer.id].w/2, - document.body.offsetHeight/2 - dd.elements[this.winContainer.id].h/2) - } - else - { - dd.elements[this.title.id].moveTo(window.innerWidth/2 - dd.elements[this.clientArea.id].w/2, - window.innerHeight/2 - dd.elements[this.clientArea.id].h/2); - } - dd.elements[this.title.id].hide(); - } - - function _dJSWinElement(id) - { - if (document.getElementById) - { - return document.getElementById(id); - } - else if (document.all) - { - return document.all[id]; - } - else - { - throw("Browser Not Supported!"); - } + dd.elements[this.title.id].moveTo(window.innerWidth/2 - dd.elements[this.clientArea.id].w/2, + window.innerHeight/2 - dd.elements[this.clientArea.id].h/2); + } if (!dd.elements) @@ -460,4 +316,3 @@ if (!window.__DEFINE_DJSWIN__) SET_DHTML(div.id); } -} diff --git a/phpgwapi/js/dTabs/dTabs.js b/phpgwapi/js/dTabs/dTabs.js index cdc5729037..77ba15bbb2 100644 --- a/phpgwapi/js/dTabs/dTabs.js +++ b/phpgwapi/js/dTabs/dTabs.js @@ -21,61 +21,19 @@ * 'selectedClass': , * 'unselectedClass': }); */ - - if (document.all) - { - navigator.userAgent.toLowerCase().indexOf('msie 5') != -1 ? is_ie5 = true : is_ie5 = false; - is_ie = true; - is_moz1_6 = false; - is_mozilla = false; - is_ns4 = false; - } - else if (document.getElementById) - { - navigator.userAgent.toLowerCase().match('mozilla.*rv[:]1\.6.*gecko') ? is_moz1_6 = true : is_moz1_6 = false; - is_ie = false; - is_ie5 = false; - is_mozilla = true; - is_ns4 = false; - } - else if (document.layers) - { - is_ie = false; - is_ie5 = false - is_moz1_6 = false; - is_mozilla = false; - is_ns4 = true; - } - - /* The code below is a wrapper to call the dTabs content insertion - * just after the document is loaded in IE... I still don't know why people - * use this crap! + + /* Mozilla 1.6 has a bug which impossibilitates it to manage DOM contents that are defined in the + * original document. So we have to workaround it. */ - var _dTabs_onload; - - if (document.all) - { - _dTabs_onload = false; - - var _dTabs_onload_f = document.body.onload; - var _dTabs_f = function(e) - { - _dTabs_onload = true; - - if (_dTabs_onload_f) - { - _dTabs_onload_f(); - } - }; - - document.body.onload = _dTabs_f; - } - else - { - _dTabs_onload = true; - } - + navigator.userAgent.toLowerCase().match('mozilla.*rv[:]1\.6.*gecko') ? is_moz1_6 = true : is_moz1_6 = false; + function dTabsManager(params) + { + this._tabEvents = { show: {}, hide: {}}; + this.init(params); + } + + dTabsManager.prototype.init = function(params) { /* Attributes definition */ this._Tabs = new Array(); @@ -91,7 +49,13 @@ /* Create and insert the container */ - var table, tbody, tr, td; + var table, tbody, tr, td, style; + + style = document.createElement('link'); + style.href = GLOBALS['serverRoot'] + "phpgwapi/js/dTabs/dTabs.css"; + style.rel = "stylesheet"; + style.type = "text/css"; + document.body.appendChild(style); this._Tabs['root'] = document.createElement('div'); this._Tabs['root'].id = params['id']; @@ -106,11 +70,18 @@ table.style.border = '0px solid black'; table.style.width = '100%'; table.style.height = '100%'; + table.cellpadding = '10px'; this._Tabs['tabIndexTR'] = document.createElement('tr'); this._Tabs['tabIndexTR'].style.height = '30px'; + this._Tabs['tabIndexTR'].className = 'dTabs_tr_index'; //this._Tabs['tabIndexTR'].style.width = '100%'; + this._Tabs['emptyTab'] = document.createElement('td'); + this._Tabs['emptyTab'].className = 'dTabs_noTabs'; + this._Tabs['emptyTab'].innerHTML = ' '; + this._Tabs['tabIndexTR'].appendChild(this._Tabs['emptyTab']); + tr = document.createElement('tr'); td = document.createElement('td'); @@ -131,29 +102,7 @@ this._Tabs['contents'] = new Array(); - if (!_dTabs_onload) - { - if (document.getElementById && !document.all) - { - document.body.appendChild(this._Tabs['root']); - } - else - { - var _this = this; - var now = document.body.onload ? document.body.onload : null; - var f = function(e) - { - document.body.appendChild(_this._Tabs['root']); - now ? now() : false; - }; - - document.body.onload = f; - } - } - else - { - document.body.appendChild(this._Tabs['root']); - } + document.body.appendChild(this._Tabs['root']); } /* @@ -167,9 +116,9 @@ return false; } - if (!params['id'] || !_dtElement(params['id']) || - _dtElement(params['id']).tagName.toLowerCase() != 'div' || - _dtElement(params['id']).style.position.toLowerCase() != 'absolute') + if (!params['id'] || !Element(params['id']) || + Element(params['id']).tagName.toLowerCase() != 'div' || + Element(params['id']).style.position.toLowerCase() != 'absolute') { return false; } @@ -181,7 +130,7 @@ } //var contents, tdIndex; - var element = _dtElement(params['id']); + var element = Element(params['id']); if (is_moz1_6) {/* @@ -205,23 +154,23 @@ this._Tabs.tabIndexTDs[params['id']] = document.createElement('td'); var _this = this; + this._Tabs.tabIndexTDs[params['id']].innerHTML = '  '+(params['name'] ? params['name'] : 'undefined')+'  '; + this._Tabs.tabIndexTDs[params['id']].selectedClassName = 'dTabs_selected'; + this._Tabs.tabIndexTDs[params['id']].unselectedClassName = 'dTabs_unselected'; + this._Tabs.tabIndexTDs[params['id']].className = 'dTabs_unselected'; + this._Tabs.tabIndexTDs[params['id']].onclick = function() {_this._showTab(params['id']);}; + + /* Old Version this._Tabs.tabIndexTDs[params['id']].innerHTML = params['name'] ? params['name'] : 'undefined'; this._Tabs.tabIndexTDs[params['id']].selectedClassName = params['selectedClass']; this._Tabs.tabIndexTDs[params['id']].unselectedClassName = params['unselectedClass']; this._Tabs.tabIndexTDs[params['id']].className = params['unselectedClass']; this._Tabs.tabIndexTDs[params['id']].onclick = function() {_this._showTab(params['id']);}; + */ - for (var i in this._Tabs.tabIndexTDs) - { - if (i == 'length') - { - return; - } - - this._Tabs.tabIndexTDs[i].style.width = (100/(this._nTabs+1)) + '%'; - } - + this._Tabs.tabIndexTR.removeChild(this._Tabs['emptyTab']); this._Tabs.tabIndexTR.appendChild(this._Tabs.tabIndexTDs[params['id']]); + this._Tabs.tabIndexTR.appendChild(this._Tabs['emptyTab']); if (!is_moz1_6) { @@ -258,6 +207,25 @@ return this._Tabs.root; } + dTabsManager.prototype.enableTab = function(id) + { + if (this._Tabs.contents[id]) + { + var _this = this; + this._Tabs.tabIndexTDs[id].className = 'dTabs_unselected'; + this._Tabs.tabIndexTDs[id].onclick = function() {_this._showTab(id);}; + } + } + + dTabsManager.prototype.disableTab = function(id) + { + if (this._Tabs.contents[id]) + { + this._Tabs.tabIndexTDs[id].className = 'dTabs_disabled'; + this._Tabs.tabIndexTDs[id].onclick = false; + } + } + /****************************************************************************\ * Private Methods * \****************************************************************************/ @@ -290,20 +258,25 @@ continue; } - this._Tabs.contents[i].style.visibility = 'hidden'; - this._Tabs.contents[i].style.zIndex = '-1'; + //viniciuscb: mystery + if (this._Tabs.contents[i].style != null) + { + this._Tabs.contents[i].style.visibility = 'hidden'; + this._Tabs.contents[i].style.display = 'none'; + this._Tabs.contents[i].style.zIndex = '-1'; + if (this._tabEvents['hide'][i]) this._tabEvents['hide'][i](); + } } this._Tabs.contents[id].style.visibility = 'visible'; + this._Tabs.contents[id].style.display = ''; this._Tabs.contents[id].style.zIndex = '10'; + if (this._tabEvents['show'][id]) this._tabEvents['show'][id](); if (this._selectedIndex && this._selectedIndex != id) { this._Tabs.tabIndexTDs[this._selectedIndex].className = this._Tabs.tabIndexTDs[this._selectedIndex].unselectedClassName; - if (!document.all) - { - this._focus(this._Tabs.contents[id]); - } + this._focus(this._Tabs.contents[id]); } this._selectedIndex = id; @@ -327,19 +300,3 @@ } } } - - function _dtElement(id) - { - if (document.getElementById) - { - return document.getElementById(id); - } - else if (document.all) - { - return document.all[id]; - } - else - { - throw('Browser not supported'); - } - } diff --git a/phpgwapi/js/htmlarea/examples/core.html b/phpgwapi/js/htmlarea/examples/core.html index 536be0e954..1fc5022501 100755 --- a/phpgwapi/js/htmlarea/examples/core.html +++ b/phpgwapi/js/htmlarea/examples/core.html @@ -12,6 +12,7 @@ _editor_lang = "en"; + \n"; - html += "\n"; - html += "\n"; - html += editor._textArea.value; - html += "\n"; - html += ""; - doc.write(html); - doc.close(); - } else { - var html = editor._textArea.value; - if (html.match(HTMLArea.RE_doctype)) { - editor.setDoctype(RegExp.$1); - html = html.replace(HTMLArea.RE_doctype, ""); - } - doc.open(); - doc.write(html); - doc.close(); - } + // Calculate the starting size, EXCLUDING THE TOOLBAR & STATUS BAR (always) + var height = null; + var width = null; - if (HTMLArea.is_ie) { - // enable editable mode for IE. For some reason this - // doesn't work if done in the same place as for Gecko - // (above). - doc.body.contentEditable = true; - } + switch(this.config.height) + { + // "auto" means the same height as the original textarea + case 'auto' : { height = parseInt(this._ta_size.h); break; } + // otherwise we expect it to be a PIXEL height + default : { height = parseInt(this.config.height); break; } + } - editor.focusEditor(); - // intercept some events; for updating the toolbar & keyboard handlers - HTMLArea._addEvents - (doc, ["keydown", "keypress", "mousedown", "mouseup", "drag"], - function (event) { - return editor._editorEvent(HTMLArea.is_ie ? editor._iframe.contentWindow.event : event); - }); + switch(this.config.width) + { + // toolbar means the width is the same as the toolbar + case 'toolbar': {width = parseInt(this._toolbar.offsetWidth); break; } + // auto means the same as the textarea + case 'auto' : {width = parseInt(this._ta_size.w); break; } + // otherwise it is expected to be a PIXEL width + default : {width = parseInt(this.config.width); break; } + } - // check if any plugins have registered refresh handlers - for (var i in editor.plugins) { - var plugin = editor.plugins[i].instance; - if (typeof plugin.onGenerate == "function") - plugin.onGenerate(); - if (typeof plugin.onGenerateOnce == "function") { - plugin.onGenerateOnce(); - plugin.onGenerateOnce = null; - } - } + if (this.config.sizeIncludesToolbar) + { + // substract toolbar height + height -= this._toolbar.offsetHeight; + height -= this._statusBar.offsetHeight; + } - setTimeout(function() { - editor.updateToolbar(); - }, 250); + // Minimal size = 100x100 + width = Math.max(width, 100); + height = Math.max(height,100); - if (typeof editor.onGenerate == "function") - editor.onGenerate(); - }; - setTimeout(initIframe, 100); + this.setInnerSize(width,height); + this.notifyOn('panel_change',function(){editor.setInnerSize();}); + + + // IMPORTANT: we have to allow Mozilla a short time to recognize the + // new frame. Otherwise we get a stupid exception. + + setTimeout(function() { editor.initIframe()}, 50); }; + /** Size the htmlArea according to the available space + * Width and Height include toolbar! + **/ + + HTMLArea.prototype.getInnerSize = function() + { + return this._innerSize; + } + + HTMLArea.prototype.setInnerSize = function(width, height) + { + if(typeof width == 'undefined' || width == null) + { + width = this._innerSize.width; + } + + if(typeof height == 'undefined' || height == null) + { + height = this._innerSize.height; + } + + this._innerSize = {'width':width,'height':height}; + + var editorWidth = width; + var editorHeight = height; + var editorLeft = 0; + var editorTop = 0; + var panels = this._panels; + + var panel = panels.right; + if(panel.on && panel.panels.length && HTMLArea.hasDisplayedChildren(panel.div)) + { + panel.div.style.position = 'absolute'; + panel.div.style.width = parseInt(this.config.panel_dimensions.right) + (HTMLArea.ie_ie ? -1 : -2) + 'px'; + panel.div.style.height = height + (HTMLArea.is_ie ? -1 : -1) + 'px'; + panel.div.style.top = '0px'; + panel.div.style.right = (HTMLArea.is_ie ? 1 : 2) + 'px'; + panel.div.style.padding = "0px"; + panel.div.style.overflow = "auto"; + panel.div.style.display = 'block'; + editorWidth -= parseInt(this.config.panel_dimensions.right) + (HTMLArea.is_ie ? 2 : 0); + } + else + { + panel.div.style.display = 'none'; + } + + var panel = panels.left; + if(panel.on && panel.panels.length && HTMLArea.hasDisplayedChildren(panel.div)) + { + panel.div.style.position = 'absolute'; + panel.div.style.width = parseInt(this.config.panel_dimensions.left) + (HTMLArea.ie_ie ? -1 : -1) + 'px'; + panel.div.style.height = height + (HTMLArea.is_ie ? -1 : -1) + 'px'; + panel.div.style.top = '0px'; + panel.div.style.left = (HTMLArea.is_ie ? 0 : 0) + 'px'; + panel.div.style.padding = "0px"; + panel.div.style.overflow = "auto"; + panel.div.style.display = "block"; + editorWidth -= parseInt(this.config.panel_dimensions.left) + (HTMLArea.is_ie ? 2 : 0); + editorLeft = parseInt(this.config.panel_dimensions.left) + (HTMLArea.is_ie ? 2 : 0) + 'px'; + } + else + { + panel.div.style.display = 'none'; + } + + var panel = panels.top; + if(panel.on && panel.panels.length && HTMLArea.hasDisplayedChildren(panel.div)) + { + panel.div.style.position = 'absolute'; + panel.div.style.top = '0px'; + panel.div.style.left = '0px'; + panel.div.style.width = width + 'px'; + panel.div.style.height = parseInt(this.config.panel_dimensions.top) + 'px'; + panel.div.style.padding = "0px"; + panel.div.style.overflow = "auto"; + panel.div.style.display = "block"; + editorHeight -= parseInt(this.config.panel_dimensions.top); + editorTop = parseInt(this.config.panel_dimensions.top) + 'px'; + } + else + { + panel.div.style.display = 'none'; + } + + var panel = panels.bottom; + if(panel.on && panel.panels.length && HTMLArea.hasDisplayedChildren(panel.div)) + { + panel.div.style.position = 'absolute'; + panel.div.style.bottom = '0px'; + panel.div.style.left = '0px'; + panel.div.style.width = width + 'px'; + panel.div.style.height = parseInt(this.config.panel_dimensions.bottom) + 'px'; + panel.div.style.padding = "0px"; + panel.div.style.overflow = "auto"; + panel.div.style.display = "block"; + editorHeight -= parseInt(this.config.panel_dimensions.bottom); + } + else + { + panel.div.style.display = 'none'; + } + + // Set the dimensions of the container + this.innerEditor.style.width = width + 'px'; + this.innerEditor.style.height = height + 'px'; + this.innerEditor.style.position = 'relative'; + + // and the iframe + this._iframe.style.width = editorWidth + 'px'; + this._iframe.style.height = editorHeight + 'px'; + this._iframe.style.position = 'absolute'; + this._iframe.style.left = editorLeft; + this._iframe.style.top = editorTop; + + + // the editor including the toolbar now have the same size as the + // original textarea.. which means that we need to reduce that a bit. + this._textArea.style.width = editorWidth + 'px'; + this._textArea.style.height = editorHeight + 'px'; + this._textArea.style.position = 'absolute'; + this._textArea.style.left = editorLeft; + this._textArea.style.top = editorTop; + + this.notifyOf('resize', {'width':width,'height':height,'editorWidth':editorWidth,'editorHeight':editorHeight,'editorTop':editorTop,'editorLeft':editorLeft}); + } + + HTMLArea.prototype.addPanel = function(side) + { + var div = document.createElement('div'); + div.side = side; + HTMLArea.addClasses(div, 'panel'); + this._panels[side].panels.push(div); + this._panels[side].div.appendChild(div); + + this.notifyOf('panel_change', {'action':'add','panel':div}); + + return div; + } + + HTMLArea.prototype.removePanel = function(panel) + { + panel.side.div.removeChild(panel); + var clean = [ ]; + for(var i = 0; i < panel.side.panels.length; i++) + { + if(panel.side.panels[i] != panel) + { + clean.push(panel.side.panels[i]); + } + } + panel.side.panels = clean; + this.notifyOf('panel_change', {'action':'add','panel':panel}); + } + + HTMLArea.prototype.hidePanel = function(panel) + { + panel.style.display = 'none'; + this.notifyOf('panel_change', {'action':'hide','panel':panel}); + } + + HTMLArea.prototype.showPanel = function(panel) + { + panel.style.display = ''; + this.notifyOf('panel_change', {'action':'show','panel':panel}); + } + + HTMLArea.prototype.hidePanels = function(sides) + { + if(typeof sides == 'undefined') + { + sides = ['left','right','top','bottom']; + } + + var reShow = []; + for(var i = 0; i < sides.length;i++) + { + if(this._panels[sides[i]].on) + { + reShow.push(sides[i]); + this._panels[sides[i]].on = false; + } + } + this.notifyOf('panel_change', {'action':'multi_hide','sides':sides}); + } + + HTMLArea.prototype.showPanels = function(sides) + { + if(typeof sides == 'undefined') + { + sides = ['left','right','top','bottom']; + } + + var reHide = []; + for(var i = 0; i < sides.length;i++) + { + if(!this._panels[sides[i]].on) + { + reHide.push(sides[i]); + this._panels[sides[i]].on = true; + } + } + this.notifyOf('panel_change', {'action':'multi_show','sides':sides}); + } + + HTMLArea.objectProperties = function(obj) + { + var props = [ ]; + for(var x in obj) + { + props[props.length] = x; + } + return props; + } + + HTMLArea.prototype.activateEditor = function() + { + if (HTMLArea.is_gecko && this._doc.designMode != 'on') + { + try{HTMLArea.last_on.designMode = 'off';} catch(e) { } + if(this._iframe.style.display == 'none') + { + this._iframe.style.display = ''; + this._doc.designMode = 'on'; + this._iframe.style.display = 'none'; + } + else + { + + this._doc.designMode = 'on'; + } + } + else + { + this._doc.body.contentEditable = true; + } + HTMLArea.last_on = this._doc; + } + + HTMLArea.prototype.deactivateEditor = function() + { + if(HTMLArea.is_gecko && this._doc.designMode == 'on') + { + this._doc.designMode = 'off'; + HTMLArea.last_on = null; + } + else + { + this._doc.body.contentEditable = false; + } + } + + HTMLArea.prototype.initIframe = function() + { + var doc = null; + var editor = this; + try + { + doc = editor._iframe.contentWindow.document; + if (!doc) { + // Try again.. + // FIXME: don't know what else to do here. Normally + // we'll never reach this point. + if (HTMLArea.is_gecko) { + setTimeout(function() { editor.initIframe()}, 50); + return false; + } else { + alert("ERROR: IFRAME can't be initialized."); + } + } + } + catch(e) + { + setTimeout(function() { editor.initIframe()}, 50); + } + + if (!editor.config.fullPage) { + doc.open(); + var html = "\n"; + html += "\n"; + if(typeof editor.config.baseHref != 'undefined') + { + html += ""; + } + html += "\n"; + html += "\n"; + if(typeof editor.config.pageStyleSheets !== 'undefined') + { + for(style_i = 0; style_i < editor.config.pageStyleSheets.length; style_i++) + { + if(editor.config.pageStyleSheets[style_i].length > 0) + html += ""; + //html += "\n"; + } + } + html += "\n"; + html += "\n"; + html += editor.inwardHtml(editor._textArea.value); + html += "\n"; + html += ""; + doc.write(html); + doc.close(); + } else { + var html = editor.inwardHtml(editor._textArea.value); + if (html.match(HTMLArea.RE_doctype)) { + editor.setDoctype(RegExp.$1); + html = html.replace(HTMLArea.RE_doctype, ""); + } + doc.open(); + doc.write(html); + doc.close(); + } + + this._doc = doc; + + // If we have multiple editors some bug in Mozilla makes some lose editing ability + if(HTMLArea.is_gecko) + { + HTMLArea._addEvents( + editor._iframe.contentWindow, + ["mousedown"], + function() { editor.activateEditor(); } + ); + } + else + { + editor.activateEditor(); + } + + // editor.focusEditor(); + // intercept some events; for updating the toolbar & keyboard handlers + HTMLArea._addEvents + (doc, ["keydown", "keypress", "mousedown", "mouseup", "drag"], + function (event) { + return editor._editorEvent(HTMLArea.is_ie ? editor._iframe.contentWindow.event : event); + }); + + // check if any plugins have registered refresh handlers + for (var i in editor.plugins) { + var plugin = editor.plugins[i].instance; + if (typeof plugin.onGenerate == "function") + plugin.onGenerate(); + if (typeof plugin.onGenerateOnce == "function") { + plugin.onGenerateOnce(); + plugin.onGenerateOnce = null; + } + } + + if(typeof editor._onGenerate == "function") { editor._onGenerate();} + + setTimeout(function() { + editor.updateToolbar(); + }, 250); + + if (typeof editor.onGenerate == "function") + editor.onGenerate(); + } + // Switches editor mode; parameter can be "textmode" or "wysiwyg". If no // parameter was passed this function toggles between modes. HTMLArea.prototype.setMode = function(mode) { - if (typeof mode == "undefined") { - mode = ((this._editMode == "textmode") ? "wysiwyg" : "textmode"); - } - switch (mode) { - case "textmode": - this._textArea.value = this.getHTML(); - this._iframe.style.display = "none"; - this._textArea.style.display = "block"; - if (this.config.statusBar) { - this._statusBar.innerHTML = HTMLArea.I18N.msg["TEXT_MODE"]; - } - break; - case "wysiwyg": - if (HTMLArea.is_gecko) { - // disable design mode before changing innerHTML - try { - this._doc.designMode = "off"; - } catch(e) {}; - } - if (!this.config.fullPage) - this._doc.body.innerHTML = this.getHTML(); - else - this.setFullHTML(this.getHTML()); - this._iframe.style.display = "block"; - this._textArea.style.display = "none"; - if (HTMLArea.is_gecko) { - // we need to refresh that info for Moz-1.3a - try { - this._doc.designMode = "on"; - } catch(e) {}; - } - if (this.config.statusBar) { - this._statusBar.innerHTML = ''; - this._statusBar.appendChild(document.createTextNode(HTMLArea.I18N.msg["Path"] + ": ")); - this._statusBar.appendChild(this._statusBarTree); - } - break; - default: - alert("Mode <" + mode + "> not defined!"); - return false; - } - this._editMode = mode; - this.focusEditor(); + if (typeof mode == "undefined") { + mode = ((this._editMode == "textmode") ? "wysiwyg" : "textmode"); + } + switch (mode) { + case "textmode": + { + var html = this.outwardHtml(this.getHTML()); + this._textArea.value = html; - for (var i in this.plugins) { - var plugin = this.plugins[i].instance; - if (typeof plugin.onMode == "function") plugin.onMode(mode); - } + // Hide the iframe + this.deactivateEditor(); + this._iframe.style.display = 'none'; + this._textArea.style.display = "block"; + if (this.config.statusBar) + { + this._statusBar.innerHTML = HTMLArea.I18N.msg["TEXT_MODE"]; + } + + this.notifyOf('modechange', {'mode':'text'}); + break; + } + + case "wysiwyg": + { + var html = this.inwardHtml(this.getHTML()); + this.deactivateEditor(); + if (!this.config.fullPage) + { + this._doc.body.innerHTML = html; + } + else + { + this.setFullHTML(html); + } + this._iframe.style.display = ''; + this._textArea.style.display = "none"; + this.activateEditor(); + if (this.config.statusBar) + { + this._statusBar.innerHTML = ''; + this._statusBar.appendChild(this._statusBarTree); + } + + this.notifyOf('modechange', {'mode':'wysiwyg'}); + break; + } + + default: + { + alert("Mode <" + mode + "> not defined!"); + return false; + } + } + this._editMode = mode; + // this.focusEditor(); + + for (var i in this.plugins) { + var plugin = this.plugins[i].instance; + if (typeof plugin.onMode == "function") plugin.onMode(mode); + } }; HTMLArea.prototype.setFullHTML = function(html) { - var save_multiline = RegExp.multiline; - RegExp.multiline = true; - if (html.match(HTMLArea.RE_doctype)) { - this.setDoctype(RegExp.$1); - html = html.replace(HTMLArea.RE_doctype, ""); - } - RegExp.multiline = save_multiline; - if (!HTMLArea.is_ie) { - if (html.match(HTMLArea.RE_head)) - this._doc.getElementsByTagName("head")[0].innerHTML = RegExp.$1; - if (html.match(HTMLArea.RE_body)) - this._doc.getElementsByTagName("body")[0].innerHTML = RegExp.$1; - } else { - var html_re = /((.|\n)*?)<\/html>/i; - html = html.replace(html_re, "$1"); - this._doc.open(); - this._doc.write(html); - this._doc.close(); - this._doc.body.contentEditable = true; - return true; - } + var save_multiline = RegExp.multiline; + RegExp.multiline = true; + if (html.match(HTMLArea.RE_doctype)) { + this.setDoctype(RegExp.$1); + html = html.replace(HTMLArea.RE_doctype, ""); + } + RegExp.multiline = save_multiline; + if (!HTMLArea.is_ie) { + if (html.match(HTMLArea.RE_head)) + this._doc.getElementsByTagName("head")[0].innerHTML = RegExp.$1; + if (html.match(HTMLArea.RE_body)) + this._doc.getElementsByTagName("body")[0].innerHTML = RegExp.$1; + } else { + var html_re = /((.|\n)*?)<\/html>/i; + html = html.replace(html_re, "$1"); + this._doc.open(); + this._doc.write(html); + this._doc.close(); + this.activateEditor(); + // this._doc.body.contentEditable = true; + return true; + } }; /*************************************************** * Category: PLUGINS ***************************************************/ +// Create the specified plugin and register it with this HTMLArea +HTMLArea.prototype.registerPlugin = function() { + var plugin = arguments[0]; + var args = []; + for (var i = 1; i < arguments.length; ++i) + args.push(arguments[i]); + this.registerPlugin2(plugin, args); +}; + // this is the variant of the function above where the plugin arguments are // already packed in an array. Externally, it should be only used in the // full-screen editor code, in order to initialize plugins with the same // parameters as in the opener window. HTMLArea.prototype.registerPlugin2 = function(plugin, args) { - if (typeof plugin == "string") - plugin = eval(plugin); - if (typeof plugin == "undefined") { - /* FIXME: This should never happen. But why does it do? */ - return false; - } - var obj = new plugin(this, args); - if (obj) { - var clone = {}; - var info = plugin._pluginInfo; - for (var i in info) - clone[i] = info[i]; - clone.instance = obj; - clone.args = args; - this.plugins[plugin._pluginInfo.name] = clone; - } else - alert("Can't register plugin " + plugin.toString() + "."); -}; - -// Create the specified plugin and register it with this HTMLArea -HTMLArea.prototype.registerPlugin = function() { - var plugin = arguments[0]; - var args = []; - for (var i = 1; i < arguments.length; ++i) - args.push(arguments[i]); - this.registerPlugin2(plugin, args); + if (typeof plugin == "string") + plugin = eval(plugin); + if (typeof plugin == "undefined") { + /* FIXME: This should never happen. But why does it do? */ + return false; + } + var obj = new plugin(this, args); + if (obj) { + var clone = {}; + var info = plugin._pluginInfo; + for (var i in info) + clone[i] = info[i]; + clone.instance = obj; + clone.args = args; + this.plugins[plugin._pluginInfo.name] = clone; + } else + alert("Can't register plugin " + plugin.toString() + "."); }; // static function that loads the required plugin and lang file, based on the // language loaded already for HTMLArea. You better make sure that the plugin // _has_ that language, otherwise shit might happen ;-) +HTMLArea.getPluginDir = function(pluginName) { + return _editor_url + "plugins/" + pluginName; +}; + HTMLArea.loadPlugin = function(pluginName) { - var dir = _editor_url + "plugins/" + pluginName; - var plugin = pluginName.replace(/([a-z])([A-Z])([a-z])/g, - function (str, l1, l2, l3) { - return l1 + "-" + l2.toLowerCase() + l3; - }).toLowerCase() + ".js"; - var plugin_file = dir + "/" + plugin; - var plugin_lang = dir + "/lang/" + HTMLArea.I18N.lang + ".js"; - HTMLArea._scripts.push(plugin_file, plugin_lang); - document.write(""); - document.write(""); + var dir = this.getPluginDir(pluginName); + var plugin = pluginName.replace(/([a-z])([A-Z])([a-z])/g, + function (str, l1, l2, l3) { + return l1 + "-" + l2.toLowerCase() + l3; + }).toLowerCase() + ".js"; + var plugin_file = dir + "/" + plugin; + var plugin_lang = dir + "/lang/" + _editor_lang + ".js"; + document.write(""); + document.write(""); + //this.loadScript(plugin_file); + //this.loadScript(plugin_lang); }; HTMLArea.loadStyle = function(style, plugin) { - var url = _editor_url || ''; - if (typeof plugin != "undefined") { - url += "plugins/" + plugin + "/"; - } - url += style; - document.write(""); + var url = _editor_url || ''; + if (typeof plugin != "undefined") { + url += "plugins/" + plugin + "/"; + } + url += style; + if (/^\//.test(style)) + url = style; + var head = document.getElementsByTagName("head")[0]; + var link = document.createElement("link"); + link.rel = "stylesheet"; + link.href = url; + head.appendChild(link); + //document.write(""); }; -HTMLArea.loadStyle("htmlarea.css"); +HTMLArea.loadStyle(typeof _editor_css == "string" ? _editor_css : "htmlarea.css"); /*************************************************** * Category: EDITOR UTILITIES ***************************************************/ -// The following function is a slight variation of the word cleaner code posted -// by Weeezl (user @ InteractiveTools forums). -HTMLArea.prototype._wordClean = function() { - var D = this.getInnerHTML(); - if (D.indexOf('class=Mso') >= 0) { - - // make one line - D = D.replace(/\r\n/g, ' '). - replace(/\n/g, ' '). - replace(/\r/g, ' '). - replace(/\ \;/g,' '); - - // keep tags, strip attributes - D = D.replace(/ class=[^\s|>]*/gi,''). - //replace(/

]*TEXT-ALIGN: justify[^>]*>/gi,'

'). - replace(/ style=\"[^>]*\"/gi,''). - replace(/ align=[^\s|>]*/gi,''); - - //clean up tags - D = D.replace(/]*>/gi,''). - replace(/]*>/gi,''). - replace(/

  • ]*>/gi,'
  • '). - replace(/
      ]*>/gi,'
        '); - - // replace outdated tags - D = D.replace(//gi,''). - replace(/<\/b>/gi,''); - - // mozilla doesn't like tags - D = D.replace(//gi,''). - replace(/<\/em>/gi,''); - - // kill unwanted tags - D = D.replace(/<\?xml:[^>]*>/g, ''). // Word xml - replace(/<\/?st1:[^>]*>/g,''). // Word SmartTags - replace(/<\/?[a-z]\:[^>]*>/g,''). // All other funny Word non-HTML stuff - replace(/<\/?font[^>]*>/gi,''). // Disable if you want to keep font formatting - replace(/<\/?span[^>]*>/gi,' '). - replace(/<\/?div[^>]*>/gi,' '). - replace(/<\/?pre[^>]*>/gi,' '). - replace(/<\/?h[1-6][^>]*>/gi,' '); - - //remove empty tags - //D = D.replace(/<\/strong>/gi,''). - //replace(/<\/i>/gi,''). - //replace(/]*><\/P>/gi,''); - - // nuke double tags - oldlen = D.length + 1; - while(oldlen > D.length) { - oldlen = D.length; - // join us now and free the tags, we'll be free hackers, we'll be free... ;-) - D = D.replace(/<([a-z][a-z]*)> *<\/\1>/gi,' '). - replace(/<([a-z][a-z]*)> *<([a-z][^>]*)> *<\/\1>/gi,'<$2>'); - } - D = D.replace(/<([a-z][a-z]*)><\1>/gi,'<$1>'). - replace(/<\/([a-z][a-z]*)><\/\1>/gi,'<\/$1>'); - - // nuke double spaces - D = D.replace(/ */gi,' '); - - this.setHTML(D); - this.updateToolbar(); - } +HTMLArea.prototype.debugTree = function() { + var ta = document.createElement("textarea"); + ta.style.width = "100%"; + ta.style.height = "20em"; + ta.value = ""; + function debug(indent, str) { + for (; --indent >= 0;) + ta.value += " "; + ta.value += str + "\n"; + }; + function _dt(root, level) { + var tag = root.tagName.toLowerCase(), i; + var ns = HTMLArea.is_ie ? root.scopeName : root.prefix; + debug(level, "- " + tag + " [" + ns + "]"); + for (i = root.firstChild; i; i = i.nextSibling) + if (i.nodeType == 1) + _dt(i, level + 2); + }; + _dt(this._doc.body, 0); + document.body.appendChild(ta); }; +HTMLArea.getInnerText = function(el) { + var txt = '', i; + for (i = el.firstChild; i; i = i.nextSibling) { + if (i.nodeType == 3) + txt += i.data; + else if (i.nodeType == 1) + txt += HTMLArea.getInnerText(i); + } + return txt; +}; + +HTMLArea.prototype._wordClean = function() { + var + editor = this, + stats = { + empty_tags : 0, + mso_class : 0, + mso_style : 0, + mso_xmlel : 0, + orig_len : this._doc.body.innerHTML.length, + T : (new Date()).getTime() + }, + stats_txt = { + empty_tags : "Empty tags removed: ", + mso_class : "MSO class names removed: ", + mso_style : "MSO inline style removed: ", + mso_xmlel : "MSO XML elements stripped: " + }; + function showStats() { + var txt = "HTMLArea word cleaner stats: \n\n"; + for (var i in stats) + if (stats_txt[i]) + txt += stats_txt[i] + stats[i] + "\n"; + txt += "\nInitial document length: " + stats.orig_len + "\n"; + txt += "Final document length: " + editor._doc.body.innerHTML.length + "\n"; + txt += "Clean-up took " + (((new Date()).getTime() - stats.T) / 1000) + " seconds"; + alert(txt); + }; + function clearClass(node) { + var newc = node.className.replace(/(^|\s)mso.*?(\s|$)/ig, ' '); + if (newc != node.className) { + node.className = newc; + if (!/\S/.test(node.className)) { + node.removeAttribute("className"); + ++stats.mso_class; + } + } + }; + function clearStyle(node) { + var declarations = node.style.cssText.split(/\s*;\s*/); + for (var i = declarations.length; --i >= 0;) + if (/^mso|^tab-stops/i.test(declarations[i]) || + /^margin\s*:\s*0..\s+0..\s+0../i.test(declarations[i])) { + ++stats.mso_style; + declarations.splice(i, 1); + } + node.style.cssText = declarations.join("; "); + }; + function stripTag(el) { + if (HTMLArea.is_ie) + el.outerHTML = HTMLArea.htmlEncode(el.innerText); + else { + var txt = document.createTextNode(HTMLArea.getInnerText(el)); + el.parentNode.insertBefore(txt, el); + el.parentNode.removeChild(el); + } + ++stats.mso_xmlel; + }; + function checkEmpty(el) { + if (/^(a|span|b|strong|i|em|font)$/i.test(el.tagName) && + !el.firstChild) { + el.parentNode.removeChild(el); + ++stats.empty_tags; + } + }; + function parseTree(root) { + var tag = root.tagName.toLowerCase(), i, next; + if ((HTMLArea.is_ie && root.scopeName != 'HTML') || (!HTMLArea.is_ie && /:/.test(tag))) { + stripTag(root); + return false; + } else { + clearClass(root); + clearStyle(root); + for (i = root.firstChild; i; i = next) { + next = i.nextSibling; + if (i.nodeType == 1 && parseTree(i)) + checkEmpty(i); + } + } + return true; + }; + parseTree(this._doc.body); + // showStats(); + // this.debugTree(); + // this.setHTML(this.getHTML()); + // this.setHTML(this.getInnerHTML()); + // this.forceRedraw(); + this.updateToolbar(); +}; + +HTMLArea.prototype._clearFonts = function() { + var D = this.getInnerHTML(); + + if(confirm('Would you like to clear font typefaces?')) + { + D = D.replace(/face="[^"]*"/gi, ''); + D = D.replace(/font-family:[^;}"']+;?/gi, ''); + } + + if(confirm('Would you like to clear font sizes?')) + { + D = D.replace(/size="[^"]*"/gi, ''); + D = D.replace(/font-size:[^;}"']+;?/gi, ''); + } + + if(confirm('Would you like to clear font colours?')) + { + D = D.replace(/color="[^"]*"/gi, ''); + D = D.replace(/([^-])color:[^;}"']+;?/gi, '$1'); + } + + D = D.replace(/(style|class)="\s*"/gi, ''); + D = D.replace(/<(font|span)\s*>/gi, ''); + this.setHTML(D); + this.updateToolbar(); +} + +HTMLArea.prototype._splitBlock = function() +{ + this._doc.execCommand('formatblock', false, '
        '); +} + HTMLArea.prototype.forceRedraw = function() { - this._doc.body.style.visibility = "hidden"; - this._doc.body.style.visibility = "visible"; - // this._doc.body.innerHTML = this.getInnerHTML(); + this._doc.body.style.visibility = "hidden"; + this._doc.body.style.visibility = "visible"; + // this._doc.body.innerHTML = this.getInnerHTML(); }; // focuses the iframe window. returns a reference to the editor document. HTMLArea.prototype.focusEditor = function() { - switch (this._editMode) { - // notice the try { ... } catch block to avoid some rare exceptions in FireFox - // (perhaps also in other Gecko browsers). Manual focus by user is required in + switch (this._editMode) { + // notice the try { ... } catch block to avoid some rare exceptions in FireFox + // (perhaps also in other Gecko browsers). Manual focus by user is required in // case of an error. Somebody has an idea? - case "wysiwyg" : try { this._iframe.contentWindow.focus() } catch (e) {} break; - case "textmode": try { this._textArea.focus() } catch (e) {} break; - default : alert("ERROR: mode " + this._editMode + " is not defined"); - } - return this._doc; + case "wysiwyg" : + try + { + // We don't want to focus the field unless at least one field has been activated. + if(HTMLArea.last_on) + { + this.activateEditor(); + this._iframe.contentWindow.focus(); + } + + } catch (e) {} break; + case "textmode": try { this._textArea.focus() } catch (e) {} break; + default : alert("ERROR: mode " + this._editMode + " is not defined"); + } + return this._doc; }; // takes a snapshot of the current text (for undo) HTMLArea.prototype._undoTakeSnapshot = function() { - ++this._undoPos; - if (this._undoPos >= this.config.undoSteps) { - // remove the first element - this._undoQueue.shift(); - --this._undoPos; - } - // use the fasted method (getInnerHTML); - var take = true; - var txt = this.getInnerHTML(); - if (this._undoPos > 0) - take = (this._undoQueue[this._undoPos - 1] != txt); - if (take) { - this._undoQueue[this._undoPos] = txt; - } else { - this._undoPos--; - } + ++this._undoPos; + if (this._undoPos >= this.config.undoSteps) { + // remove the first element + this._undoQueue.shift(); + --this._undoPos; + } + // use the fasted method (getInnerHTML); + var take = true; + var txt = this.getInnerHTML(); + if (this._undoPos > 0) + take = (this._undoQueue[this._undoPos - 1] != txt); + if (take) { + this._undoQueue[this._undoPos] = txt; + } else { + this._undoPos--; + } }; HTMLArea.prototype.undo = function() { - if (this._undoPos > 0) { - var txt = this._undoQueue[--this._undoPos]; - if (txt) this.setHTML(txt); - else ++this._undoPos; - } + if (this._undoPos > 0) { + var txt = this._undoQueue[--this._undoPos]; + if (txt) this.setHTML(txt); + else ++this._undoPos; + } }; HTMLArea.prototype.redo = function() { - if (this._undoPos < this._undoQueue.length - 1) { - var txt = this._undoQueue[++this._undoPos]; - if (txt) this.setHTML(txt); - else --this._undoPos; - } + if (this._undoPos < this._undoQueue.length - 1) { + var txt = this._undoQueue[++this._undoPos]; + if (txt) this.setHTML(txt); + else --this._undoPos; + } }; +HTMLArea.prototype.disableToolbar = function(except) +{ + if(typeof except == 'undefined') + { + except = [ ]; + } + else if(typeof except != 'object') + { + except = [except]; + } + + for (var i in this._toolbarObjects) + { + var btn = this._toolbarObjects[i]; + if(except.contains(i)) + { + continue; + } + btn.state("enabled", false); + } +} + +HTMLArea.prototype.enableToolbar = function() +{ + this.updateToolbar(); +} + +if(!Array.prototype.contains) +{ + Array.prototype.contains = function(needle) + { + var haystack = this; + for(var i = 0; i < haystack.length; i++) + { + if(needle == haystack[i]) return true; + } + + return false; + } + +} + + // updates enabled/disable/active state of the toolbar elements HTMLArea.prototype.updateToolbar = function(noStatus) { - var doc = this._doc; - var text = (this._editMode == "textmode"); - var ancestors = null; - if (!text) { - ancestors = this.getAllAncestors(); - if (this.config.statusBar && !noStatus) { - this._statusBarTree.innerHTML = HTMLArea.I18N.msg["Path"] + ": "; // clear - for (var i = ancestors.length; --i >= 0;) { - var el = ancestors[i]; - if (!el) { - // hell knows why we get here; this - // could be a classic example of why - // it's good to check for conditions - // that are impossible to happen ;-) - continue; - } - var a = document.createElement("a"); - a.href = "#"; - a.el = el; - a.editor = this; - a.onclick = function() { - this.blur(); - this.editor.selectNodeContents(this.el); - this.editor.updateToolbar(true); - return false; - }; - a.oncontextmenu = function() { - // TODO: add context menu here - this.blur(); - var info = "Inline style:\n\n"; - info += this.el.style.cssText.split(/;\s*/).join(";\n"); - alert(info); - return false; - }; - var txt = el.tagName.toLowerCase(); - a.title = el.style.cssText; - if (el.id) { - txt += "#" + el.id; - } - if (el.className) { - txt += "." + el.className; - } - a.appendChild(document.createTextNode(txt)); - this._statusBarTree.appendChild(a); - if (i != 0) { - this._statusBarTree.appendChild(document.createTextNode(String.fromCharCode(0xbb))); - } - } - } - } + var doc = this._doc; + var text = (this._editMode == "textmode"); + var ancestors = null; + if (!text) { + ancestors = this.getAllAncestors(); + if (this.config.statusBar && !noStatus) { + this._statusBarTree.innerHTML = HTMLArea.I18N.msg["Path"] + ": "; // clear + for (var i = ancestors.length; --i >= 0;) { + var el = ancestors[i]; + if (!el) { + // hell knows why we get here; this + // could be a classic example of why + // it's good to check for conditions + // that are impossible to happen ;-) + continue; + } + var a = document.createElement("a"); + a.href = "javascript:void(0)"; + a.el = el; + a.editor = this; + a.onclick = function() { + this.blur(); + this.editor.selectNodeContents(this.el); + this.editor.updateToolbar(true); + return false; + }; + a.oncontextmenu = function() { + // TODO: add context menu here + this.blur(); + var info = "Inline style:\n\n"; + info += this.el.style.cssText.split(/;\s*/).join(";\n"); + alert(info); + return false; + }; + var txt = el.tagName.toLowerCase(); + a.title = el.style.cssText; + if (el.id) { + txt += "#" + el.id; + } + if (el.className) { + txt += "." + el.className; + } + a.appendChild(document.createTextNode(txt)); + this._statusBarTree.appendChild(a); + if (i != 0) { + this._statusBarTree.appendChild(document.createTextNode(String.fromCharCode(0xbb))); + } + } + } + } - for (var i in this._toolbarObjects) { - var btn = this._toolbarObjects[i]; - var cmd = i; - var inContext = true; - if (btn.context && !text) { - inContext = false; - var context = btn.context; - var attrs = []; - if (/(.*)\[(.*?)\]/.test(context)) { - context = RegExp.$1; - attrs = RegExp.$2.split(","); - } - context = context.toLowerCase(); - var match = (context == "*"); - for (var k in ancestors) { - if (!ancestors[k]) { - // the impossible really happens. - continue; - } - if (match || (ancestors[k].tagName.toLowerCase() == context)) { - inContext = true; - for (var ka in attrs) { - if (!eval("ancestors[k]." + attrs[ka])) { - inContext = false; - break; - } - } - if (inContext) { - break; - } - } - } - } - btn.state("enabled", (!text || btn.text) && inContext); - if (typeof cmd == "function") { - continue; - } - // look-it-up in the custom dropdown boxes - var dropdown = this.config.customSelects[cmd]; - if ((!text || btn.text) && (typeof dropdown != "undefined")) { - dropdown.refresh(this); - continue; - } - switch (cmd) { - case "fontname": - case "fontsize": - case "formatblock": - if (!text) try { - var value = ("" + doc.queryCommandValue(cmd)).toLowerCase(); - if (!value) { - // FIXME: what do we do here? - break; - } - // HACK -- retrieve the config option for this - // combo box. We rely on the fact that the - // variable in config has the same name as - // button name in the toolbar. - var options = this.config[cmd]; - var k = 0; - // btn.element.selectedIndex = 0; - for (var j in options) { - // FIXME: the following line is scary. - if ((j.toLowerCase() == value) || - (options[j].substr(0, value.length).toLowerCase() == value)) { - btn.element.selectedIndex = k; - break; - } - ++k; - } - } catch(e) {}; - break; - case "textindicator": - if (!text) { - try {with (btn.element.style) { - backgroundColor = HTMLArea._makeColor( - doc.queryCommandValue(HTMLArea.is_ie ? "backcolor" : "hilitecolor")); - if (/transparent/i.test(backgroundColor)) { - // Mozilla - backgroundColor = HTMLArea._makeColor(doc.queryCommandValue("backcolor")); - } - color = HTMLArea._makeColor(doc.queryCommandValue("forecolor")); - fontFamily = doc.queryCommandValue("fontname"); - fontWeight = doc.queryCommandState("bold") ? "bold" : "normal"; - fontStyle = doc.queryCommandState("italic") ? "italic" : "normal"; - }} catch (e) { - // alert(e + "\n\n" + cmd); - } - } - break; - case "htmlmode": btn.state("active", text); break; - case "lefttoright": - case "righttoleft": - var el = this.getParentElement(); - while (el && !HTMLArea.isBlockElement(el)) - el = el.parentNode; - if (el) - btn.state("active", (el.style.direction == ((cmd == "righttoleft") ? "rtl" : "ltr"))); - break; - default: - cmd = cmd.replace(/(un)?orderedlist/i, "insert$1orderedlist"); - try { - btn.state("active", (!text && doc.queryCommandState(cmd))); - } catch (e) {} - } - } - // take undo snapshots - if (this._customUndo && !this._timerUndo) { - this._undoTakeSnapshot(); - var editor = this; - this._timerUndo = setTimeout(function() { - editor._timerUndo = null; - }, this.config.undoTimeout); - } + for (var i in this._toolbarObjects) { + var btn = this._toolbarObjects[i]; + var cmd = i; + var inContext = true; + if (btn.context && !text) { + inContext = false; + var context = btn.context; + var attrs = []; + if (/(.*)\[(.*?)\]/.test(context)) { + context = RegExp.$1; + attrs = RegExp.$2.split(","); + } + context = context.toLowerCase(); + var match = (context == "*"); + for (var k = 0; k < ancestors.length; ++k) { + if (!ancestors[k]) { + // the impossible really happens. + continue; + } + if (match || (ancestors[k].tagName.toLowerCase() == context)) { + inContext = true; + for (var ka = 0; ka < attrs.length; ++ka) { + if (!eval("ancestors[k]." + attrs[ka])) { + inContext = false; + break; + } + } + if (inContext) { + break; + } + } + } + } + btn.state("enabled", (!text || btn.text) && inContext); + if (typeof cmd == "function") { + continue; + } + // look-it-up in the custom dropdown boxes + var dropdown = this.config.customSelects[cmd]; + if ((!text || btn.text) && (typeof dropdown != "undefined")) { + dropdown.refresh(this); + continue; + } + switch (cmd) { + case "fontname": + case "fontsize": + if (!text) try { + var value = ("" + doc.queryCommandValue(cmd)).toLowerCase(); + if (!value) { + btn.element.selectedIndex = 0; + break; + } + // HACK -- retrieve the config option for this + // combo box. We rely on the fact that the + // variable in config has the same name as + // button name in the toolbar. + var options = this.config[cmd]; + var k = 0; + for (var j in options) { + // FIXME: the following line is scary. + if ((j.toLowerCase() == value) || + (options[j].substr(0, value.length).toLowerCase() == value)) { + btn.element.selectedIndex = k; + throw "ok"; + } + ++k; + } + btn.element.selectedIndex = 0; + } catch(e) {}; + + // It's better to search for the format block by tag name from the + // current selection upwards, because IE has a tendancy to return + // things like 'heading 1' for 'h1', which breaks things if you want + // to call your heading blocks 'header 1'. Stupid MS. + case "formatblock" : + var blocks = [ ]; + for(var i in this.config['formatblock']) + { + blocks[blocks.length] = this.config['formatblock'][i]; + } + + var deepestAncestor = this._getFirstAncestor(this._getSelection(), blocks); + if(deepestAncestor) + { + for(var x= 0; x < blocks.length; x++) + { + if(blocks[x].toLowerCase() == deepestAncestor.tagName.toLowerCase()) + { + btn.element.selectedIndex = x; + } + } + } + else + { + btn.element.selectedIndex = 0; + } + break; + + break; + case "textindicator": + if (!text) { + try {with (btn.element.style) { + backgroundColor = HTMLArea._makeColor( + doc.queryCommandValue(HTMLArea.is_ie ? "backcolor" : "hilitecolor")); + if (/transparent/i.test(backgroundColor)) { + // Mozilla + backgroundColor = HTMLArea._makeColor(doc.queryCommandValue("backcolor")); + } + color = HTMLArea._makeColor(doc.queryCommandValue("forecolor")); + fontFamily = doc.queryCommandValue("fontname"); + fontWeight = doc.queryCommandState("bold") ? "bold" : "normal"; + fontStyle = doc.queryCommandState("italic") ? "italic" : "normal"; + }} catch (e) { + // alert(e + "\n\n" + cmd); + } + } + break; + case "htmlmode": btn.state("active", text); break; + case "lefttoright": + case "righttoleft": + var el = this.getParentElement(); + while (el && !HTMLArea.isBlockElement(el)) + el = el.parentNode; + if (el) + btn.state("active", (el.style.direction == ((cmd == "righttoleft") ? "rtl" : "ltr"))); + break; + default: + cmd = cmd.replace(/(un)?orderedlist/i, "insert$1orderedlist"); + try { + btn.state("active", (!text && doc.queryCommandState(cmd))); + } catch (e) {} + } + } + // take undo snapshots + if (this._customUndo && !this._timerUndo) { + this._undoTakeSnapshot(); + var editor = this; + this._timerUndo = setTimeout(function() { + editor._timerUndo = null; + }, this.config.undoTimeout); + } + + // Insert a space in certain locations, this is just to make editing a little + // easier (to "get out of" tags), it's not essential. + if(HTMLArea.is_gecko) + { + var s = this._getSelection(); + if(s && s.isCollapsed && s.anchorNode && s.anchorNode.nodeType == 3) + { + if(s.anchorOffset == s.anchorNode.length) + { + if(HTMLArea.isBlockElement(s.anchorNode.parentNode)) + { + if(s.anchorNode.data != '\xA0' && !s.anchorNode.nextSibling) + { + s.anchorNode.parentNode.insertBefore(this._doc.createTextNode('\xA0'), s.anchorNode.nextSibling); + } + } + else + { + if(!s.anchorNode.parentNode.nextSibling) + { + s.anchorNode.parentNode.parentNode.insertBefore(this._doc.createTextNode('\xA0'), s.anchorNode.parentNode.nextSibling); + } + } + } + } + } + + // check if any plugins have registered refresh handlers + for (var i in this.plugins) { + var plugin = this.plugins[i].instance; + if (typeof plugin.onUpdateToolbar == "function") + plugin.onUpdateToolbar(); + } - // check if any plugins have registered refresh handlers - for (var i in this.plugins) { - var plugin = this.plugins[i].instance; - if (typeof plugin.onUpdateToolbar == "function") - plugin.onUpdateToolbar(); - } }; /** Returns a node after which we can insert other nodes, in the current * selection. The selection is removed. It splits a text node, if needed. */ HTMLArea.prototype.insertNodeAtSelection = function(toBeInserted) { - if (!HTMLArea.is_ie) { - var sel = this._getSelection(); - var range = this._createRange(sel); - // remove the current selection - sel.removeAllRanges(); - range.deleteContents(); - var node = range.startContainer; - var pos = range.startOffset; - switch (node.nodeType) { - case 3: // Node.TEXT_NODE - // we have to split it at the caret position. - if (toBeInserted.nodeType == 3) { - // do optimized insertion - node.insertData(pos, toBeInserted.data); - range = this._createRange(); - range.setEnd(node, pos + toBeInserted.length); - range.setStart(node, pos + toBeInserted.length); - sel.addRange(range); - } else { - node = node.splitText(pos); - var selnode = toBeInserted; - if (toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */) { - selnode = selnode.firstChild; - } - node.parentNode.insertBefore(toBeInserted, node); - this.selectNodeContents(selnode); - this.updateToolbar(); - } - break; - case 1: // Node.ELEMENT_NODE - var selnode = toBeInserted; - if (toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */) { - selnode = selnode.firstChild; - } - node.insertBefore(toBeInserted, node.childNodes[pos]); - this.selectNodeContents(selnode); - this.updateToolbar(); - break; - } - } else { - return null; // this function not yet used for IE - } + if (!HTMLArea.is_ie) { + var sel = this._getSelection(); + var range = this._createRange(sel); + // remove the current selection + sel.removeAllRanges(); + range.deleteContents(); + var node = range.startContainer; + var pos = range.startOffset; + switch (node.nodeType) { + case 3: // Node.TEXT_NODE + // we have to split it at the caret position. + if (toBeInserted.nodeType == 3) { + // do optimized insertion + node.insertData(pos, toBeInserted.data); + range = this._createRange(); + range.setEnd(node, pos + toBeInserted.length); + range.setStart(node, pos + toBeInserted.length); + sel.addRange(range); + } else { + node = node.splitText(pos); + var selnode = toBeInserted; + if (toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */) { + selnode = selnode.firstChild; + } + node.parentNode.insertBefore(toBeInserted, node); + this.selectNodeContents(selnode); + this.updateToolbar(); + } + break; + case 1: // Node.ELEMENT_NODE + var selnode = toBeInserted; + if (toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */) { + selnode = selnode.firstChild; + } + node.insertBefore(toBeInserted, node.childNodes[pos]); + this.selectNodeContents(selnode); + this.updateToolbar(); + break; + } + } else { + return null; // this function not yet used for IE + } }; // Returns the deepest node that contains both endpoints of the selection. -HTMLArea.prototype.getParentElement = function() { - var sel = this._getSelection(); - var range = this._createRange(sel); - if (HTMLArea.is_ie) { - switch (sel.type) { - case "Text": - case "None": - // It seems that even for selection of type "None", - // there _is_ a parent element and it's value is not - // only correct, but very important to us. MSIE is - // certainly the buggiest browser in the world and I - // wonder, God, how can Earth stand it? - return range.parentElement(); - case "Control": - return range.item(0); - default: - return this._doc.body; - } - } else try { - var p = range.commonAncestorContainer; - if (!range.collapsed && range.startContainer == range.endContainer && - range.startOffset - range.endOffset <= 1 && range.startContainer.hasChildNodes()) - p = range.startContainer.childNodes[range.startOffset]; - /* - alert(range.startContainer + ":" + range.startOffset + "\n" + - range.endContainer + ":" + range.endOffset); - */ - while (p.nodeType == 3) { - p = p.parentNode; - } - return p; - } catch (e) { - return null; - } +HTMLArea.prototype.getParentElement = function(sel) { + if(typeof sel == 'undefined') + { + sel = this._getSelection(); + } + var range = this._createRange(sel); + if (HTMLArea.is_ie) { + switch (sel.type) { + case "Text": + case "None": + // It seems that even for selection of type "None", + // there _is_ a parent element and it's value is not + // only correct, but very important to us. MSIE is + // certainly the buggiest browser in the world and I + // wonder, God, how can Earth stand it? + return range.parentElement(); + case "Control": + return range.item(0); + default: + return this._doc.body; + } + } else try { + var p = range.commonAncestorContainer; + if (!range.collapsed && range.startContainer == range.endContainer && + range.startOffset - range.endOffset <= 1 && range.startContainer.hasChildNodes()) + p = range.startContainer.childNodes[range.startOffset]; + /* + alert(range.startContainer + ":" + range.startOffset + "\n" + + range.endContainer + ":" + range.endOffset); + */ + while (p.nodeType == 3) { + p = p.parentNode; + } + return p; + } catch (e) { + return null; + } }; // Returns an array with all the ancestor nodes of the selection. HTMLArea.prototype.getAllAncestors = function() { - var p = this.getParentElement(); - var a = []; - while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) { - a.push(p); - p = p.parentNode; - } - a.push(this._doc.body); - return a; + var p = this.getParentElement(); + var a = []; + while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) { + a.push(p); + p = p.parentNode; + } + a.push(this._doc.body); + return a; }; +// Returns the deepest ancestor of the selection that is of the current type +HTMLArea.prototype._getFirstAncestor = function(sel, types) +{ + var prnt = this._activeElement(sel); + if(prnt == null) + { + try + { + prnt = (HTMLArea.is_ie ? this._createRange(sel).parentElement() : this._createRange(sel).commonAncestorContainer); + } + catch(e) + { + return null; + } + } + + if(typeof types == 'string') + { + types = [types]; + } + + while(prnt) + { + if(prnt.nodeType == 1) + { + if(types == null) return prnt; + if(types.contains(prnt.tagName.toLowerCase())) + { + + return prnt; + } + if(prnt.tagName.toLowerCase() == 'body') break; + if(prnt.tagName.toLowerCase() == 'table') break; + } + prnt = prnt.parentNode; + } + + return null; +} + +/** + * Returns the selected element, if any. That is, + * the element that you have last selected in the "path" + * at the bottom of the editor, or a "control" (eg image) + * + * @returns null | element + */ +HTMLArea.prototype._activeElement = function(sel) +{ + if(sel == null) return null; + if(this._selectionEmpty(sel)) return null; + + if(HTMLArea.is_ie) + { + if(sel.type.toLowerCase() == "control") + { + return sel.createRange().item(0); + } + else + { + + // If it's not a control, then we need to see if + // the selection is the _entire_ text of a parent node + // (this happens when a node is clicked in the tree) + var range = sel.createRange(); + var p_elm = this.getParentElement(sel); + if(p_elm.innerHTML == range.htmlText) + { + return p_elm; + } + /* + if(p_elm) + { + var p_rng = this._doc.body.createTextRange(); + p_rng.moveToElementText(p_elm); + if(p_rng.isEqual(range)) + { + return p_elm; + } + } + + if(range.parentElement()) + { + var prnt_range = this._doc.body.createTextRange(); + prnt_range.moveToElementText(range.parentElement()); + if(prnt_range.isEqual(range)) + { + return range.parentElement(); + } + } + */ + return null; + } + } + else + { + // For Mozilla we just see if the selection is not collapsed (something is selected) + // and that the anchor (start of selection) is an element. This might not be totally + // correct, we possibly should do a simlar check to IE? + if(! sel.isCollapsed) + { + if(sel.anchorNode.nodeType == 1) + { + return sel.anchorNode; + } + } + return null; + } +} + + +HTMLArea.prototype._selectionEmpty = function(sel) +{ + if(!sel) return true; + + if(HTMLArea.is_ie) + { + return this._createRange(sel).htmlText == ''; + } + else if(typeof sel.isCollapsed != 'undefined') + { + return sel.isCollapsed; + } + + return true; +} + +HTMLArea.prototype._getAncestorBlock = function(sel) +{ + // Scan upwards to find a block level element that we can change or apply to + var prnt = (HTMLArea.is_ie ? this._createRange(sel).parentElement : this._createRange(sel).commonAncestorContainer); + + while(prnt && (prnt.nodeType == 1)) + { + switch(prnt.tagName.toLowerCase()) + { + case 'div' : + case 'p' : + case 'address' : + case 'blockquote' : + case 'center' : + case 'del' : + case 'ins' : + case 'pre' : + case 'h1' : + case 'h2' : + case 'h3' : + case 'h4' : + case 'h5' : + case 'h6' : + case 'h7' : + // Block Element + return prnt; + + case 'body' : + case 'noframes' : + case 'dd' : + case 'li' : + case 'th' : + case 'td' : + case 'noscript' : + // Halting element (stop searching) + return null; + + default : + // Keep lookin + break; + } + } + + return null; +} + +HTMLArea.prototype._createImplicitBlock = function(type) +{ + // expand it until we reach a block element in either direction + // then wrap the selection in a block and return + var sel = this._getSelection(); + if(HTMLArea.is_ie) + { + sel.empty(); + } + else + { + sel.collapseToStart(); + } + + var rng = this._createRange(sel); + + // Expand UP + + // Expand DN +} + +HTMLArea.prototype._formatBlock = function(block_format) +{ + var ancestors = this.getAllAncestors(); + var apply_to = null; + + // Block format can be a tag followed with class defs + // eg div.blue.left + var target_tag = null; + var target_classNames = [ ]; + + if(block_format.indexOf('.') >= 0) + { + target_tag = block_format.substr(0, block_format.indexOf('.')).toLowerCase();; + + target_classNames = block_format.substr(block_format.indexOf('.'), block_format.length - block_format.indexOf('.')).replace(/\./g, '').replace(/^\s*/, '').replace(/\s*$/, '').split(' '); + } + else + { + target_tag = block_format.toLowerCase(); + } + + var sel = this._getSelection(); + var rng = this._createRange(sel); + var apply_to = null; + + if(HTMLArea.is_gecko) + { + if(sel.isCollapsed) + { + // With no selection we want to apply to the whole contents of the ancestor block + apply_to = this._getAncestorBlock(sel); + if(apply_to == null) + { + // If there wasn't an ancestor, make one. + apply_to = this._createImplicitBlock(sel, target_tag); + } + } + else + { + // With a selection it's more tricky + switch(target_tag) + { + + case 'h1' : + case 'h2' : + case 'h3' : + case 'h4' : + case 'h5' : + case 'h6' : + case 'h7' : + apply_to = [ ]; + var search_tags = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7']; + for(var y = 0; y < search_tags.length; y++) + { + var headers = this._doc.getElementsByTagName(search_tag[y]); + for(var x = 0; x < headers.length; x++) + { + if(sel.containsNode(headers[x])) + { + apply_to[apply_to.length] = headers[x]; + } + } + } + if(apply_to.length > 0) break; + // If there wern't any in the selection drop through + case 'div' : + apply_to = this._doc.createElement(target_tag); + apply_to.appendChild(rng.extractContents()); + rng.insertNode(apply_to); + break; + + case 'p' : + case 'center' : + case 'pre' : + case 'ins' : + case 'del' : + case 'blockquote' : + case 'address' : + apply_to = [ ]; + var paras = this._doc.getElementsByTagName(target_tag); + for(var x = 0; x < paras.length; x++) + { + if(sel.containsNode(paras[x])) + { + apply_to[apply_to.length] = paras[x]; + } + } + + if(apply_to.length == 0) + { + sel.collapseToStart(); + return this._formatBlock(block_format); + } + break; + } + } + } + +} + // Selects the contents inside the given node HTMLArea.prototype.selectNodeContents = function(node, pos) { - this.focusEditor(); - this.forceRedraw(); - var range; - var collapsed = (typeof pos != "undefined"); - if (HTMLArea.is_ie) { - range = this._doc.body.createTextRange(); - range.moveToElementText(node); - (collapsed) && range.collapse(pos); - range.select(); - } else { - var sel = this._getSelection(); - range = this._doc.createRange(); - range.selectNodeContents(node); - (collapsed) && range.collapse(pos); - sel.removeAllRanges(); - sel.addRange(range); - } + this.focusEditor(); + this.forceRedraw(); + var range; + var collapsed = (typeof pos != "undefined"); + if (HTMLArea.is_ie) { + // Tables and Images get selected as "objects" rather than the text contents + if(!collapsed && node.tagName && node.tagName.toLowerCase().match(/table|img/)) + { + range = this._doc.body.createControlRange(); + range.add(node); + } + else + { + range = this._doc.body.createTextRange(); + range.moveToElementText(node); + (collapsed) && range.collapse(pos); + } + range.select(); + } else { + var sel = this._getSelection(); + range = this._doc.createRange(); + // Tables and Images get selected as "objects" rather than the text contents + if(!collapsed && node.tagName && node.tagName.toLowerCase().match(/table|img/)) + { + range.selectNode(node); + (collapsed) && range.collapse(pos); + } + else + { + range.selectNodeContents(node); + (collapsed) && range.collapse(pos); + } + sel.removeAllRanges(); + sel.addRange(range); + } }; /** Call this function to insert HTML code at the current position. It deletes * the selection, if any. */ HTMLArea.prototype.insertHTML = function(html) { - var sel = this._getSelection(); - var range = this._createRange(sel); - if (HTMLArea.is_ie) { - range.pasteHTML(html); - } else { - // construct a new document fragment with the given HTML - var fragment = this._doc.createDocumentFragment(); - var div = this._doc.createElement("div"); - div.innerHTML = html; - while (div.firstChild) { - // the following call also removes the node from div - fragment.appendChild(div.firstChild); - } - // this also removes the selection - var node = this.insertNodeAtSelection(fragment); - } + var sel = this._getSelection(); + var range = this._createRange(sel); + if (HTMLArea.is_ie) { + range.pasteHTML(html); + } else { + // construct a new document fragment with the given HTML + var fragment = this._doc.createDocumentFragment(); + var div = this._doc.createElement("div"); + div.innerHTML = html; + while (div.firstChild) { + // the following call also removes the node from div + fragment.appendChild(div.firstChild); + } + // this also removes the selection + var node = this.insertNodeAtSelection(fragment); + } }; /** @@ -1394,182 +2543,213 @@ HTMLArea.prototype.insertHTML = function(html) { * your tags. FIXME: buggy! This function will be deprecated "soon". */ HTMLArea.prototype.surroundHTML = function(startTag, endTag) { - var html = this.getSelectedHTML(); - // the following also deletes the selection - this.insertHTML(startTag + html + endTag); + var html = this.getSelectedHTML(); + // the following also deletes the selection + this.insertHTML(startTag + html + endTag); }; /// Retrieve the selected block HTMLArea.prototype.getSelectedHTML = function() { - var sel = this._getSelection(); - var range = this._createRange(sel); - var existing = null; - if (HTMLArea.is_ie) { - existing = range.htmlText; - } else { - existing = HTMLArea.getHTML(range.cloneContents(), false, this); - } - return existing; + var sel = this._getSelection(); + var range = this._createRange(sel); + var existing = null; + if (HTMLArea.is_ie) { + existing = range.htmlText; + } else { + existing = HTMLArea.getHTML(range.cloneContents(), false, this); + } + return existing; }; /// Return true if we have some selection HTMLArea.prototype.hasSelectedText = function() { - // FIXME: come _on_ mishoo, you can do better than this ;-) - return this.getSelectedHTML() != ''; + // FIXME: come _on_ mishoo, you can do better than this ;-) + return this.getSelectedHTML() != ''; }; HTMLArea.prototype._createLink = function(link) { - var editor = this; - var outparam = null; - if (typeof link == "undefined") { - link = this.getParentElement(); - if (link && !/^a$/i.test(link.tagName)) - link = null; - } - if (link) outparam = { - f_href : HTMLArea.is_ie ? editor.stripBaseURL(link.href) : link.getAttribute("href"), - f_title : link.title, - f_target : link.target - }; - this._popupDialog("link.html", function(param) { - if (!param) - return false; - var a = link; - if (!a) try { - editor._doc.execCommand("createlink", false, param.f_href); - a = editor.getParentElement(); - var sel = editor._getSelection(); - var range = editor._createRange(sel); - if (!HTMLArea.is_ie) { - a = range.startContainer; - if (!/^a$/i.test(a.tagName)) { - a = a.nextSibling; - if (a == null) - a = range.startContainer.parentNode; - } - } - } catch(e) {} - else { - var href = param.f_href.trim(); - editor.selectNodeContents(a); - if (href == "") { - editor._doc.execCommand("unlink", false, null); - editor.updateToolbar(); - return false; - } - else { - a.href = href; - } - } - if (!(a && /^a$/i.test(a.tagName))) - return false; - a.target = param.f_target.trim(); - a.title = param.f_title.trim(); - editor.selectNodeContents(a); - editor.updateToolbar(); - }, outparam); + var editor = this; + var outparam = null; + if (typeof link == "undefined") { + link = this.getParentElement(); + if (link) { + if (/^img$/i.test(link.tagName)) + link = link.parentNode; + if (!/^a$/i.test(link.tagName)) + link = null; + } + } + if (!link) { + var sel = editor._getSelection(); + var range = editor._createRange(sel); + var compare = 0; + if (HTMLArea.is_ie) { + compare = range.compareEndPoints("StartToEnd", range); + } else { + compare = range.compareBoundaryPoints(range.START_TO_END, range); + } + if (compare == 0) { + alert("You need to select some text before creating a link"); + return; + } + outparam = { + f_href : '', + f_title : '', + f_target : '', + f_usetarget : editor.config.makeLinkShowsTarget + }; + } else + outparam = { + f_href : HTMLArea.is_ie ? editor.stripBaseURL(link.href) : link.getAttribute("href"), + f_title : link.title, + f_target : link.target, + f_usetarget : editor.config.makeLinkShowsTarget + }; + this._popupDialog("link.html", function(param) { + if (!param) + return false; + var a = link; + if (!a) try { + editor._doc.execCommand("createlink", false, param.f_href); + a = editor.getParentElement(); + var sel = editor._getSelection(); + var range = editor._createRange(sel); + if (!HTMLArea.is_ie) { + a = range.startContainer; + if (!/^a$/i.test(a.tagName)) { + a = a.nextSibling; + if (a == null) + a = range.startContainer.parentNode; + } + } + } catch(e) {} + else { + var href = param.f_href.trim(); + editor.selectNodeContents(a); + if (href == "") { + editor._doc.execCommand("unlink", false, null); + editor.updateToolbar(); + return false; + } + else { + a.href = href; + } + } + if (!(a && /^a$/i.test(a.tagName))) + return false; + a.target = param.f_target.trim(); + a.title = param.f_title.trim(); + editor.selectNodeContents(a); + editor.updateToolbar(); + }, outparam); }; // Called when the user clicks on "InsertImage" button. If an image is already // there, it will just modify it's properties. HTMLArea.prototype._insertImage = function(image) { - var editor = this; // for nested functions - var outparam = null; - if (typeof image == "undefined") { - image = this.getParentElement(); - if (image && !/^img$/i.test(image.tagName)) - image = null; - } - if (image) outparam = { - f_url : HTMLArea.is_ie ? editor.stripBaseURL(image.src) : image.getAttribute("src"), - f_alt : image.alt, - f_border : image.border, - f_align : image.align, - f_vert : image.vspace, - f_horiz : image.hspace - }; - this._popupDialog("insert_image.html", function(param) { - if (!param) { // user must have pressed Cancel - return false; - } - var img = image; - if (!img) { - var sel = editor._getSelection(); - var range = editor._createRange(sel); - editor._doc.execCommand("insertimage", false, param.f_url); - if (HTMLArea.is_ie) { - img = range.parentElement(); - // wonder if this works... - if (img.tagName.toLowerCase() != "img") { - img = img.previousSibling; - } - } else { - img = range.startContainer.previousSibling; - } - } else { - img.src = param.f_url; - } + var editor = this; // for nested functions + var outparam = null; + if (typeof image == "undefined") { + image = this.getParentElement(); + if (image && !/^img$/i.test(image.tagName)) + image = null; + } + if (image) outparam = { + f_base : editor.config.baseURL, + f_url : HTMLArea.is_ie ? editor.stripBaseURL(image.src) : image.getAttribute("src"), + f_alt : image.alt, + f_border : image.border, + f_align : image.align, + f_vert : image.vspace, + f_horiz : image.hspace + }; + this._popupDialog("insert_image.html", function(param) { + if (!param) { // user must have pressed Cancel + return false; + } + var img = image; + if (!img) { + var sel = editor._getSelection(); + var range = editor._createRange(sel); + editor._doc.execCommand("insertimage", false, param.f_url); + if (HTMLArea.is_ie) { + img = range.parentElement(); + // wonder if this works... + if (img.tagName.toLowerCase() != "img") { + img = img.previousSibling; + } + } else { + img = range.startContainer.previousSibling; + } + } else { + img.src = param.f_url; + } - for (field in param) { - var value = param[field]; - switch (field) { - case "f_alt" : img.alt = value; break; - case "f_border" : img.border = parseInt(value || "0"); break; - case "f_align" : img.align = value; break; - case "f_vert" : img.vspace = parseInt(value || "0"); break; - case "f_horiz" : img.hspace = parseInt(value || "0"); break; - } - } - }, outparam); + for (var field in param) { + var value = param[field]; + switch (field) { + case "f_alt" : img.alt = value; break; + case "f_border" : img.border = parseInt(value || "0"); break; + case "f_align" : img.align = value; break; + case "f_vert" : img.vspace = parseInt(value || "0"); break; + case "f_horiz" : img.hspace = parseInt(value || "0"); break; + } + } + }, outparam); }; // Called when the user clicks the Insert Table button HTMLArea.prototype._insertTable = function() { - var sel = this._getSelection(); - var range = this._createRange(sel); - var editor = this; // for nested functions - this._popupDialog("insert_table.html", function(param) { - if (!param) { // user must have pressed Cancel - return false; - } - var doc = editor._doc; - // create the table element - var table = doc.createElement("table"); - // assign the given arguments + var sel = this._getSelection(); + var range = this._createRange(sel); + var editor = this; // for nested functions + this._popupDialog("insert_table.html", function(param) { + if (!param) { // user must have pressed Cancel + return false; + } + var doc = editor._doc; + // create the table element + var table = doc.createElement("table"); + // assign the given arguments - for (var field in param) { - var value = param[field]; - if (!value) { - continue; - } - switch (field) { - case "f_width" : table.style.width = value + param["f_unit"]; break; - case "f_align" : table.align = value; break; - case "f_border" : table.border = parseInt(value); break; - case "f_spacing" : table.cellSpacing = parseInt(value); break; - case "f_padding" : table.cellPadding = parseInt(value); break; - } - } - var tbody = doc.createElement("tbody"); - table.appendChild(tbody); - for (var i = 0; i < param["f_rows"]; ++i) { - var tr = doc.createElement("tr"); - tbody.appendChild(tr); - for (var j = 0; j < param["f_cols"]; ++j) { - var td = doc.createElement("td"); - tr.appendChild(td); - // Mozilla likes to see something inside the cell. - (HTMLArea.is_gecko) && td.appendChild(doc.createElement("br")); - } - } - if (HTMLArea.is_ie) { - range.pasteHTML(table.outerHTML); - } else { - // insert the table - editor.insertNodeAtSelection(table); - } - return true; - }, null); + for (var field in param) { + var value = param[field]; + if (!value) { + continue; + } + switch (field) { + case "f_width" : table.style.width = value + param["f_unit"]; break; + case "f_align" : table.align = value; break; + case "f_border" : table.border = parseInt(value); break; + case "f_spacing" : table.cellSpacing = parseInt(value); break; + case "f_padding" : table.cellPadding = parseInt(value); break; + } + } + var cellwidth = 0; + if (param.f_fixed) + cellwidth = Math.floor(100 / parseInt(param.f_cols)); + var tbody = doc.createElement("tbody"); + table.appendChild(tbody); + for (var i = 0; i < param["f_rows"]; ++i) { + var tr = doc.createElement("tr"); + tbody.appendChild(tr); + for (var j = 0; j < param["f_cols"]; ++j) { + var td = doc.createElement("td"); + if (cellwidth) + td.style.width = cellwidth + "%"; + tr.appendChild(td); + // Mozilla likes to see something inside the cell. + (HTMLArea.is_gecko) && td.appendChild(doc.createElement("br")); + } + } + if (HTMLArea.is_ie) { + range.pasteHTML(table.outerHTML); + } else { + // insert the table + editor.insertNodeAtSelection(table); + } + return true; + }, null); }; /*************************************************** @@ -1579,613 +2759,1113 @@ HTMLArea.prototype._insertTable = function() { // el is reference to the SELECT object // txt is the name of the select field, as in config.toolbar HTMLArea.prototype._comboSelected = function(el, txt) { - this.focusEditor(); - var value = el.options[el.selectedIndex].value; - switch (txt) { - case "fontname": - case "fontsize": this.execCommand(txt, false, value); break; - case "formatblock": - (HTMLArea.is_ie) && (value = "<" + value + ">"); - this.execCommand(txt, false, value); - break; - default: - // try to look it up in the registered dropdowns - var dropdown = this.config.customSelects[txt]; - if (typeof dropdown != "undefined") { - dropdown.action(this); - } else { - alert("FIXME: combo box " + txt + " not implemented"); - } - } + this.focusEditor(); + var value = el.options[el.selectedIndex].value; + switch (txt) { + case "fontname": + case "fontsize": this.execCommand(txt, false, value); break; + case "formatblock": + // (HTMLArea.is_ie) && (value = "<" + value + ">"); + value = "<" + value + ">" + this.execCommand(txt, false, value); + break; + default: + // try to look it up in the registered dropdowns + var dropdown = this.config.customSelects[txt]; + if (typeof dropdown != "undefined") { + dropdown.action(this); + } else { + alert("FIXME: combo box " + txt + " not implemented"); + } + } }; // the execCommand function (intercepts some commands and replaces them with // our own implementation) HTMLArea.prototype.execCommand = function(cmdID, UI, param) { - var editor = this; // for nested functions - this.focusEditor(); - cmdID = cmdID.toLowerCase(); - switch (cmdID) { - case "htmlmode" : this.setMode(); break; - case "hilitecolor": - (HTMLArea.is_ie) && (cmdID = "backcolor"); - case "forecolor": - this._popupDialog("select_color.html", function(color) { - if (color) { // selection not canceled - editor._doc.execCommand(cmdID, false, "#" + color); - } - }, HTMLArea._colorToRgb(this._doc.queryCommandValue(cmdID))); - break; - case "createlink": - this._createLink(); - break; - case "popupeditor": - // this object will be passed to the newly opened window - HTMLArea._object = this; - if (HTMLArea.is_ie) { - //if (confirm(HTMLArea.I18N.msg["IE-sucks-full-screen"])) - { - window.open(this.popupURL("fullscreen.html"), "ha_fullscreen", - "toolbar=no,location=no,directories=no,status=no,menubar=no," + - "scrollbars=no,resizable=yes,width=640,height=480"); - } - } else { - window.open(this.popupURL("fullscreen.html"), "ha_fullscreen", - "toolbar=no,menubar=no,personalbar=no,width=640,height=480," + - "scrollbars=no,resizable=yes"); - } - break; - case "undo": - case "redo": - if (this._customUndo) - this[cmdID](); - else - this._doc.execCommand(cmdID, UI, param); - break; - case "inserttable": this._insertTable(); break; - case "insertimage": this._insertImage(); break; - case "about" : this._popupDialog("about.html", null, this); break; - case "showhelp" : window.open(_editor_url + "reference.html", "ha_help"); break; + var editor = this; // for nested functions + this.focusEditor(); + cmdID = cmdID.toLowerCase(); + if (HTMLArea.is_gecko) try { this._doc.execCommand('useCSS', false, true); } catch (e) {}; + switch (cmdID) { + case "htmlmode" : this.setMode(); break; + case "hilitecolor": + (HTMLArea.is_ie) && (cmdID = "backcolor"); + case "forecolor": + this._popupDialog("select_color.html", function(color) { + if (color) { // selection not canceled + editor._doc.execCommand(cmdID, false, "#" + color); + } + }, HTMLArea._colorToRgb(this._doc.queryCommandValue(cmdID))); + break; + case "createlink": + this._createLink(); + break; + case "popupeditor": + // this object will be passed to the newly opened window + HTMLArea._object = this; + if (HTMLArea.is_ie) { + //if (confirm(HTMLArea.I18N.msg["IE-sucks-full-screen"])) + { + window.open(this.popupURL("fullscreen.html"), "ha_fullscreen", + "toolbar=no,location=no,directories=no,status=no,menubar=no," + + "scrollbars=no,resizable=yes,width=640,height=480"); + } + } else { + window.open(this.popupURL("fullscreen.html"), "ha_fullscreen", + "toolbar=no,menubar=no,personalbar=no,width=640,height=480," + + "scrollbars=no,resizable=yes"); + } + break; + case "undo": + case "redo": + if (this._customUndo) + this[cmdID](); + else + this._doc.execCommand(cmdID, UI, param); + break; + case "inserttable": this._insertTable(); break; + case "insertimage": this._insertImage(); break; + case "about" : this._popupDialog("about.html", null, this); break; + case "showhelp" : window.open(_editor_url + "reference.html", "ha_help"); break; - case "killword": this._wordClean(); break; + case "killword": this._wordClean(); break; - case "cut": - case "copy": -/* case "paste": - try { - if (this.config.killWordOnPaste) - this._wordClean(); - this._doc.execCommand(cmdID, UI, param); - } catch (e) { - if (HTMLArea.is_gecko) { - if (typeof HTMLArea.I18N.msg["Moz-Clipboard"] == "undefined") { - HTMLArea.I18N.msg["Moz-Clipboard"] = - "Unprivileged scripts cannot access Cut/Copy/Paste programatically " + - "for security reasons. Click OK to see a technical note at mozilla.org " + - "which shows you how to allow a script to access the clipboard.\n\n" + - "[FIXME: please translate this message in your language definition file.]"; - } - if (confirm(HTMLArea.I18N.msg["Moz-Clipboard"])) - window.open("http://mozilla.org/editor/midasdemo/securityprefs.html"); - } - } - break; - */ - case "lefttoright": - case "righttoleft": - var dir = (cmdID == "righttoleft") ? "rtl" : "ltr"; - var el = this.getParentElement(); - while (el && !HTMLArea.isBlockElement(el)) - el = el.parentNode; - if (el) { - if (el.style.direction == dir) - el.style.direction = ""; - else - el.style.direction = dir; - } - break; - default: this._doc.execCommand(cmdID, UI, param); - } - this.updateToolbar(); - return false; + case "cut": + case "copy": + case "paste": + try { + this._doc.execCommand(cmdID, UI, param); + if (this.config.killWordOnPaste) + this._wordClean(); + } catch (e) { + if (HTMLArea.is_gecko) { + if (typeof HTMLArea.I18N.msg["Moz-Clipboard"] == "undefined") { + HTMLArea.I18N.msg["Moz-Clipboard"] = + "Unprivileged scripts cannot access Cut/Copy/Paste programatically " + + "for security reasons. Click OK to see a technical note at mozilla.org " + + "which shows you how to allow a script to access the clipboard.\n\n" + + "[FIXME: please translate this message in your language definition file.]"; + } + if (confirm(HTMLArea.I18N.msg["Moz-Clipboard"])) + window.open("http://mozilla.org/editor/midasdemo/securityprefs.html"); + } + } + break; + case "lefttoright": + case "righttoleft": + var dir = (cmdID == "righttoleft") ? "rtl" : "ltr"; + var el = this.getParentElement(); + while (el && !HTMLArea.isBlockElement(el)) + el = el.parentNode; + if (el) { + if (el.style.direction == dir) + el.style.direction = ""; + else + el.style.direction = dir; + } + break; + default: try { this._doc.execCommand(cmdID, UI, param); } + catch(e) { if (this.config.debug) { alert(e + "\n\nby execCommand(" + cmdID + ");"); } } + } + this.updateToolbar(); + return false; }; /** A generic event handler for things that happen in the IFRAME's document. * This function also handles key bindings. */ HTMLArea.prototype._editorEvent = function(ev) { - var editor = this; - var keyEvent = (HTMLArea.is_ie && ev.type == "keydown") || (ev.type == "keypress"); + var editor = this; + var keyEvent = (HTMLArea.is_ie && ev.type == "keydown") || (!HTMLArea.is_ie && ev.type == "keypress"); - if (keyEvent) { - for (var i in editor.plugins) { - var plugin = editor.plugins[i].instance; - if (typeof plugin.onKeyPress == "function") plugin.onKeyPress(ev); - } - } - if (keyEvent && ev.ctrlKey && !ev.altKey) { - var sel = null; - var range = null; - var key = String.fromCharCode(HTMLArea.is_ie ? ev.keyCode : ev.charCode).toLowerCase(); - var cmd = null; - var value = null; - switch (key) { - case 'a': - if (!HTMLArea.is_ie) { - // KEY select all - sel = this._getSelection(); - sel.removeAllRanges(); - range = this._createRange(); - range.selectNodeContents(this._doc.body); - sel.addRange(range); - HTMLArea._stopEvent(ev); - } - break; + if (keyEvent) + for (var i in editor.plugins) { + var plugin = editor.plugins[i].instance; + if (typeof plugin.onKeyPress == "function") + if (plugin.onKeyPress(ev)) + return false; + } + if (keyEvent && ev.ctrlKey && !ev.altKey) { + var sel = null; + var range = null; + var key = String.fromCharCode(HTMLArea.is_ie ? ev.keyCode : ev.charCode).toLowerCase(); + var cmd = null; + var value = null; + switch (key) { + case 'a': + if (!HTMLArea.is_ie) { + // KEY select all + sel = this._getSelection(); + sel.removeAllRanges(); + range = this._createRange(); + range.selectNodeContents(this._doc.body); + sel.addRange(range); + HTMLArea._stopEvent(ev); + } + break; - // simple key commands follow + // simple key commands follow - case 'b': cmd = "bold"; break; - case 'i': cmd = "italic"; break; - case 'u': cmd = "underline"; break; - case 's': cmd = "strikethrough"; break; - case 'l': cmd = "justifyleft"; break; - case 'e': cmd = "justifycenter"; break; - case 'r': cmd = "justifyright"; break; - case 'j': cmd = "justifyfull"; break; - case 'z': cmd = "undo"; break; - case 'y': cmd = "redo"; break; - case 'v': cmd = "paste"; break; + case 'b': cmd = "bold"; break; + case 'i': cmd = "italic"; break; + case 'u': cmd = "underline"; break; + case 's': cmd = "strikethrough"; break; + case 'l': cmd = "justifyleft"; break; + case 'e': cmd = "justifycenter"; break; + case 'r': cmd = "justifyright"; break; + case 'j': cmd = "justifyfull"; break; + case 'z': cmd = "undo"; break; + case 'y': cmd = "redo"; break; + case 'v': if (HTMLArea.is_ie || editor.config.htmlareaPaste) { cmd = "paste"; } break; + case 'n': cmd = "formatblock"; value = HTMLArea.is_ie ? "

        " : "p"; break; - case '0': cmd = "killword"; break; + case '0': cmd = "killword"; break; - // headings - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - cmd = "formatblock"; - value = "h" + key; - if (HTMLArea.is_ie) { - value = "<" + value + ">"; - } - break; - } - if (cmd) { - // execute simple command - this.execCommand(cmd, false, value); - HTMLArea._stopEvent(ev); - } - } - /* - else if (keyEvent) { - // other keys here - switch (ev.keyCode) { - case 13: // KEY enter - // if (HTMLArea.is_ie) { - this.insertHTML("
        "); - HTMLArea._stopEvent(ev); - // } - break; - } - } - */ - // update the toolbar state after some time - if (editor._timerToolbar) { - clearTimeout(editor._timerToolbar); - } - editor._timerToolbar = setTimeout(function() { - editor.updateToolbar(); - editor._timerToolbar = null; - }, 50); + // headings + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + cmd = "formatblock"; + value = "h" + key; + if (HTMLArea.is_ie) + value = "<" + value + ">"; + break; + } + if (cmd) { + // execute simple command + this.execCommand(cmd, false, value); + HTMLArea._stopEvent(ev); + } + } + else if (keyEvent) { + // other keys here + switch (ev.keyCode) { + case 13: // KEY enter + if (HTMLArea.is_gecko && !ev.shiftKey) { + this.dom_checkInsertP(); + HTMLArea._stopEvent(ev); + } + break; + case 8: // KEY backspace + case 46: // KEY delete + if (HTMLArea.is_gecko && !ev.shiftKey) { + if (this.dom_checkBackspace()) + HTMLArea._stopEvent(ev); + } else if (HTMLArea.is_ie) { + if (this.ie_checkBackspace()) + HTMLArea._stopEvent(ev); + } + break; + } + } + + // update the toolbar state after some time + if (editor._timerToolbar) { + clearTimeout(editor._timerToolbar); + } + editor._timerToolbar = setTimeout(function() { + editor.updateToolbar(); + editor._timerToolbar = null; + }, 100); }; +HTMLArea.prototype.convertNode = function(el, newTagName) { + var newel = this._doc.createElement(newTagName); + while (el.firstChild) + newel.appendChild(el.firstChild); + return newel; +}; + +HTMLArea.prototype.ie_checkBackspace = function() { + var sel = this._getSelection(); + var range = this._createRange(sel); + var r2 = range.duplicate(); + r2.moveStart("character", -1); + var a = r2.parentElement(); + if (a != range.parentElement() && + /^a$/i.test(a.tagName)) { + r2.collapse(true); + r2.moveEnd("character", 1); + r2.pasteHTML(''); + r2.select(); + return true; + } +}; + +HTMLArea.prototype.dom_checkBackspace = function() { + var self = this; + setTimeout(function() { + var sel = self._getSelection(); + var range = self._createRange(sel); + var SC = range.startContainer; + var SO = range.startOffset; + var EC = range.endContainer; + var EO = range.endOffset; + var newr = SC.nextSibling; + if (SC.nodeType == 3) + SC = SC.parentNode; + if (!/\S/.test(SC.tagName)) { + var p = document.createElement("p"); + while (SC.firstChild) + p.appendChild(SC.firstChild); + SC.parentNode.insertBefore(p, SC); + SC.parentNode.removeChild(SC); + var r = range.cloneRange(); + r.setStartBefore(newr); + r.setEndAfter(newr); + r.extractContents(); + sel.removeAllRanges(); + sel.addRange(r); + } + }, 10); +}; + +/** The idea here is + * 1. See if we are in a block element + * 2. If we are not, then wrap the current "block" of text into a paragraph + * 3. Now that we have a block element, select all the text between the insertion point + * and just AFTER the end of the block + * eg

        The quick |brown fox jumped over the lazy dog.

        | + * --------------------------------------- + * 4. Extract that from the document, making + *

        The quick

        + * and a document fragment with + *

        brown fox jumped over the lazy dog.

        + * 5. Reinsert it just after the block element + *

        The quick

        brown fox jumped over the lazy dog.

        + * + * Along the way, allow inserting blank paragraphs, which will look like


        + */ + +HTMLArea.prototype.dom_checkInsertP = function() { + + // Get the insertion point, we'll scrub any highlighted text the user wants rid of while we are there. + var sel = this._getSelection(); + var range = this._createRange(sel); + if (!range.collapsed) + { + range.deleteContents(); + } + this.deactivateEditor(); + //sel.removeAllRanges(); + //sel.addRange(range); + + var SC = range.startContainer; + var SO = range.startOffset; + var EC = range.endContainer; + var EO = range.endOffset; + + // If the insertion point is character 0 of the + // document, then insert a space character that we will wrap into a paragraph + // in a bit. + if (SC == EC && SC == body && !SO && !EO) + { + p = this._doc.createTextNode(" "); + body.insertBefore(p, body.firstChild); + range.selectNodeContents(p); + SC = range.startContainer; + SO = range.startOffset; + EC = range.endContainer; + EO = range.endOffset; + } + + // See if we are in a block element, if so, great. + var p = this.getAllAncestors(); + + var block = null; + var body = this._doc.body; + for (var i = 0; i < p.length; ++i) + { + if(HTMLArea.isParaContainer(p[i])) + { + break; + } + else if (HTMLArea.isBlockElement(p[i]) && !/body|html/i.test(p[i].tagName)) + { + block = p[i]; + break; + } + } + + // If not in a block element, we'll have to turn some stuff into a paragraph + if (!block) + { + // We want to wrap as much stuff as possible into the paragraph in both directions + // from the insertion point. We start with the start container and walk back up to the + // node just before any of the paragraph containers. + var wrap = range.startContainer; + while(wrap.parentNode && !HTMLArea.isParaContainer(wrap.parentNode)) + { + wrap = wrap.parentNode; + } + var start = wrap; + var end = wrap; + + // Now we walk up the sibling list until we hit the top of the document + // or an element that we shouldn't put in a p (eg other p, div, ul, ol, table) + while(start.previousSibling) + { + if(start.previousSibling.tagName) + { + if(!HTMLArea.isBlockElement(start.previousSibling)) + { + start = start.previousSibling; + } + else + { + break; + } + } + else + { + start = start.previousSibling; + } + } + + // Same down the list + while(end.nextSibling) + { + if(end.nextSibling.tagName) + { + if(!HTMLArea.isBlockElement(end.nextSibling)) + { + end = end.nextSibling; + } + else + { + break; + } + } + else + { + end = end.nextSibling; + } + } + + // Select the entire block + range.setStartBefore(start); + range.setEndAfter(end); + + // Make it a paragraph + range.surroundContents(this._doc.createElement('p')); + + // Which becomes the block element + block = range.startContainer.firstChild; + + // And finally reset the insertion point to where it was originally + range.setStart(SC, SO); + } + + // The start point is the insertion point, so just move the end point to immediatly + // after the block + range.setEndAfter(block); + + // Extract the range, to split the block + // If we just did range.extractContents() then Mozilla does wierd stuff + // with selections, but if we clone, then remove the original range and extract + // the clone, it's quite happy. + var r2 = range.cloneRange(); + sel.removeRange(range); + var df = r2.extractContents(); + + if(df.childNodes.length == 0) + { + df.appendChild(this._doc.createElement('p')); + df.firstChild.appendChild(this._doc.createElement('br')); + } + + if(df.childNodes.length > 1) + { + var nb = this._doc.createElement('p'); + while(df.firstChild) + { + var s = df.firstChild; + df.removeChild(s); + nb.appendChild(s); + } + df.appendChild(nb); + } + + // If the original block is empty, put a nsbp in it. + if (!/\S/.test(block.innerHTML)) + block.innerHTML = " "; + + p = df.firstChild; + if (!/\S/.test(p.innerHTML)) + p.innerHTML = "
        "; + + // If the new block is empty and it's a heading, make it a paragraph + // note, the new block is empty when you are hitting enter at the end of the existing block + if (/^\s*\s*$/.test(p.innerHTML) && /^h[1-6]$/i.test(p.tagName)) + { + df.appendChild(this.convertNode(p, "p")); + df.removeChild(p); + } + + var newblock = block.parentNode.insertBefore(df.firstChild, block.nextSibling); + + /* + if(block.nextSibling) + { + block.parentNode.insertBefore(df, block.nextSibling); + } + else + { + block.parentNode.appendChild(df); + } + */ + + // Select the range (to set the insertion) + // collapse to the start of the new block + // (remember the block might be


        , so if we collapsed to the end the
        would be noticable) + + //range.selectNode(newblock.firstChild); + //range.collapse(true); + + this.activateEditor(); + + var sel = this._getSelection(); + sel.removeAllRanges(); + sel.collapse(newblock,0); + + // scroll into view + this.scrollToElement(newblock); + + //this.forceRedraw(); + +}; + +HTMLArea.prototype.scrollToElement = function(e) +{ + if(HTMLArea.is_gecko) + { + var top = 0; + var left = 0; + while(e) + { + top += e.offsetTop; + left += e.offsetLeft; + if(e.offsetParent && e.offsetParent.tagName.toLowerCase() != 'body') + { + e = e.offsetParent; + } + else + { + e = null; + } + } + this._iframe.contentWindow.scrollTo(left, top); + } +} + // retrieve the HTML HTMLArea.prototype.getHTML = function() { - switch (this._editMode) { - case "wysiwyg" : - if (!this.config.fullPage) { - return HTMLArea.getHTML(this._doc.body, false, this); - } else - return this.doctype + "\n" + HTMLArea.getHTML(this._doc.documentElement, true, this); - case "textmode" : return this._textArea.value; - default : alert("Mode <" + mode + "> not defined!"); - } - return false; + var html = ''; + switch (this._editMode) { + case "wysiwyg" : + { + if (!this.config.fullPage) + html = HTMLArea.getHTML(this._doc.body, false, this); + else + html = this.doctype + "\n" + HTMLArea.getHTML(this._doc.documentElement, true, this); + break; + } + case "textmode" : + { + html = this._textArea.value; + break; + } + default : + { + alert("Mode <" + mode + "> not defined!"); + return false; + } + } + return html; }; +HTMLArea.prototype.outwardHtml = function(html) +{ + html = html.replace(/<(\/?)b(\s|>|\/)/ig, "<$1strong$2"); + html = html.replace(/<(\/?)i(\s|>|\/)/ig, "<$1em$2"); + + // Figure out what our server name is, and how it's referenced + var serverBase = location.href.replace(/(https?:\/\/[^\/]*)\/.*/, '$1') + '/'; + + // IE puts this in can't figure out why + html = html.replace(/https?:\/\/null\//g, serverBase); + + // Make semi-absolute links to be truely absolute + // we do this just to standardize so that special replacements knows what + // to expect + html = html.replace(/((href|src|background)=[\'\"])\/+/ig, '$1' + serverBase); + + html = this.outwardSpecialReplacements(html); + + html = this.fixRelativeLinks(html); + return html; +} + +HTMLArea.prototype.inwardHtml = function(html) +{ + // Midas uses b and i instead of strong and em, um, hello, + // mozilla, this is the 21st century calling! + if (HTMLArea.is_gecko) { + html = html.replace(/<(\/?)strong(\s|>|\/)/ig, "<$1b$2"); + html = html.replace(/<(\/?)em(\s|>|\/)/ig, "<$1i$2"); + } + html = this.inwardSpecialReplacements(html); + + // For IE's sake, make any URLs that are semi-absolute (="/....") to be + // truely absolute + var nullRE = new RegExp('((href|src|background)=[\'"])/+', 'gi'); + html = html.replace(nullRE, '$1' + location.href.replace(/(https?:\/\/[^\/]*)\/.*/, '$1') + '/'); + + html = this.fixRelativeLinks(html); + return html; +} + +HTMLArea.prototype.outwardSpecialReplacements = function(html) +{ + for(var i in this.config.specialReplacements) + { + var from = this.config.specialReplacements[i]; + var to = i; + // alert('out : ' + from + '=>' + to); + var reg = new RegExp(from.replace(HTMLArea.RE_Specials, '\\$1'), 'g'); + html = html.replace(reg, to.replace(/\$/g, '$$$$')); + //html = html.replace(from, to); + } + return html; +} + +HTMLArea.prototype.inwardSpecialReplacements = function(html) +{ + // alert("inward"); + for(var i in this.config.specialReplacements) + { + var from = i; + var to = this.config.specialReplacements[i]; + // alert('in : ' + from + '=>' + to); + // + // html = html.replace(reg, to); + // html = html.replace(from, to); + var reg = new RegExp(from.replace(HTMLArea.RE_Specials, '\\$1'), 'g'); + html = html.replace(reg, to.replace(/\$/g, '$$$$')); // IE uses doubled dollar signs to escape backrefs, also beware that IE also implements $& $_ and $' like perl. + } + return html; +} + + +HTMLArea.prototype.fixRelativeLinks = function(html) +{ + + if(typeof this.config.stripSelfNamedAnchors != 'undefined' && this.config.stripSelfNamedAnchors) + { + var stripRe = new RegExp(document.location.href.replace(HTMLArea.RE_Specials, '\\$1') + '(#.*)', 'g'); + html = html.replace(stripRe, '$1'); + } + + + if(typeof this.config.stripBaseHref != 'undefined' && this.config.stipBaseHref) + { + var baseRe = null + if(typeof this.config.baseHref != 'undefined' && this.config.baseHref != null) + { + baseRe = new RegExp(this.config.baseHref.replace(HTMLArea.RE_Specials, '\\$1'), 'g'); + } + else + { + baseRe = new RegExp(document.location.href.replace(/([^\/]*\/?)$/, '').replace(HTMLArea.RE_Specials, '\\$1'), 'g'); + } + + html = html.replace(baseRe, ''); + } + + if(HTMLArea.is_ie) + { + // This is now done in inward & outward + // Don't know why but IE is doing this (putting http://null/ on links?! + // alert(html); + // var nullRE = new RegExp('https?:\/\/null\/', 'g'); + // html = html.replace(nullRE, location.href.replace(/(https?:\/\/[^\/]*\/).*/, '$1')); + // alert(html); + } + + return html; +} + // retrieve the HTML (fastest version, but uses innerHTML) HTMLArea.prototype.getInnerHTML = function() { - switch (this._editMode) { - case "wysiwyg" : - if (!this.config.fullPage) - return this._doc.body.innerHTML; - else - return this.doctype + "\n" + this._doc.documentElement.innerHTML; - case "textmode" : return this._textArea.value; - default : alert("Mode <" + mode + "> not defined!"); - } - return false; + if(!this._doc.body) return ''; + switch (this._editMode) { + case "wysiwyg" : + if (!this.config.fullPage) + // return this._doc.body.innerHTML; + html = this._doc.body.innerHTML; + else + html = this.doctype + "\n" + this._doc.documentElement.innerHTML; + break; + case "textmode" : + html = this._textArea.value; + break; + default : + alert("Mode <" + mode + "> not defined!"); + return false; + } + + return html; }; // completely change the HTML inside HTMLArea.prototype.setHTML = function(html) { - switch (this._editMode) { - case "wysiwyg" : - if (!this.config.fullPage) - this._doc.body.innerHTML = html; - else - // this._doc.documentElement.innerHTML = html; - this._doc.body.innerHTML = html; - break; - case "textmode" : this._textArea.value = html; break; - default : alert("Mode <" + mode + "> not defined!"); - } - return false; + switch (this._editMode) { + case "wysiwyg" : + if (!this.config.fullPage) + this._doc.body.innerHTML = html; + else + // this._doc.documentElement.innerHTML = html; + this._doc.body.innerHTML = html; + break; + case "textmode" : this._textArea.value = html; break; + default : alert("Mode <" + mode + "> not defined!"); + } + return false; }; // sets the given doctype (useful when config.fullPage is true) HTMLArea.prototype.setDoctype = function(doctype) { - this.doctype = doctype; + this.doctype = doctype; }; /*************************************************** * Category: UTILITY FUNCTIONS ***************************************************/ -// browser identification - -HTMLArea.agt = navigator.userAgent.toLowerCase(); -HTMLArea.is_ie = ((HTMLArea.agt.indexOf("msie") != -1) && (HTMLArea.agt.indexOf("opera") == -1)); -HTMLArea.is_opera = (HTMLArea.agt.indexOf("opera") != -1); -HTMLArea.is_mac = (HTMLArea.agt.indexOf("mac") != -1); -HTMLArea.is_mac_ie = (HTMLArea.is_ie && HTMLArea.is_mac); -HTMLArea.is_win_ie = (HTMLArea.is_ie && !HTMLArea.is_mac); -HTMLArea.is_gecko = (navigator.product == "Gecko"); - // variable used to pass the object to the popup editor window. HTMLArea._object = null; // function that returns a clone of the given object HTMLArea.cloneObject = function(obj) { - var newObj = new Object; + if (!obj) return null; + var newObj = new Object; - // check for array objects - if (obj.constructor.toString().indexOf("function Array(") == 1) { - newObj = obj.constructor(); - } + // check for array objects + if (obj.constructor.toString().indexOf("function Array(") == 1) { + newObj = obj.constructor(); + } - // check for function objects (as usual, IE is fucked up) - if (obj.constructor.toString().indexOf("function Function(") == 1) { - newObj = obj; // just copy reference to it - } else for (var n in obj) { - var node = obj[n]; - if (typeof node == 'object') { newObj[n] = HTMLArea.cloneObject(node); } - else { newObj[n] = node; } - } + // check for function objects (as usual, IE is fucked up) + if (obj.constructor.toString().indexOf("function Function(") == 1) { + newObj = obj; // just copy reference to it + } else for (var n in obj) { + var node = obj[n]; + if (typeof node == 'object') { newObj[n] = HTMLArea.cloneObject(node); } + else { newObj[n] = node; } + } - return newObj; + return newObj; }; // FIXME!!! this should return false for IE < 5.5 HTMLArea.checkSupportedBrowser = function() { - if (HTMLArea.is_gecko) { - if (navigator.productSub < 20021201) { - alert("You need at least Mozilla-1.3 Alpha.\n" + - "Sorry, your Gecko is not supported."); - return false; - } - if (navigator.productSub < 20030210) { - alert("Mozilla < 1.3 Beta is not supported!\n" + - "I'll try, though, but it might not work."); - } - } - return HTMLArea.is_gecko || HTMLArea.is_ie; + if (HTMLArea.is_gecko) { + if (navigator.productSub < 20021201) { + alert("You need at least Mozilla-1.3 Alpha.\n" + + "Sorry, your Gecko is not supported."); + return false; + } + if (navigator.productSub < 20030210) { + alert("Mozilla < 1.3 Beta is not supported!\n" + + "I'll try, though, but it might not work."); + } + } + return HTMLArea.is_gecko || HTMLArea.is_ie; }; // selection & ranges // returns the current selection object HTMLArea.prototype._getSelection = function() { - if (HTMLArea.is_ie) { - return this._doc.selection; - } else { - return this._iframe.contentWindow.getSelection(); - } + if (HTMLArea.is_ie) { + return this._doc.selection; + } else { + return this._iframe.contentWindow.getSelection(); + } }; // returns a range for the current selection HTMLArea.prototype._createRange = function(sel) { - if (HTMLArea.is_ie) { - return sel.createRange(); - } else { - this.focusEditor(); - if (typeof sel != "undefined") { - try { - return sel.getRangeAt(0); - } catch(e) { - return this._doc.createRange(); - } - } else { - return this._doc.createRange(); - } - } + if (HTMLArea.is_ie) { + return sel.createRange(); + } else { + this.focusEditor(); + if (typeof sel != "undefined") { + try { + return sel.getRangeAt(0); + } catch(e) { + return this._doc.createRange(); + } + } else { + return this._doc.createRange(); + } + } }; // event handling HTMLArea._addEvent = function(el, evname, func) { - if (HTMLArea.is_ie) { - el.attachEvent("on" + evname, func); - } else { - el.addEventListener(evname, func, true); - } + if (HTMLArea.is_ie) { + el.attachEvent("on" + evname, func); + } else { + el.addEventListener(evname, func, true); + } }; HTMLArea._addEvents = function(el, evs, func) { - for (var i in evs) { - HTMLArea._addEvent(el, evs[i], func); - } + for (var i = evs.length; --i >= 0;) { + HTMLArea._addEvent(el, evs[i], func); + } }; HTMLArea._removeEvent = function(el, evname, func) { - if (HTMLArea.is_ie) { - el.detachEvent("on" + evname, func); - } else { - el.removeEventListener(evname, func, true); - } + if (HTMLArea.is_ie) { + el.detachEvent("on" + evname, func); + } else { + el.removeEventListener(evname, func, true); + } }; HTMLArea._removeEvents = function(el, evs, func) { - for (var i in evs) { - HTMLArea._removeEvent(el, evs[i], func); - } + for (var i = evs.length; --i >= 0;) { + HTMLArea._removeEvent(el, evs[i], func); + } }; HTMLArea._stopEvent = function(ev) { - if (HTMLArea.is_ie) { - ev.cancelBubble = true; - ev.returnValue = false; - } else { - ev.preventDefault(); - ev.stopPropagation(); - } + if (HTMLArea.is_ie) { + ev.cancelBubble = true; + ev.returnValue = false; + } else { + ev.preventDefault(); + ev.stopPropagation(); + } }; + +HTMLArea.prototype.notifyOn = function(ev, fn) +{ + if(typeof this._notifyListeners[ev] == 'undefined') + { + this._notifyListeners[ev] = [ ]; + } + + this._notifyListeners[ev].push(fn); +} + +HTMLArea.prototype.notifyOf = function(ev, args) +{ + + if(this._notifyListeners[ev]) + { + + for(var i = 0; i < this._notifyListeners[ev].length; i++) + { + this._notifyListeners[ev][i](ev, args); + } + } +} + + HTMLArea._removeClass = function(el, className) { - if (!(el && el.className)) { - return; - } - var cls = el.className.split(" "); - var ar = new Array(); - for (var i = cls.length; i > 0;) { - if (cls[--i] != className) { - ar[ar.length] = cls[i]; - } - } - el.className = ar.join(" "); + if (!(el && el.className)) { + return; + } + var cls = el.className.split(" "); + var ar = new Array(); + for (var i = cls.length; i > 0;) { + if (cls[--i] != className) { + ar[ar.length] = cls[i]; + } + } + el.className = ar.join(" "); }; HTMLArea._addClass = function(el, className) { - // remove the class first, if already there - HTMLArea._removeClass(el, className); - el.className += " " + className; + // remove the class first, if already there + HTMLArea._removeClass(el, className); + el.className += " " + className; }; HTMLArea._hasClass = function(el, className) { - if (!(el && el.className)) { - return false; - } - var cls = el.className.split(" "); - for (var i = cls.length; i > 0;) { - if (cls[--i] == className) { - return true; - } - } - return false; + if (!(el && el.className)) { + return false; + } + var cls = el.className.split(" "); + for (var i = cls.length; i > 0;) { + if (cls[--i] == className) { + return true; + } + } + return false; }; +HTMLArea._blockTags = " body form textarea fieldset ul ol dl li div " + +"p h1 h2 h3 h4 h5 h6 quote pre table thead " + +"tbody tfoot tr td th iframe address blockquote"; HTMLArea.isBlockElement = function(el) { - var blockTags = " body form textarea fieldset ul ol dl li div " + - "p h1 h2 h3 h4 h5 h6 quote pre table thead " + - "tbody tfoot tr td iframe address "; - return (blockTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1); + return el && el.nodeType == 1 && (HTMLArea._blockTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1); }; +HTMLArea._paraContainerTags = " body td th caption fieldset div"; +HTMLArea.isParaContainer = function(el) +{ + return el && el.nodeType == 1 && (HTMLArea._paraContainerTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1); +} + +HTMLArea._closingTags = " head script style div span tr td tbody table em strong b i code cite dfn abbr acronym font a title "; HTMLArea.needsClosingTag = function(el) { - var closingTags = " head script style div span tr td tbody table em strong font a title "; - return (closingTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1); + return el && el.nodeType == 1 && (HTMLArea._closingTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1); }; // performs HTML encoding of some given string HTMLArea.htmlEncode = function(str) { - // we don't need regexp for that, but.. so be it for now. - str = str.replace(/&/ig, "&"); - str = str.replace(//ig, ">"); - str = str.replace(/\x22/ig, """); - // \x22 means '"' -- we use hex reprezentation so that we don't disturb - // JS compressors (well, at least mine fails.. ;) - return str; + if(typeof str.replace == 'undefined') str = str.toString(); + // we don't need regexp for that, but.. so be it for now. + str = str.replace(/&/ig, "&"); + str = str.replace(//ig, ">"); + str = str.replace(/\xA0/g, " "); // Decimal 160, non-breaking-space + str = str.replace(/\x22/g, """); + // \x22 means '"' -- we use hex reprezentation so that we don't disturb + // JS compressors (well, at least mine fails.. ;) + return str; }; // Retrieves the HTML code from the given node. This is a replacement for // getting innerHTML, using standard DOM calls. -HTMLArea.getHTML = function(root, outputRoot, editor) { - var html = ""; - switch (root.nodeType) { - case 1: // Node.ELEMENT_NODE - case 11: // Node.DOCUMENT_FRAGMENT_NODE - var closed; - var i; - var root_tag = (root.nodeType == 1) ? root.tagName.toLowerCase() : ''; - if (HTMLArea.is_ie && root_tag == "head") { - if (outputRoot) - html += ""; - // lowercasize - var save_multiline = RegExp.multiline; - RegExp.multiline = true; - var txt = root.innerHTML.replace(HTMLArea.RE_tagName, function(str, p1, p2) { - return p1 + p2.toLowerCase(); - }); - RegExp.multiline = save_multiline; - html += txt; - if (outputRoot) - html += ""; - break; - } else if (outputRoot) { - closed = (!(root.hasChildNodes() || HTMLArea.needsClosingTag(root))); - html = "<" + root.tagName.toLowerCase(); - var attrs = root.attributes; - for (i = 0; i < attrs.length; ++i) { - var a = attrs.item(i); - if (!a.specified) { - continue; - } - var name = a.nodeName.toLowerCase(); - if (/_moz_editor_bogus_node/.test(name)) { - html = ""; - break; - } - if (/_moz|contenteditable|_msh/.test(name)) { - // avoid certain attributes - continue; - } - var value; - if (name != "style") { - // IE5.5 reports 25 when cellSpacing is - // 1; other values might be doomed too. - // For this reason we extract the - // values directly from the root node. - // I'm starting to HATE JavaScript - // development. Browser differences - // suck. - // - // Using Gecko the values of href and src are converted to absolute links - // unless we get them using nodeValue() - if (typeof root[a.nodeName] != "undefined" && name != "href" && name != "src") { - value = root[a.nodeName]; - } else { - value = a.nodeValue; - // IE seems not willing to return the original values - it converts to absolute - // links using a.nodeValue, a.value, a.stringValue, root.getAttribute("href") - // So we have to strip the baseurl manually -/ - if (HTMLArea.is_ie && (name == "href" || name == "src")) { - value = editor.stripBaseURL(value); - } - } - } else { // IE fails to put style in attributes list - // FIXME: cssText reported by IE is UPPERCASE - value = root.style.cssText; - } - if (/(_moz|^$)/.test(value)) { - // Mozilla reports some special tags - // here; we don't need them. - continue; - } - html += " " + name + '="' + value + '"'; - } - if (html != "") { - html += closed ? " />" : ">"; - } - } - for (i = root.firstChild; i; i = i.nextSibling) { - html += HTMLArea.getHTML(i, true, editor); - } - if (outputRoot && !closed) { - html += ""; - } - break; - case 3: // Node.TEXT_NODE - // If a text node is alone in an element and all spaces, replace it with an non breaking one - // This partially undoes the damage done by moz, which translates ' 's into spaces in the data element - if ( !root.previousSibling && !root.nextSibling && root.data.match(/^\s*$/i) ) html = ' '; - else html = /^script|style$/i.test(root.parentNode.tagName) ? root.data : HTMLArea.htmlEncode(root.data); - break; - case 4: // Node.CDATA_SECTION_NODE - // FIXME: it seems we never get here, but I believe we should.. - // maybe a browser problem?--CDATA sections are converted to plain text nodes and normalized - // CDATA sections should go "as is" without further encoding - html = ""; - break; - case 8: // Node.COMMENT_NODE - html = ""; - break; // skip comments, for now. - } - return html; +// Wrapper catch a Mozilla-Exception with non well formed html source code +HTMLArea.getHTML = function(root, outputRoot, editor){ + try{ + return HTMLArea.getHTMLWrapper(root,outputRoot,editor); + } + catch(e){ + alert('Your Document is not well formed. Check JavaScript console for details.'); + return editor._iframe.contentWindow.document.body.innerHTML; + } +} + +HTMLArea.getHTMLWrapper = function(root, outputRoot, editor) { + var html = ""; + switch (root.nodeType) { + case 10:// Node.DOCUMENT_TYPE_NODE + case 6: // Node.ENTITY_NODE + case 12:// Node.NOTATION_NODE + // this all are for the document type, probably not necessary + break; + + case 2: // Node.ATTRIBUTE_NODE + // Never get here, this has to be handled in the ELEMENT case because + // of IE crapness requring that some attributes are grabbed directly from + // the attribute (nodeValue doesn't return correct values), see + //http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&oe=UTF-8&safe=off&selm=3porgu4mc4ofcoa1uqkf7u8kvv064kjjb4%404ax.com + // for information + break; + + case 4: // Node.CDATA_SECTION_NODE + // Mozilla seems to convert CDATA into a comment when going into wysiwyg mode, + // don't know about IE + html += ''; + break; + + case 5: // Node.ENTITY_REFERENCE_NODE + html += '&' + root.nodeValue + ';'; + break; + + case 7: // Node.PROCESSING_INSTRUCTION_NODE + // PI's don't seem to survive going into the wysiwyg mode, (at least in moz) + // so this is purely academic + html += ''; + break; + + + case 1: // Node.ELEMENT_NODE + case 11: // Node.DOCUMENT_FRAGMENT_NODE + case 9: // Node.DOCUMENT_NODE + { + var closed; + var i; + var root_tag = (root.nodeType == 1) ? root.tagName.toLowerCase() : ''; + if (root_tag == 'br' && !root.nextSibling) + break; + if (outputRoot) + outputRoot = !(editor.config.htmlRemoveTags && editor.config.htmlRemoveTags.test(root_tag)); + if (HTMLArea.is_ie && root_tag == "head") { + if (outputRoot) + html += ""; + // lowercasize + var save_multiline = RegExp.multiline; + RegExp.multiline = true; + var txt = root.innerHTML.replace(HTMLArea.RE_tagName, function(str, p1, p2) { + return p1 + p2.toLowerCase(); + }); + RegExp.multiline = save_multiline; + html += txt; + if (outputRoot) + html += ""; + break; + } else if (outputRoot) { + closed = (!(root.hasChildNodes() || HTMLArea.needsClosingTag(root))); + html = "<" + root.tagName.toLowerCase(); + var attrs = root.attributes; + for (i = 0; i < attrs.length; ++i) { + var a = attrs.item(i); + if (!a.specified) { + continue; + } + var name = a.nodeName.toLowerCase(); + if (/_moz_editor_bogus_node/.test(name)) { + html = ""; + break; + } + if (/(_moz)|(contenteditable)|(_msh)/.test(name)) { + // avoid certain attributes + continue; + } + var value; + if (name != "style") { + // IE5.5 reports 25 when cellSpacing is + // 1; other values might be doomed too. + // For this reason we extract the + // values directly from the root node. + // I'm starting to HATE JavaScript + // development. Browser differences + // suck. + // + // Using Gecko the values of href and src are converted to absolute links + // unless we get them using nodeValue() + if (typeof root[a.nodeName] != "undefined" && name != "href" && name != "src" && !/^on/.test(name)) { + value = root[a.nodeName]; + } else { + value = a.nodeValue; + // IE seems not willing to return the original values - it converts to absolute + // links using a.nodeValue, a.value, a.stringValue, root.getAttribute("href") + // So we have to strip the baseurl manually :-/ + if (HTMLArea.is_ie && (name == "href" || name == "src")) { + value = editor.stripBaseURL(value); + } + } + } else { // IE fails to put style in attributes list + // FIXME: cssText reported by IE is UPPERCASE + value = root.style.cssText; + } + if (/^(_moz)?$/.test(value)) { + // Mozilla reports some special tags + // here; we don't need them. + continue; + } + html += " " + name + '="' + HTMLArea.htmlEncode(value) + '"'; + } + if (html != "") { + html += closed ? " />" : ">"; + } + } + for (i = root.firstChild; i; i = i.nextSibling) { + html += HTMLArea.getHTMLWrapper(i, true, editor); + } + if (outputRoot && !closed) { + html += ""; + } + break; + } + case 3: // Node.TEXT_NODE + + // If a text node is alone in an element and all spaces, replace it with an non breaking one + // This partially undoes the damage done by moz, which translates ' 's into spaces in the data element + + html = /^script|style$/i.test(root.parentNode.tagName) ? root.data : HTMLArea.htmlEncode(root.data); + break; + + case 8: // Node.COMMENT_NODE + html = ""; + break; // skip comments, for now. + } + return html; }; HTMLArea.prototype.stripBaseURL = function(string) { - var baseurl = this.config.baseURL; + var baseurl = this.config.baseURL; - // strip to last directory in case baseurl points to a file - baseurl = baseurl.replace(/[^\/]+$/, ''); - var basere = new RegExp(baseurl); - string = string.replace(basere, ""); + // strip to last directory in case baseurl points to a file + baseurl = baseurl.replace(/[^\/]+$/, ''); + var basere = new RegExp(baseurl); + string = string.replace(basere, ""); - // strip host-part of URL which is added by MSIE to links relative to server root - baseurl = baseurl.replace(/^(https?:\/\/[^\/]+)(.*)$/, '$1'); - basere = new RegExp(baseurl); - return string.replace(basere, ""); + // strip host-part of URL which is added by MSIE to links relative to server root + baseurl = baseurl.replace(/^(https?:\/\/[^\/]+)(.*)$/, '$1'); + basere = new RegExp(baseurl); + return string.replace(basere, ""); }; String.prototype.trim = function() { - a = this.replace(/^\s+/, ''); - return a.replace(/\s+$/, ''); + return this.replace(/^\s+/, '').replace(/\s+$/, ''); }; // creates a rgb-style color from a number HTMLArea._makeColor = function(v) { - if (typeof v != "number") { - // already in rgb (hopefully); IE doesn't get here. - return v; - } - // IE sends number; convert to rgb. - var r = v & 0xFF; - var g = (v >> 8) & 0xFF; - var b = (v >> 16) & 0xFF; - return "rgb(" + r + "," + g + "," + b + ")"; + if (typeof v != "number") { + // already in rgb (hopefully); IE doesn't get here. + return v; + } + // IE sends number; convert to rgb. + var r = v & 0xFF; + var g = (v >> 8) & 0xFF; + var b = (v >> 16) & 0xFF; + return "rgb(" + r + "," + g + "," + b + ")"; }; // returns hexadecimal color representation from a number or a rgb-style color. HTMLArea._colorToRgb = function(v) { - if (!v) - return ''; + if (!v) + return ''; - // returns the hex representation of one byte (2 digits) - function hex(d) { - return (d < 16) ? ("0" + d.toString(16)) : d.toString(16); - }; + // returns the hex representation of one byte (2 digits) + function hex(d) { + return (d < 16) ? ("0" + d.toString(16)) : d.toString(16); + }; - if (typeof v == "number") { - // we're talking to IE here - var r = v & 0xFF; - var g = (v >> 8) & 0xFF; - var b = (v >> 16) & 0xFF; - return "#" + hex(r) + hex(g) + hex(b); - } + if (typeof v == "number") { + // we're talking to IE here + var r = v & 0xFF; + var g = (v >> 8) & 0xFF; + var b = (v >> 16) & 0xFF; + return "#" + hex(r) + hex(g) + hex(b); + } - if (v.substr(0, 3) == "rgb") { - // in rgb(...) form -- Mozilla - var re = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/; - if (v.match(re)) { - var r = parseInt(RegExp.$1); - var g = parseInt(RegExp.$2); - var b = parseInt(RegExp.$3); - return "#" + hex(r) + hex(g) + hex(b); - } - // doesn't match RE?! maybe uses percentages or float numbers - // -- FIXME: not yet implemented. - return null; - } + if (v.substr(0, 3) == "rgb") { + // in rgb(...) form -- Mozilla + var re = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/; + if (v.match(re)) { + var r = parseInt(RegExp.$1); + var g = parseInt(RegExp.$2); + var b = parseInt(RegExp.$3); + return "#" + hex(r) + hex(g) + hex(b); + } + // doesn't match RE?! maybe uses percentages or float numbers + // -- FIXME: not yet implemented. + return null; + } - if (v.substr(0, 1) == "#") { - // already hex rgb (hopefully :D ) - return v; - } + if (v.substr(0, 1) == "#") { + // already hex rgb (hopefully :D ) + return v; + } - // if everything else fails ;) - return null; + // if everything else fails ;) + return null; }; // modal dialogs for Mozilla (for IE we're using the showModalDialog() call). @@ -2194,29 +3874,29 @@ HTMLArea._colorToRgb = function(v) { // this function will get called after the dialog is closed, with the return // value of the dialog. HTMLArea.prototype._popupDialog = function(url, action, init) { - Dialog(this.popupURL(url), action, init); + Dialog(this.popupURL(url), action, init); }; // paths HTMLArea.prototype.imgURL = function(file, plugin) { - if (typeof plugin == "undefined") - return _editor_url + file; - else - return _editor_url + "plugins/" + plugin + "/img/" + file; + if (typeof plugin == "undefined") + return _editor_url + file; + else + return _editor_url + "plugins/" + plugin + "/img/" + file; }; HTMLArea.prototype.popupURL = function(file) { - var url = ""; - if (file.match(/^plugin:\/\/(.*?)\/(.*)/)) { - var plugin = RegExp.$1; - var popup = RegExp.$2; - if (!/\.html$/.test(popup)) - popup += ".html"; - url = _editor_url + "plugins/" + plugin + "/popups/" + popup; - } else - url = _editor_url + this.config.popupURL + file; - return url; + var url = ""; + if (file.match(/^plugin:\/\/(.*?)\/(.*)/)) { + var plugin = RegExp.$1; + var popup = RegExp.$2; + if (!/\.html$/.test(popup)) + popup += ".html"; + url = _editor_url + "plugins/" + plugin + "/popups/" + popup; + } else + url = _editor_url + this.config.popupURL + file; + return url; }; /** @@ -2226,15 +3906,369 @@ HTMLArea.prototype.popupURL = function(file) { * specifically looking to search only elements having a certain tag name. */ HTMLArea.getElementById = function(tag, id) { - var el, i, objs = document.getElementsByTagName(tag); - for (i = objs.length; --i >= 0 && (el = objs[i]);) - if (el.id == id) - return el; - return null; + var el, i, objs = document.getElementsByTagName(tag); + for (i = objs.length; --i >= 0 && (el = objs[i]);) + if (el.id == id) + return el; + return null; }; +/** Use some CSS trickery to toggle borders on tables */ +HTMLArea.prototype._toggleBorders = function() +{ + tables = this._doc.getElementsByTagName('TABLE'); + if(tables.length != 0) + { + if(!this.borders) + { + name = "bordered"; + this.borders = true; + } + else + { + name = ""; + this.borders = false; + } + + for (var ix=0;ix < tables.length;ix++) + { + if(this.borders) + { + HTMLArea._addClass(tables[ix], 'htmtableborders'); + } + else + { + HTMLArea._removeClass(tables[ix], 'htmtableborders'); + } + } + } + return true; +} + + +HTMLArea.addClasses = function(el, classes) + { + if(el != null) + { + var thiers = el.className.trim().split(' '); + var ours = classes.split(' '); + for(var x = 0; x < ours.length; x++) + { + var exists = false; + for(var i = 0; exists == false && i < thiers.length; i++) + { + if(thiers[i] == ours[x]) + { + exists = true; + } + } + if(exists == false) + { + thiers[thiers.length] = ours[x]; + } + } + el.className = thiers.join(' ').trim(); + } + } + +HTMLArea.removeClasses = function(el, classes) +{ + var existing = el.className.trim().split(); + var new_classes = [ ]; + var remove = classes.trim().split(); + + for(var i = 0; i < existing.length; i++) + { + var found = false; + for(var x = 0; x < remove.length && !found; x++) + { + if(existing[i] == remove[x]) + { + found = true; + } + } + if(!found) + { + new_classes[new_classes.length] = existing[i]; + } + } + return new_classes.join(' '); +} + +/** Alias these for convenience */ +HTMLArea.addClass = HTMLArea._addClass; +HTMLArea.removeClass = HTMLArea._removeClass; +HTMLArea._addClasses = HTMLArea.addClasses; +HTMLArea._removeClasses = HTMLArea.removeClasses; + +/** Use XML HTTPRequest to post some data back to the server and do something + * with the response (asyncronously!), this is used by such things as the tidy functions + */ +HTMLArea._postback = function(url, data, handler) +{ + var req = null; + if(HTMLArea.is_ie) + { + req = new ActiveXObject("Microsoft.XMLHTTP"); + } + else + { + req = new XMLHttpRequest(); + } + + var content = ''; + for(var i in data) + { + content += (content.length ? '&' : '') + i + '=' + escape(data[i]); + } + + function callBack() + { + if(req.readyState == 4) + { + if(req.status == 200) + { + handler(req.responseText, req); + } + else + { + alert('An error has occurred: ' + req.statusText); + } + } + } + + req.onreadystatechange = callBack; + + req.open('POST', url, true); + req.setRequestHeader + ( + 'Content-Type', + 'application/x-www-form-urlencoded; charset=UTF-8' + ); + //alert(content); + req.send(content); +} + +HTMLArea._getback = function(url, handler) +{ + var req = null; + if(HTMLArea.is_ie) + { + req = new ActiveXObject("Microsoft.XMLHTTP"); + } + else + { + req = new XMLHttpRequest(); + } + + function callBack() + { + if(req.readyState == 4) + { + if(req.status == 200) + { + handler(req.responseText, req); + } + else + { + alert('An error has occurred: ' + req.statusText); + } + } + } + + req.onreadystatechange = callBack; + req.open('GET', url, true); + req.send(null); +} + +HTMLArea._geturlcontent = function(url) +{ + var req = null; + if(HTMLArea.is_ie) + { + req = new ActiveXObject("Microsoft.XMLHTTP"); + } + else + { + req = new XMLHttpRequest(); + } + + // Synchronous! + req.open('GET', url, false); + req.send(null); + if(req.status == 200) + { + return req.responseText; + } + else + { + return ''; + } + +} + +/** + * Unless somebody already has, make a little function to debug things + */ +if(typeof dump == 'undefined') +{ + function dump(o) { + var s = ''; + for (var prop in o) { + s += prop + ' = ' + o[prop] + '\n'; + } + + x = window.open("", "debugger"); + x.document.write('
        ' + s + '
        '); + } +} + + +HTMLArea.arrayContainsArray = function(a1, a2) +{ + var all_found = true; + for(var x = 0; x < a2.length; x++) + { + var found = false; + for(var i = 0; i < a1.length; i++) + { + if(a1[i] == a2[x]) + { + found = true; + break; + } + } + if(!found) + { + all_found = false; + break; + } + } + return all_found; +} + +HTMLArea.arrayFilter = function(a1, filterfn) +{ + var new_a = [ ]; + for(var x = 0; x < a1.length; x++) + { + if(filterfn(a1[x])) + new_a[new_a.length] = a1[x]; + } + + return new_a; +} + +HTMLArea.uniq_count = 0; +HTMLArea.uniq = function(prefix) +{ + return prefix + HTMLArea.uniq_count++; +} + +/** New language handling functions **/ + + +/** Load a language file. + * This function should not be used directly, HTMLArea._lc will use it when necessary. + * @param context Case sensitive context name, eg 'HTMLArea', 'TableOperations', ... + * @TODO Make this useful. + */ +HTMLArea._loadlang = function(context) +{ + return { }; +} + +/** Return a localised string. + * @param string English language string + * @param context Case sensitive context name, eg 'HTMLArea' (default), 'TableOperations'... + */ +HTMLArea._lc = function(string, context) +{ + if(typeof HTMLArea._lc_catalog == 'undefined') + { + HTMLArea._lc_catalog = [ ]; + } + + if(typeof context == 'undefined') + { + context = 'HTMLArea'; + } + + if(typeof HTMLArea._lc_catalog[context] == 'undefined') + { + HTMLArea._lc_catalog[context] = HTMLArea._loadlang(context); + } + + if(typeof HTMLArea._lc_catalog[context][string] == 'undefined') + { + return string; // Indicate it's untranslated + } + else + { + return HTMLArea._lc_catalog[context][string]; + } +} + +HTMLArea.hasDisplayedChildren = function(el) +{ + var children = el.childNodes; + for(var i =0; i < children.length;i++) + { + if(children[i].tagName) + { + if(children[i].style.display != 'none') + { + return true; + } + } + } + return false; +} + + +HTMLArea._loadback = function(src, callback) +{ + var head = document.getElementsByTagName("head")[0]; + var evt = HTMLArea.is_ie ? "onreadystatechange" : "onload"; + + var script = document.createElement("script"); + script.type = "text/javascript"; + script.src = src; + script[evt] = function() + { + if(HTMLArea.is_ie && !/loaded|complete/.test(window.event.srcElement.readyState)) return; + callback(); + } + head.appendChild(script); +}; + +HTMLArea.collectionToArray = function(collection) +{ + var array = [ ]; + for(var i = 0; i < collection.length; i++) + { + array.push(collection.item(i)); + } + return array; +} + +if(!Array.prototype.append) +{ + Array.prototype.append = function(a) + { + for(var i = 0; i -

        Online demos

        +

        Online demos

          @@ -184,7 +184,7 @@ find htmlarea/ -name "*.cgi" -exec chmod 755 {} \;
        -

        You can contact me directly +

        You can contact me directly only if you want to pay me for implementing custom features to HTMLArea. If you want to sponsor these features (that is, allow them to get back into the public HTMLArea distribution) I'll be cheaper. ;-)

        @@ -192,7 +192,7 @@ find htmlarea/ -name "*.cgi" -exec chmod 755 {} \;
        Mihai Bazon
        - Last modified: Wed Jan 28 11:54:47 EET 2004 + Last modified: Wed Jul 14 13:20:53 CEST 2004 diff --git a/phpgwapi/js/htmlarea/lang/de.js b/phpgwapi/js/htmlarea/lang/de.js index 7cb00b3a20..8b4474b415 100644 --- a/phpgwapi/js/htmlarea/lang/de.js +++ b/phpgwapi/js/htmlarea/lang/de.js @@ -43,7 +43,8 @@ HTMLArea.I18N = { copy: "Kopieren", paste: "Einfgen aus der Zwischenablage", lefttoright: "Textrichtung von Links nach Rechts", - righttoleft: "Textrichtung von Rechts nach Links" + righttoleft: "Textrichtung von Rechts nach Links", + removeformat: "Formatierung entfernen" }, buttons: { diff --git a/phpgwapi/js/htmlarea/lang/en.js b/phpgwapi/js/htmlarea/lang/en.js index 90a7649a86..e49e89c78a 100644 --- a/phpgwapi/js/htmlarea/lang/en.js +++ b/phpgwapi/js/htmlarea/lang/en.js @@ -50,7 +50,10 @@ HTMLArea.I18N = { copy: "Copy selection", paste: "Paste from clipboard", lefttoright: "Direction left to right", - righttoleft: "Direction right to left" + righttoleft: "Direction right to left", + removeformat: "Remove formatting", + print: "Print document", + killword: "Clear MSOffice tags" }, buttons: { @@ -70,24 +73,74 @@ HTMLArea.I18N = { "it's very likely that you'll get a 'General Protection Fault' and need to reboot.\n\n" + "You have been warned. Please press OK if you still want to try the full screen editor.", - "Moz-Clipboard" : - "Unprivileged scripts cannot access Cut/Copy/Paste programatically " + - "for security reasons. Click OK to see a technical note at mozilla.org " + - "which shows you how to allow a script to access the clipboard." + "MOZ-security-clipboard" : + // Translate Here + "The Paste button does not work in Mozilla based web browsers (technical security reasons). Press CTRL-V on your keyboard to paste directly." }, dialogs: { - "Cancel" : "Cancel", - "Insert/Modify Link" : "Insert/Modify Link", - "New window (_blank)" : "New window (_blank)", - "None (use implicit)" : "None (use implicit)", + // Common "OK" : "OK", - "Other" : "Other", + "Cancel" : "Cancel", + + "Alignment:" : "Alignment:", + "Not set" : "Not set", + "Left" : "Left", + "Right" : "Right", + "Texttop" : "Texttop", + "Absmiddle" : "Absmiddle", + "Baseline" : "Baseline", + "Absbottom" : "Absbottom", + "Bottom" : "Bottom", + "Middle" : "Middle", + "Top" : "Top", + + "Layout" : "Layout", + "Spacing" : "Spacing", + "Horizontal:" : "Horizontal:", + "Horizontal padding" : "Horizontal padding", + "Vertical:" : "Vertical:", + "Vertical padding" : "Vertical padding", + "Border thickness:" : "Border thickness:", + "Leave empty for no border" : "Leave empty for no border", + + // Insert Link + "Insert/Modify Link" : "Insert/Modify Link", + "None (use implicit)" : "None (use implicit)", + "New window (_blank)" : "New window (_blank)", "Same frame (_self)" : "Same frame (_self)", + "Top frame (_top)" : "Top frame (_top)", + "Other" : "Other", "Target:" : "Target:", "Title (tooltip):" : "Title (tooltip):", - "Top frame (_top)" : "Top frame (_top)", "URL:" : "URL:", - "You must enter the URL where this link points to" : "You must enter the URL where this link points to" + "You must enter the URL where this link points to" : "You must enter the URL where this link points to", + // Insert Table + "Insert Table" : "Insert Table", + "Rows:" : "Rows:", + "Number of rows" : "Number of rows", + "Cols:" : "Cols:", + "Number of columns" : "Number of columns", + "Width:" : "Width:", + "Width of the table" : "Width of the table", + "Percent" : "Percent", + "Pixels" : "Pixels", + "Em" : "Em", + "Width unit" : "Width unit", + "Positioning of this table" : "Positioning of this table", + "Cell spacing:" : "Cell spacing:", + "Space between adjacent cells" : "Space between adjacent cells", + "Cell padding:" : "Cell padding:", + "Space between content and border in cell" : "Space between content and border in cell", + // Insert Image + "Insert Image" : "Insert Image", + "Image URL:" : "Image URL:", + "Enter the image URL here" : "Enter the image URL here", + "Preview" : "Preview", + "Preview the image in a new window" : "Preview the image in a new window", + "Alternate text:" : "Alternate text:", + "For browsers that don't support images" : "For browsers that don't support images", + "Positioning of this image" : "Positioning of this image", + "Image Preview:" : "Image Preview:" } }; diff --git a/phpgwapi/js/htmlarea/lang/es.js b/phpgwapi/js/htmlarea/lang/es.js index 5a1e86fdda..8d0594bd3b 100644 --- a/phpgwapi/js/htmlarea/lang/es.js +++ b/phpgwapi/js/htmlarea/lang/es.js @@ -2,50 +2,50 @@ HTMLArea.I18N = { - // the following should be the filename without .js extension - // it will be used for automatically load plugin language. - lang: "es", + // the following should be the filename without .js extension + // it will be used for automatically load plugin language. + lang: "es", - tooltips: { - bold: "Negrita", - italic: "Cursiva", - underline: "Subrayado", - strikethrough: "Tachado", - subscript: "Subndice", - superscript: "Superndice", - justifyleft: "Alinear a la Izquierda", - justifycenter: "Centrar", - justifyright: "Alinear a la Derecha", - justifyfull: "Justificar", - orderedlist: "Lista Ordenada", - unorderedlist: "Lista No Ordenada", - outdent: "Aumentar Sangra", - indent: "Disminuir Sangra", - forecolor: "Color del Texto", - hilitecolor: "Color del Fondo", - inserthorizontalrule: "Lnea Horizontal", - createlink: "Insertar Enlace", - insertimage: "Insertar Imagen", - inserttable: "Insertar Tabla", - htmlmode: "Ver Documento en HTML", - popupeditor: "Ampliar Editor", - about: "Acerca del Editor", - showhelp: "Ayuda", - textindicator: "Estilo Actual", - undo: "Deshacer", - redo: "Rehacer", - cut: "Cortar seleccin", - copy: "Copiar seleccin", - paste: "Pegar desde el portapapeles" - }, + tooltips: { + bold: "Negrita", + italic: "Cursiva", + underline: "Subrayado", + strikethrough: "Tachado", + subscript: "Sub?ndice", + superscript: "Super?ndice", + justifyleft: "Alinear a la Izquierda", + justifycenter: "Centrar", + justifyright: "Alinear a la Derecha", + justifyfull: "Justificar", + orderedlist: "Lista Ordenada", + unorderedlist: "Lista No Ordenada", + outdent: "Aumentar Sangr?a", + indent: "Disminuir Sangr?a", + forecolor: "Color del Texto", + hilitecolor: "Color del Fondo", + inserthorizontalrule: "L?nea Horizontal", + createlink: "Insertar Enlace", + insertimage: "Insertar Imagen", + inserttable: "Insertar Tabla", + htmlmode: "Ver Documento en HTML", + popupeditor: "Ampliar Editor", + about: "Acerca del Editor", + showhelp: "Ayuda", + textindicator: "Estilo Actual", + undo: "Deshacer", + redo: "Rehacer", + cut: "Cortar selecci?n", + copy: "Copiar selecci?n", + paste: "Pegar desde el portapapeles" + }, - buttons: { - "ok": "Aceptar", - "cancel": "Cancelar" - }, + buttons: { + "ok": "Aceptar", + "cancel": "Cancelar" + }, - msg: { - "Path": "Ruta", - "TEXT_MODE": "Esta en modo TEXTO. Use el boton [<>] para cambiar a WYSIWIG", - } + msg: { + "Path": "Ruta", + "TEXT_MODE": "Esta en modo TEXTO. Use el boton [<>] para cambiar a WYSIWIG" + } }; diff --git a/phpgwapi/js/htmlarea/lang/nl.js b/phpgwapi/js/htmlarea/lang/nl.js index aa2229a243..320d38c72d 100644 --- a/phpgwapi/js/htmlarea/lang/nl.js +++ b/phpgwapi/js/htmlarea/lang/nl.js @@ -47,7 +47,7 @@ HTMLArea.I18N = { undo: "Ongedaan maken", redo: "Herhalen", cut: "Knippen", - copy: "Kopiren", + copy: "Kopi?ren", paste: "Plakken", lefttoright: "Tekstrichting links naar rechts", righttoleft: "Tekstrichting rechts naar links" @@ -61,7 +61,7 @@ HTMLArea.I18N = { msg: { "Path": "Pad", "TEXT_MODE": "Je bent in TEKST-mode. Gebruik de [<>] knop om terug te keren naar WYSIWYG-mode.", - + "IE-sucks-full-screen" : // translate here "Fullscreen-mode veroorzaakt problemen met Internet Explorer door bugs in de webbrowser " + @@ -86,5 +86,4 @@ HTMLArea.I18N = { "URL:" : "URL:", "You must enter the URL where this link points to" : "Geef de URL in waar de link naar verwijst" } -}; - +}; \ No newline at end of file diff --git a/phpgwapi/js/htmlarea/lang/vn.js b/phpgwapi/js/htmlarea/lang/vn.js index 77424965b6..a391dd3ee4 100644 --- a/phpgwapi/js/htmlarea/lang/vn.js +++ b/phpgwapi/js/htmlarea/lang/vn.js @@ -1,51 +1,77 @@ // I18N constants : Vietnamese // LANG: "en", ENCODING: UTF-8 // Author: Nguyễn Đình Nam, +// Modified 21/07/2004 by Phạm Mai Quân HTMLArea.I18N = { - // the following should be the filename without .js extension - // it will be used for automatically load plugin language. - lang: "vn", + // the following should be the filename without .js extension + // it will be used for automatically load plugin language. + lang: "vn", - tooltips: { - bold: "Đậm", - italic: "Nghiêng", - underline: "Gạch Chân", - strikethrough: "Gạch Xóa", - subscript: "Viết Xuống Dưới", - superscript: "Viết Lên Trên", - justifyleft: "Căn Trái", - justifycenter: "Căn Giữa", - justifyright: "Căn Phải", - justifyfull: "Căn Đều", - orderedlist: "Danh Sách Có Thứ Tự", - unorderedlist: "Danh Sách Phi Thứ Tự", - outdent: "Lùi Ra Ngoài", - indent: "Thụt Vào Trong", - forecolor: "Màu Chữ", - backcolor: "Màu Nền", - horizontalrule: "Dòng Kẻ Ngang", - createlink: "Tạo Liên Kết", - insertimage: "Chèn Ảnh", - inserttable: "Chèn Bảng", - htmlmode: "Chế Độ Mã HTML", - popupeditor: "Phóng To Ô Soạn Thảo", - about: "Tự Giới Thiệu", - showhelp: "Giúp Đỡ", - textindicator: "Định Dạng Hiện Thời", - undo: "Undo", - redo: "Redo", - cut: "Cắt", - copy: "Copy", - paste: "Dán" - }, - buttons: { - "ok": "OK", - "cancel": "Hủy" - }, - msg: { - "Path": "Đường Dẫn", - "TEXT_MODE": "Bạn đang ở chế độ text. Sử dụng nút [<>] để chuyển lại chế độ WYSIWIG." - } + tooltips: { + bold: "Đậm", + italic: "Nghiêng", + underline: "Gạch Chân", + strikethrough: "Gạch Xóa", + subscript: "Viết Xuống Dưới", + superscript: "Viết Lên Trên", + justifyleft: "Căn Trái", + justifycenter: "Căn Giữa", + justifyright: "Căn Phải", + justifyfull: "Căn Đều", + insertorderedlist: "Danh Sách Có Thứ Tự (1, 2, 3)", + insertunorderedlist: "Danh Sách Phi Thứ Tự (Chấm đầu dòng)", + outdent: "Lùi Ra Ngoài", + indent: "Thụt Vào Trong", + forecolor: "Màu Chữ", + hilitecolor: "Màu Nền", + inserthorizontalrule: "Dòng Kẻ Ngang", + createlink: "Tạo Liên Kết", + insertimage: "Chèn Ảnh", + inserttable: "Chèn Bảng", + htmlmode: "Chế Độ Mã HTML", + popupeditor: "Phóng To Ô Soạn Thảo", + about: "Tự Giới Thiệu", + showhelp: "Giúp Đỡ", + textindicator: "Định Dạng Hiện Thời", + undo: "Hủy thao tác trước", + redo: "Lấy lại thao tác vừa bỏ", + cut: "Cắt", + copy: "Sao chép", + paste: "Dán", + lefttoright: "Viết từ trái sang phải", + righttoleft: "Viết từ phải sang trái" + }, + buttons: { + "ok": "Đồng ý", + "cancel": "Hủy", + + "IE-sucks-full-screen" : + // translate here + "Chế độ phóng to ô soạn thảo có thể gây lỗi với Internet Explorer vì một số lỗi của trình duyệt này," + + " vì thế chế độ này có thể sẽ không chạy. Hiển thị không đúng, lộn xộn, không có đầy đủ chức năng," + + " và cũng có thể làm trình duyệt của bạn bị tắt ngang. Nếu bạn đang sử dụng Windows 9x " + + "bạn có thể bị báo lỗi 'General Protection Fault' và máy tính của bạn buộc phải khởi động lại.\n\n" + + "Chúng tôi đã cảnh báo bạn. Nhấn nút 'Đồng ý' nếu bạn vẫn muốn sử dụng tính năng này." + }, + msg: { + "Path": "Đường Dẫn", + "TEXT_MODE": "Bạn đang ở chế độ text. Sử dụng nút [<>] để chuyển lại chế độ WYSIWIG." + }, + + dialogs: { + "Cancel" : "Hủy", + "Insert/Modify Link" : "Thêm/Chỉnh sửa đường dẫn", + "New window (_blank)" : "Cửa sổ mới (_blank)", + "None (use implicit)" : "Không (sử dụng implicit)", + "OK" : "Đồng ý", + "Other" : "Khác", + "Same frame (_self)" : "Trên cùng khung (_self)", + "Target:" : "Nơi hiện thị:", + "Title (tooltip):" : "Tiêu đề (của hướng dẫn):", + "Top frame (_top)" : "Khung trên cùng (_top)", + "URL:" : "URL:", + "You must enter the URL where this link points to" : "Bạn phải điền địa chỉ (URL) mà đường dẫn sẽ liên kết tới" + } }; diff --git a/phpgwapi/js/htmlarea/plugins/ContextMenu/context-menu.js b/phpgwapi/js/htmlarea/plugins/ContextMenu/context-menu.js index 3d2e8233eb..d31808fd56 100755 --- a/phpgwapi/js/htmlarea/plugins/ContextMenu/context-menu.js +++ b/phpgwapi/js/htmlarea/plugins/ContextMenu/context-menu.js @@ -63,6 +63,26 @@ ContextMenu.prototype.getContextMenu = function(target) { tbo.buttonPress(editor, opcode); }; + function insertPara(after) { + var el = currentTarget; + var par = el.parentNode; + var p = editor._doc.createElement("p"); + p.appendChild(editor._doc.createElement("br")); + par.insertBefore(p, after ? el.nextSibling : el); + var sel = editor._getSelection(); + var range = editor._createRange(sel); + if (!HTMLArea.is_ie) { + sel.removeAllRanges(); + range.selectNodeContents(p); + range.collapse(true); + sel.addRange(range); + } else { + range.moveToElementText(p); + range.collapse(true); + range.select(); + } + }; + for (; target; target = target.parentNode) { var tag = target.tagName; if (!tag) @@ -189,32 +209,41 @@ ContextMenu.prototype.getContextMenu = function(target) { i18n["Create a link"], config.btnList["createlink"][1] ]); - for (var i in elmenus) + for (var i = 0; i < elmenus.length; ++i) menu.push(elmenus[i]); - menu.push(null, - [ i18n["Remove the"] + " <" + currentTarget.tagName + "> " + i18n["Element"], - function() { - if (confirm(i18n["Please confirm that you want to remove this element:"] + " " + currentTarget.tagName)) { - var el = currentTarget; - var p = el.parentNode; - p.removeChild(el); - if (HTMLArea.is_gecko) { - if (p.tagName.toLowerCase() == "td" && !p.hasChildNodes()) - p.appendChild(editor._doc.createElement("br")); - editor.forceRedraw(); - editor.focusEditor(); - editor.updateToolbar(); - if (table) { - var save_collapse = table.style.borderCollapse; - table.style.borderCollapse = "collapse"; - table.style.borderCollapse = "separate"; - table.style.borderCollapse = save_collapse; + if (!/html|body/i.test(currentTarget.tagName)) + menu.push(null, + [ i18n["Remove the"] + " <" + currentTarget.tagName + "> " + i18n["Element"], + function() { + if (confirm(i18n["Please confirm that you want to remove this element:"] + " " + + currentTarget.tagName)) { + var el = currentTarget; + var p = el.parentNode; + p.removeChild(el); + if (HTMLArea.is_gecko) { + if (p.tagName.toLowerCase() == "td" && !p.hasChildNodes()) + p.appendChild(editor._doc.createElement("br")); + editor.forceRedraw(); + editor.focusEditor(); + editor.updateToolbar(); + if (table) { + var save_collapse = table.style.borderCollapse; + table.style.borderCollapse = "collapse"; + table.style.borderCollapse = "separate"; + table.style.borderCollapse = save_collapse; + } } } - } - }, - i18n["Remove this node from the document"] ]); + }, + i18n["Remove this node from the document"] ], + [ i18n["Insert paragraph before"], + function() { insertPara(false); }, + i18n["Insert a paragraph before the current node"] ], + [ i18n["Insert paragraph after"], + function() { insertPara(true); }, + i18n["Insert a paragraph after the current node"] ] + ); return menu; }; @@ -385,12 +414,8 @@ ContextMenu.prototype.popupMenu = function(ev) { } if (!HTMLArea.is_ie) { -// var dx = x + div.offsetWidth - window.innerWidth + 4; -// var dy = y + div.offsetHeight - window.innerHeight + 4; - - var dx = x + div.offsetWidth - window.innerWidth - window.pageXOffset + 4; - var dy = y + div.offsetHeight - window.innerHeight - window.pageYOffset + 4; - + var dx = x + div.offsetWidth - window.innerWidth + 4; + var dy = y + div.offsetHeight - window.innerHeight + 4; if (dx > 0) x -= dx; if (dy > 0) y -= dy; div.style.left = x + "px"; diff --git a/phpgwapi/js/htmlarea/plugins/ContextMenu/lang/en.js b/phpgwapi/js/htmlarea/plugins/ContextMenu/lang/en.js index 8d15914d82..98b35728a6 100755 --- a/phpgwapi/js/htmlarea/plugins/ContextMenu/lang/en.js +++ b/phpgwapi/js/htmlarea/plugins/ContextMenu/lang/en.js @@ -40,6 +40,8 @@ ContextMenu.I18N = { "Make link" : "Make lin_k...", "Remove the" : "Remove the", "Element" : "Element...", + "Insert paragraph before" : "Insert paragraph before", + "Insert paragraph after" : "Insert paragraph after", // Other labels (tooltips and alert/confirm box messages) @@ -62,5 +64,7 @@ ContextMenu.I18N = { "Insert a new column before the current one" : "Insert a new column before the current one", "Insert a new column after the current one" : "Insert a new column after the current one", "Delete the current column" : "Delete the current column", - "Create a link" : "Create a link" + "Create a link" : "Create a link", + "Insert a paragraph before the current node" : "Insert a paragraph before the current node", + "Insert a paragraph after the current node" : "Insert a paragraph after the current node" }; diff --git a/phpgwapi/js/htmlarea/plugins/ContextMenu/lang/lang.php b/phpgwapi/js/htmlarea/plugins/ContextMenu/lang/lang.php index b74c82ca57..f49f456c3e 100644 --- a/phpgwapi/js/htmlarea/plugins/ContextMenu/lang/lang.php +++ b/phpgwapi/js/htmlarea/plugins/ContextMenu/lang/lang.php @@ -24,7 +24,6 @@ $GLOBALS['phpgw_info']['flags'] = Array( ); include('../../../../../../header.inc.php'); -header('Content-type: text/javascript; charset='.$GLOBALS['phpgw']->translation->charset()); $GLOBALS['phpgw']->translation->add_app('htmlarea-ContextMenu'); // I18N constants diff --git a/phpgwapi/js/htmlarea/plugins/ContextMenu/menu.css b/phpgwapi/js/htmlarea/plugins/ContextMenu/menu.css index 70626b5045..35b17a81de 100644 --- a/phpgwapi/js/htmlarea/plugins/ContextMenu/menu.css +++ b/phpgwapi/js/htmlarea/plugins/ContextMenu/menu.css @@ -7,6 +7,7 @@ div.htmlarea-context-menu { border: 1px solid #aca899; padding: 2px; background-color: #fff; + color: #000; cursor: default; z-index: 1000; } diff --git a/phpgwapi/js/htmlarea/plugins/EnterParagraphs/enter-paragraphs.js b/phpgwapi/js/htmlarea/plugins/EnterParagraphs/enter-paragraphs.js index c9a768da12..2b901b679f 100755 --- a/phpgwapi/js/htmlarea/plugins/EnterParagraphs/enter-paragraphs.js +++ b/phpgwapi/js/htmlarea/plugins/EnterParagraphs/enter-paragraphs.js @@ -6,20 +6,20 @@ // This notice MUST stay intact for use (see license.txt). function EnterParagraphs(editor, params) { - this.editor = editor; - // activate only if we're talking to Gecko - if (HTMLArea.is_gecko) - this.onKeyPress = this.__onKeyPress; + this.editor = editor; + // activate only if we're talking to Gecko + if (HTMLArea.is_gecko) + this.onKeyPress = this.__onKeyPress; }; EnterParagraphs._pluginInfo = { - name : "EnterParagraphs", - version : "1.0", - developer : "Adam Wright", - developer_url : "http://blog.hipikat.org/", - sponsor : "The University of Western Australia", - sponsor_url : "http://www.uwa.edu.au/", - license : "htmlArea" + name : "EnterParagraphs", + version : "1.0", + developer : "Adam Wright", + developer_url : "http://blog.hipikat.org/", + sponsor : "The University of Western Australia", + sponsor_url : "http://www.uwa.edu.au/", + license : "htmlArea" }; // An array of elements who, in html4, by default, have an inline display and can have children @@ -28,177 +28,177 @@ EnterParagraphs.prototype._html4_inlines_re = /^(a|abbr|acronym|b|bdo|big|cite|c // Finds the first parent element of a given node whose display is probably not inline EnterParagraphs.prototype.parentBlock = function(node) { - while (node.parentNode && (node.nodeType != 1 || this._html4_inlines_re.test(node.tagName))) - node = node.parentNode; - return node; + while (node.parentNode && (node.nodeType != 1 || this._html4_inlines_re.test(node.tagName))) + node = node.parentNode; + return node; }; // Internal function for recursively itterating over a all nodes in a fragment // If a callback function returns a non-null value, that is returned and the crawl is therefore broken EnterParagraphs.prototype.walkNodeChildren = function(me, callback) { - if (me.firstChild) { - var myChild = me.firstChild; - var retVal; - while (myChild) { - if ((retVal = callback(this, myChild)) != null) - return retVal; - if ((retVal = this.walkNodeChildren(myChild, callback)) != null) - return retVal; - myChild = myChild.nextSibling; - } - } + if (me.firstChild) { + var myChild = me.firstChild; + var retVal; + while (myChild) { + if ((retVal = callback(this, myChild)) != null) + return retVal; + if ((retVal = this.walkNodeChildren(myChild, callback)) != null) + return retVal; + myChild = myChild.nextSibling; + } + } }; // Callback function to be performed on each node in the hierarchy // Sets flag to true if we find actual text or an element that's not usually displayed inline EnterParagraphs.prototype._isFilling = function(self, node) { - if (node.nodeType == 1 && !self._html4_inlines_re.test(node.nodeName)) - return true; - else if (node.nodeType == 3 && node.nodeValue != '') - return true; - return null; - //alert(node.nodeName); + if (node.nodeType == 1 && !self._html4_inlines_re.test(node.nodeName)) + return true; + else if (node.nodeType == 3 && node.nodeValue != '') + return true; + return null; + //alert(node.nodeName); }; // Inserts a node deeply on the left of a hierarchy of nodes EnterParagraphs.prototype.insertDeepLeftText = function(target, toInsert) { - var falling = target; - while (falling.firstChild && falling.firstChild.nodeType == 1) - falling = falling.firstChild; - //var refNode = falling.firstChild ? falling.firstChild : null; - //falling.insertBefore(toInsert, refNode); - falling.innerHTML = toInsert; + var falling = target; + while (falling.firstChild && falling.firstChild.nodeType == 1) + falling = falling.firstChild; + //var refNode = falling.firstChild ? falling.firstChild : null; + //falling.insertBefore(toInsert, refNode); + falling.innerHTML = toInsert; }; // Kind of like a macros, for a frequent query... EnterParagraphs.prototype.isElem = function(node, type) { - return node.nodeName.toLowerCase() == type.toLowerCase(); + return node.nodeName.toLowerCase() == type.toLowerCase(); }; // The onKeyPress even that does all the work - nicely breaks the line into paragraphs EnterParagraphs.prototype.__onKeyPress = function(ev) { - if (ev.keyCode == 13 && !ev.shiftKey && this.editor._iframe.contentWindow.getSelection) { + if (ev.keyCode == 13 && !ev.shiftKey && this.editor._iframe.contentWindow.getSelection) { - var editor = this.editor; + var editor = this.editor; - // Get the selection and solid references to what we're dealing with chopping - var sel = editor._iframe.contentWindow.getSelection(); + // Get the selection and solid references to what we're dealing with chopping + var sel = editor._iframe.contentWindow.getSelection(); - // Set the start and end points such that they're going /forward/ through the document - var rngLeft = editor._doc.createRange(); var rngRight = editor._doc.createRange(); - rngLeft.setStart(sel.anchorNode, sel.anchorOffset); rngRight.setStart(sel.focusNode, sel.focusOffset); - rngLeft.collapse(true); rngRight.collapse(true); + // Set the start and end points such that they're going /forward/ through the document + var rngLeft = editor._doc.createRange(); var rngRight = editor._doc.createRange(); + rngLeft.setStart(sel.anchorNode, sel.anchorOffset); rngRight.setStart(sel.focusNode, sel.focusOffset); + rngLeft.collapse(true); rngRight.collapse(true); - var direct = rngLeft.compareBoundaryPoints(rngLeft.START_TO_END, rngRight) < 0; + var direct = rngLeft.compareBoundaryPoints(rngLeft.START_TO_END, rngRight) < 0; - var startNode = direct ? sel.anchorNode : sel.focusNode; - var startOffset = direct ? sel.anchorOffset : sel.focusOffset; - var endNode = direct ? sel.focusNode : sel.anchorNode; - var endOffset = direct ? sel.focusOffset : sel.anchorOffset; + var startNode = direct ? sel.anchorNode : sel.focusNode; + var startOffset = direct ? sel.anchorOffset : sel.focusOffset; + var endNode = direct ? sel.focusNode : sel.anchorNode; + var endOffset = direct ? sel.focusOffset : sel.anchorOffset; - // Find the parent blocks of nodes at either end, and their attributes if they're paragraphs - var startBlock = this.parentBlock(startNode); var endBlock = this.parentBlock(endNode); - var attrsLeft = new Array(); var attrsRight = new Array(); + // Find the parent blocks of nodes at either end, and their attributes if they're paragraphs + var startBlock = this.parentBlock(startNode); var endBlock = this.parentBlock(endNode); + var attrsLeft = new Array(); var attrsRight = new Array(); - // If a list, let the browser take over, if we're in a paragraph, gather it's attributes - if (this.isElem(startBlock, 'li') || this.isElem(endBlock, 'li')) - return; + // If a list, let the browser take over, if we're in a paragraph, gather it's attributes + if (this.isElem(startBlock, 'li') || this.isElem(endBlock, 'li')) + return; - if (this.isElem(startBlock, 'p')) { - for (var i = 0; i < startBlock.attributes.length; i++) { - attrsLeft[startBlock.attributes[i].nodeName] = startBlock.attributes[i].nodeValue; - } - } - if (this.isElem(endBlock, 'p')) { - for (var i = 0; i < endBlock.attributes.length; i++) { - // If we start and end within one paragraph, don't duplicate the 'id' - if (endBlock != startBlock || endBlock.attributes[i].nodeName.toLowerCase() != 'id') - attrsRight[endBlock.attributes[i].nodeName] = endBlock.attributes[i].nodeValue; - } - } + if (this.isElem(startBlock, 'p')) { + for (var i = 0; i < startBlock.attributes.length; i++) { + attrsLeft[startBlock.attributes[i].nodeName] = startBlock.attributes[i].nodeValue; + } + } + if (this.isElem(endBlock, 'p')) { + for (var i = 0; i < endBlock.attributes.length; i++) { + // If we start and end within one paragraph, don't duplicate the 'id' + if (endBlock != startBlock || endBlock.attributes[i].nodeName.toLowerCase() != 'id') + attrsRight[endBlock.attributes[i].nodeName] = endBlock.attributes[i].nodeValue; + } + } - // Look for where to start and end our chopping - within surrounding paragraphs - // if they exist, or at the edges of the containing block, otherwise - var startChop = startNode; var endChop = endNode; + // Look for where to start and end our chopping - within surrounding paragraphs + // if they exist, or at the edges of the containing block, otherwise + var startChop = startNode; var endChop = endNode; - while ((startChop.previousSibling && !this.isElem(startChop.previousSibling, 'p')) - || (startChop.parentNode && startChop.parentNode != startBlock && startChop.parentNode.nodeType != 9)) - startChop = startChop.previousSibling ? startChop.previousSibling : startChop.parentNode; + while ((startChop.previousSibling && !this.isElem(startChop.previousSibling, 'p')) + || (startChop.parentNode && startChop.parentNode != startBlock && startChop.parentNode.nodeType != 9)) + startChop = startChop.previousSibling ? startChop.previousSibling : startChop.parentNode; - while ((endChop.nextSibling && !this.isElem(endChop.nextSibling, 'p')) - || (endChop.parentNode && endChop.parentNode != endBlock && endChop.parentNode.nodeType != 9)) - endChop = endChop.nextSibling ? endChop.nextSibling : endChop.parentNode; + while ((endChop.nextSibling && !this.isElem(endChop.nextSibling, 'p')) + || (endChop.parentNode && endChop.parentNode != endBlock && endChop.parentNode.nodeType != 9)) + endChop = endChop.nextSibling ? endChop.nextSibling : endChop.parentNode; - // Set up new paragraphs - var pLeft = editor._doc.createElement('p'); var pRight = editor._doc.createElement('p'); + // Set up new paragraphs + var pLeft = editor._doc.createElement('p'); var pRight = editor._doc.createElement('p'); - for (var attrName in attrsLeft) { - var thisAttr = editor._doc.createAttribute(attrName); - thisAttr.value = attrsLeft[attrName]; - pLeft.setAttributeNode(thisAttr); - } - for (var attrName in attrsRight) { - var thisAttr = editor._doc.createAttribute(attrName); - thisAttr.value = attrsRight[attrName]; - pRight.setAttributeNode(thisAttr); - } + for (var attrName in attrsLeft) { + var thisAttr = editor._doc.createAttribute(attrName); + thisAttr.value = attrsLeft[attrName]; + pLeft.setAttributeNode(thisAttr); + } + for (var attrName in attrsRight) { + var thisAttr = editor._doc.createAttribute(attrName); + thisAttr.value = attrsRight[attrName]; + pRight.setAttributeNode(thisAttr); + } - // Get the ranges destined to be stuffed into new paragraphs - rngLeft.setStartBefore(startChop); - rngLeft.setEnd(startNode,startOffset); - pLeft.appendChild(rngLeft.cloneContents()); // Copy into pLeft + // Get the ranges destined to be stuffed into new paragraphs + rngLeft.setStartBefore(startChop); + rngLeft.setEnd(startNode,startOffset); + pLeft.appendChild(rngLeft.cloneContents()); // Copy into pLeft - rngRight.setEndAfter(endChop); - rngRight.setStart(endNode,endOffset); - pRight.appendChild(rngRight.cloneContents()); // Copy into pRight + rngRight.setEndAfter(endChop); + rngRight.setStart(endNode,endOffset); + pRight.appendChild(rngRight.cloneContents()); // Copy into pRight - // If either paragraph is empty, fill it with a nonbreakable space - var foundBlock = false; - foundBlock = this.walkNodeChildren(pLeft, this._isFilling); - if (foundBlock != true) - this.insertDeepLeftText(pLeft, ' '); + // If either paragraph is empty, fill it with a nonbreakable space + var foundBlock = false; + foundBlock = this.walkNodeChildren(pLeft, this._isFilling); + if (foundBlock != true) + this.insertDeepLeftText(pLeft, ' '); - foundBlock = false; - foundBlock = this.walkNodeChildren(pRight, this._isFilling); - if (foundBlock != true) - this.insertDeepLeftText(pRight, ' '); + foundBlock = false; + foundBlock = this.walkNodeChildren(pRight, this._isFilling); + if (foundBlock != true) + this.insertDeepLeftText(pRight, ' '); - // Get a range for everything to be replaced and replace it - var rngAround = editor._doc.createRange(); + // Get a range for everything to be replaced and replace it + var rngAround = editor._doc.createRange(); - if (!startChop.previousSibling && this.isElem(startChop.parentNode, 'p')) - rngAround.setStartBefore(startChop.parentNode); - else - rngAround.setStart(rngLeft.startContainer, rngLeft.startOffset); + if (!startChop.previousSibling && this.isElem(startChop.parentNode, 'p')) + rngAround.setStartBefore(startChop.parentNode); + else + rngAround.setStart(rngLeft.startContainer, rngLeft.startOffset); - if (!endChop.nextSibling && this.isElem(endChop.parentNode, 'p')) - rngAround.setEndAfter(endChop.parentNode); - else - rngAround.setEnd(rngRight.endContainer, rngRight.endOffset); + if (!endChop.nextSibling && this.isElem(endChop.parentNode, 'p')) + rngAround.setEndAfter(endChop.parentNode); + else + rngAround.setEnd(rngRight.endContainer, rngRight.endOffset); - rngAround.deleteContents(); - rngAround.insertNode(pRight); - rngAround.insertNode(pLeft); + rngAround.deleteContents(); + rngAround.insertNode(pRight); + rngAround.insertNode(pLeft); - // Set the selection to the start of the (second) new paragraph - if (pRight.firstChild) { - while (pRight.firstChild && this._html4_inlines_re.test(pRight.firstChild.nodeName)) - pRight = pRight.firstChild; - // Slip into any inline tags - if (pRight.firstChild && pRight.firstChild.nodeType == 3) - pRight = pRight.firstChild; // and text, if they've got it + // Set the selection to the start of the (second) new paragraph + if (pRight.firstChild) { + while (pRight.firstChild && this._html4_inlines_re.test(pRight.firstChild.nodeName)) + pRight = pRight.firstChild; + // Slip into any inline tags + if (pRight.firstChild && pRight.firstChild.nodeType == 3) + pRight = pRight.firstChild; // and text, if they've got it - var rngCaret = editor._doc.createRange(); - rngCaret.setStart(pRight, 0); - rngCaret.collapse(true); + var rngCaret = editor._doc.createRange(); + rngCaret.setStart(pRight, 0); + rngCaret.collapse(true); - sel = editor._iframe.contentWindow.getSelection(); - sel.removeAllRanges(); - sel.addRange(rngCaret); - } + sel = editor._iframe.contentWindow.getSelection(); + sel.removeAllRanges(); + sel.addRange(rngCaret); + } - // Stop the bubbling - HTMLArea._stopEvent(ev); - } + // Stop the bubbling + HTMLArea._stopEvent(ev); + } }; diff --git a/phpgwapi/js/htmlarea/plugins/FullPage/full-page.js b/phpgwapi/js/htmlarea/plugins/FullPage/full-page.js index ad024cee2b..7806ccfb89 100755 --- a/phpgwapi/js/htmlarea/plugins/FullPage/full-page.js +++ b/phpgwapi/js/htmlarea/plugins/FullPage/full-page.js @@ -49,6 +49,7 @@ FullPage.prototype.buttonPress = function(editor, id) { var links = doc.getElementsByTagName("link"); var style1 = ''; var style2 = ''; + var charset = ''; for (var i = links.length; --i >= 0;) { var link = links[i]; if (/stylesheet/i.test(link.rel)) { @@ -58,6 +59,14 @@ FullPage.prototype.buttonPress = function(editor, id) { style1 = link.href; } } + var metas = doc.getElementsByTagName("meta"); + for (var i = metas.length; --i >= 0;) { + var meta = metas[i]; + if (/content-type/i.test(meta.httpEquiv)) { + r = /^text\/html; *charset=(.*)$/i.exec(meta.content); + charset = r[1]; + } + } var title = doc.getElementsByTagName("title")[0]; title = title ? title.innerHTML : ''; var init = { @@ -67,7 +76,7 @@ FullPage.prototype.buttonPress = function(editor, id) { f_body_fgcolor : HTMLArea._colorToRgb(doc.body.style.color), f_base_style : style1, f_alt_style : style2, - + f_charset : charset, editor : editor }; editor._popupDialog("plugin://FullPage/docprop", function(params) { @@ -82,8 +91,11 @@ FullPage.prototype.setDocProp = function(params) { var doc = this.editor._doc; var head = doc.getElementsByTagName("head")[0]; var links = doc.getElementsByTagName("link"); + var metas = doc.getElementsByTagName("meta"); var style1 = null; var style2 = null; + var charset = null; + var charset_meta = null; for (var i = links.length; --i >= 0;) { var link = links[i]; if (/stylesheet/i.test(link.rel)) { @@ -93,12 +105,27 @@ FullPage.prototype.setDocProp = function(params) { style1 = link; } } + for (var i = metas.length; --i >= 0;) { + var meta = metas[i]; + if (/content-type/i.test(meta.httpEquiv)) { + r = /^text\/html; *charset=(.*)$/i.exec(meta.content); + charset = r[1]; + charset_meta = meta; + } + } function createLink(alt) { var link = doc.createElement("link"); link.rel = alt ? "alternate stylesheet" : "stylesheet"; head.appendChild(link); return link; }; + function createMeta(name, content) { + var meta = doc.createElement("meta"); + meta.httpEquiv = name; + meta.content = content; + head.appendChild(meta); + return meta; + }; if (!style1 && params.f_base_style) style1 = createLink(false); @@ -114,7 +141,14 @@ FullPage.prototype.setDocProp = function(params) { else if (style2) head.removeChild(style2); - for (var i in params) { + if (charset_meta) { + head.removeChild(charset_meta); + charset_meta = null; + } + if (!charset_meta && params.f_charset) + charset_meta = createMeta("Content-Type", "text/html; charset="+params.f_charset); + + for (var i in params) { var val = params[i]; switch (i) { case "f_title": diff --git a/phpgwapi/js/htmlarea/plugins/FullPage/lang/lang.php b/phpgwapi/js/htmlarea/plugins/FullPage/lang/lang.php index f7d91d205a..ef5a0d9fdc 100644 --- a/phpgwapi/js/htmlarea/plugins/FullPage/lang/lang.php +++ b/phpgwapi/js/htmlarea/plugins/FullPage/lang/lang.php @@ -24,7 +24,6 @@ $GLOBALS['phpgw_info']['flags'] = Array( ); include('../../../../../../header.inc.php'); -header('Content-type: text/javascript; charset='.$GLOBALS['phpgw']->translation->charset()); $GLOBALS['phpgw']->translation->add_app('htmlarea-FullPage'); // I18N for the FullPage plugin diff --git a/phpgwapi/js/htmlarea/plugins/FullPage/popups/docprop.html b/phpgwapi/js/htmlarea/plugins/FullPage/popups/docprop.html index ee00e2d563..eab9a9c288 100755 --- a/phpgwapi/js/htmlarea/plugins/FullPage/popups/docprop.html +++ b/phpgwapi/js/htmlarea/plugins/FullPage/popups/docprop.html @@ -16,7 +16,8 @@ window.resizeTo(400, 100); f_body_bgcolor : true, f_body_fgcolor : true, f_base_style : true, - f_alt_style : true + f_alt_style : true, + f_charset : true }; var editor = null; @@ -120,6 +121,17 @@ border-bottom: 1px solid black; letter-spacing: 2px; Text color: + + Character set: + +
        diff --git a/phpgwapi/js/htmlarea/plugins/HtmlTidy/html-tidy.js b/phpgwapi/js/htmlarea/plugins/HtmlTidy/html-tidy.js index 7fc52521c0..71a2027f3c 100755 --- a/phpgwapi/js/htmlarea/plugins/HtmlTidy/html-tidy.js +++ b/phpgwapi/js/htmlarea/plugins/HtmlTidy/html-tidy.js @@ -16,7 +16,7 @@ function HtmlTidy(editor) { // register the toolbar buttons provided by this plugin var toolbar = []; - for (var i in bl) { + for (var i = 0; i < bl.length; ++i) { var btn = bl[i]; if (btn == "html-tidy") { var id = "HT-html-tidy"; diff --git a/phpgwapi/js/htmlarea/plugins/HtmlTidy/lang/lang.php b/phpgwapi/js/htmlarea/plugins/HtmlTidy/lang/lang.php index 8c429427e9..d9bc8a5abb 100644 --- a/phpgwapi/js/htmlarea/plugins/HtmlTidy/lang/lang.php +++ b/phpgwapi/js/htmlarea/plugins/HtmlTidy/lang/lang.php @@ -24,7 +24,6 @@ $GLOBALS['phpgw_info']['flags'] = Array( ); include('../../../../../../header.inc.php'); -header('Content-type: text/javascript; charset='.$GLOBALS['phpgw']->translation->charset()); $GLOBALS['phpgw']->translation->add_app('htmlarea-HtmlTidy'); // I18N constants diff --git a/phpgwapi/js/htmlarea/plugins/ListType/lang/lang.php b/phpgwapi/js/htmlarea/plugins/ListType/lang/lang.php index 6b5a1dbb7b..ca38f0bfdb 100644 --- a/phpgwapi/js/htmlarea/plugins/ListType/lang/lang.php +++ b/phpgwapi/js/htmlarea/plugins/ListType/lang/lang.php @@ -24,7 +24,6 @@ $GLOBALS['phpgw_info']['flags'] = Array( ); include('../../../../../../header.inc.php'); -header('Content-type: text/javascript; charset='.$GLOBALS['phpgw']->translation->charset()); $GLOBALS['phpgw']->translation->add_app('htmlarea-ListType'); // I18N constants diff --git a/phpgwapi/js/htmlarea/plugins/SpellChecker/.htaccess b/phpgwapi/js/htmlarea/plugins/SpellChecker/.htaccess index 2f341a6448..5505990855 100644 --- a/phpgwapi/js/htmlarea/plugins/SpellChecker/.htaccess +++ b/phpgwapi/js/htmlarea/plugins/SpellChecker/.htaccess @@ -1 +1 @@ -Options +ExecCGI +Options +ExecCGI \ No newline at end of file diff --git a/phpgwapi/js/htmlarea/plugins/SpellChecker/lang/lang.php b/phpgwapi/js/htmlarea/plugins/SpellChecker/lang/lang.php index 09be0aa762..72cd33dee9 100644 --- a/phpgwapi/js/htmlarea/plugins/SpellChecker/lang/lang.php +++ b/phpgwapi/js/htmlarea/plugins/SpellChecker/lang/lang.php @@ -24,7 +24,6 @@ $GLOBALS['phpgw_info']['flags'] = Array( ); include('../../../../../../header.inc.php'); -header('Content-type: text/javascript; charset='.$GLOBALS['phpgw']->translation->charset()); $GLOBALS['phpgw']->translation->add_app('htmlarea-SpellChecker'); // I18N constants diff --git a/phpgwapi/js/htmlarea/plugins/SpellChecker/spell-check-ui.js b/phpgwapi/js/htmlarea/plugins/SpellChecker/spell-check-ui.js index 52a79d05e4..6bb2e68b40 100644 --- a/phpgwapi/js/htmlarea/plugins/SpellChecker/spell-check-ui.js +++ b/phpgwapi/js/htmlarea/plugins/SpellChecker/spell-check-ui.js @@ -126,7 +126,7 @@ function replaceAllClicked() { } */ if (ok) { - for (var i in spans) { + for (var i = 0; i < spans.length; ++i) { if (spans[i] != currentElement) { replaceWord(spans[i]); } @@ -156,7 +156,7 @@ function learnClicked() { function internationalizeWindow() { var types = ["div", "span", "button"]; - for (var i in types) { + for (var i = 0; i < types.length; ++i) { var tag = types[i]; var els = document.getElementsByTagName(tag); for (var j = els.length; --j >= 0;) { @@ -237,7 +237,7 @@ function wordClicked(scroll) { if (currentElement) { var a = allWords[currentElement.__msh_origWord]; currentElement.className = currentElement.className.replace(/\s*HA-spellcheck-current\s*/g, " "); - for (var i in a) { + for (var i = 0; i < a.length; ++i) { var el = a[i]; if (el != currentElement) { el.className = el.className.replace(/\s*HA-spellcheck-same\s*/g, " "); @@ -247,7 +247,7 @@ function wordClicked(scroll) { currentElement = this; this.className += " HA-spellcheck-current"; var a = allWords[currentElement.__msh_origWord]; - for (var i in a) { + for (var i = 0; i < a.length; ++i) { var el = a[i]; if (el != currentElement) { el.className += " HA-spellcheck-same"; diff --git a/phpgwapi/js/htmlarea/plugins/SpellChecker/spell-checker.js b/phpgwapi/js/htmlarea/plugins/SpellChecker/spell-checker.js index 7e274f7b37..7e7529b94c 100644 --- a/phpgwapi/js/htmlarea/plugins/SpellChecker/spell-checker.js +++ b/phpgwapi/js/htmlarea/plugins/SpellChecker/spell-checker.js @@ -18,7 +18,7 @@ function SpellChecker(editor) { // register the toolbar buttons provided by this plugin var toolbar = []; - for (var i in bl) { + for (var i = 0; i < bl.length; ++i) { var btn = bl[i]; if (!btn) { toolbar.push("separator"); @@ -33,7 +33,7 @@ function SpellChecker(editor) { } } - for (var i in toolbar) { + for (var i = 0; i < toolbar.length; ++i) { cfg.toolbar[0].push(toolbar[i]); } }; diff --git a/phpgwapi/js/htmlarea/plugins/TableOperations/lang/lang.php b/phpgwapi/js/htmlarea/plugins/TableOperations/lang/lang.php index 56b370f3fe..d58ae8ab0e 100644 --- a/phpgwapi/js/htmlarea/plugins/TableOperations/lang/lang.php +++ b/phpgwapi/js/htmlarea/plugins/TableOperations/lang/lang.php @@ -24,7 +24,6 @@ $GLOBALS['phpgw_info']['flags'] = Array( ); include('../../../../../../header.inc.php'); -header('Content-type: text/javascript; charset='.$GLOBALS['phpgw']->translation->charset()); $GLOBALS['phpgw']->translation->add_app('htmlarea-TableOperations'); // I18N constants diff --git a/phpgwapi/js/htmlarea/plugins/TableOperations/lang/no.js b/phpgwapi/js/htmlarea/plugins/TableOperations/lang/no.js index a29e9034eb..91fc5e5725 100644 --- a/phpgwapi/js/htmlarea/plugins/TableOperations/lang/no.js +++ b/phpgwapi/js/htmlarea/plugins/TableOperations/lang/no.js @@ -1,7 +1,7 @@ // I18N constants // LANG: "en", ENCODING: UTF-8 | ISO-8859-1 -// Author: Mihai Bazon, +// Author: Mihai Bazon, // translated into Norwegia: ses@online.no 11.11.03 // FOR TRANSLATORS: diff --git a/phpgwapi/js/htmlarea/plugins/TableOperations/table-operations.js b/phpgwapi/js/htmlarea/plugins/TableOperations/table-operations.js index a376f3a488..1b5b0566ec 100644 --- a/phpgwapi/js/htmlarea/plugins/TableOperations/table-operations.js +++ b/phpgwapi/js/htmlarea/plugins/TableOperations/table-operations.js @@ -24,7 +24,7 @@ function TableOperations(editor) { // register the toolbar buttons provided by this plugin var toolbar = ["linebreak"]; - for (var i in bl) { + for (var i = 0; i < bl.length; ++i) { var btn = bl[i]; if (!btn) { toolbar.push("separator"); @@ -65,7 +65,7 @@ TableOperations.prototype.getClosest = function(tagName) { var ancestors = editor.getAllAncestors(); var ret = null; tagName = ("" + tagName).toLowerCase(); - for (var i in ancestors) { + for (var i = 0; i < ancestors.length; ++i) { var el = ancestors[i]; if (el.tagName.toLowerCase() == tagName) { ret = el; @@ -502,11 +502,17 @@ TableOperations.prototype.buttonPress = function(editor, button_id) { var rows = td.parentNode.parentNode.rows; var index = td.cellIndex; for (var i = rows.length; --i >= 0;) { + /* + var tr = rows; + var otd = tr.insertCell(index + (/after/.test(button_id) ? 1 : 0)); + otd.innerHTML = mozbr; + */ var tr = rows[i]; var ref = tr.cells[index + (/after/.test(button_id) ? 1 : 0)]; var otd = editor._doc.createElement("td"); otd.innerHTML = mozbr; tr.insertBefore(otd, ref); + } editor.focusEditor(); break; @@ -868,7 +874,7 @@ TableOperations.createStyleLayoutFieldset = function(doc, editor, el) { td.appendChild(select); select.name = "f_st_float"; options = ["None", "Left", "Right"]; - for (i in options) { + for (var i = 0; i < options.length; ++i) { var Val = options[i]; var val = options[i].toLowerCase(); option = doc.createElement("option"); @@ -923,7 +929,7 @@ TableOperations.createStyleLayoutFieldset = function(doc, editor, el) { input.size = "1"; input.style.fontFamily = "monospace"; td.appendChild(input); - for (i in options) { + for (var i = 0; i < options.length; ++i) { var Val = options[i]; var val = Val.toLowerCase(); option = doc.createElement("option"); @@ -978,7 +984,7 @@ TableOperations.createStyleLayoutFieldset = function(doc, editor, el) { select.style.marginLeft = "0.5em"; td.appendChild(select); options = ["Top", "Middle", "Bottom", "Baseline"]; - for (i in options) { + for (var i = 0; i < options.length; ++i) { var Val = options[i]; var val = Val.toLowerCase(); option = doc.createElement("option"); @@ -1070,7 +1076,7 @@ TableOperations.createStyleFieldset = function(doc, editor, el) { // That is, "top right bottom left" -- we only consider the first // value. (currentBorderStyle.match(/([^\s]*)\s/)) && (currentBorderStyle = RegExp.$1); - for (i in options) { + for (var i in options) { var val = options[i]; option = doc.createElement("option"); option.value = val; @@ -1080,7 +1086,7 @@ TableOperations.createStyleFieldset = function(doc, editor, el) { } select.style.marginRight = "0.5em"; function setBorderFieldsStatus(value) { - for (i in borderFields) { + for (var i = 0; i < borderFields.length; ++i) { var el = borderFields[i]; el.style.visibility = value ? "hidden" : "visible"; if (!value && (el.tagName.toLowerCase() == "input")) { diff --git a/phpgwapi/js/htmlarea/plugins/UploadImage/lang/lang.php b/phpgwapi/js/htmlarea/plugins/UploadImage/lang/lang.php index 0b2445723c..6984e872d1 100644 --- a/phpgwapi/js/htmlarea/plugins/UploadImage/lang/lang.php +++ b/phpgwapi/js/htmlarea/plugins/UploadImage/lang/lang.php @@ -24,7 +24,6 @@ $GLOBALS['phpgw_info']['flags'] = Array( ); include('../../../../../../header.inc.php'); -header('Content-type: text/javascript; charset='.$GLOBALS['phpgw']->translation->charset()); $GLOBALS['phpgw']->translation->add_app('htmlarea-SpellChecker'); // I18N for the FullPage plugin diff --git a/phpgwapi/js/htmlarea/plugins/UploadImage/popups/ImageManager/config.inc.php b/phpgwapi/js/htmlarea/plugins/UploadImage/popups/ImageManager/config.inc.php index a2eb83de33..0045788e5e 100755 --- a/phpgwapi/js/htmlarea/plugins/UploadImage/popups/ImageManager/config.inc.php +++ b/phpgwapi/js/htmlarea/plugins/UploadImage/popups/ImageManager/config.inc.php @@ -1,111 +1,87 @@ * - * Modified for eGW by and (c) by Pim Snel * - * -------------------------------------------- * - * This program is free software; you can redistribute it and/or modify it * - * under the terms of the GNU General Public License as published by the * - * Free Software Foundation; version 2 of the License. * - \**************************************************************************/ - - /* $id$ */ - - // FIXME: remove imageMagick shit, we only use gdlib - // FIXME: autodetect safe_mode - // FIXME include header nicer - - $phpgw_flags = Array( - 'currentapp' => 'home', - 'noheader' => True, - 'nonavbar' => True, - 'noappheader' => True, - 'noappfooter' => True, - 'nofooter' => True - ); - - $GLOBALS['phpgw_info']['flags'] = $phpgw_flags; - - if(@include('../../../../../../header.inc.php')) - { - // I know this is very ugly - } - else - { - @include('../../../../../../../header.inc.php'); - } - - $sessdata = $GLOBALS['phpgw']->session->appsession('UploadImage','phpgwapi'); - $phpgw_flags['currentapp'] = $sessdata['app'] ? $sessdata['app'] : 'jinn'; - - define('IMAGE_CLASS', 'GD'); - - //In safe mode, directory creation is not permitted. - $SAFE_MODE = false; - - switch ($phpgw_flags['currentapp']) - { - case 'jinn' : - $BASE_DIR = $sessdata[UploadImageBaseDir]; - $BASE_URL = $sessdata[UploadImageBaseURL]; - $MAX_HEIGHT = $sessdata[UploadImageMaxHeight]; - $MAX_WIDTH = $sessdata[UploadImageMaxWidth]; - // _debug_array($sessdata); - //die(); - break; - case 'sitemgr' : - if(is_writeable($sessdata['upload_dir'])) - { - $BASE_DIR = $sessdata['upload_dir']; - $BASE_URL = str_replace($GLOBALS['_SERVER']['DOCUMENT_ROOT'],'',$sessdata['upload_dir']); - } - else - { - echo '

        Error

        '; - echo '

        Upload directory does not exist, or is not writeable by webserver

        '; - echo $GLOBALS['egw_info']['user']['apps']['admin'] ? - 'Choose an other directory
        - or make "'. $sessdata['upload_dir']. '" writeable by webserver' : - 'Notify your Administrator to correct this Situation'; - die(); - } - default : - break; - } - - if(!$MAX_HEIGHT) $MAX_HEIGHT = 10000; - if(!$MAX_WIDTH) $MAX_WIDTH = 10000; - - - //After defining which library to use, if it is NetPBM or IM, you need to - //specify where the binary for the selected library are. And of course - //your server and PHP must be able to execute them (i.e. safe mode is OFF). - //If you have safe mode ON, or don't have the binaries, your choice is - //GD only. GD does not require the following definition. - //define('IMAGE_TRANSFORM_LIB_PATH', '/usr/bin/netpbm/'); - //define('IMAGE_TRANSFORM_LIB_PATH', '"D:\\Program Files\\ImageMagick\\'); - - $BASE_ROOT = ''; - $IMG_ROOT = $BASE_ROOT; - - if(strrpos($BASE_DIR, '/')!= strlen($BASE_DIR)-1) - $BASE_DIR .= '/'; - - if(strrpos($BASE_URL, '/')!= strlen($BASE_URL)-1) - $BASE_URL .= '/'; - - //Built in function of dirname is faulty - //It assumes that the directory nane can not contain a . (period) - function dir_name($dir) - { - $lastSlash = intval(strrpos($dir, '/')); - if($lastSlash == strlen($dir)-1){ - return substr($dir, 0, $lastSlash); - } - else - return dirname($dir); - } + /**************************************************************************\ + * eGroupWare - UploadImage-plugin for htmlArea * + * http://www.eGroupWare.org * + * Written and (c) by Xiang Wei ZHUO * + * Modified for eGW by and (c) by Pim Snel * + * -------------------------------------------- * + * This program is free software; you can redistribute it and/or modify it * + * under the terms of the GNU General Public License as published by the * + * Free Software Foundation; version 2 of the License. * + \**************************************************************************/ + + /* $id$ */ + + // FIXME: remove imageMagick shit, we only use gdlib + // FIXME: autodetect safe_mode + // FIXME set current app to the calling app + // FIXME include header nicer + + $phpgw_flags = Array( + 'currentapp' => 'jinn', + 'noheader' => True, + 'nonavbar' => True, + 'noappheader' => True, + 'noappfooter' => True, + 'nofooter' => True + ); + + $GLOBALS['phpgw_info']['flags'] = $phpgw_flags; + + if(@include('../../../../../../header.inc.php')) + { + // I know this is very ugly + } + else + { + @include('../../../../../../../header.inc.php'); + } + + define('IMAGE_CLASS', 'GD'); + + //In safe mode, directory creation is not permitted. + $SAFE_MODE = false; + + $sessdata = $GLOBALS['phpgw']->session->appsession('UploadImage','phpgwapi'); + + $BASE_DIR = $sessdata[UploadImageBaseDir]; + $BASE_URL = $sessdata[UploadImageBaseURL]; + $MAX_HEIGHT = $sessdata[UploadImageMaxHeight]; + $MAX_WIDTH = $sessdata[UploadImageMaxWidth]; + + if(!$MAX_HEIGHT) $MAX_HEIGHT = 10000; + if(!$MAX_WIDTH) $MAX_WIDTH = 10000; +// _debug_array($sessdata); + //die(); + + + //After defining which library to use, if it is NetPBM or IM, you need to + //specify where the binary for the selected library are. And of course + //your server and PHP must be able to execute them (i.e. safe mode is OFF). + //If you have safe mode ON, or don't have the binaries, your choice is + //GD only. GD does not require the following definition. + //define('IMAGE_TRANSFORM_LIB_PATH', '/usr/bin/netpbm/'); + //define('IMAGE_TRANSFORM_LIB_PATH', '"D:\\Program Files\\ImageMagick\\'); + + $BASE_ROOT = ''; + $IMG_ROOT = $BASE_ROOT; + + if(strrpos($BASE_DIR, '/')!= strlen($BASE_DIR)-1) + $BASE_DIR .= '/'; + + if(strrpos($BASE_URL, '/')!= strlen($BASE_URL)-1) + $BASE_URL .= '/'; + + //Built in function of dirname is faulty + //It assumes that the directory nane can not contain a . (period) + function dir_name($dir) + { + $lastSlash = intval(strrpos($dir, '/')); + if($lastSlash == strlen($dir)-1){ + return substr($dir, 0, $lastSlash); + } + else + return dirname($dir); + } ?> diff --git a/phpgwapi/js/htmlarea/popupdiv.js b/phpgwapi/js/htmlarea/popupdiv.js index 084ff8e10c..c2f8d3be94 100644 --- a/phpgwapi/js/htmlarea/popupdiv.js +++ b/phpgwapi/js/htmlarea/popupdiv.js @@ -191,7 +191,7 @@ PopupDiv.prototype.getForm = function() { PopupDiv.prototype.callHandler = function() { var tags = ["input", "textarea", "select"]; var params = new Object(); - for (var ti in tags) { + for (var ti = tags.length; --ti >= 0;) { var tag = tags[ti]; var els = this.content.getElementsByTagName(tag); for (var j = 0; j < els.length; ++j) { diff --git a/phpgwapi/js/htmlarea/popups/fullscreen.html b/phpgwapi/js/htmlarea/popups/fullscreen.html index 8a9f27fd18..6291d8e0a8 100644 --- a/phpgwapi/js/htmlarea/popups/fullscreen.html +++ b/phpgwapi/js/htmlarea/popups/fullscreen.html @@ -3,8 +3,9 @@ Fullscreen HTMLArea - - + @@ -33,7 +36,7 @@ var editor = null; // to be initialized later [ function init() ] \* ---------------------------------------------------------------------- */ function _CloseOnEsc(ev) { - ev || (ev = window.event); + ev || (ev = window.event) || (ev = editor._iframe.contentWindow.event); if (ev.keyCode == 27) { // update_parent(); window.close(); @@ -59,7 +62,7 @@ function resize_editor() { // resize editor to fix window if (editor.config.statusBar) { newHeight -= editor._statusBar.offsetHeight; } - editor._textArea.style.height = editor._iframe.style.height = newHeight + "px"; + editor._textArea.style.height = editor._iframe.style.height = newHeight - (HTMLArea.is_gecko ? 8 : 0) + "px"; } /* ---------------------------------------------------------------------- *\ @@ -83,7 +86,10 @@ function init() { // register the plugins, if any for (var i in parent_object.plugins) { var plugin = parent_object.plugins[i]; - editor.registerPlugin2(plugin.name, plugin.args); + try { + eval(plugin.name); + editor.registerPlugin2(plugin.name, plugin.args); + } catch(e) {}; } // and restore the original toolbar config.toolbar = parent_object.config.toolbar; @@ -125,7 +131,7 @@ function update_parent() { - +
        diff --git a/phpgwapi/js/htmlarea/popups/insert_table.html b/phpgwapi/js/htmlarea/popups/insert_table.html index 508dbf8e15..05db221dfe 100644 --- a/phpgwapi/js/htmlarea/popups/insert_table.html +++ b/phpgwapi/js/htmlarea/popups/insert_table.html @@ -10,6 +10,8 @@ window.resizeTo(400, 100); function Init() { + i18n = window.opener.HTMLArea.I18N.dialogs; // load the HTMLArea plugin and lang file + __dlg_translate(i18n); __dlg_init(); document.getElementById("f_rows").focus(); }; @@ -27,13 +29,13 @@ function onOK() { return false; } } - var fields = ["f_rows", "f_cols", "f_width", "f_unit", + var fields = ["f_rows", "f_cols", "f_width", "f_unit", "f_fixed", "f_align", "f_border", "f_spacing", "f_padding"]; var param = new Object(); for (var i in fields) { var id = fields[i]; var el = document.getElementById(id); - param[id] = el.value; + param[id] = (el.type == "checkbox") ? el.checked : el.value; } __dlg_close(param); return false; @@ -88,13 +90,6 @@ form { padding: 0px; margin: 0px; } Rows: - - - - - - Cols: - Width: - + + Cols: + + + + @@ -116,7 +117,7 @@ form { padding: 0px; margin: 0px; }
        Alignment:
        - Target: + Target: