Changeset 344 in subversion


Ignore:
Timestamp:
Sep 13, 2006 11:49:28 PM (7 years ago)
Author:
estadtherr
Message:

Initial TinyMCE editor support (still need to work on spellcheck and skins)

Location:
trunk/roundcubemail
Files:
204 added
24 edited

Legend:

Unmodified
Added
Removed
  • trunk/roundcubemail/CHANGELOG

    r342 r344  
    11CHANGELOG RoundCube Webmail 
    22--------------------------- 
     3 
     42006/09/13 (estadtherr) 
     5---------- 
     6- Introduction of TinyMCE HTML editor support for message composition and signatures 
     7  Note : a new column is added to the "identities" database table 
     8 
    39 
    4102006/09/12 (estadtherr) 
  • trunk/roundcubemail/SQL/mysql.initial.sql

    r325 r344  
    5959  `bcc` varchar(128) NOT NULL default '', 
    6060  `signature` text NOT NULL, 
     61  `html_signature` tinyint(1) NOT NULL default '0', 
    6162  PRIMARY KEY  (`identity_id`), 
    6263  KEY `user_id` (`user_id`) 
  • trunk/roundcubemail/SQL/mysql.update.sql

    r325 r344  
    88  ADD UNIQUE `uniqueness` (`cache_key`, `uid`); 
    99 
     10ALTER TABLE 'identities' 
     11  ADD 'html_signature' tinyint(1) default 0 NOT NULL; 
  • trunk/roundcubemail/SQL/mysql5.initial.sql

    r325 r344  
    115115 `bcc` varchar(128) NOT NULL, 
    116116 `signature` text NOT NULL, 
     117 `html_signature` tinyint(1) NOT NULL DEFAULT '0', 
    117118 `user_id` int(10) UNSIGNED NOT NULL DEFAULT '0', 
    118119 PRIMARY KEY(`identity_id`), 
  • trunk/roundcubemail/SQL/postgres.initial.sql

    r325 r344  
    7070    "reply-to" character varying(128), 
    7171    bcc character varying(128), 
    72     signature text 
     72    signature text, 
     73    html_signature integer DEFAULT 0 NOT NULL 
    7374); 
    7475 
  • trunk/roundcubemail/SQL/postgres.update.sql

    r325 r344  
    66ALTER TABLE "messages" ADD UNIQUE (cache_key, uid); 
    77 
     8ALTER TABLE "identities" ADD html_signature integer DEFAULT 0 NOT NULL; 
     9 
  • trunk/roundcubemail/SQL/sqlite.initial.sql

    r325 r344  
    5959  "reply-to" varchar(128) NOT NULL default '', 
    6060  bcc varchar(128) NOT NULL default '', 
    61   signature text NOT NULL default '' 
     61  signature text NOT NULL default '', 
     62  html_signature tinyint NOT NULL default '0' 
    6263); 
    6364 
  • trunk/roundcubemail/index.php

    r338 r344  
    8282require_once('include/main.inc'); 
    8383require_once('include/cache.inc'); 
     84require_once('lib/html2text.inc'); 
    8485require_once('PEAR.php'); 
    8586 
     
    146147  } 
    147148 
     149// handle HTML->text conversion 
     150if ($_action=='html2text') 
     151  { 
     152  $htmlText = $HTTP_RAW_POST_DATA; 
     153  $converter = new html2text($htmlText); 
     154 
     155  // TODO possibly replace with rcube_remote_response() 
     156  send_nocacheing_headers(); 
     157  header('Content-Type: text/plain'); 
     158  $plaintext = $converter->get_text(); 
     159  print $plaintext; 
     160 
     161  exit; 
     162  } 
     163 
    148164 
    149165// try to log in 
     
    241257  exit; 
    242258  } 
    243  
    244259 
    245260// include task specific files 
  • trunk/roundcubemail/program/include/bugs.inc

    r90 r344  
    116116  } 
    117117 
     118function log_debug($filename, $text) 
     119{ 
     120   global $CONFIG, $INSTALL_PATH; 
     121 
     122   if (empty($CONFIG['log_dir'])) 
     123      $CONFIG['log_dir'] = $INSTALL_PATH.'logs'; 
     124 
     125   // try to open specific log file for writing 
     126   if ($fp = @fopen($CONFIG['log_dir'].'/'.$filename, 'a')) 
     127   { 
     128      fwrite($fp, date("d-M-Y H:i:s", mktime()) . ' ' . $text . "\n"); 
     129      fclose($fp); 
     130   } 
     131} 
    118132 
    119133?> 
  • trunk/roundcubemail/program/include/main.inc

    r341 r344  
    376376  // don't wait for page onload. Call init at the bottom of the page (delayed) 
    377377  $javascript_foot = "if (window.call_init)\n call_init('$JS_OBJECT_NAME');"; 
    378    
     378 
    379379  if (!empty($GLOBALS['_framed'])) 
    380380    $javascript .= "$JS_OBJECT_NAME.set_env('framed', true);\n"; 
     
    11981198        'priorityselector' => 'rcmail_priority_selector', 
    11991199        'charsetselector' => 'rcmail_charset_selector', 
     1200        'editorselector' => 'rcmail_editor_selector', 
    12001201        'searchform' => 'rcmail_search_form', 
    12011202        'receiptcheckbox' => 'rcmail_receipt_checkbox', 
     
    12801281    $attrib['type'] = strtolower($attrib['type']); 
    12811282  else 
    1282     $attrib['type'] = ($attrib['image'] || $attrib['imagepas'] || $arg['imageact']) ? 'image' : 'link'; 
    1283    
     1283    $attrib['type'] = ($attrib['image'] || $attrib['imagepas'] || $attrib['imageact']) ? 'image' : 'link'; 
    12841284   
    12851285  $command = $attrib['command']; 
     
    12901290 
    12911291  // add button to button stack 
    1292   else if($attrib['image'] || $arg['imageact'] || $attrib['imagepas'] || $attrib['class']) 
     1292  else if($attrib['image'] || $attrib['imageact'] || $attrib['imagepas'] || $attrib['class']) 
    12931293    { 
    12941294    if(!$attrib['name']) 
     
    14881488 
    14891489 
    1490  
     1490/** 
     1491 * Create an edit field for inclusion on a form 
     1492 *  
     1493 * @param string col field name 
     1494 * @param string value field value 
     1495 * @param array attrib HTML element attributes for field 
     1496 * @param string type HTML element type (default 'text') 
     1497 * @return string HTML field definition 
     1498 */ 
    14911499function rcmail_get_edit_field($col, $value, $attrib, $type='text') 
    14921500  { 
  • trunk/roundcubemail/program/include/rcube_shared.inc

    r331 r344  
    2929  var $scripts_path = ''; 
    3030  var $script_files = array(); 
     31  var $external_scripts = array(); 
    3132  var $scripts = array(); 
    3233  var $charset = 'ISO-8859-1'; 
     
    3536  var $script_tag      = "<script type=\"text/javascript\">\n<!--\n%s\n\n//-->\n</script>\n"; 
    3637  var $default_template = "<html>\n<head><title></title></head>\n<body></body>\n</html>"; 
    37    
     38  var $tag_format_external_script = "<script type=\"text/javascript\" src=\"%s\"></script>\n"; 
     39 
    3840  var $title = ''; 
    3941  var $header = ''; 
     
    7072    } 
    7173     
    72    
     74  function include_external_script($script_location, $position='head') 
     75  { 
     76     if (!is_array($this->external_scripts[$position])) 
     77     { 
     78        $this->external_scripts[$position] = array(); 
     79     } 
     80      
     81     $this->external_scripts[$position][] = $script_location; 
     82  } 
     83 
    7384  function add_script($script, $position='head') 
    7485    { 
    7586    if (!isset($this->scripts[$position])) 
    76       $this->scripts[$position] = ''; 
    77  
    78     $this->scripts[$position] .= "\n$script"; 
     87      $this->scripts[$position] = "\n$script"; 
     88    else 
     89      $this->scripts[$position] .= "\n$script"; 
    7990    } 
    8091 
     
    140151        $__page_header .= sprintf($this->script_tag_file, $this->scripts_path, $file); 
    141152 
     153   if (is_array($this->external_scripts['head'])) 
     154   { 
     155      foreach ($this->external_scripts['head'] as $xscript) 
     156      { 
     157         $__page_header .= sprintf($this->tag_format_external_script, $xscript); 
     158      } 
     159   } 
     160 
    142161    if (strlen($this->scripts['head'])) 
    143162      $__page_header .= sprintf($this->script_tag, $this->scripts['head']); 
    144163           
    145164    if (is_array($this->script_files['foot'])) 
     165      { 
    146166      foreach ($this->script_files['foot'] as $file) 
    147167        $__page_footer .= sprintf($this->script_tag_file, $this->scripts_path, $file); 
     168      } 
    148169 
    149170    if (strlen($this->scripts['foot'])) 
    150171      $__page_footer .= sprintf($this->script_tag, $this->scripts['foot']); 
    151172 
    152  
    153173    $__page_header .= $this->css->show(); 
    154  
    155174   
    156175    // find page header 
     
    193212   
    194213    // find and add page footer 
    195     if(($fpos = strpos(strtolower($output), '</body>')) || ($fpos = strpos(strtolower($output), '</html>'))) 
     214    $output_lc = strtolower($output); 
     215    if(($fpos = strrpos($output_lc, '</body>')) || 
     216       ($fpos = strrpos($output_lc, '</html>'))) 
     217      { 
    196218      $output = substr($output,0,$fpos) . "$__page_footer\n" . substr($output,$fpos,strlen($output)); 
     219      } 
    197220    else 
    198221      $output .= "\n$__page_footer"; 
     
    203226   
    204227   
    205     // correct absolute pathes in images and other tags 
     228    // correct absolute paths in images and other tags 
    206229    $output = preg_replace('/(src|href|background)=(["\']?)(\/[a-z0-9_\-]+)/Ui', "\\1=\\2$base_path\\3", $output); 
    207230    $output = str_replace('$__skin_path', $base_path, $output); 
     
    855878      unset($this->attrib['value']); 
    856879 
    857     if (strlen($value)) 
     880    if (strlen($value) && !isset($this->attrib['mce_editable'])) 
    858881      $value = rep_specialchars_output($value, 'html', 'replace', FALSE); 
    859      
     882 
    860883    // return final tag 
    861884    return sprintf('<%s%s>%s</%s>%s', 
     
    12341257          $key = "'$key'"; 
    12351258 
    1236         if (!is_array($value)) 
     1259        if (!is_array($value) && is_string($value)) 
    12371260          { 
    12381261          $value = str_replace("\r\n", '\n', $value); 
     
    12451268          if ($type=='string') 
    12461269            $is_string = true; 
     1270          else if (($type == 'mixed' && is_bool($value)) || $type == 'bool') 
     1271            { 
     1272            $is_string = false; 
     1273            $value = $value ? "true" : "false"; 
     1274            } 
    12471275          else if ((($type=='mixed' && is_numeric($value)) || $type=='int') && strlen($value)<16)   // js interprets numbers with digits >15 as ...e+...  
    12481276            $is_string = FALSE; 
     
    12711299    } 
    12721300  else 
     1301    { 
    12731302    return $arr; 
     1303    } 
    12741304  } 
    12751305 
     
    14381468 
    14391469 
     1470/** 
     1471 * strrstr 
     1472 * 
     1473 * return the last occurence of a string in another string 
     1474 * @param haystack string string in which to search 
     1475 * @param needle string string for which to search 
     1476 * @return index of needle within haystack, or false if not found 
     1477 */ 
     1478function strrstr($haystack, $needle) 
     1479  { 
     1480    $pver = phpversion(); 
     1481    if ($pver[0] >= 5) 
     1482      { 
     1483        return strrpos($haystack, $needle); 
     1484      } 
     1485    else 
     1486      { 
     1487        $index = strpos(strrev($haystack), strrev($needle)); 
     1488        if($index === false) { 
     1489            return false; 
     1490        } 
     1491        $index = strlen($haystack) - strlen($needle) - $index; 
     1492        return $index; 
     1493      } 
     1494  } 
     1495 
     1496 
    14401497?> 
  • trunk/roundcubemail/program/js/app.js

    r341 r344  
    143143            this.gui_objects.remoteobjectsmsg.style.display = 'block'; 
    144144          this.enable_command('load-images', true); 
    145           }   
     145          } 
    146146 
    147147        if (this.env.action=='compose') 
     
    156156            this.enable_command('savedraft', true); 
    157157          } 
    158            
     158 
    159159        if (this.env.messagecount) 
    160160          this.enable_command('select-all', 'select-none', 'sort', 'expunge', true); 
     
    175175        if (this.env.action=='print') 
    176176          window.print(); 
    177            
     177 
    178178        // get unread count for each mailbox 
    179179        if (this.gui_objects.mailboxlist) 
     
    438438    var input_subject = rcube_find_object('_subject'); 
    439439    var input_message = rcube_find_object('_message'); 
    440      
     440 
    441441    // init live search events 
    442442    if (input_to) 
     
    457457    else if (input_message) 
    458458      this.set_caret2start(input_message); // input_message.focus(); 
    459      
     459 
    460460    // get summary of all field values 
    461461    this.cmp_hash = this.compose_field_hash(); 
     
    20202020 
    20212021    // check for empty body 
    2022     if (input_message.value=='') 
     2022    if ((input_message.value=='')&&(tinyMCE.getContent()=='')) 
    20232023      { 
    20242024      if (!confirm(this.get_label('nobodywarning'))) 
     
    20802080    var input_message = rcube_find_object('_message'); 
    20812081    var message = input_message ? input_message.value : ''; 
     2082    var is_html = (rcube_find_object('_is_html').value == '1'); 
    20822083    var sig, p; 
    20832084 
    20842085    if (!this.env.identity) 
    20852086      this.env.identity = id 
    2086  
    2087     // remove the 'old' signature 
    2088     if (this.env.identity && this.env.signatures && this.env.signatures[this.env.identity]) 
    2089       { 
    2090       sig = this.env.signatures[this.env.identity]; 
    2091       if (sig.indexOf('--')!=0) 
    2092         sig = '--\n'+sig; 
    2093  
    2094       p = message.lastIndexOf(sig); 
    2095       if (p>=0) 
    2096         message = message.substring(0, p-1) + message.substring(p+sig.length, message.length); 
    2097       } 
    2098  
    2099     // add the new signature string 
    2100     if (this.env.signatures && this.env.signatures[id]) 
    2101       { 
    2102       sig = this.env.signatures[id]; 
    2103       if (sig.indexOf('--')!=0) 
    2104         sig = '--\n'+sig; 
    2105       message += '\n'+sig; 
     2087   
     2088    if (!is_html) 
     2089      { 
     2090      // remove the 'old' signature 
     2091      if (this.env.identity && this.env.signatures && this.env.signatures[this.env.identity]) 
     2092        { 
     2093        sig = this.env.signatures[this.env.identity]['text']; 
     2094        if (sig.indexOf('--')!=0) 
     2095          sig = '--\n'+sig; 
     2096   
     2097        p = message.lastIndexOf(sig); 
     2098        if (p>=0) 
     2099          message = message.substring(0, p-1) + message.substring(p+sig.length, message.length); 
     2100        } 
     2101   
     2102      // add the new signature string 
     2103      if (this.env.signatures && this.env.signatures[id]) 
     2104        { 
     2105        sig = this.env.signatures[id]['text']; 
     2106        if (sig.indexOf('--')!=0) 
     2107          sig = '--\n'+sig; 
     2108        message += '\n'+sig; 
     2109        } 
     2110      } 
     2111    else 
     2112      { 
     2113        var eid = tinyMCE.getEditorId('_message'); 
     2114        // editor is a TinyMCE_Control object 
     2115        var editor = tinyMCE.getInstanceById(eid); 
     2116        var msgDoc = editor.getDoc(); 
     2117        var msgBody = msgDoc.body; 
     2118 
     2119        if (this.env.signatures && this.env.signatures[id]) 
     2120          { 
     2121          // Append the signature as a span within the body 
     2122          var sigElem = msgDoc.getElementById("_rc_sig"); 
     2123          if (!sigElem) 
     2124            { 
     2125            sigElem = msgDoc.createElement("span"); 
     2126            sigElem.setAttribute("id", "_rc_sig"); 
     2127            msgBody.appendChild(sigElem); 
     2128            } 
     2129          if (this.env.signatures[id]['is_html']) 
     2130            { 
     2131            sigElem.innerHTML = this.env.signatures[id]['text']; 
     2132            } 
     2133          else 
     2134            { 
     2135            sigElem.innerHTML = '<pre>' + this.env.signatures[id]['text'] + '</pre>'; 
     2136            } 
     2137          } 
    21062138      } 
    21072139 
    21082140    if (input_message) 
    21092141      input_message.value = message; 
    2110        
     2142 
    21112143    this.env.identity = id; 
    21122144    return true; 
     
    34323464 
    34333465 
     3466  this.toggle_editor = function(checkbox, textElementName) 
     3467    { 
     3468    var ischecked = checkbox.checked; 
     3469    if (ischecked) 
     3470      { 
     3471        tinyMCE.execCommand('mceAddControl', true, textElementName); 
     3472      } 
     3473    else 
     3474      { 
     3475        tinyMCE.execCommand('mceRemoveControl', true, textElementName); 
     3476      } 
     3477    } 
    34343478 
    34353479  /********************************************************/ 
     
    38213865    } 
    38223866 
    3823   // sedn GET request 
     3867  // send GET request 
    38243868  this.GET = function(url) 
    38253869    { 
     
    38423886 
    38433887 
    3844   this.POST = function(url, a_param) 
    3845     { 
    3846     // not implemented yet 
     3888  this.POST = function(url, body, contentType) 
     3889    { 
     3890    // default value for contentType if not provided 
     3891    contentType = typeof(contentType) != 'undefined' ? 
     3892        contentType : 'application/x-www-form-urlencoded'; 
     3893 
     3894    this.build(); 
     3895     
     3896    if (!this.xmlhttp) 
     3897    { 
     3898       this.onerror(this); 
     3899       return false; 
     3900    } 
     3901 
     3902    var ref=this; 
     3903    this.url = url; 
     3904    this.busy = true; 
     3905     
     3906    this.xmlhttp.onreadystatechange = function() { ref.xmlhttp_onreadystatechange(); }; 
     3907    this.xmlhttp.open('POST', url, true); 
     3908    this.xmlhttp.setRequestHeader('Content-Type', contentType); 
     3909    this.xmlhttp.send(body); 
    38473910    }; 
    38483911 
  • trunk/roundcubemail/program/lib/Mail/mime.php

    r343 r344  
    199199 
    200200    /** 
     201    * returns the HTML body portion of the message 
     202    * @return string HTML body of the message 
     203    * @access public 
     204    */ 
     205    function getHTMLBody() 
     206    { 
     207       return $this->_htmlbody; 
     208    } 
     209     
     210    /** 
    201211     * Adds an image to the list of embedded images. 
    202212     * 
     
    207217     * @param  bool    $isfilename Whether $file is a filename or not 
    208218     *                             Defaults to true 
     219     * @param  string  $contentid  Desired Content-ID of MIME part 
     220     *                             Defaults to generated unique ID 
    209221     * @return mixed   true on success or PEAR_Error object 
    210222     * @access public 
    211223     */ 
    212224    function addHTMLImage($file, $c_type='application/octet-stream', 
    213                           $name = '', $isfilename = true) 
     225                          $name = '', $isfilename = true, $contentid = '') 
    214226    { 
    215227        $filedata = ($isfilename === true) ? $this->_file2str($file) 
     
    223235            return $filedata; 
    224236        } 
     237        if ($contentid == '') { 
     238           $contentid = md5(uniqid(time())); 
     239        } 
    225240        $this->_html_images[] = array( 
    226241                                      'body'   => $filedata, 
    227242                                      'name'   => $filename, 
    228243                                      'c_type' => $c_type, 
    229                                       'cid'    => md5(uniqid(time())) 
     244                                      'cid'    => $contentid 
    230245                                     ); 
    231246        return true; 
  • trunk/roundcubemail/program/localization/en_US/labels.inc

    r337 r344  
    6464$labels['filesize'] = 'File size'; 
    6565 
    66 $labels['preferhtml'] = 'Prefer HTML'; 
     66$labels['preferhtml'] = 'Display HTML'; 
    6767$labels['htmlmessage'] = 'HTML Message'; 
    6868$labels['prettydate'] = 'Pretty dates'; 
     
    121121 
    122122// message compose 
    123 $labels['compose']  = 'Compose a message'; 
     123$labels['compose']        = 'Compose a message'; 
     124$labels['sendmessage']    = 'Send the message now'; 
    124125$labels['savemessage']  = 'Save this draft'; 
    125 $labels['sendmessage']  = 'Send the message now'; 
    126126$labels['addattachment']  = 'Attach a file'; 
    127 $labels['charset']  = 'Charset'; 
    128 $labels['returnreceipt'] = 'Return receipt'; 
     127$labels['charset']        = 'Charset'; 
     128$labels['editortype']     = 'Editor type'; 
     129$labels['returnreceipt']  = 'Return receipt'; 
    129130 
    130131$labels['checkspelling'] = 'Check spelling'; 
    131132$labels['resumeediting'] = 'Resume editing'; 
    132 $labels['revertto'] = 'Revert to'; 
     133$labels['revertto']      = 'Revert to'; 
    133134 
    134135$labels['attachments'] = 'Attachments'; 
     
    202203$labels['signature'] = 'Signature'; 
    203204$labels['dstactive']  = 'Daylight savings'; 
     205$labels['htmleditor'] = 'Use HTML editor'; 
     206$labels['htmlsignature'] = 'HTML signature'; 
    204207 
    205208$labels['autosavedraft']  = 'Automatically save draft'; 
  • trunk/roundcubemail/program/localization/en_US/messages.inc

    r285 r344  
    107107$messages['folderdeleted'] = 'Folder successfully deleted'; 
    108108 
     109$messages['converting'] = 'Removing formatting from message...'; 
    109110 
    110111?> 
  • trunk/roundcubemail/program/steps/mail/compose.inc

    r339 r344  
    5454 
    5555// add some labels to client 
    56 rcube_add_label('nosubject', 'norecipientwarning', 'nosubjectwarning', 'nobodywarning', 'notsentwarning', 'savingmessage', 'sendingmessage', 'messagesaved'); 
     56rcube_add_label('nosubject', 'norecipientwarning', 'nosubjectwarning', 'nobodywarning', 'notsentwarning', 'savingmessage', 'sendingmessage', 'messagesaved', 'converting'); 
    5757 
    5858// add config parameter to client script 
     
    273273 
    274274  // get this user's identities 
    275   $sql_result = $DB->query("SELECT identity_id, name, email, signature 
     275  $sql_result = $DB->query("SELECT identity_id, name, email, signature, html_signature 
    276276                            FROM   ".get_table_name('identities')." 
    277277                            WHERE user_id=? 
     
    279279                            ORDER BY ".$DB->quoteIdentifier('standard')." DESC, name ASC", 
    280280                           $_SESSION['user_id']); 
    281                                     
     281 
    282282  if ($DB->num_rows($sql_result)) 
    283283    { 
    284284    $from_id = 0; 
    285285    $a_signatures = array(); 
    286      
     286 
    287287    $field_attrib['onchange'] = "$JS_OBJECT_NAME.change_identity(this)"; 
    288288    $select_from = new select($field_attrib); 
    289      
     289 
    290290    while ($sql_arr = $DB->fetch_assoc($sql_result)) 
    291291      { 
    292       $select_from->add(format_email_recipient($sql_arr['email'], $sql_arr['name']), $sql_arr['identity_id']); 
     292      $identity_id = $sql_arr['identity_id']; 
     293      $select_from->add(format_email_recipient($sql_arr['email'], $sql_arr['name']), $identity_id); 
    293294 
    294295      // add signature to array 
    295296      if (!empty($sql_arr['signature'])) 
    296         $a_signatures[$sql_arr['identity_id']] = $sql_arr['signature']; 
    297        
     297        { 
     298        $a_signatures[$identity_id]['text'] = $sql_arr['signature']; 
     299        $a_signatures[$identity_id]['is_html'] = ($sql_arr['html_signature'] == 1) ? true : false; 
     300        } 
     301 
    298302      // set identity if it's one of the reply-message recipients 
    299303      if (in_array($sql_arr['email'], $a_recipients)) 
    300304        $from_id = $sql_arr['identity_id']; 
    301          
     305 
    302306      if ($compose_mode == RCUBE_COMPOSE_REPLY && is_array($MESSAGE['FROM'])) 
    303307        $MESSAGE['FROM'][] = $sql_arr['email']; 
     
    305309      if ($compose_mode == RCUBE_COMPOSE_DRAFT && strstr($MESSAGE['headers']->from, $sql_arr['email'])) 
    306310        $from_id = $sql_arr['identity_id']; 
    307  
    308311      } 
    309312 
     
    313316 
    314317    $out = $select_from->show($from_id); 
    315      
    316318 
    317319    // add signatures to client 
     
    341343  if (empty($attrib['id'])) 
    342344    $attrib['id'] = 'rcmComposeMessage'; 
    343    
     345 
    344346  $attrib['name'] = '_message'; 
    345   $textarea = new textarea($attrib); 
     347 
     348  if ($CONFIG['htmleditor']) 
     349    $isHtml = true; 
     350  else 
     351    $isHtml = false; 
    346352 
    347353  $body = ''; 
    348    
     354 
    349355  // use posted message body 
    350356  if (!empty($_POST['_message'])) 
     357    { 
    351358    $body = get_input_value('_message', RCUBE_INPUT_POST, TRUE); 
    352      
     359    } 
    353360  // compose reply-body 
    354361  else if ($compose_mode == RCUBE_COMPOSE_REPLY) 
    355362    { 
    356     $body = rcmail_first_text_part($MESSAGE); 
     363    $hasHtml = rcmail_has_html_part($MESSAGE['parts']);  
     364    if ($hasHtml && $CONFIG['htmleditor']) 
     365      { 
     366      $body = rcmail_first_html_part($MESSAGE); 
     367      $isHtml = true; 
     368      } 
     369    else 
     370      { 
     371      $body = rcmail_first_text_part($MESSAGE); 
     372      $isHtml = false; 
     373      } 
    357374    if (strlen($body)) 
    358       $body = rcmail_create_reply_body($body); 
    359     } 
    360  
     375      $body = rcmail_create_reply_body($body, $isHtml); 
     376    } 
    361377  // forward message body inline 
    362378  else if ($compose_mode == RCUBE_COMPOSE_FORWARD) 
    363379    { 
    364     $body = rcmail_first_text_part($MESSAGE); 
     380    $hasHtml = rcmail_has_html_part($MESSAGE['parts']); 
     381    if ($hasHtml && $CONFIG['htmleditor']) 
     382      { 
     383      $body = rcmail_first_html_part($MESSAGE); 
     384      $isHtml = true; 
     385      } 
     386    else 
     387      { 
     388      $body = rcmail_first_text_part($MESSAGE); 
     389      $isHtml = false; 
     390      } 
    365391    if (strlen($body)) 
    366       $body = rcmail_create_forward_body($body); 
    367     } 
    368  
    369   // forward message body inline 
     392      $body = rcmail_create_forward_body($body, $isHtml); 
     393    } 
    370394  else if ($compose_mode == RCUBE_COMPOSE_DRAFT) 
    371395    { 
    372     $body = rcmail_first_text_part($MESSAGE); 
     396    $hasHtml = rcmail_has_html_part($MESSAGE['parts']); 
     397    if ($hasHtml && $CONFIG['htmleditor']) 
     398      { 
     399      $body = rcmail_first_html_part($MESSAGE); 
     400      $isHtml = true; 
     401      } 
     402    else 
     403      { 
     404      $body = rcmail_first_text_part($MESSAGE); 
     405      $isHtml = false; 
     406      } 
    373407    if (strlen($body)) 
    374       $body = rcmail_create_draft_body($body); 
    375     } 
    376    
     408      $body = rcmail_create_draft_body($body, $isHtml); 
     409    } 
     410 
     411  $OUTPUT->include_script('tiny_mce/tiny_mce.js'); 
     412  $OUTPUT->include_script("editor.js"); 
     413  $OUTPUT->add_script('rcmail_editor_init($__skin_path);'); 
     414 
    377415  $out = $form_start ? "$form_start\n" : ''; 
    378416 
     
    383421  $out .= $drafttoggle->show(); 
    384422 
     423  $msgtype = new hiddenfield(array('name' => '_is_html', 'value' => ($isHtml?"1":"0"))); 
     424  $out .= $msgtype->show(); 
     425 
     426  // If desired, set this text area to be editable by TinyMCE 
     427  if ($isHtml) 
     428    $attrib['mce_editable'] = "true"; 
     429  $textarea = new textarea($attrib); 
    385430  $out .= $textarea->show($body); 
    386431  $out .= $form_end ? "\n$form_end" : ''; 
    387    
     432 
    388433  // include GoogieSpell 
    389   if (!empty($CONFIG['enable_spellcheck'])) 
     434  if (!empty($CONFIG['enable_spellcheck']) && !$isHtml) 
    390435    { 
    391436    $lang_set = ''; 
     
    423468 
    424469 
    425 function rcmail_create_reply_body($body) 
     470function rcmail_create_reply_body($body, $bodyIsHtml) 
    426471  { 
    427472  global $IMAP, $MESSAGE; 
    428473 
    429   // soft-wrap message first 
    430   $body = wordwrap($body, 75); 
    431    
    432   // split body into single lines 
    433   $a_lines = preg_split('/\r?\n/', $body); 
    434    
    435   // add > to each line 
    436   for($n=0; $n<sizeof($a_lines); $n++) 
    437     { 
    438     if (strpos($a_lines[$n], '>')===0) 
    439       $a_lines[$n] = '>'.$a_lines[$n]; 
    440     else 
    441       $a_lines[$n] = '> '.$a_lines[$n]; 
    442     } 
     474  if (! $bodyIsHtml) 
     475  { 
     476    // soft-wrap message first 
     477    $body = wordwrap($body, 75); 
     478   
     479    // split body into single lines 
     480    $a_lines = preg_split('/\r?\n/', $body); 
     481   
     482    // add > to each line 
     483    for($n=0; $n<sizeof($a_lines); $n++) 
     484      { 
     485      if (strpos($a_lines[$n], '>')===0) 
     486        $a_lines[$n] = '>'.$a_lines[$n]; 
     487      else 
     488        $a_lines[$n] = '> '.$a_lines[$n]; 
     489      } 
    443490  
    444   $body = join("\n", $a_lines); 
    445  
    446   // add title line 
    447   $pefix = sprintf("\n\n\nOn %s, %s wrote:\n", 
    448            $MESSAGE['headers']->date, 
    449            $IMAP->decode_header($MESSAGE['headers']->from)); 
    450             
    451  
    452   // try to remove the signature 
    453   if ($sp = strrpos($body, '-- ')) 
    454     { 
    455     if ($body{$sp+3}==' ' || $body{$sp+3}=="\n" || $body{$sp+3}=="\r") 
    456       $body = substr($body, 0, $sp-1); 
    457     } 
    458  
    459   return $pefix.$body; 
    460   } 
    461  
    462  
    463 function rcmail_create_forward_body($body) 
     491    $body = join("\n", $a_lines); 
     492 
     493    // add title line 
     494    $prefix = sprintf("\n\n\nOn %s, %s wrote:\n", 
     495             $MESSAGE['headers']->date, 
     496             $IMAP->decode_header($MESSAGE['headers']->from)); 
     497 
     498    // try to remove the signature 
     499    if ($sp = strrstr($body, '-- ')) 
     500      { 
     501      if ($body{$sp+3}==' ' || $body{$sp+3}=="\n" || $body{$sp+3}=="\r") 
     502        $body = substr($body, 0, $sp-1); 
     503      } 
     504    $suffix = ''; 
     505  } 
     506  else 
     507  { 
     508     $prefix = sprintf("<br><br>On %s, %s wrote:<br><blockquote type=\"cite\" " . 
     509                       "style=\"padding-left: 5px; border-left: #1010ff 2px solid; " . 
     510                       "margin-left: 5px; width: 100%%\">", 
     511                       $MESSAGE['headers']->date, 
     512                       $IMAP->decode_header($MESSAGE['headers']->from)); 
     513 
     514     $suffix = "</blockquote>"; 
     515  } 
     516 
     517  return $prefix.$body.$suffix; 
     518  } 
     519 
     520 
     521function rcmail_create_forward_body($body, $bodyIsHtml) 
    464522  { 
    465523  global $IMAP, $MESSAGE; 
    466524 
    467   // soft-wrap message first 
    468   $body = wordwrap($body, 80); 
    469    
    470   $prefix = sprintf("\n\n\n-------- Original Message --------\nSubject: %s\nDate: %s\nFrom: %s\nTo: %s\n\n", 
    471                    $MESSAGE['subject'], 
    472                    $MESSAGE['headers']->date, 
    473                    $IMAP->decode_header($MESSAGE['headers']->from), 
    474                    $IMAP->decode_header($MESSAGE['headers']->to)); 
    475                     
     525  if (! $bodyIsHtml) 
     526  { 
     527    // soft-wrap message first 
     528    $body = wordwrap($body, 80); 
     529   
     530    $prefix = sprintf("\n\n\n-------- Original Message --------\nSubject: %s\nDate: %s\nFrom: %s\nTo: %s\n\n", 
     531                     $MESSAGE['subject'], 
     532                     $MESSAGE['headers']->date, 
     533                     $IMAP->decode_header($MESSAGE['headers']->from), 
     534                     $IMAP->decode_header($MESSAGE['headers']->to)); 
     535  } 
     536  else 
     537  { 
     538      $prefix = sprintf( 
     539        "<br><br>-------- Original Message --------" . 
     540        "<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\"><tbody>" . 
     541        "<tr><th align=\"right\" nowrap=\"nowrap\" valign=\"baseline\">Subject: </th><td>%s</td></tr>" . 
     542        "<tr><th align=\"right\" nowrap=\"nowrap\" valign=\"baseline\">Date: </th><td>%s</td></tr>" . 
     543        "<tr><th align=\"right\" nowrap=\"nowrap\" valign=\"baseline\">From: </th><td>%s</td></tr>" . 
     544        "<tr><th align=\"right\" nowrap=\"nowrap\" valign=\"baseline\">To: </th><td>%s</td></tr>" . 
     545        "</tbody></table><br>", 
     546                     $MESSAGE['subject'], 
     547                     $MESSAGE['headers']->date, 
     548                     $IMAP->decode_header($MESSAGE['headers']->from), 
     549                     $IMAP->decode_header($MESSAGE['headers']->to)); 
     550  } 
     551 
    476552  // add attachments 
    477553  if (!isset($_SESSION['compose']['forward_attachments']) && 
     
    483559 
    484560 
    485 function rcmail_create_draft_body($body) 
     561function rcmail_create_draft_body($body, $bodyIsHtml) 
    486562  { 
    487563  global $IMAP, $MESSAGE; 
     
    582658function rcmail_compose_attachment_list($attrib) 
    583659  { 
    584   global $OUTPUT, $JS_OBJECT_NAME; 
     660  global $OUTPUT, $JS_OBJECT_NAME, $CONFIG; 
    585661   
    586662  // add ID if not given 
     
    707783 
    708784 
     785function rcmail_editor_selector($attrib) 
     786{ 
     787  global $CONFIG, $MESSAGE, $compose_mode; 
     788 
     789  $choices = array( 
     790    'html'  => 'HTML', 
     791    'plain' => 'Plain text' 
     792  ); 
     793 
     794  // determine whether HTML or plain text should be checked  
     795  if ($CONFIG['htmleditor']) 
     796    $useHtml = true; 
     797  else 
     798    $useHtml = false; 
     799 
     800  if ($compose_mode == RCUBE_COMPOSE_REPLY || 
     801      $compose_mode == RCUBE_COMPOSE_FORWARD || 
     802      $compose_mode == RCUBE_COMPOSE_DRAFT) 
     803    { 
     804    $hasHtml = rcmail_has_html_part($MESSAGE['parts']); 
     805    $useHtml = ($hasHtml && $CONFIG['htmleditor']); 
     806    } 
     807 
     808  $selector = ''; 
     809  foreach ($choices as $value => $text) 
     810    { 
     811    $checked = ''; 
     812    if ((($text == 'HTML') && $useHtml) || 
     813        (($text != 'HTML') && !$useHtml)) 
     814      $checked = 'checked'; 
     815 
     816    $selector .= sprintf("<input type='radio' name='_editorSelect' value='%s' %s onclick='return rcmail_toggle_editor(this)'>%s</input>\n", 
     817                          $value, $checked, $text); 
     818    } 
     819 
     820  return $selector; 
     821} 
     822 
     823 
    709824function get_form_tags($attrib) 
    710825  { 
  • trunk/roundcubemail/program/steps/mail/func.inc

    r339 r344  
    690690   
    691691  $body = is_array($part->replaces) ? strtr($part->body, $part->replaces) : $part->body; 
    692    
     692 
    693693  // text/html 
    694694  if ($part->ctype_secondary=='html') 
     
    10841084        if (empty($part->ctype_parameters) || empty($part->ctype_parameters['charset'])) 
    10851085          $$part->ctype_parameters['charset'] = $MESSAGE['headers']->charset; 
    1086           
     1086 
    10871087        // fetch part if not available 
    10881088        if (!isset($part->body)) 
    10891089          $part->body = $IMAP->get_message_part($MESSAGE['UID'], $part->mime_id, $part); 
    1090          
     1090 
    10911091        $body = rcmail_print_body($part, $safe_mode); 
    10921092        $out .= '<div class="message-part">'; 
     
    11881188                      '<!--\\1-->', 
    11891189                      $body); 
    1190                        
     1190 
    11911191  $out = preg_replace(array('/(<body[^>]*>)/i', 
    11921192                            '/(<\/body>)/i'), 
     
    11941194                            '</div>'), 
    11951195                      $out); 
    1196    
     1196 
    11971197  return $out; 
    11981198  } 
     
    12391239  } 
    12401240 
     1241 
     1242function rcmail_has_html_part($message_parts) 
     1243{ 
     1244   if (!is_array($message_parts)) 
     1245      return FALSE; 
     1246 
     1247   // check all message parts 
     1248   foreach ($message_parts as $pid => $part) 
     1249   { 
     1250      $mimetype = strtolower($part->ctype_primary.'/'.$part->ctype_secondary); 
     1251      if ($mimetype=='text/html') 
     1252      { 
     1253         return TRUE; 
     1254      } 
     1255   } 
     1256     
     1257   return FALSE; 
     1258} 
     1259 
     1260// return first HTML part of a message 
     1261function rcmail_first_html_part($message_struct) 
     1262  { 
     1263  global $IMAP; 
     1264 
     1265  if (!is_array($message_struct['parts'])) 
     1266    return FALSE; 
     1267     
     1268  $html_part = NULL; 
     1269 
     1270  // check all message parts 
     1271  foreach ($message_struct['parts'] as $pid => $part) 
     1272    { 
     1273    $mimetype = strtolower($part->ctype_primary.'/'.$part->ctype_secondary); 
     1274    if ($mimetype=='text/html') 
     1275      { 
     1276      $html_part = $IMAP->get_message_part($message_struct['UID'], $pid, $part); 
     1277      } 
     1278    } 
     1279 
     1280  if ($html_part) 
     1281    { 
     1282    // remove special chars encoding 
     1283    //$trans = array_flip(get_html_translation_table(HTML_ENTITIES)); 
     1284    //$html_part = strtr($html_part, $trans); 
     1285 
     1286    return $html_part; 
     1287    } 
     1288 
     1289  return FALSE; 
     1290} 
    12411291 
    12421292 
  • trunk/roundcubemail/program/steps/mail/sendmail.inc

    r332 r344  
    2424//require_once('lib/smtp.inc'); 
    2525require_once('include/rcube_smtp.inc'); 
     26require_once('lib/html2text.inc'); 
    2627require_once('Mail/mime.php'); 
    2728 
     
    6364  } 
    6465 
     66/** 
     67 * go from this: 
     68 * <img src=".../tiny_mce/plugins/emotions/images/smiley-cool.gif" border="0" alt="Cool" title="Cool" /> 
     69 * 
     70 * to this: 
     71 * 
     72 * <IMG src="cid:smiley-cool.gif"/> 
     73 * ... 
     74 * ------part... 
     75 * Content-Type: image/gif 
     76 * Content-Transfer-Encoding: base64 
     77 * Content-ID: <smiley-cool.gif> 
     78 */ 
     79function rcmail_attach_emoticons(&$mime_message) 
     80{ 
     81  global $CONFIG, $INSTALL_PATH; 
     82 
     83  $htmlContents = $mime_message->getHtmlBody(); 
     84 
     85  // remove any null-byte characters before parsing 
     86  $body = preg_replace('/\x00/', '', $htmlContents); 
     87   
     88  $last_img_pos = 0; 
     89 
     90  $searchstr = 'program/js/tiny_mce/plugins/emotions/images/'; 
     91 
     92  // find emoticon image tags 
     93  while ($pos = strpos($body, $searchstr, $last_img_pos)) 
     94    { 
     95    $pos2 = strpos($body, '"', $pos); 
     96    $body_pre = substr($body, 0, $pos); 
     97    $image_name = substr($body, $pos + strlen($searchstr), $pos2 - ($pos + strlen($searchstr))); 
     98    $body_post = substr($body, $pos2); 
     99 
     100    // add the image to the MIME message 
     101    $img_file = $INSTALL_PATH . '/' . $searchstr . $image_name; 
     102    if(! $mime_message->addHTMLImage($img_file, 'image/gif', '', true, '_' . $image_name)) 
     103      { 
     104      show_message("emoticonerror", 'error'); 
     105      } 
     106 
     107    $body = $body_pre . 'cid:_' . $image_name . $body_post; 
     108 
     109    $last_img_pos = $pos2; 
     110    } 
     111    
     112  $mime_message->setHTMLBody($body); 
     113} 
    65114 
    66115if (strlen($_POST['_draft_saveid']) > 3) 
     
    185234 
    186235// create PEAR::Mail_mime instance 
     236 
     237$isHtmlVal = strtolower(get_input_value('_is_html', RCUBE_INPUT_POST)); 
     238$isHtml = ($isHtmlVal == "1"); 
     239 
    187240$MAIL_MIME = new Mail_mime($header_delm); 
    188 $MAIL_MIME->setTXTBody($message_body, FALSE, TRUE); 
    189 //$MAIL_MIME->setTXTBody(wordwrap($message_body), FALSE, TRUE); 
     241 
     242// For HTML-formatted messages, construct the MIME message with both 
     243// the HTML part and the plain-text part 
     244 
     245if ($isHtml) 
     246  { 
     247  $MAIL_MIME->setHTMLBody($message_body); 
     248 
     249  // add a plain text version of the e-mail as an alternative part. 
     250  $h2t = new html2text($message_body); 
     251  $plainTextPart = $h2t->get_text(); 
     252  $MAIL_MIME->setTXTBody($plainTextPart); 
     253 
     254  // look for "emoticon" images from TinyMCE and copy into message as attachments 
     255  rcmail_attach_emoticons($MAIL_MIME); 
     256  } 
     257else 
     258  { 
     259  $MAIL_MIME->setTXTBody($message_body, FALSE, TRUE); 
     260  } 
    190261 
    191262 
  • trunk/roundcubemail/program/steps/settings/edit_identity.inc

    r319 r344  
    4343function rcube_identity_form($attrib) 
    4444  { 
    45   global $IDENTITY_RECORD, $JS_OBJECT_NAME; 
     45  global $IDENTITY_RECORD, $JS_OBJECT_NAME, $OUTPUT; 
     46 
     47  $OUTPUT->include_script('tiny_mce/tiny_mce_src.js'); 
     48  $OUTPUT->add_script("tinyMCE.init({ mode : 'specific_textareas'," . 
     49                                    "apply_source_formatting : true," . 
     50                                    "theme : 'advanced'," . 
     51                                    "theme_advanced_toolbar_location : 'top'," . 
     52                                    "theme_advanced_toolbar_align : 'left'," . 
     53                                    "theme_advanced_buttons1 : 'bold,italic,underline,strikethrough,justifyleft,justifycenter,justifyright,justifyfull,separator,outdent,indent,charmap,hr'," . 
     54                                    "theme_advanced_buttons2 : 'link,unlink,forecolor,fontselect,fontsizeselect'," . 
     55                                    "theme_advanced_buttons3 : '' });"); 
    4656 
    4757  if (!$IDENTITY_RECORD && $GLOBALS['_action']!='add-identity') 
     
    6373                       'reply-to'     => array('type' => 'text', 'label' => 'replyto'), 
    6474                       'bcc'          => array('type' => 'text'), 
    65                        'signature'        => array('type' => 'textarea'), 
     75                       'signature'        => array('type' => 'textarea', 'size' => "40", 'rows' => "6"), 
     76                       'html_signature'=>array('type' => 'checkbox', 'label' => 'htmlsignature', 'onclick' => 'return rcmail.toggle_editor(this, \'_signature\');'), 
    6677                       'standard'     => array('type' => 'checkbox', 'label' => 'setdefault')); 
    6778 
     
    8899    { 
    89100    $attrib['id'] = 'rcmfd_'.$col; 
     101 
     102    if (strlen($colprop['onclick'])) 
     103      $attrib['onclick'] = $colprop['onclick']; 
     104    else 
     105      unset($attrib['onclick']); 
     106 
     107    if ($col == 'signature') 
     108      { 
     109      $attrib['size'] = $colprop['size']; 
     110      $attrib['rows'] = $colprop['rows']; 
     111      $attrib['mce_editable'] = $IDENTITY_RECORD['html_signature'] ? "true" : "false"; 
     112      } 
     113    else 
     114      { 
     115      unset($attrib['size']); 
     116      unset($attrib['rows']); 
     117      unset($attrib['mce_editable']); 
     118      } 
     119 
    90120    $label = strlen($colprop['label']) ? $colprop['label'] : $col; 
    91121    $value = rcmail_get_edit_field($col, $IDENTITY_RECORD[$col], $attrib, $colprop['type']); 
  • trunk/roundcubemail/program/steps/settings/func.inc

    r337 r344  
    148148                  $input_prettydate->show($CONFIG['prettydate']?1:0)); 
    149149 
     150  // Show checkbox for HTML Editor 
     151  $field_id = 'rcmfd_htmleditor'; 
     152  $input_htmleditor = new checkbox(array('name' => '_htmleditor', 'id' => $field_id, 'value' => 1)); 
     153  $out .= sprintf("<tr><td class=\"title\"><label for=\"%s\">%s</label></td><td>%s</td></tr>\n", 
     154                  $field_id, 
     155                  rep_specialchars_output(rcube_label('htmleditor')), 
     156                  $input_htmleditor->show($CONFIG['htmleditor']?1:0)); 
     157 
    150158  if (!empty($CONFIG['drafts_mbox'])) 
    151159    { 
     
    169177 
    170178 
    171  
    172179function rcmail_identities_list($attrib) 
    173180  { 
  • trunk/roundcubemail/program/steps/settings/save_identity.inc

    r330 r344  
    2020*/ 
    2121 
    22 $a_save_cols = array('name', 'email', 'organization', 'reply-to', 'bcc', 'standard', 'signature'); 
     22$a_save_cols = array('name', 'email', 'organization', 'reply-to', 'bcc', 'standard', 'signature', 'html_signature'); 
    2323$a_html_cols = array('signature'); 
    24  
     24$a_boolean_cols = array('standard', 'html_signature'); 
    2525 
    2626// check input 
     
    4747                             $DB->quoteIdentifier($col), 
    4848                             $DB->quote(get_input_value($fname, RCUBE_INPUT_POST, in_array($col, $a_html_cols)))); 
     49    } 
     50 
     51  // set "off" values for checkboxes that were not checked, and therefore 
     52  // not included in the POST body. 
     53  foreach ($a_boolean_cols as $col) 
     54    { 
     55    $fname = '_' . $col; 
     56    if (!isset($_POST[$fname])) 
     57      { 
     58      $a_write_sql[] = sprintf("%s=0", $DB->quoteIdentifier($col)); 
     59      } 
    4960    } 
    5061 
  • trunk/roundcubemail/program/steps/settings/save_prefs.inc

    r337 r344  
    2929$a_user_prefs['pagesize'] = is_numeric($_POST['_pagesize']) ? (int)$_POST['_pagesize'] : $CONFIG['pagesize']; 
    3030$a_user_prefs['prefer_html'] = isset($_POST['_prefer_html']) ? TRUE : FALSE; 
     31$a_user_prefs['htmleditor'] = isset($_POST['_htmleditor']) ? TRUE : FALSE; 
    3132$a_user_prefs['draft_autosave'] = isset($_POST['_draft_autosave']) ? intval($_POST['_draft_autosave']) : 0; 
    3233 
  • trunk/roundcubemail/skins/default/templates/compose.html

    r281 r344  
    107107<roundcube:label name="charset" />:&nbsp;<roundcube:object name="charsetSelector" tabindex="8" /> 
    108108</td> 
    109  
     109<td align="right"> 
     110 <roundcube:label name="editortype" />:&nbsp;<roundcube:object name="editorSelector" tabindex="9" /> 
     111</td> 
    110112</tr></tbody></table> 
    111113 
  • trunk/roundcubemail/skins/default/templates/ldappublicsearch.html

    r95 r344  
    2323  id="ldappublicaddresslist" 
    2424  cellspacing="0" 
    25   summary="Ldap email address list" /> 
     25  summary="LDAP email address list" /> 
    2626</div> 
    2727 
Note: See TracChangeset for help on using the changeset viewer.