Aleksander Machniak
2013-05-08 a522971cf853b2f0ccd1b569491a06218ebbaee9
Merge branch 'master' of github.com:roundcube/roundcubemail

Conflicts:
program/js/list.js
15 files added
67 files modified
1448 ■■■■ changed files
CHANGELOG 15 ●●●●● patch | view | raw | blame | history
bin/updatecss.sh 122 ●●●●● patch | view | raw | blame | history
config/main.inc.php.dist 3 ●●●●● patch | view | raw | blame | history
installer/rcube_install.php 9 ●●●●● patch | view | raw | blame | history
plugins/acl/acl.php 2 ●●● patch | view | raw | blame | history
plugins/attachment_reminder/attachment_reminder.js 51 ●●●●● patch | view | raw | blame | history
plugins/attachment_reminder/attachment_reminder.php 81 ●●●●● patch | view | raw | blame | history
plugins/attachment_reminder/localization/de_CH.inc 5 ●●●●● patch | view | raw | blame | history
plugins/attachment_reminder/localization/de_DE.inc 5 ●●●●● patch | view | raw | blame | history
plugins/attachment_reminder/localization/en_US.inc 6 ●●●●● patch | view | raw | blame | history
plugins/attachment_reminder/localization/es_ES.inc 5 ●●●●● patch | view | raw | blame | history
plugins/attachment_reminder/localization/fr_FR.inc 5 ●●●●● patch | view | raw | blame | history
plugins/attachment_reminder/localization/it_IT.inc 6 ●●●●● patch | view | raw | blame | history
plugins/attachment_reminder/localization/nl_NL.inc 5 ●●●●● patch | view | raw | blame | history
plugins/attachment_reminder/localization/pl_PL.inc 6 ●●●●● patch | view | raw | blame | history
plugins/attachment_reminder/localization/zh_CN.inc 5 ●●●●● patch | view | raw | blame | history
plugins/attachment_reminder/localization/zh_TW.inc 5 ●●●●● patch | view | raw | blame | history
plugins/attachment_reminder/package.xml 66 ●●●●● patch | view | raw | blame | history
plugins/autologon/autologon.php 2 ●●●●● patch | view | raw | blame | history
plugins/debug_logger/runlog/runlog.php 2 ●●● patch | view | raw | blame | history
plugins/enigma/enigma.php 7 ●●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_engine.php 24 ●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_ui.php 5 ●●●●● patch | view | raw | blame | history
plugins/help/help.php 2 ●●●●● patch | view | raw | blame | history
plugins/managesieve/lib/Roundcube/rcube_sieve_script.php 6 ●●●●● patch | view | raw | blame | history
plugins/managesieve/managesieve.php 8 ●●●● patch | view | raw | blame | history
plugins/new_user_identity/new_user_identity.php 2 ●●●●● patch | view | raw | blame | history
plugins/password/drivers/directadmin.php 1 ●●●● patch | view | raw | blame | history
plugins/password/drivers/pam.php 1 ●●●● patch | view | raw | blame | history
plugins/password/drivers/sql.php 7 ●●●●● patch | view | raw | blame | history
plugins/password/drivers/xmail.php 4 ●●●● patch | view | raw | blame | history
plugins/squirrelmail_usercopy/squirrelmail_usercopy.php 2 ●●● patch | view | raw | blame | history
plugins/vcard_attachments/vcard_attachments.php 3 ●●●● patch | view | raw | blame | history
plugins/zipdownload/zipdownload.php 4 ●●●● patch | view | raw | blame | history
program/include/bc.php 2 ●●● patch | view | raw | blame | history
program/include/rcmail.php 30 ●●●● patch | view | raw | blame | history
program/include/rcmail_output_html.php 40 ●●●●● patch | view | raw | blame | history
program/js/app.js 101 ●●●● patch | view | raw | blame | history
program/js/list.js 173 ●●●● patch | view | raw | blame | history
program/lib/Roundcube/html.php 34 ●●●● patch | view | raw | blame | history
program/lib/Roundcube/rcube_addressbook.php 18 ●●●●● patch | view | raw | blame | history
program/lib/Roundcube/rcube_contacts.php 28 ●●●● patch | view | raw | blame | history
program/lib/Roundcube/rcube_csv2vcard.php 51 ●●●●● patch | view | raw | blame | history
program/lib/Roundcube/rcube_db.php 13 ●●●● patch | view | raw | blame | history
program/lib/Roundcube/rcube_db_mysql.php 4 ●●●● patch | view | raw | blame | history
program/lib/Roundcube/rcube_enriched.php 2 ●●● patch | view | raw | blame | history
program/lib/Roundcube/rcube_imap.php 9 ●●●●● patch | view | raw | blame | history
program/lib/Roundcube/rcube_imap_cache.php 4 ●●●● patch | view | raw | blame | history
program/lib/Roundcube/rcube_imap_generic.php 18 ●●●●● patch | view | raw | blame | history
program/lib/Roundcube/rcube_ldap.php 66 ●●●●● patch | view | raw | blame | history
program/lib/Roundcube/rcube_message.php 2 ●●● patch | view | raw | blame | history
program/lib/Roundcube/rcube_mime.php 34 ●●●●● patch | view | raw | blame | history
program/lib/Roundcube/rcube_output.php 2 ●●● patch | view | raw | blame | history
program/lib/Roundcube/rcube_plugin_api.php 1 ●●●● patch | view | raw | blame | history
program/lib/Roundcube/rcube_smtp.php 6 ●●●● patch | view | raw | blame | history
program/lib/Roundcube/rcube_spellchecker.php 17 ●●●●● patch | view | raw | blame | history
program/lib/Roundcube/rcube_utils.php 2 ●●● patch | view | raw | blame | history
program/lib/Roundcube/rcube_vcard.php 27 ●●●● patch | view | raw | blame | history
program/lib/utf8.class.php 4 ●●●● patch | view | raw | blame | history
program/localization/en_GB/labels.inc 1 ●●●● patch | view | raw | blame | history
program/localization/en_US/csv2vcard.inc 17 ●●●●● patch | view | raw | blame | history
program/localization/en_US/labels.inc 1 ●●●● patch | view | raw | blame | history
program/localization/fr_FR/csv2vcard.inc 96 ●●●●● patch | view | raw | blame | history
program/steps/addressbook/func.inc 7 ●●●● patch | view | raw | blame | history
program/steps/addressbook/import.inc 4 ●●●● patch | view | raw | blame | history
program/steps/addressbook/show.inc 8 ●●●●● patch | view | raw | blame | history
program/steps/mail/autocomplete.inc 2 ●●● patch | view | raw | blame | history
program/steps/mail/compose.inc 36 ●●●●● patch | view | raw | blame | history
program/steps/mail/func.inc 47 ●●●●● patch | view | raw | blame | history
program/steps/mail/show.inc 4 ●●●● patch | view | raw | blame | history
program/steps/settings/edit_prefs.inc 2 ●●● patch | view | raw | blame | history
program/steps/settings/folders.inc 3 ●●●● patch | view | raw | blame | history
program/steps/settings/func.inc 11 ●●●●● patch | view | raw | blame | history
program/steps/settings/save_prefs.inc 1 ●●●● patch | view | raw | blame | history
program/steps/utils/spell.inc 7 ●●●●● patch | view | raw | blame | history
program/steps/utils/spell_html.inc 5 ●●●●● patch | view | raw | blame | history
tests/Framework/Mime.php 4 ●●●● patch | view | raw | blame | history
tests/Framework/VCard.php 14 ●●●●● patch | view | raw | blame | history
tests/src/Csv2vcard/tb_plain.csv 2 ●●● patch | view | raw | blame | history
tests/src/Csv2vcard/tb_plain.vcf 2 ●●● patch | view | raw | blame | history
tests/src/format-flowed-unfolded.txt 2 ●●● patch | view | raw | blame | history
tests/src/format-flowed.txt 2 ●●● patch | view | raw | blame | history
CHANGELOG
@@ -1,12 +1,25 @@
CHANGELOG Roundcube Webmail
===========================
- Added attachment_reminder plugin
- Fix IMAP connection issue with default_socket_timeout < 0 and imap_timeout < 0 (#1489090)
- Fix various PHP code bugs found using static analysis (#1489086)
- Fix backslash character handling on vCard import (#1489085)
- Fix csv import from Thunderbird with French localization (#1489059)
- Fix messages list focus issue in Opera and Webkit (#1489058)
- Make PHP code eval() free, use create_function()
- Add option to display email address together with a name in mail preview (#1488732)
- Fix Reply-To header handling in Reply-All action (#1489037)
- Fix so Sender: address is added to Cc: field on reply to all (#1489011)
- Fix so addressbook_search_mode works also for group search (#1489079)
- Fix removal of a contact from a group in LDAP addressbook (#1489081)
- Support CSV import from Atmail (#1489045)
- Inlcude SQL query in the log on SQL error (#1489064)
- Fix handling untagged responses in IMAP FETCH - "could not load message" error (#1489074)
- Fix very small window size in Chrome (#1488931)
- Fix list page reset when viewing a message in Larry skin (#1489076)
- Fix min_refresh_interval handling on preferences save (#1489073)
- Fix PDF support detection for Firefox PDF.js (#1488972)
- Fix messages list focus issue in Internet Explorer (#1489058)
- Add db_prefix configuration option in place of db_table_*/db_sequence_* options
- Make possible to use db_prefix for schema initialization in Installer (#1489067)
- Fix updatedb.sh script so it recognizes also table prefix for external DDL files
bin/updatecss.sh
New file
@@ -0,0 +1,122 @@
#!/usr/bin/env php
<?php
/*
 +-----------------------------------------------------------------------+
 | bin/updatecss.sh                                                      |
 |                                                                       |
 | This file is part of the Roundcube Webmail client                     |
 | Copyright (C) 2010-2013, The Roundcube Dev Team                       |
 |                                                                       |
 | Licensed under the GNU General Public License version 3 or            |
 | any later version with exceptions for skins & plugins.                |
 | See the README file for a full license statement.                     |
 |                                                                       |
 | PURPOSE:                                                              |
 |   Update cache-baster marks for css background images                 |
 +-----------------------------------------------------------------------+
 | Author: Aleksander Machniak <alec@alec.pl>                            |
 +-----------------------------------------------------------------------+
*/
define('INSTALL_PATH', realpath(dirname(__FILE__) . '/..') . '/' );
require_once INSTALL_PATH . 'program/include/clisetup.php';
// get arguments
$opts = rcube_utils::get_opt(array(
    'd' => 'dir',
));
if (empty($opts['dir'])) {
    print "Skin directory not specified (--dir). Using skins/ and plugins/skins/.\n";
    $dir     = INSTALL_PATH . 'skins';
    $dir_p   = INSTALL_PATH . 'plugins';
    $skins   = glob("$dir/*", GLOB_ONLYDIR);
    $skins_p = glob("$dir_p/*/skins/*", GLOB_ONLYDIR);
    $dirs = array_merge($skins, $skins_p);
}
// Check if directory exists
else if (!file_exists($opts['dir'])) {
    rcube::raise_error("Specified directory doesn't exist.", false, true);
}
else {
    $dirs = array($opts['dir']);
}
foreach ($dirs as $dir) {
    $img_dir = $dir . '/images';
    if (!file_exists($img_dir)) {
        continue;
    }
    $files   = get_files($dir);
    $images  = get_images($img_dir);
    $find    = array();
    $replace = array();
    // build regexps array
    foreach ($images as $path => $sum) {
        $path_ex   = str_replace('.', '\\.', $path);
        $find[]    = "#url\(['\"]?images/$path_ex(\?v=[a-f0-9-\.]+)?['\"]?\)#";
        $replace[] = "url(images/$path?v=$sum)";
    }
    foreach ($files as $file) {
        $file    = $dir . '/' . $file;
        print "File: $file\n";
        $content = file_get_contents($file);
        $content = preg_replace($find, $replace, $content, -1, $count);
        if ($count) {
            file_put_contents($file, $content);
        }
    }
}
function get_images($dir)
{
    $images = array();
    $dh     = opendir($dir);
    while ($file = readdir($dh)) {
        if (preg_match('/^(.+)\.(gif|ico|png|jpg|jpeg)$/', $file, $m)) {
            $filepath = "$dir/$file";
            $images[$file] = substr(md5_file($filepath), 0, 4) . '.' . filesize($filepath);
            print "Image: $filepath ({$images[$file]})\n";
        }
        else if ($file != '.' && $file != '..' && is_dir($dir . '/' . $file)) {
            foreach (get_images($dir . '/' . $file) as $img => $sum) {
                $images[$file . '/' . $img] = $sum;
            }
        }
    }
    closedir($dh);
    return $images;
}
function get_files($dir)
{
    $files = array();
    $dh    = opendir($dir);
    while ($file = readdir($dh)) {
        if (preg_match('/^(.+)\.(css|html)$/', $file, $m)) {
            $files[] = $file;
        }
        else if ($file != '.' && $file != '..' && is_dir($dir . '/' . $file)) {
            foreach (get_files($dir . '/' . $file) as $f) {
                $files[] = $file . '/' . $f;
            }
        }
    }
    closedir($dh);
    return $files;
}
?>
config/main.inc.php.dist
@@ -890,4 +890,7 @@
// Georgia, Helvetica, Impact, Tahoma, Terminal, Times New Roman, Trebuchet MS, Verdana
$rcmail_config['default_font'] = '';
// Enables display of email address with name instead of a name (and address in title)
$rcmail_config['message_show_email'] = false;
// end of config file
installer/rcube_install.php
@@ -347,7 +347,7 @@
    $this->config  = array_merge($this->config, $current);
    foreach ((array)$current['ldap_public'] as $key => $values) {
    foreach (array_keys((array)$current['ldap_public']) as $key) {
      $this->config['ldap_public'][$key] = $current['ldap_public'][$key];
    }
  }
@@ -356,10 +356,11 @@
   * Compare the local database schema with the reference schema
   * required for this version of Roundcube
   *
   * @param boolean True if the schema schould be updated
   * @param rcube_db Database object
   *
   * @return boolean True if the schema is up-to-date, false if not or an error occured
   */
  function db_schema_check($DB, $update = false)
  function db_schema_check($DB)
  {
    if (!$this->configured)
      return false;
@@ -583,7 +584,7 @@
      }
      else {  // check if all keys are numeric
        $isnum = true;
        foreach ($var as $key => $value) {
        foreach (array_keys($var) as $key) {
          if (!is_numeric($key)) {
            $isnum = false;
            break;
plugins/acl/acl.php
@@ -384,7 +384,6 @@
            $table->add_header(array('class' => 'acl'.$key, 'title' => $label), $label);
        }
        $i = 1;
        $js_table = array();
        foreach ($acl as $user => $rights) {
            if ($this->rc->storage->conn->user == $user) {
@@ -435,6 +434,7 @@
        $acl   = array_intersect(str_split($acl), $this->rights_supported());
        $users = $oldid ? array($user) : explode(',', $user);
        $result = 0;
        foreach ($users as $user) {
            $user = trim($user);
plugins/attachment_reminder/attachment_reminder.js
New file
@@ -0,0 +1,51 @@
/* Attachment Reminder plugin script */
function rcmail_get_compose_message()
{
  var msg;
  if (window.tinyMCE && (ed = tinyMCE.get(rcmail.env.composebody))) {
    msg = ed.getContent();
    msg = msg.replace(/<blockquote[^>]*>(.|[\r\n])*<\/blockquote>/gmi, '');
  }
  else {
    msg = $('#' + rcmail.env.composebody).val();
    msg = msg.replace(/^>.*$/gmi, '');
  }
  return msg;
}
function rcmail_check_message(msg)
{
  var i, rx, keywords = rcmail.gettext('keywords', 'attachment_reminder').split(",").concat([".doc", ".pdf"]);
  $.each(keywords, function(n) { return RegExp.escape(n); });
  rx = new RegExp('(' + keywords.join('|') + ')', 'i');
  return msg.search(rx) != -1;
}
function rcmail_have_attachments()
{
  return rcmail.env.attachments && $('li', rcmail.gui_objects.attachmentlist).length;
}
if (window.rcmail) {
  rcmail.addEventListener('beforesend', function(evt) {
    var msg = rcmail_get_compose_message(),
      subject = $('#compose-subject').val();
    if (!rcmail_have_attachments() && (rcmail_check_message(msg) || rcmail_check_message(subject))) {
      if (confirm(rcmail.gettext('forgotattachment', 'attachment_reminder'))) {
        if (window.UI && UI.show_uploadform) // Larry skin
          UI.show_uploadform();
        else if (window.rcmail_ui && rcmail_ui.show_popup) // classic skin
          rcmail_ui.show_popup('uploadmenu', true);
        return false;
      }
    }
  });
}
plugins/attachment_reminder/attachment_reminder.php
New file
@@ -0,0 +1,81 @@
<?php
/**
 * Attachement Reminder
 *
 * A plugin that reminds a user to attach the files
 *
 * @version @package_version@
 * @author Thomas Yu - Sian, Liu
 * @author Aleksander Machniak <machniak@kolabsys.com>
 *
 * Copyright (C) 2013 Thomas Yu - Sian, Liu
 * Copyright (C) 2013, Kolab Systems AG
 *
 * 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, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
class attachment_reminder extends rcube_plugin
{
    public $task = 'mail|settings';
    public $noajax = true;
    function init()
    {
        $rcmail = rcube::get_instance();
        if ($rcmail->task == 'mail' && $rcmail->action == 'compose') {
            $this->include_script('attachment_reminder.js');
            $this->add_texts('localization/', array('keywords', 'forgotattachment'));
        }
        if ($rcmail->task == 'settings') {
            $dont_override = $rcmail->config->get('dont_override', array());
            if (!in_array('attachment_reminder', $dont_override)) {
                $this->add_hook('preferences_list', array($this, 'prefs_list'));
                $this->add_hook('preferences_save', array($this, 'prefs_save'));
            }
        }
    }
    function prefs_list($args)
    {
        if ($args['section'] == 'compose') {
            $this->add_texts('localization/');
            $reminder = rcube::get_instance()->config->get('attachment_reminder');
            $field_id = 'rcmfd_attachment_reminder';
            $checkbox = new html_checkbox(array('name' => '_attachment_reminder', 'id' => $field_id, 'value' => 1));
            $args['blocks']['main']['options']['attachment_reminder'] = array(
                'title' => html::label($field_id, rcube::Q($this->gettext('reminderoption'))),
                'content' => $checkbox->show($reminder ? 1 : 0),
            );
        }
        return $args;
    }
    function prefs_save($args)
    {
        if ($args['section'] == 'compose') {
            $dont_override = rcube::get_instance()->config->get('dont_override', array());
            if (!in_array('attachment_reminder', $dont_override)) {
                $args['prefs']['attachment_reminder'] = !empty($_POST['_attachment_reminder']);
            }
        }
        return $args;
    }
}
plugins/attachment_reminder/localization/de_CH.inc
New file
@@ -0,0 +1,5 @@
<?php
$messages = array();
$messages['forgotattachment'] = "Haben Sie möglicherweise vergessen eine Datei anzuhängen?";
$messages['keywords'] = "anbei,im anhang,angehängt,angefügt,beigefügt,beliegend";
plugins/attachment_reminder/localization/de_DE.inc
New file
@@ -0,0 +1,5 @@
<?php
$messages = array();
$messages['forgotattachment'] = "Haben Sie möglicherweise vergessen eine Datei anzuhängen?";
$messages['keywords'] = "anbei,im anhang,angehängt,angefügt,beigefügt,beliegend";
plugins/attachment_reminder/localization/en_US.inc
New file
@@ -0,0 +1,6 @@
<?php
$messages = array();
$messages['forgotattachment'] = "Did you forget to attach a file?";
$messages['reminderoption'] = "Remind about forgotten attachments";
$messages['keywords'] = "attachment,file,attach,attached,attaching,enclosed,CV,cover letter";
plugins/attachment_reminder/localization/es_ES.inc
New file
@@ -0,0 +1,5 @@
<?php
$messages = array();
$messages['forgotattachment'] = "¿Olvidó adjuntar un fichero al mensaje?";
$messages['keywords'] = "adjunto";
plugins/attachment_reminder/localization/fr_FR.inc
New file
@@ -0,0 +1,5 @@
<?php
$messages = array();
$messages['forgotattachment'] = "Avez vous oublié d'attacher un fichier ?";
$messages['keywords'] = "joins,joint,attaché,CV";
plugins/attachment_reminder/localization/it_IT.inc
New file
@@ -0,0 +1,6 @@
<?php
$messages = array();
$messages['forgotattachment'] = "Sembra che tu abbia dimenticato di allegare un file!\nPremere Annulla per inviare lo stesso.\nOK per tornare al messaggio senza inviare.";
$messages['keywords'] = "allegato,allegati,allegata,allegate,allega,allego,alleghi,attaccato,file,attachment,attach";
plugins/attachment_reminder/localization/nl_NL.inc
New file
@@ -0,0 +1,5 @@
<?php
$messages = array();
$messages['forgotattachment'] = "Ben je vergeten het bestand bij te voegen?";
$messages['keywords'] = "attachment,bestand,bijgaand,bijgaande,brief,bijgevoegd,bijgesloten,CV";
plugins/attachment_reminder/localization/pl_PL.inc
New file
@@ -0,0 +1,6 @@
<?php
$messages = array();
$messages['forgotattachment'] = "Czy nie zapomniałeś załączyć pliku?";
$messages['reminderoption'] = "Włącz przypominanie o brakującym załączniku";
$messages['keywords'] = "załącznik,plik,załącz,CV";
plugins/attachment_reminder/localization/zh_CN.inc
New file
@@ -0,0 +1,5 @@
<?php
$messages = array();
$messages['forgotattachment'] = "您似乎忘記加入附件了,你確定要寄出?";
$messages['keywords'] = "附件,附加,附檔,附上,附加檔案";
plugins/attachment_reminder/localization/zh_TW.inc
New file
@@ -0,0 +1,5 @@
<?php
$messages = array();
$messages['forgotattachment'] = "您似乎忘記加入附件了,你確定要寄出?";
$messages['keywords'] = "附件,附加,附檔,附上,附加檔案";
plugins/attachment_reminder/package.xml
New file
@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<package xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" packagerversion="1.9.0" version="2.0" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
    http://pear.php.net/dtd/tasks-1.0.xsd
    http://pear.php.net/dtd/package-2.0
    http://pear.php.net/dtd/package-2.0.xsd">
    <name>Attachment Reminder</name>
    <summary>Roundcube plugin that prompts you if it looks like you wanted to attach a file but you didn't.</summary>
    <description>
        This Roundcube plugin reminds the user to attach a file if the composed message text indicates that there should be any.
    </description>
    <lead>
        <name>Aleksander Machniak</name>
        <user>alec</user>
        <email>alec@alec.pl</email>
        <active>yes</active>
    </lead>
    <lead>
        <name>Thomas Yu - Sian , Liu</name>
        <active>yes</active>
    </lead>
    <version>
        <release>1.1</release>
        <api>1.0</api>
    </version>
    <stability>
        <release>stable</release>
        <api>stable</api>
    </stability>
    <license uri="http://www.gnu.org/licenses/gpl.html">GNU GPLv3+</license>
    <notes>-</notes>
    <contents>
        <dir baseinstalldir="/" name="/">
            <file name="attachment_reminder.php" role="php">
                <tasks:replace from="@name@" to="name" type="package-info"/>
                <tasks:replace from="@package_version@" to="version" type="package-info"/>
            </file>
            <file name="attachment_reminder.js" role="data">
                <tasks:replace from="@name@" to="name" type="package-info"/>
                <tasks:replace from="@package_version@" to="version" type="package-info"/>
            </file>
            <file name="localization/de_CH.inc" role="data"></file>
            <file name="localization/de_DE.inc" role="data"></file>
            <file name="localization/en_US.inc" role="data"></file>
            <file name="localization/es_ES.inc" role="data"></file>
            <file name="localization/fr_FR.inc" role="data"></file>
            <file name="localization/it_IT.inc" role="data"></file>
            <file name="localization/nl_NL.inc" role="data"></file>
            <file name="localization/pl_PL.inc" role="data"></file>
            <file name="localization/zh_CN.inc" role="data"></file>
            <file name="localization/zh_TW.inc" role="data"></file>
        </dir>
        <!-- / -->
    </contents>
    <dependencies>
        <required>
            <php>
                <min>5.2.1</min>
            </php>
            <pearinstaller>
                <min>1.7.0</min>
            </pearinstaller>
        </required>
    </dependencies>
    <phprelease/>
</package>
plugins/autologon/autologon.php
@@ -19,8 +19,6 @@
  function startup($args)
  {
    $rcmail = rcmail::get_instance();
    // change action to login
    if (empty($_SESSION['user_id']) && !empty($_GET['_autologin']) && $this->is_localhost())
      $args['action'] = 'login';
plugins/debug_logger/runlog/runlog.php
@@ -194,7 +194,7 @@
    public function print_totals(){
        $totals = array();
        foreach ( $this->run_log as $k => $entry ) {
        foreach ($this->run_log as $entry) {
            if ( $entry['type'] == 'start'  && $entry['ended'] == true) {
                $totals[$entry['value']]['duration'] += $entry['duration'];
                $totals[$entry['value']]['count'] += 1;
plugins/enigma/enigma.php
@@ -47,6 +47,8 @@
        $rcmail = rcmail::get_instance();
        $this->rc = $rcmail;
        $section = rcube_utils::get_input_value('_section', rcube_utils::INPUT_GET);
        if ($this->rc->task == 'mail') {
            // message parse/display hooks
            $this->add_hook('message_part_structure', array($this, 'parse_structure'));
@@ -79,7 +81,6 @@
            $this->register_action('plugin.enigma', array($this, 'preferences_ui'));
            // grab keys/certs management iframe requests
            $section = rcube_utils::get_input_value('_section', rcube_utils::INPUT_GET);
            if ($this->rc->action == 'edit-prefs' && preg_match('/^enigma(certs|keys)/', $section)) {
                $this->load_ui();
                $this->ui->init($section);
@@ -148,7 +149,7 @@
     */
    function parse_structure($p)
    {
        $struct = $p['structure'];
//        $struct = $p['structure'];
        if ($p['mimetype'] == 'text/plain' || $p['mimetype'] == 'application/pgp') {
            $this->parse_plain($p);
@@ -398,7 +399,7 @@
            }
        }
        // the same with message bodies
        foreach ((array)$this->message->parts as $idx => $part) {
        foreach ((array)$this->message->parts as $part) {
            if ($this->is_keys_part($part)) {
                $this->keys_parts[] = $part->mime_id;
                $this->keys_bodies[] = $part->mime_id;
plugins/enigma/lib/enigma_engine.php
@@ -374,17 +374,15 @@
    {
        // @TODO: Handle big bodies using (temp) files
        // @TODO: caching of verification result
         $sig = $this->pgp_driver->verify($msg_body, $sig_body);
         if (($sig instanceof enigma_error) && $sig->getCode() != enigma_error::E_KEYNOTFOUND)
             rcube::raise_error(array(
                'code' => 600, 'type' => 'php',
                'file' => __FILE__, 'line' => __LINE__,
                'message' => "Enigma plugin: " . $error->getMessage()
                'message' => "Enigma plugin: " . $sig->getMessage()
                ), true, false);
//print_r($sig);
        return $sig;
    }
@@ -399,10 +397,8 @@
    {
        // @TODO: Handle big bodies using (temp) files
        // @TODO: caching of verification result
        $key = ''; $pass = ''; // @TODO
        $result = $this->pgp_driver->decrypt($msg_body, $key, $pass);
//print_r($result);
        if ($result instanceof enigma_error) {
            $err_code = $result->getCode();
@@ -501,9 +497,11 @@
        $uid     = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_POST);
        $mbox    = rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_POST);
        $mime_id = rcube_utils::get_input_value('_part', rcube_utils::INPUT_POST);
        $storage = $this->rc->get_storage();
        if ($uid && $mime_id) {
            $part = $this->rc->storage->get_message_part($uid, $mime_id);
            $storage->set_folder($mbox);
            $part = $storage->get_message_part($uid, $mime_id);
        }
        if ($part && is_array($result = $this->import_key($part))) {
@@ -531,17 +529,5 @@
            $part->body = $this->rc->storage->get_message_part(
                $uid, $part->mime_id, $part);
        }
    }
    /**
     * Adds CSS style file to the page header.
     */
    private function add_css()
    {
        $skin = $this->rc->config->get('skin');
        if (!file_exists($this->home . "/skins/$skin/enigma.css"))
            $skin = 'default';
        $this->include_stylesheet("skins/$skin/enigma.css");
    }
}
plugins/enigma/lib/enigma_ui.php
@@ -176,8 +176,7 @@
        $search   = rcube_utils::get_input_value('_q', rcube_utils::INPUT_GPC);
        // define list of cols to be displayed
        $a_show_cols = array('name');
        $result = array();
//        $a_show_cols = array('name');
        // Get the list
        $list = $this->enigma->engine->list_keys($search);
@@ -200,7 +199,7 @@
                $size = count($list);
                // Add rows
                foreach($list as $idx => $key) {
                foreach ($list as $key) {
                    $this->rc->output->command('enigma_add_list_row',
                        array('name' => rcube::Q($key->name), 'id' => $key->id));
                }
plugins/help/help.php
@@ -21,8 +21,6 @@
    function init()
    {
        $rcmail = rcmail::get_instance();
        $this->add_texts('localization/', false);
        // register task
plugins/managesieve/lib/Roundcube/rcube_sieve_script.php
@@ -206,7 +206,6 @@
        // rules
        foreach ($this->content as $rule) {
            $extension = '';
            $script    = '';
            $tests     = array();
            $i         = 0;
@@ -1015,11 +1014,10 @@
     * @param mixed  $num     Number of tokens to return, 0 for all
     *                        or True for all tokens until separator is found.
     *                        Separator will be returned as last token.
     * @param int    $in_list Enable to call recursively inside a list
     *
     * @return mixed Tokens array or string if $num=1
     */
    static function tokenize(&$str, $num=0, $in_list=false)
    static function tokenize(&$str, $num=0)
    {
        $result = array();
@@ -1054,7 +1052,7 @@
            // Parenthesized list
            case '[':
                $str = substr($str, 1);
                $result[] = self::tokenize($str, 0, true);
                $result[] = self::tokenize($str, 0);
                break;
            case ']':
                $str = substr($str, 1);
plugins/managesieve/managesieve.php
@@ -1640,7 +1640,7 @@
            .'value="' . rcube::Q($action['value']) . '" size="35" '
            . $this->error_class($id, 'action', 'value', 'action_varvalue') .' />';
        $out .= '<br /><span class="label">' .rcube::Q($this->gettext('setvarmodifiers')) . '</span><br />';
        foreach ($set_modifiers as $j => $s_m) {
        foreach ($set_modifiers as $s_m) {
            $s_m_id = 'action_varmods' . $id . $s_m;
            $out .= sprintf('<input type="checkbox" name="_action_varmods[%s][]" value="%s" id="%s"%s />%s<br>',
                $id, $s_m, $s_m_id,
@@ -1902,7 +1902,7 @@
            $user_script = $_SESSION['managesieve_user_script'];
            // if the script is not active...
            if ($user_script && ($key = array_search($name, $this->active)) === false) {
            if ($user_script && array_search($name, $this->active) === false) {
                // ...rewrite USER file adding appropriate include command
                if ($this->sieve->load($user_script)) {
                    $script = $this->sieve->script->as_array();
@@ -1920,7 +1920,7 @@
                    // get all active scripts for sorting
                    foreach ($script as $rid => $rules) {
                        foreach ($rules['actions'] as $aid => $action) {
                        foreach ($rules['actions'] as $action) {
                            if ($action['type'] == 'include' && empty($action['global'])) {
                                $target = $extension ? preg_replace($regexp, '', $action['target']) : $action['target'];
                                $list[] = $target;
@@ -1988,7 +1988,7 @@
                    $name   = $name.$extension;
                    foreach ($script as $rid => $rules) {
                        foreach ($rules['actions'] as $aid => $action) {
                        foreach ($rules['actions'] as $action) {
                            if ($action['type'] == 'include' && empty($action['global'])
                                && $action['target'] == $name
                            ) {
plugins/new_user_identity/new_user_identity.php
@@ -33,8 +33,6 @@
    function lookup_user_name($args)
    {
        $rcmail = rcmail::get_instance();
        if ($this->init_ldap($args['host'])) {
            $results = $this->ldap->search('*', $args['user'], true);
            if (count($results->records) == 1) {
plugins/password/drivers/directadmin.php
@@ -297,7 +297,6 @@
            $status = socket_get_status($socket);
            $startTime = time();
            $length = 0;
            $prevSecond = 0;
            while ( !feof($socket) && !$status['timed_out'] )
            {
                $chunk = fgets($socket,1024);
plugins/password/drivers/pam.php
@@ -12,6 +12,7 @@
    function save($currpass, $newpass)
    {
        $user = $_SESSION['username'];
        $error = '';
        if (extension_loaded('pam') || extension_loaded('pam_auth')) {
            if (pam_auth($user, $currpass, $error, false)) {
plugins/password/drivers/sql.php
@@ -34,8 +34,9 @@
            $db = $rcmail->get_dbh();
        }
        if ($err = $db->is_error())
        if ($db->is_error()) {
            return PASSWORD_ERROR;
        }
        // crypted password
        if (strpos($sql, '%c') !== FALSE) {
@@ -183,8 +184,8 @@
        $res = $db->query($sql, $sql_vars);
        if (!$db->is_error()) {
            if (strtolower(substr(trim($query),0,6))=='select') {
                if ($result = $db->fetch_array($res))
            if (strtolower(substr(trim($sql),0,6)) == 'select') {
                if ($db->fetch_array($res))
                    return PASSWORD_SUCCESS;
            } else {
                // This is the good case: 1 row updated
plugins/password/drivers/xmail.php
@@ -67,7 +67,7 @@
    function send($msg)
    {
        socket_write($this->socket,$msg);
        if (substr($in = socket_read($this->socket, 512, PHP_BINARY_READ),0,1) != "+") {
        if (substr(socket_read($this->socket, 512, PHP_BINARY_READ),0,1) != "+") {
            return false;
        }
        return true;
@@ -85,7 +85,7 @@
            return false;
        }
        if (substr($in = socket_read($this->socket, 512, PHP_BINARY_READ),0,1) != "+") {
        if (substr(socket_read($this->socket, 512, PHP_BINARY_READ),0,1) != "+") {
            socket_close($this->socket);
            return false;
        }
plugins/squirrelmail_usercopy/squirrelmail_usercopy.php
@@ -63,7 +63,7 @@
                    if ($this->prefs['___sig'.$i.'___'])
                        $ident_data['signature'] = $this->prefs['___sig'.$i.'___'];
                    // insert identity
                    $identid = $rcmail->user->insert_identity($ident_data);
                    $rcmail->user->insert_identity($ident_data);
                }
            }
plugins/vcard_attachments/vcard_attachments.php
@@ -45,7 +45,7 @@
            }
        }
        // the same with message bodies
        foreach ((array)$this->message->parts as $idx => $part) {
        foreach ((array)$this->message->parts as $part) {
            if ($this->is_vcard($part)) {
                $this->vcard_parts[] = $part->mime_id;
                $this->vcard_bodies[] = $part->mime_id;
@@ -63,7 +63,6 @@
    function html_output($p)
    {
        $attach_script = false;
        $icon = 'plugins/vcard_attachments/' .$this->local_skin_path(). '/vcard_add_contact.png';
        foreach ($this->vcard_parts as $part) {
            $vcards = rcube_vcard::import($this->message->get_part_content($part, null, true));
plugins/zipdownload/zipdownload.php
@@ -169,7 +169,7 @@
            for ($i = 0; ($i * $imap->get_pagesize()) <= $count; $i++) {
                $a_headers = $imap->list_messages($mbox_name, ($i + 1));
                foreach ($a_headers as $n => $header) {
                foreach ($a_headers as $header) {
                    if (empty($header))
                        continue;
@@ -199,7 +199,7 @@
        $zip = new ZipArchive();
        $zip->open($tmpfname, ZIPARCHIVE::OVERWRITE);
        foreach ($uids as $key => $uid){
        foreach ($uids as $uid){
            $headers = $imap->get_message_headers($uid);
            $subject = rcube_mime::decode_mime_string((string)$headers->subject);
            $subject = $this->_convert_filename($subject);
program/include/bc.php
@@ -62,7 +62,7 @@
function rcmail_temp_gc()
{
  $rcmail = rcmail::get_instance()->temp_gc();
  rcmail::get_instance()->temp_gc();
}
function rcube_charset_convert($str, $from, $to=NULL)
program/include/rcmail.php
@@ -596,7 +596,7 @@
      $post_host = rcube_utils::get_input_value('_host', rcube_utils::INPUT_POST);
      $post_user = rcube_utils::get_input_value('_user', rcube_utils::INPUT_POST);
      list($user, $domain) = explode('@', $post_user);
      list(, $domain) = explode('@', $post_user);
      // direct match in default_host array
      if ($default_host[$post_host] || in_array($post_host, array_values($default_host))) {
@@ -696,28 +696,6 @@
    $token = rcube_utils::get_input_value('_token', $mode);
    $sess_id = $_COOKIE[ini_get('session.name')];
    return !empty($sess_id) && $token == $this->get_request_token();
  }
  /**
   * Create unique authorization hash
   *
   * @param string Session ID
   * @param int Timestamp
   * @return string The generated auth hash
   */
  private function get_auth_hash($sess_id, $ts)
  {
    $auth_string = sprintf('rcmail*sess%sR%s*Chk:%s;%s',
      $sess_id,
      $ts,
      $this->config->get('ip_check') ? $_SERVER['REMOTE_ADDR'] : '***.***.***.***',
      $_SERVER['HTTP_USER_AGENT']);
    if (function_exists('sha1'))
      return sha1($auth_string);
    else
      return md5($auth_string);
  }
@@ -1167,7 +1145,7 @@
     */
    public function table_output($attrib, $table_data, $a_show_cols, $id_col)
    {
        $table = new html_table(/*array('cols' => count($a_show_cols))*/);
        $table = new html_table($attrib);
        // add table header
        if (!$attrib['noheader']) {
@@ -1532,7 +1510,7 @@
        $collapsed = $this->config->get('collapsed_folders');
        $out = '';
        foreach ($arrFolders as $key => $folder) {
        foreach ($arrFolders as $folder) {
            $title        = null;
            $folder_class = $this->folder_classname($folder['id']);
            $is_collapsed = strpos($collapsed, '&'.rawurlencode($folder['id']).'&') !== false;
@@ -1618,7 +1596,7 @@
    {
        $out = '';
        foreach ($arrFolders as $key => $folder) {
        foreach ($arrFolders as $folder) {
            // skip exceptions (and its subfolders)
            if (!empty($opts['exceptions']) && in_array($folder['id'], $opts['exceptions'])) {
                continue;
program/include/rcmail_output_html.php
@@ -731,14 +731,13 @@
    /**
     * Determines if a given condition is met
     *
     * @todo   Get rid off eval() once I understand what this does.
     * @todo   Extend this to allow real conditions, not just "set"
     * @param  string Condition statement
     * @return boolean True if condition is met, False if not
     */
    protected function check_condition($condition)
    {
        return eval("return (".$this->parse_expression($condition).");");
        return $this->eval_expression($condition);
    }
@@ -760,14 +759,15 @@
    /**
     * Parses expression and replaces variables
     * Parse & evaluate a given expression and return its result.
     *
     * @param  string Expression statement
     * @return string Expression value
     *
     * @return mixed Expression result
     */
    protected function parse_expression($expression)
    protected function eval_expression ($expression)
    {
        return preg_replace(
        $expression = preg_replace(
            array(
                '/session:([a-z0-9_]+)/i',
                '/config:([a-z0-9_]+)(:([a-z0-9_]+))?/i',
@@ -779,14 +779,29 @@
            ),
            array(
                "\$_SESSION['\\1']",
                "\$this->app->config->get('\\1',rcube_utils::get_boolean('\\3'))",
                "\$this->env['\\1']",
                "\$app->config->get('\\1',rcube_utils::get_boolean('\\3'))",
                "\$env['\\1']",
                "rcube_utils::get_input_value('\\1', rcube_utils::INPUT_GPC)",
                "\$_COOKIE['\\1']",
                "\$this->browser->{'\\1'}",
                "\$browser->{'\\1'}",
                $this->template_name,
            ),
            $expression);
            $expression
        );
        $fn = create_function('$app,$browser,$env', "return ($expression);");
        if (!$fn) {
            rcube::raise_error(array(
                'code' => 505,
                'type' => 'php',
                'file' => __FILE__,
                'line' => __LINE__,
                'message' => "Expression parse error on: ($expression)"), true, false);
            return null;
        }
        return $fn($this->app, $this->browser, $this->env);
    }
@@ -839,7 +854,7 @@
            // show a label
            case 'label':
                if ($attrib['expression'])
                    $attrib['name'] = eval("return " . $this->parse_expression($attrib['expression']) .";");
                    $attrib['name'] = $this->eval_expression($attrib['expression']);
                if ($attrib['name'] || $attrib['command']) {
                    // @FIXME: 'noshow' is useless, remove?
@@ -971,8 +986,7 @@
            // return code for a specified eval expression
            case 'exp':
                $value = $this->parse_expression($attrib['expression']);
                return eval("return html::quote($value);");
                return html::quote($this->eval_expression($attrib['expression']));
            // return variable
            case 'var':
program/js/app.js
@@ -936,16 +936,13 @@
            url._to = props;
          }
          else {
            // use contact_id passed as command parameter
            var n, len, a_cids = [];
            var a_cids = [];
            // use contact id passed as command parameter
            if (props)
              a_cids.push(props);
            // get selected contacts
            else if (this.contact_list) {
              var selection = this.contact_list.get_selection();
              for (n=0, len=selection.length; n<len; n++)
                a_cids.push(selection[n]);
            }
            else if (this.contact_list)
              a_cids = this.contact_list.get_selection();
            if (a_cids.length)
              this.http_post('mailto', { _cid: a_cids.join(','), _source: this.env.source }, true);
@@ -1582,7 +1579,7 @@
  this.msglist_set_coltypes = function(list)
  {
    var i, found, name, cols = list.list.tHead.rows[0].cells;
    var i, found, name, cols = list.thead.rows[0].cells;
    this.env.coltypes = [];
@@ -1632,13 +1629,17 @@
  this.open_window = function(url, width, height)
  {
    var w = Math.min(width, screen.width - 10),
      h = Math.min(height, screen.height - 100),
      l = (screen.width - w) / 2 + (screen.left || 0),
      t = Math.max(0, (screen.height - h) / 2 + (screen.top || 0) - 20),
    var dh = (window.outerHeight || 0) - (window.innerHeight || 0),
      dw = (window.outerWidth || 0) - (window.innerWidth || 0),
      sh = screen.availHeight || screen.height,
      sw = screen.availWidth || screen.width,
      w = Math.min(width, sw),
      h = Math.min(height, sh),
      l = Math.max(0, (sw - w) / 2 + (screen.left || 0)),
      t = Math.max(0, (sh - h) / 2 + (screen.top || 0)),
      wname = 'rcmextwin' + new Date().getTime(),
      extwin = window.open(url + (url.match(/\?/) ? '&' : '?') + '_extwin=1', wname,
        'width='+w+',height='+h+',top='+t+',left='+l+',resizable=yes,toolbar=no,status=no,location=no');
        'width='+(w-dw)+',height='+(h-dh)+',top='+t+',left='+l+',resizable=yes,toolbar=no,status=no,location=no');
    // write loading... message to empty windows
    if (!url && extwin.document) {
@@ -1732,10 +1733,7 @@
        + (flags.flagged ? ' flagged' : '')
        + (flags.unread_children && flags.seen && !this.env.autoexpand_threads ? ' unroot' : '')
        + (message.selected ? ' selected' : ''),
      // for performance use DOM instead of jQuery here
      row = document.createElement('tr');
    row.id = 'rcmrow'+uid;
      row = { cols:[], style:{}, id:'rcmrow'+uid };
    // message status icons
    css_class = 'msgicon';
@@ -1799,8 +1797,7 @@
    // add each submitted col
    for (n in this.env.coltypes) {
      c = this.env.coltypes[n];
      col = document.createElement('td');
      col.className = String(c).toLowerCase();
      col = { className: String(c).toLowerCase() };
      if (c == 'flag') {
        css_class = (flags.flagged ? 'flagged' : 'unflagged');
@@ -1845,8 +1842,7 @@
        html = cols[c];
      col.innerHTML = html;
      row.appendChild(col);
      row.cols.push(col);
    }
    list.insert_row(row, attop);
@@ -2209,7 +2205,7 @@
    if (root)
      row = rows[root] ? rows[root].obj : null;
    else
      row = this.message_list.list.tBodies[0].firstChild;
      row = this.message_list.tbody.firstChild;
    while (row) {
      if (row.nodeType == 1 && (r = rows[row.uid])) {
@@ -2385,7 +2381,7 @@
  this.delete_excessive_thread_rows = function()
  {
    var rows = this.message_list.rows,
      tbody = this.message_list.list.tBodies[0],
      tbody = this.message_list.tbody,
      row = tbody.firstChild,
      cnt = this.env.pagesize + 1;
@@ -4328,21 +4324,7 @@
        newcid = newcid+'-'+source;
    }
    if (list.rows[cid] && (row = list.rows[cid].obj)) {
      for (c=0; c<cols_arr.length; c++)
        if (row.cells[c])
          $(row.cells[c]).html(cols_arr[c]);
      // cid change
      if (newcid) {
        newcid = this.html_identifier(newcid);
        row.id = 'rcmrow' + newcid;
        list.remove_row(cid);
        list.init_row(row);
        list.selection[0] = newcid;
        row.style.display = '';
      }
    }
    list.update_row(cid, cols_arr, newcid, true);
  };
  // add row to contacts list
@@ -4352,7 +4334,7 @@
      return false;
    var c, col, list = this.contact_list,
      row = document.createElement('tr');
      row = { cols:[] };
    row.id = 'rcmrow'+this.html_identifier(cid);
    row.className = 'contact ' + (classes || '');
@@ -4362,10 +4344,10 @@
    // add each submitted col
    for (c in cols) {
      col = document.createElement('td');
      col = {};
      col.className = String(c).toLowerCase();
      col.innerHTML = cols[c];
      row.appendChild(col);
      row.cols.push(col);
    }
    list.insert_row(row);
@@ -4471,11 +4453,22 @@
      this.name_input.bind('keydown', function(e){ return rcmail.add_input_keydown(e); });
      this.name_input_li = $('<li>').addClass(type).append(this.name_input);
      var li = type == 'contactsearch' ? $('li:last', this.gui_objects.folderlist) : $('ul.groups li:last', this.get_folder_li(this.env.source,'',true));
      var ul, li;
      // find list (UL) element
      if (type == 'contactsearch')
        ul = this.gui_objects.folderlist;
      else
        ul = $('ul.groups', this.get_folder_li(this.env.source,'',true));
      // append to the list
      li = $('li:last', ul);
      if (li.length)
        this.name_input_li.insertAfter(li);
      else
        this.name_input_li.appendTo(type == 'contactsearch' ? this.gui_objects.folderlist : $('ul.groups', this.get_folder_li(this.env.source,'',true)));
      else {
        this.name_input_li.appendTo(ul);
        ul.show(); // make sure the list is visible
      }
    }
    this.name_input.select().focus();
@@ -4532,11 +4525,13 @@
  this.reset_add_input = function()
  {
    if (this.name_input) {
      if (this.env.group_renaming) {
        var li = this.name_input.parent();
      if (this.env.group_renaming) {
        li.children().last().show();
        this.env.group_renaming = false;
      }
      else if ($('li', li.parent()).length == 1)
        li.parent().hide();
      this.name_input.remove();
@@ -4964,17 +4959,15 @@
  this.update_identity_row = function(id, name, add)
  {
    var row, col, list = this.identity_list,
    var list = this.identity_list,
      rid = this.html_identifier(id);
    if (list.rows[rid] && (row = list.rows[rid].obj)) {
      $(row.cells[0]).html(name);
    }
    else if (add) {
      row = $('<tr>').attr('id', 'rcmrow'+rid).get(0);
      col = $('<td>').addClass('mail').html(name).appendTo(row);
      list.insert_row(row);
    if (add) {
      list.insert_row({ id:'rcmrow'+rid, cols:[ { className:'mail', innerHTML:name } ] });
      list.select(rid);
    }
    else {
      list.update_row(rid, [ name ]);
    }
  };
@@ -5750,7 +5743,7 @@
  this.set_message_coltypes = function(coltypes, repl, smart_col)
  {
    var list = this.message_list,
      thead = list ? list.list.tHead : null,
      thead = list ? list.thead : null,
      cell, col, n, len, th, tr;
    this.env.coltypes = coltypes;
program/js/list.js
@@ -30,6 +30,9 @@
  this.BACKSPACE_KEY = 8;
  this.list = list ? list : null;
  this.tagname = this.list ? this.list.nodeName.toLowerCase() : 'table';
  this.thead;
  this.tbody;
  this.frame = null;
  this.rows = [];
  this.selection = [];
@@ -56,7 +59,7 @@
  this.focused = false;
  this.drag_mouse_start = null;
  this.dblclick_time = 600;
  this.row_init = function(){};
  this.row_init = function(){};  // @deprecated; use list.addEventListener('initrow') instead
  // overwrite default paramaters
  if (p && typeof p === 'object')
@@ -73,11 +76,19 @@
 */
init: function()
{
  if (this.list && this.list.tBodies[0]) {
  if (this.tagname == 'table' && this.list && this.list.tBodies[0]) {
    this.thead = this.list.tHead;
    this.tbody = this.list.tBodies[0];
  }
  else if (this.tagname != 'table' && this.list) {
    this.tbody = this.list;
  }
  if (this.tbody) {
    this.rows = [];
    this.rowcount = 0;
    var r, len, rows = this.list.tBodies[0].rows;
    var r, len, rows = this.tbody.childNodes;
    for (r=0, len=rows.length; r<len; r++) {
      this.init_row(rows[r]);
@@ -127,7 +138,8 @@
    if (document.all)
      row.onselectstart = function() { return false; };
    this.row_init(this.rows[uid]);
    this.row_init(this.rows[uid]);  // legacy support
    this.triggerEvent('initrow', this.rows[uid]);
  }
},
@@ -137,16 +149,16 @@
 */
init_header: function()
{
  if (this.list && this.list.tHead) {
  if (this.thead) {
    this.colcount = 0;
    var col, r, p = this;
    // add events for list columns moving
    if (this.column_movable && this.list.tHead && this.list.tHead.rows) {
      for (r=0; r<this.list.tHead.rows[0].cells.length; r++) {
    if (this.column_movable && this.thead && this.thead.rows) {
      for (r=0; r<this.thead.rows[0].cells.length; r++) {
        if (this.column_fixed == r)
          continue;
        col = this.list.tHead.rows[0].cells[r];
        col = this.thead.rows[0].cells[r];
        col.onmousedown = function(e){ return p.drag_column(e, this); };
        this.colcount++;
      }
@@ -160,10 +172,16 @@
 */
clear: function(sel)
{
  if (this.tagname == 'table') {
  var tbody = document.createElement('tbody');
  this.list.insertBefore(tbody, this.list.tBodies[0]);
    this.list.insertBefore(tbody, this.tbody);
  this.list.removeChild(this.list.tBodies[1]);
    this.tbody = tbody;
  }
  else {
    $(this.row_tagname() + ':not(.thead)', this.tbody).remove();
  }
  this.rows = [];
  this.rowcount = 0;
@@ -181,12 +199,12 @@
 */
remove_row: function(uid, sel_next)
{
  var obj = this.rows[uid] ? this.rows[uid].obj : null;
  var node = this.rows[uid] ? this.rows[uid].obj : null;
  if (!obj)
  if (!node)
    return;
  obj.style.display = 'none';
  node.style.display = 'none';
  if (sel_next)
    this.select_next();
@@ -201,9 +219,28 @@
 */
insert_row: function(row, before)
{
  var tbody = this.list.tBodies[0];
  var tbody = this.tbody;
  if (before && tbody.rows.length)
  // create a real dom node first
  if (row.nodeName === undefined) {
    // for performance reasons use DOM instead of jQuery here
    var domrow = document.createElement(this.row_tagname());
    if (row.id) domrow.id = row.id;
    if (row.className) domrow.className = row.className;
    if (row.style) $.extend(domrow.style, row.style);
    for (var domcell, col, i=0; row.cols && i < row.cols.length; i++) {
      col = row.cols[i];
      domcell = document.createElement(this.col_tagname());
      if (col.className) domcell.className = col.className;
      if (col.innerHTML) domcell.innerHTML = col.innerHTML;
      domrow.appendChild(domcell);
    }
    row = domrow;
  }
  if (before && tbody.childNodes.length)
    tbody.insertBefore(row, (typeof before == 'object' && before.parentNode == tbody) ? before : tbody.firstChild);
  else
    tbody.appendChild(row);
@@ -212,6 +249,28 @@
  this.rowcount++;
},
/**
 *
 */
update_row: function(id, cols, newid, select)
{
  var row = this.rows[id];
  if (!row) return false;
  var domrow = row.obj;
  for (var domcell, col, i=0; cols && i < cols.length; i++) {
    this.get_cell(domrow, i).html(cols[i]);
  }
  if (newid) {
    delete this.rows[id];
    domrow.id = 'rcmrow' + newid;
    this.init_row(domrow);
    if (select)
      this.selection[0] = newid;
  }
},
/**
@@ -230,9 +289,9 @@
  }
  // Un-focus already focused elements (#1487123, #1487316, #1488600, #1488620)
  // It looks that window.focus() does the job for all browsers, but not Firefox (#1489058)
  $(':focus:not(body)').blur();
  // un-focus iframe bodies (#1489058), this doesn't work in Opera and Chrome
  $('iframe').contents().find('body').blur();
  window.focus();
  if (e || (e = window.event))
    rcube_event.cancel(e);
@@ -271,8 +330,8 @@
    this.add_dragfix();
    // find selected column number
    for (var i=0; i<this.list.tHead.rows[0].cells.length; i++) {
      if (col == this.list.tHead.rows[0].cells[i]) {
    for (var i=0; i<this.thead.rows[0].cells.length; i++) {
      if (col == this.thead.rows[0].cells[i]) {
        this.selected_column = i;
        break;
      }
@@ -451,7 +510,7 @@
    this.triggerEvent('expandcollapse', { uid:row.uid, expanded:row.expanded, obj:row.obj });
  }
  else {
    var tbody = this.list.tBodies[0];
    var tbody = this.tbody;
    new_row = tbody.firstChild;
    depth = 0;
    last_expanded_parent_depth = 0;
@@ -504,7 +563,7 @@
      return false;
  }
  else {
    new_row = this.list.tBodies[0].firstChild;
    new_row = this.tbody.firstChild;
    depth = 0;
  }
@@ -543,7 +602,7 @@
    this.triggerEvent('expandcollapse', { uid:row.uid, expanded:row.expanded, obj:row.obj });
  }
  else {
    new_row = this.list.tBodies[0].firstChild;
    new_row = this.tbody.firstChild;
    depth = 0;
  }
@@ -611,7 +670,7 @@
get_first_row: function()
{
  if (this.rowcount) {
    var i, len, rows = this.list.tBodies[0].rows;
    var i, len, rows = this.tbody.childNodes;
    for (i=0, len=rows.length-1; i<len; i++)
      if (rows[i].id && String(rows[i].id).match(/^rcmrow([a-z0-9\-_=\+\/]+)/i) && this.rows[RegExp.$1] != null)
@@ -624,7 +683,7 @@
get_last_row: function()
{
  if (this.rowcount) {
    var i, rows = this.list.tBodies[0].rows;
    var i, rows = this.tbody.childNodes;
    for (i=rows.length-1; i>=0; i--)
      if (rows[i].id && String(rows[i].id).match(/^rcmrow([a-z0-9\-_=\+\/]+)/i) && this.rows[RegExp.$1] != null)
@@ -634,6 +693,22 @@
  return null;
},
row_tagname: function()
{
  var row_tagnames = { table:'tr', ul:'li', '*':'div' };
  return row_tagnames[this.tagname] || row_tagnames['*'];
},
col_tagname: function()
{
  var col_tagnames = { table:'td', '*':'span' };
  return col_tagnames[this.tagname] || col_tagnames['*'];
},
get_cell: function(row, index)
{
  return $(this.col_tagname(), row).eq(index);
},
/**
 * selects or unselects the proper row depending on the modifier key pressed
@@ -781,19 +856,19 @@
    this.shift_start = id;
  var n, i, j, to_row = this.rows[id],
    from_rowIndex = this.rows[this.shift_start].obj.rowIndex,
    to_rowIndex = to_row.obj.rowIndex;
    from_rowIndex = this._rowIndex(this.rows[this.shift_start].obj),
    to_rowIndex = this._rowIndex(to_row.obj);
  if (!to_row.expanded && to_row.has_children)
    if (to_row = this.rows[(this.row_children(id)).pop()])
      to_rowIndex = to_row.obj.rowIndex;
      to_rowIndex = this._rowIndex(to_row.obj);
  i = ((from_rowIndex < to_rowIndex) ? from_rowIndex : to_rowIndex),
  j = ((from_rowIndex > to_rowIndex) ? from_rowIndex : to_rowIndex);
  // iterate through the entire message list
  for (n in this.rows) {
    if (this.rows[n].obj.rowIndex >= i && this.rows[n].obj.rowIndex <= j) {
    if (this._rowIndex(this.rows[n].obj) >= i && this._rowIndex(this.rows[n].obj) <= j) {
      if (!this.in_selection(n)) {
        this.highlight_row(n, true);
      }
@@ -806,6 +881,13 @@
  }
},
/**
 * Helper method to emulate the rowIndex property of non-tr elements
 */
_rowIndex: function(obj)
{
  return (obj.rowIndex !== undefined) ? obj.rowIndex : $(obj).prevAll().length;
},
/**
 * Check if given id is part of the current selection
@@ -1150,7 +1232,7 @@
    this.draglayer.html('');
    // get subjects of selected messages
    var i, n, obj;
    var i, n, obj, me;
    for (n=0; n<this.selection.length; n++) {
      // only show 12 lines
      if (n>12) {
@@ -1158,29 +1240,28 @@
        break;
      }
      me = this;
      if (obj = this.rows[this.selection[n]].obj) {
        for (i=0; i<obj.childNodes.length; i++) {
          if (obj.childNodes[i].nodeName == 'TD') {
        $('> '+this.col_tagname(), obj).each(function(i,elem){
            if (n == 0)
              this.drag_start_pos = $(obj.childNodes[i]).offset();
            me.drag_start_pos = $(elem).offset();
            if (this.subject_col < 0 || (this.subject_col >= 0 && this.subject_col == i)) {
              var subject = $(obj.childNodes[i]).text();
          if (me.subject_col < 0 || (me.subject_col >= 0 && me.subject_col == i)) {
            var subject = $(elem).text();
              if (!subject)
                break;
            if (subject) {
              // remove leading spaces
              subject = $.trim(subject);
              // truncate line to 50 characters
              subject = (subject.length > 50 ? subject.substring(0, 50) + '...' : subject);
              var entry = $('<div>').text(subject);
              this.draglayer.append(entry);
              break;
              me.draglayer.append(entry);
            }
            return false;  // break
          }
        }
        });
      }
    }
@@ -1255,7 +1336,7 @@
    if (!this.col_draglayer) {
      var lpos = $(this.list).offset(),
        cells = this.list.tHead.rows[0].cells;
        cells = this.thead.rows[0].cells;
      // create dragging layer
      this.col_draglayer = $('<div>').attr('id', 'rcmcoldraglayer')
@@ -1411,7 +1492,11 @@
 */
column_replace: function(from, to)
{
  var len, cells = this.list.tHead.rows[0].cells,
  // only supported for <table> lists
  if (!this.thead || !this.thead.rows)
    return;
  var len, cells = this.thead.rows[0].cells,
    elem = cells[from],
    before = cells[to],
    td = document.createElement('td');
@@ -1424,8 +1509,8 @@
  cells[0].parentNode.replaceChild(elem, td);
  // replace list cells
  for (r=0, len=this.list.tBodies[0].rows.length; r<len; r++) {
    row = this.list.tBodies[0].rows[r];
  for (r=0, len=this.tbody.rows.length; r<len; r++) {
    row = this.tbody.rows[r];
    elem = row.cells[from];
    before = row.cells[to];
program/lib/Roundcube/html.php
@@ -678,6 +678,11 @@
    {
        $default_attrib = self::$doctype == 'xhtml' ? array('summary' => '', 'border' => 0) : array();
        $this->attrib = array_merge($attrib, $default_attrib);
        if (!empty($attrib['tagname']) && $attrib['tagname'] != 'table') {
          $this->tagname = $attrib['tagname'];
          $this->allowed = self::$common_attrib;
        }
    }
    /**
@@ -816,19 +821,20 @@
        if (!empty($this->header)) {
            $rowcontent = '';
            foreach ($this->header as $c => $col) {
                $rowcontent .= self::tag('td', $col->attrib, $col->content);
                $rowcontent .= self::tag($this->_col_tagname(), $col->attrib, $col->content);
            }
            $thead = self::tag('thead', null, self::tag('tr', null, $rowcontent, parent::$common_attrib));
            $thead = $this->tagname == 'table' ? self::tag('thead', null, self::tag('tr', null, $rowcontent, parent::$common_attrib)) :
                self::tag($this->_row_tagname(), array('class' => 'thead'), $rowcontent, parent::$common_attrib);
        }
        foreach ($this->rows as $r => $row) {
            $rowcontent = '';
            foreach ($row->cells as $c => $col) {
                $rowcontent .= self::tag('td', $col->attrib, $col->content);
                $rowcontent .= self::tag($this->_col_tagname(), $col->attrib, $col->content);
            }
            if ($r < $this->rowindex || count($row->cells)) {
                $tbody .= self::tag('tr', $row->attrib, $rowcontent, parent::$common_attrib);
                $tbody .= self::tag($this->_row_tagname(), $row->attrib, $rowcontent, parent::$common_attrib);
            }
        }
@@ -837,7 +843,7 @@
        }
        // add <tbody>
        $this->content = $thead . self::tag('tbody', null, $tbody);
        $this->content = $thead . ($this->tagname == 'table' ? self::tag('tbody', null, $tbody) : $tbody);
        unset($this->attrib['cols'], $this->attrib['rowsonly']);
        return parent::show();
@@ -862,4 +868,22 @@
        $this->rowindex = 0;
    }
    /**
     * Getter for the corresponding tag name for table row elements
     */
    private function _row_tagname()
    {
        static $row_tagnames = array('table' => 'tr', 'ul' => 'li', '*' => 'div');
        return $row_tagnames[$this->tagname] ?: $row_tagnames['*'];
    }
    /**
     * Getter for the corresponding tag name for table cell elements
     */
    private function _col_tagname()
    {
        static $col_tagnames = array('table' => 'td', '*' => 'span');
        return $col_tagnames[$this->tagname] ?: $col_tagnames['*'];
    }
}
program/lib/Roundcube/rcube_addressbook.php
@@ -309,9 +309,14 @@
     * List all active contact groups of this source
     *
     * @param string  Optional search string to match group name
     * @param int     Matching mode:
     *                0 - partial (*abc*),
     *                1 - strict (=),
     *                2 - prefix (abc*)
     *
     * @return array  Indexed list of contact groups, each a hash array
     */
    function list_groups($search = null)
    function list_groups($search = null, $mode = 0)
    {
        /* empty for address books don't supporting groups */
        return array();
@@ -371,7 +376,8 @@
     * Add the given contact records the a certain group
     *
     * @param string  Group identifier
     * @param array   List of contact identifiers to be added
     * @param array|string List of contact identifiers to be added
     *
     * @return int    Number of contacts added
     */
    function add_to_group($group_id, $ids)
@@ -384,7 +390,8 @@
     * Remove the given contact records from a certain group
     *
     * @param string  Group identifier
     * @param array   List of contact identifiers to be removed
     * @param array|string List of contact identifiers to be removed
     *
     * @return int    Number of deleted group members
     */
    function remove_from_group($group_id, $ids)
@@ -425,7 +432,7 @@
                    $out = array_merge($out, (array)$values);
                }
                else {
                    list($f, $type) = explode(':', $c);
                    list(, $type) = explode(':', $c);
                    $out[$type] = array_merge((array)$out[$type], (array)$values);
                }
            }
@@ -528,7 +535,7 @@
     */
    public static function compose_contact_key($contact, $sort_col)
    {
        $key = $contact[$sort_col] . ':' . $row['sourceid'];
        $key = $contact[$sort_col] . ':' . $contact['sourceid'];
        // add email to a key to not skip contacts with the same name (#1488375)
        if (!empty($contact['email'])) {
@@ -537,7 +544,6 @@
         return $key;
    }
    /**
     * Compare search value with contact data
program/lib/Roundcube/rcube_contacts.php
@@ -137,16 +137,34 @@
     * List all active contact groups of this source
     *
     * @param string  Search string to match group name
     * @param int     Matching mode:
     *                0 - partial (*abc*),
     *                1 - strict (=),
     *                2 - prefix (abc*)
     *
     * @return array  Indexed list of contact groups, each a hash array
     */
    function list_groups($search = null)
    function list_groups($search = null, $mode = 0)
    {
        $results = array();
        if (!$this->groups)
            return $results;
        $sql_filter = $search ? " AND " . $this->db->ilike('name', '%'.$search.'%') : '';
        if ($search) {
            switch (intval($mode)) {
            case 1:
                $sql_filter = $this->db->ilike('name', $search);
                break;
            case 2:
                $sql_filter = $this->db->ilike('name', $search . '%');
                break;
            default:
                $sql_filter = $this->db->ilike('name', '%' . $search . '%');
            }
            $sql_filter = " AND $sql_filter";
        }
        $sql_result = $this->db->query(
            "SELECT * FROM ".$this->db->table_name($this->db_groups).
@@ -880,7 +898,8 @@
     * Add the given contact records the a certain group
     *
     * @param string  Group identifier
     * @param array   List of contact identifiers to be added
     * @param array|string List of contact identifiers to be added
     *
     * @return int    Number of contacts added 
     */
    function add_to_group($group_id, $ids)
@@ -927,7 +946,8 @@
     * Remove the given contact records from a certain group
     *
     * @param string  Group identifier
     * @param array   List of contact identifiers to be removed
     * @param array|string List of contact identifiers to be removed
     *
     * @return int    Number of deleted group members
     */
    function remove_from_group($group_id, $ids)
program/lib/Roundcube/rcube_csv2vcard.php
@@ -130,6 +130,21 @@
        'work_state'            => 'region:work',
        'home_city_short'       => 'locality:home',
        'home_state_short'      => 'region:home',
        // Atmail
        'date_of_birth'         => 'birthday',
        'email'                 => 'email:pref',
        'home_mobile'           => 'phone:cell',
        'home_zip'              => 'zipcode:home',
        'info'                  => 'notes',
        'user_photo'            => 'photo',
        'url'                   => 'website:homepage',
        'work_company'          => 'organization',
        'work_dept'             => 'departament',
        'work_fax'              => 'phone:work,fax',
        'work_mobile'           => 'phone:work,cell',
        'work_title'            => 'jobtitle',
        'work_zip'              => 'zipcode:work',
    );
    /**
@@ -230,8 +245,29 @@
        'work_phone'        => "Work Phone",
        'work_address'      => "Work Address",
        //'work_address_2'    => "Work Address 2",
        'work_city'         => "Work City",
        'work_country'      => "Work Country",
        'work_state'        => "Work State",
        'work_zipcode'      => "Work ZipCode",
        // Atmail
        'date_of_birth'     => "Date of Birth",
        'email'             => "Email",
        //'email_2'         => "Email2",
        //'email_3'         => "Email3",
        //'email_4'         => "Email4",
        //'email_5'         => "Email5",
        'home_mobile'       => "Home Mobile",
        'home_zip'          => "Home Zip",
        'info'              => "Info",
        'user_photo'        => "User Photo",
        'url'               => "URL",
        'work_company'      => "Work Company",
        'work_dept'         => "Work Dept",
        'work_fax'          => "Work Fax",
        'work_mobile'       => "Work Mobile",
        'work_title'        => "Work Title",
        'work_zip'          => "Work Zip",
    );
    protected $local_label_map = array();
@@ -268,7 +304,6 @@
    {
        // convert to UTF-8
        $head     = substr($csv, 0, 4096);
        $fallback = rcube::get_instance()->config->get('default_charset', 'ISO-8859-1'); // fallback to Latin-1?
        $charset  = rcube_charset::detect($head, RCUBE_CHARSET);
        $csv      = rcube_charset::convert($csv, $charset);
        $head     = '';
@@ -276,7 +311,7 @@
        $this->map = array();
        // Parse file
        foreach (preg_split("/[\r\n]+/", $csv) as $i => $line) {
        foreach (preg_split("/[\r\n]+/", $csv) as $line) {
            $elements = $this->parse_line($line);
            if (empty($elements)) {
                continue;
@@ -353,6 +388,12 @@
        if (!empty($this->local_label_map)) {
            for ($i = 0; $i < $size; $i++) {
                $label = $this->local_label_map[$elements[$i]];
                // special localization label
                if ($label && $label[0] == '_') {
                    $label = substr($label, 1);
                }
                if ($label && !empty($this->csv2vcard_map[$label])) {
                    $map2[$i] = $this->csv2vcard_map[$label];
                }
@@ -384,11 +425,15 @@
            $contact['birthday'] = $contact['birthday-y'] .'-' .$contact['birthday-m'] . '-' . $contact['birthday-d'];
        }
        // Empty dates, e.g. "0/0/00", "0000-00-00 00:00:00"
        foreach (array('birthday', 'anniversary') as $key) {
            if (!empty($contact[$key]) && $contact[$key] == '0/0/00') { // @TODO: localization?
            if (!empty($contact[$key])) {
                $date = preg_replace('/[0[:^word:]]/', '', $contact[$key]);
                if (empty($date)) {
                unset($contact[$key]);
            }
        }
        }
        if (!empty($contact['gender']) && ($gender = strtolower($contact['gender']))) {
            if (!in_array($gender, array('male', 'female'))) {
program/lib/Roundcube/rcube_db.php
@@ -128,7 +128,7 @@
        $dsn_string  = $this->dsn_string($dsn);
        $dsn_options = $this->dsn_options($dsn);
        if ($db_pconn) {
        if ($this->db_pconn) {
            $dsn_options[PDO::ATTR_PERSISTENT] = true;
        }
@@ -405,21 +405,22 @@
        $this->db_error_msg = null;
        // send query
        $query = $this->dbh->query($query);
        $result = $this->dbh->query($query);
        if ($query === false) {
        if ($result === false) {
            $error = $this->dbh->errorInfo();
            $this->db_error = true;
            $this->db_error_msg = sprintf('[%s] %s', $error[1], $error[2]);
            rcube::raise_error(array('code' => 500, 'type' => 'db',
                'line' => __LINE__, 'file' => __FILE__,
                'message' => $this->db_error_msg), true, false);
                'message' => $this->db_error_msg . " (SQL Query: $query)"
                ), true, false);
        }
        $this->last_result = $query;
        $this->last_result = $result;
        return $query;
        return $result;
    }
    /**
program/lib/Roundcube/rcube_db_mysql.php
@@ -127,7 +127,7 @@
        $result[PDO::MYSQL_ATTR_FOUND_ROWS] = true;
        // Enable AUTOCOMMIT mode (#1488902)
        $dsn_options[PDO::ATTR_AUTOCOMMIT] = true;
        $result[PDO::ATTR_AUTOCOMMIT] = true;
        return $result;
    }
@@ -147,7 +147,7 @@
            $result = $this->query('SHOW VARIABLES');
            while ($sql_arr = $this->fetch_array($result)) {
            while ($row = $this->fetch_array($result)) {
                $this->variables[$row[0]] = $row[1];
            }
        }
program/lib/Roundcube/rcube_enriched.php
@@ -118,7 +118,7 @@
            $quoted = '';
            $lines  = explode('<br>', $a[2]);
            foreach ($lines as $n => $line)
            foreach ($lines as $line)
                $quoted .= '&gt;'.$line.'<br>';
            $body = $a[1].'<span class="quotes">'.$quoted.'</span>'.$a[3];
program/lib/Roundcube/rcube_imap.php
@@ -981,7 +981,7 @@
            // use memory less expensive (and quick) method for big result set
            $index = clone $this->index('', $this->sort_field, $this->sort_order);
            // get messages uids for one page...
            $index->slice($start_msg, min($cnt-$from, $this->page_size));
            $index->slice($from, min($cnt-$from, $this->page_size));
            if ($slice) {
                $index->slice(-$slice, $slice);
@@ -1423,8 +1423,6 @@
     */
    protected function search_index($folder, $criteria='ALL', $charset=NULL, $sort_field=NULL)
    {
        $orig_criteria = $criteria;
        if (!$this->check_connection()) {
            if ($this->threading) {
                return new rcube_result_thread();
@@ -2727,7 +2725,7 @@
        // filter folders list according to rights requirements
        if ($rights && $this->get_capability('ACL')) {
            $a_folders = $this->filter_rights($a_folders, $rights);
            $a_mboxes = $this->filter_rights($a_mboxes, $rights);
        }
        // filter folders and sort them
@@ -2783,7 +2781,6 @@
     */
    private function list_folders_update(&$result, $type = null)
    {
        $delim     = $this->get_hierarchy_delimiter();
        $namespace = $this->get_namespace();
        $search    = array();
@@ -3846,7 +3843,7 @@
        $delimiter = $this->get_hierarchy_delimiter();
        // find default folders and skip folders starting with '.'
        foreach ($a_folders as $i => $folder) {
        foreach ($a_folders as $folder) {
            if ($folder[0] == '.') {
                continue;
            }
program/lib/Roundcube/rcube_imap_cache.php
@@ -430,7 +430,7 @@
                    ." AND uid = ?",
                $flags, $msg, $this->userid, $mailbox, (int) $message->uid);
            if ($this->db->affected_rows()) {
            if ($this->db->affected_rows($res)) {
                return;
            }
        }
@@ -983,7 +983,7 @@
                $uids, true, array('FLAGS'), $index['modseq'], $qresync);
            if (!empty($result)) {
                foreach ($result as $id => $msg) {
                foreach ($result as $msg) {
                    $uid = $msg->uid;
                    // Remove deleted message
                    if ($this->skip_deleted && !empty($msg->flags['DELETED'])) {
program/lib/Roundcube/rcube_imap_generic.php
@@ -746,7 +746,7 @@
        }
        if ($this->prefs['timeout'] <= 0) {
            $this->prefs['timeout'] = ini_get('default_socket_timeout');
            $this->prefs['timeout'] = max(0, intval(ini_get('default_socket_timeout')));
        }
        // Connect
@@ -1077,7 +1077,7 @@
        }
        if (!$this->data['READ-WRITE']) {
            $this->setError(self::ERROR_READONLY, "Mailbox is read-only", 'EXPUNGE');
            $this->setError(self::ERROR_READONLY, "Mailbox is read-only");
            return false;
        }
@@ -1652,7 +1652,6 @@
        }
        if (!empty($criteria)) {
            $modseq = stripos($criteria, 'MODSEQ') !== false;
            $params .= ($params ? ' ' : '') . $criteria;
        }
        else {
@@ -1791,7 +1790,6 @@
                if ($skip_deleted && preg_match('/FLAGS \(([^)]+)\)/', $line, $matches)) {
                    $flags = explode(' ', strtoupper($matches[1]));
                    if (in_array('\\DELETED', $flags)) {
                        $deleted[$id] = $id;
                        continue;
                    }
                }
@@ -1936,7 +1934,7 @@
        }
        if (!$this->data['READ-WRITE']) {
            $this->setError(self::ERROR_READONLY, "Mailbox is read-only", 'STORE');
            $this->setError(self::ERROR_READONLY, "Mailbox is read-only");
            return false;
        }
@@ -1997,7 +1995,7 @@
        }
        if (!$this->data['READ-WRITE']) {
            $this->setError(self::ERROR_READONLY, "Mailbox is read-only", 'STORE');
            $this->setError(self::ERROR_READONLY, "Mailbox is read-only");
            return false;
        }
@@ -2172,7 +2170,7 @@
                // create array with header field:data
                if (!empty($headers)) {
                    $headers = explode("\n", trim($headers));
                    foreach ($headers as $hid => $resln) {
                    foreach ($headers as $resln) {
                        if (ord($resln[0]) <= 32) {
                            $lines[$ln] .= (empty($lines[$ln]) ? '' : "\n") . trim($resln);
                        } else {
@@ -2180,7 +2178,7 @@
                        }
                    }
                    while (list($lines_key, $str) = each($lines)) {
                    foreach ($lines as $str) {
                        list($field, $string) = explode(':', $str, 2);
                        $field  = strtolower($field);
@@ -2508,7 +2506,7 @@
                $tokens = $this->tokenizeResponse(preg_replace('/(^\(|\)$)/', '', $line));
                for ($i=0; $i<count($tokens); $i+=2) {
                    if (preg_match('/^(BODY|BINARY)/i', $token)) {
                    if (preg_match('/^(BODY|BINARY)/i', $tokens[$i])) {
                        $result = $tokens[$i+1];
                        $found  = true;
                        break;
@@ -3540,7 +3538,7 @@
        if (is_array($element)) {
            reset($element);
            while (list($key, $value) = each($element)) {
            foreach ($element as $value) {
                $string .= ' ' . self::r_implode($value);
            }
        }
program/lib/Roundcube/rcube_ldap.php
@@ -169,7 +169,7 @@
        // Build sub_fields filter
        if (!empty($this->prop['sub_fields']) && is_array($this->prop['sub_fields'])) {
            $this->sub_filter = '';
            foreach ($this->prop['sub_fields'] as $attr => $class) {
            foreach ($this->prop['sub_fields'] as $class) {
                if (!empty($class)) {
                    $class = is_array($class) ? array_pop($class) : $class;
                    $this->sub_filter .= '(objectClass=' . $class . ')';
@@ -1035,7 +1035,6 @@
                $mail_field  = $this->fieldmap['email'];
                // try to extract surname and firstname from displayname
                $reverse_map = array_flip($this->fieldmap);
                $name_parts  = preg_split('/[\s,.]+/', $save_data['name']);
                if ($sn_field && $missing[$sn_field]) {
@@ -1107,7 +1106,7 @@
        // Remove attributes that need to be added separately (child objects)
        $xfields = array();
        if (!empty($this->prop['sub_fields']) && is_array($this->prop['sub_fields'])) {
            foreach ($this->prop['sub_fields'] as $xf => $xclass) {
            foreach (array_keys($this->prop['sub_fields']) as $xf) {
                if (!empty($newentry[$xf])) {
                    $xfields[$xf] = $newentry[$xf];
                    unset($newentry[$xf]);
@@ -1170,7 +1169,7 @@
            }
        }
        foreach ($this->fieldmap as $col => $fld) {
        foreach ($this->fieldmap as $fld) {
            if ($fld) {
                $val = $ldap_data[$fld];
                $old = $old_data[$fld];
@@ -1396,6 +1395,10 @@
     */
    protected function add_autovalues(&$attrs)
    {
        if (empty($this->prop['autovalues'])) {
            return;
        }
        $attrvals = array();
        foreach ($attrs as $k => $v) {
            $attrvals['{'.$k.'}'] = is_array($v) ? $v[0] : $v;
@@ -1403,13 +1406,24 @@
        foreach ((array)$this->prop['autovalues'] as $lf => $templ) {
            if (empty($attrs[$lf])) {
                // replace {attr} placeholders with concrete attribute values
                $templ = preg_replace('/\{\w+\}/', '', strtr($templ, $attrvals));
                if (strpos($templ, '(') !== false) {
                    // replace {attr} placeholders with (escaped!) attribute values to be safely eval'd
                    $code = preg_replace('/\{\w+\}/', '', strtr($templ, array_map('addslashes', $attrvals)));
                    $fn   = create_function('', "return ($code);");
                    if (!$fn) {
                        rcube::raise_error(array(
                            'code' => 505, 'type' => 'php',
                            'file' => __FILE__, 'line' => __LINE__,
                            'message' => "Expression parse error on: ($code)"), true, false);
                        continue;
                    }
                if (strpos($templ, '(') !== false)
                    $attrs[$lf] = eval("return ($templ);");
                else
                    $attrs[$lf] = $templ;
                    $attrs[$lf] = $fn();
                }
                else {
                    // replace {attr} placeholders with concrete attribute values
                    $attrs[$lf] = preg_replace('/\{\w+\}/', '', strtr($templ, $attrvals));
                }
            }
        }
    }
@@ -1715,9 +1729,14 @@
     * List all active contact groups of this source
     *
     * @param string  Optional search string to match group name
     * @param int     Matching mode:
     *                0 - partial (*abc*),
     *                1 - strict (=),
     *                2 - prefix (abc*)
     *
     * @return array  Indexed list of contact groups, each a hash array
     */
    function list_groups($search = null)
    function list_groups($search = null, $mode = 0)
    {
        if (!$this->groups)
            return array();
@@ -1729,10 +1748,10 @@
        $groups = array();
        if ($search) {
            $search = mb_strtolower($search);
            foreach ($group_cache as $group) {
                if (strpos(mb_strtolower($group['name']), $search) !== false)
                if ($this->compare_search_value('name', $group['name'], $search, $mode)) {
                    $groups[] = $group;
                }
            }
        }
        else
@@ -1763,7 +1782,7 @@
            $vlv_active = $this->_vlv_set_controls($this->prop['groups'], $vlv_page+1, $page_size);
        }
        $function = $this->_scope2func($this->prop['groups']['scope'], $ns_function);
        $function = $this->_scope2func($this->prop['groups']['scope']);
        $res = @$function($this->conn, $base_dn, $filter, array_unique(array('dn', 'objectClass', $name_attr, $email_attr, $sort_attr)));
        if ($res === false)
        {
@@ -1922,7 +1941,8 @@
     * Add the given contact records the a certain group
     *
     * @param string  Group identifier
     * @param array   List of contact identifiers to be added
     * @param array|string List of contact identifiers to be added
     *
     * @return int    Number of contacts added
     */
    function add_to_group($group_id, $contact_ids)
@@ -1937,8 +1957,8 @@
        $group_name  = $group_cache[$group_id]['name'];
        $member_attr = $group_cache[$group_id]['member_attr'];
        $group_dn    = "cn=$group_name,$base_dn";
        $new_attrs = array();
        foreach ($contact_ids as $id)
            $new_attrs[$member_attr][] = self::dn_decode($id);
@@ -1949,14 +1969,15 @@
        $this->cache->remove('groups');
        return count($new_attrs['member']);
        return count($new_attrs[$member_attr]);
    }
    /**
     * Remove the given contact records from a certain group
     *
     * @param string  Group identifier
     * @param array   List of contact identifiers to be removed
     * @param array|string List of contact identifiers to be removed
     *
     * @return int    Number of deleted group members
     */
    function remove_from_group($group_id, $contact_ids)
@@ -1964,13 +1985,16 @@
        if (($group_cache = $this->cache->get('groups')) === null)
            $group_cache = $this->_fetch_groups();
        if (!is_array($contact_ids))
            $contact_ids = explode(',', $contact_ids);
        $base_dn     = $this->groups_base_dn;
        $group_name  = $group_cache[$group_id]['name'];
        $member_attr = $group_cache[$group_id]['member_attr'];
        $group_dn    = "cn=$group_name,$base_dn";
        $del_attrs = array();
        foreach (explode(",", $contact_ids) as $id)
        foreach ($contact_ids as $id)
            $del_attrs[$member_attr][] = self::dn_decode($id);
        if (!$this->ldap_mod_del($group_dn, $del_attrs)) {
@@ -1980,7 +2004,7 @@
        $this->cache->remove('groups');
        return count($del_attrs['member']);
        return count($del_attrs[$member_attr]);
    }
    /**
program/lib/Roundcube/rcube_message.php
@@ -362,7 +362,7 @@
            // parse headers from message/rfc822 part
            if (!isset($structure->headers['subject']) && !isset($structure->headers['from'])) {
                list($headers, $dump) = explode("\r\n\r\n", $this->get_part_content($structure->mime_id, null, true, 32768));
                list($headers, ) = explode("\r\n\r\n", $this->get_part_content($structure->mime_id, null, true, 32768));
                $structure->headers = rcube_mime::parse_headers($headers);
            }
        }
program/lib/Roundcube/rcube_mime.php
@@ -127,10 +127,11 @@
     * @param int     $max      List only this number of addresses
     * @param boolean $decode   Decode address strings
     * @param string  $fallback Fallback charset if none specified
     * @param boolean $addronly Return flat array with e-mail addresses only
     *
     * @return array  Indexed list of addresses
     */
    static function decode_address_list($input, $max = null, $decode = true, $fallback = null)
    static function decode_address_list($input, $max = null, $decode = true, $fallback = null, $addronly = false)
    {
        $a   = self::parse_address_list($input, $decode, $fallback);
        $out = array();
@@ -145,8 +146,12 @@
        foreach ($a as $val) {
            $j++;
            $address = trim($val['address']);
            $name    = trim($val['name']);
            if ($addronly) {
                $out[$j] = $address;
            }
            else {
                $name = trim($val['name']);
            if ($name && $address && $name != $address)
                $string = sprintf('%s <%s>', preg_match("/$special_chars/", $name) ? '"'.addcslashes($name, '"').'"' : $name, $address);
            else if ($address)
@@ -154,11 +159,8 @@
            else if ($name)
                $string = $name;
            $out[$j] = array(
                'name'   => $name,
                'mailto' => $address,
                'string' => $string
            );
                $out[$j] = array('name' => $name, 'mailto' => $address, 'string' => $string);
            }
            if ($max && $j==$max)
                break;
@@ -476,9 +478,10 @@
        $q_level = 0;
        foreach ($text as $idx => $line) {
            if ($line[0] == '>') {
                // remove quote chars, store level in $q
                $line = preg_replace('/^>+/', '', $line, -1, $q);
            if (preg_match('/^(>+)/', $line, $m)) {
                // remove quote chars
                $q    = strlen($m[1]);
                $line = preg_replace('/^>+/', '', $line);
                // remove (optional) space-staffing
                $line = preg_replace('/^ /', '', $line);
@@ -541,9 +544,10 @@
        foreach ($text as $idx => $line) {
            if ($line != '-- ') {
                if ($line[0] == '>') {
                    // remove quote chars, store level in $level
                    $line   = preg_replace('/^>+/', '', $line, -1, $level);
                if (preg_match('/^(>+)/', $line, $m)) {
                    // remove quote chars
                    $level  = strlen($m[1]);
                    $line   = preg_replace('/^>+/', '', $line);
                    // remove (optional) space-staffing and spaces before the line end
                    $line   = preg_replace('/(^ | +$)/', '', $line);
                    $prefix = str_repeat('>', $level) . ' ';
@@ -657,8 +661,8 @@
                                $cutLength = $spacePos + 1;
                            }
                            else {
                                $subString = $string;
                                $cutLength = null;
                                $subString = $substr_func($string, 0, $breakPos, $charset);
                                $cutLength = $breakPos + 1;
                            }
                        }
                        else {
program/lib/Roundcube/rcube_output.php
@@ -162,7 +162,7 @@
            header("Cache-Control: private, must-revalidate");
        }
        else {
            header("Cache-Control: private, no-cache, must-revalidate, post-check=0, pre-check=0");
            header("Cache-Control: private, no-cache, no-store, must-revalidate, post-check=0, pre-check=0");
            header("Pragma: no-cache");
        }
    }
program/lib/Roundcube/rcube_plugin_api.php
@@ -313,7 +313,6 @@
          $doc->loadXML($file);
          $xpath = new DOMXPath($doc);
          $xpath->registerNamespace('rc', "http://pear.php.net/dtd/package-2.0");
          $data = array();
          // XPaths of plugin metadata elements
          $metadata = array(
program/lib/Roundcube/rcube_smtp.php
@@ -119,7 +119,7 @@
        }
        // try to connect to server and exit on failure
        $result = $this->conn->connect($smtp_timeout);
        $result = $this->conn->connect($CONFIG['smtp_timeout']);
        if (PEAR::isError($result)) {
            $this->response[] = "Connection failed: ".$result->getMessage();
@@ -433,9 +433,9 @@
        $recipients = rcube_utils::explode_quoted_string(',', $recipients);
        reset($recipients);
        while (list($k, $recipient) = each($recipients)) {
        foreach ($recipients as $recipient) {
            $a = rcube_utils::explode_quoted_string(' ', $recipient);
            while (list($k2, $word) = each($a)) {
            foreach ($a as $word) {
                if (strpos($word, "@") > 0 && $word[strlen($word)-1] != '"') {
                    $word = preg_replace('/^<|>$/', '', trim($word));
                    if (in_array($word, $addresses) === false) {
program/lib/Roundcube/rcube_spellchecker.php
@@ -314,11 +314,6 @@
        if (!$this->plink) {
            if (!extension_loaded('pspell')) {
                $this->error = "Pspell extension not available";
                rcube::raise_error(array(
                    'code' => 500, 'type' => 'php',
                    'file' => __FILE__, 'line' => __LINE__,
                    'message' => $this->error), true, false);
                return;
            }
@@ -372,8 +367,18 @@
            fclose($fp);
        }
        // parse HTTP response
        if (preg_match('!^HTTP/1.\d (\d+)(.+)!', $store, $m)) {
            $http_status = $m[1];
            if ($http_status != '200')
                $this->error = 'HTTP ' . $m[1] . $m[2];
        }
        if (!$store) {
            $this->error = "Empty result from spelling engine";
        }
        else if (preg_match('/<spellresult error="([^"]+)"/', $store, $m)) {
            $this->error = "Error code $m[1] returned";
        }
        preg_match_all('/<c o="([^"]*)" l="([^"]*)" s="([^"]*)">([^<]*)<\/c>/', $store, $matches, PREG_SET_ORDER);
@@ -588,7 +593,7 @@
        if (empty($plugin['abort'])) {
            $dict = array();
            $this->rc->db->query(
            $sql_result = $this->rc->db->query(
                "SELECT data FROM ".$this->rc->db->table_name('dictionary')
                ." WHERE user_id ". ($plugin['userid'] ? "= ".$this->rc->db->quote($plugin['userid']) : "IS NULL")
                    ." AND " . $this->rc->db->quoteIdentifier('language') . " = ?",
program/lib/Roundcube/rcube_utils.php
@@ -404,7 +404,7 @@
        $out = array();
        $src = $mode == self::INPUT_GET ? $_GET : ($mode == self::INPUT_POST ? $_POST : $_REQUEST);
        foreach ($src as $key => $value) {
        foreach (array_keys($src) as $key) {
            $fname = $key[0] == '_' ? substr($key, 1) : $key;
            if ($ignore && !preg_match('/^(' . $ignore . ')$/', $fname)) {
                $out[$fname] = self::get_input_value($key, $mode);
program/lib/Roundcube/rcube_vcard.php
@@ -90,7 +90,7 @@
     */
    public function __construct($vcard = null, $charset = RCUBE_CHARSET, $detect = false, $fieldmap = array())
    {
        if (!empty($fielmap)) {
        if (!empty($fieldmap)) {
            $this->extend_fieldmap($fieldmap);
        }
@@ -481,7 +481,7 @@
        $vcard_block    = '';
        $in_vcard_block = false;
        foreach (preg_split("/[\r\n]+/", $data) as $i => $line) {
        foreach (preg_split("/[\r\n]+/", $data) as $line) {
            if ($in_vcard_block && !empty($line)) {
                $vcard_block .= $line . "\n";
            }
@@ -784,9 +784,30 @@
                }
                return $result;
            }
            $s = strtr($s, $rep2);
        }
        return strtr($s, array("\r" => '', '\\\\' => '\\', '\n' => "\n", '\N' => "\n", '\,' => ',', '\;' => ';'));
        // some implementations (GMail) use non-standard backslash before colon (#1489085)
        // we will handle properly any backslashed character - removing dummy backslahes
        // return strtr($s, array("\r" => '', '\\\\' => '\\', '\n' => "\n", '\N' => "\n", '\,' => ',', '\;' => ';'));
        $s   = str_replace("\r", '', $s);
        $pos = 0;
        while (($pos = strpos($s, '\\', $pos)) !== false) {
            $next = substr($s, $pos + 1, 1);
            if ($next == 'n' || $next == 'N') {
                $s = substr_replace($s, "\n", $pos, 2);
            }
            else {
                $s = substr_replace($s, '', $pos, 1);
            }
            $pos += 1;
        }
        return $s;
    }
    /**
program/lib/utf8.class.php
@@ -60,8 +60,8 @@
  function loadCharset($charset) {
    
    $charset = preg_replace(array('/^WINDOWS-*125([0-8])$/', '/^CP-/'), array('CP125\\1', 'CP'), $charset);
    if (isset($aliases[$charset]))
      $charset = $aliases[$charset];
    if (isset($this->aliases[$charset]))
      $charset = $this->aliases[$charset];
    
    $this->charset = $charset;
program/localization/en_GB/labels.inc
@@ -397,6 +397,7 @@
$labels['signature'] = 'Signature';
$labels['dstactive']  = 'Summer time';
$labels['showinextwin'] = 'Open message in a new window';
$labels['showemail'] = 'Show email address with display name';
$labels['composeextwin'] = 'Compose in a new window';
$labels['htmleditor'] = 'Compose HTML messages';
$labels['htmlonreply'] = 'on reply to HTML message only';
program/localization/en_US/csv2vcard.inc
@@ -91,3 +91,20 @@
$map['work_address'] = "Work Address";
$map['work_country'] = "Work Country";
$map['work_zipcode'] = "Work ZipCode";
// Atmail
$map['date_of_birth'] = "Date of Birth";
$map['email'] = "Email";
$map['home_mobile'] = "Home Mobile";
$map['home_zip'] = "Home Zip";
$map['info'] = "Info";
$map['user_photo'] = "User Photo";
$map['url'] = "URL";
$map['work_city'] = "Work City";
$map['work_company'] = "Work Company";
$map['work_dept'] = "Work Dept";
$map['work_fax'] = "Work Fax";
$map['work_mobile'] = "Work Mobile";
$map['work_state'] = "Work State";
$map['work_title'] = "Work Title";
$map['work_zip'] = "Work Zip";
program/localization/en_US/labels.inc
@@ -402,6 +402,7 @@
$labels['htmlonreply'] = 'on reply to HTML message';
$labels['htmlonreplyandforward'] = 'on forward or reply to HTML message';
$labels['htmlsignature'] = 'HTML signature';
$labels['showemail'] = 'Show email address with display name';
$labels['previewpane'] = 'Show preview pane';
$labels['skin'] = 'Interface skin';
$labels['logoutclear'] = 'Clear Trash on logout';
program/localization/fr_FR/csv2vcard.inc
New file
@@ -0,0 +1,96 @@
<?php
/*
 +-----------------------------------------------------------------------+
 | localization/<lang>/csv2vcard.inc                                     |
 |                                                                       |
 | Localization file of the Roundcube Webmail client                     |
 | Copyright (C) 2005-2013, The Roundcube Dev Team                       |
 |                                                                       |
 | Licensed under the GNU General Public License version 3 or            |
 | any later version with exceptions for skins & plugins.                |
 | See the README file for a full license statement.                     |
 |                                                                       |
 +-----------------------------------------------------------------------+
 | Author: Aleksander Machniak <alec@alec.pl>                            |
 +-----------------------------------------------------------------------+
*/
// This is a list of CSV column names specified in CSV file header
// These must be original texts used in Outlook/Thunderbird exported csv files
// Encoding UTF-8
$map = array();
// MS Outlook 2010
$map['anniversary'] = "Anniversaire de mariage ou fête";
$map['assistants_name'] = "Nom de l''assistant(e)";
$map['assistants_phone'] = "Téléphone de l''assistant(e)";
$map['birthday'] = "Anniversaire";
$map['business_city'] = "Ville (bureau)";
$map['business_countryregion'] = "Pays/Région (bureau)";
$map['business_fax'] = "Télécopie (bureau)";
$map['business_phone'] = "Téléphone (bureau)";
$map['business_phone_2'] = "Téléphone 2 (bureau)";
$map['business_postal_code'] = "Code postal (bureau)";
$map['business_state'] = "Dép/Région (bureau)";
$map['business_street'] = "Rue (bureau)";
$map['car_phone'] = "Téléphone (voiture)";
$map['categories'] = "Catégories";
$map['company'] = "Société";
$map['department'] = "Service";
$map['email_address'] = "Adresse de messagerie";
$map['first_name'] = "Prénom";
$map['gender'] = "Sexe";
$map['home_city'] = "Ville (domicile)";
$map['home_countryregion'] = "Pays/Région (domicile)";
$map['home_fax'] = "Télécopie (domicile)";
$map['home_phone'] = "Téléphone (domicile)";
$map['home_phone_2'] = "Téléphone 2 (domicile)";
$map['home_postal_code'] = "Code postal (domicile)";
$map['home_state'] = "Dép/Région (domicile)";
$map['home_street'] = "Rue (domicile)";
$map['job_title'] = "Profession";
$map['last_name'] = "Nom";
$map['managers_name'] = "Manager's Name";
$map['middle_name'] = "Deuxième prénom";
$map['mobile_phone'] = "Tél. mobile";
$map['notes'] = "Notes";
$map['other_city'] = "Ville (autre)";
$map['other_countryregion'] = "Pays/Région (autre)";
$map['other_fax'] = "Télécopie (autre)";
$map['other_phone'] = "Téléphone (autre)";
$map['other_postal_code'] = "Code postal (autre)";
$map['other_state'] = "Dép/Région (autre)";
$map['other_street'] = "Rue (autre)";
$map['pager'] = "Récepteur de radiomessagerie";
$map['primary_phone'] = "Téléphone principal";
$map['spouse'] = "Conjoint(e)";
$map['suffix'] = "Suffixe";
$map['title'] = "Titre";
$map['web_page'] = "Page Web";
// Thunderbird
$map['birth_day'] = "Jour";
$map['birth_month'] = "Mois";
$map['birth_year'] = "Année de naissance";
$map['display_name'] = "Nom à afficher";
$map['fax_number'] = "Fax";
$map['home_address'] = "Adresse privée";
$map['home_country'] = "Région";
$map['home_zipcode'] = "Code postal";
$map['mobile_number'] = "Portable";
$map['nickname'] = "Surnom";
$map['organization'] = "Société";
$map['pager_number'] = "Pager";
$map['primary_email'] = "Adresse électronique principale";
$map['secondary_email'] = "Adresse électronique secondaire";
$map['web_page_1'] = "Site Web 1";
$map['web_page_2'] = "Site Web 2";
$map['work_phone'] = "Tél. professionnel";
$map['work_address'] = "Adresse professionnelle";
$map['work_country'] = "Région";
$map['work_zipcode'] = "Code postal";
// Other
$map['_home_city'] = "Ville";
program/steps/addressbook/func.inc
@@ -167,7 +167,7 @@
    // get address book name (for display)
    if ($abook && $_SESSION['addressbooks_count'] > 1) {
        $name = $abook->get_name();
        if (!$name && $source == 0) {
        if (!$name) {
            $name = rcube_label('personaladrbook');
        }
        $OUTPUT->set_env('sourcename', html_entity_decode($name, ENT_COMPAT, 'UTF-8'));
@@ -183,7 +183,6 @@
        $attrib['id'] = 'rcmdirectorylist';
    $out = '';
    $local_id = '0';
    $jsdata = array();
    $line_templ = html::tag('li', array(
@@ -270,7 +269,6 @@
    $groups_html = '';
    $groups = $RCMAIL->get_address_book($args['source'])->list_groups();
    $js_id = $RCMAIL->JQ($args['source']);
    if (!empty($groups)) {
        $line_templ = html::tag('li', array(
@@ -283,7 +281,6 @@
        $is_collapsed = strpos($RCMAIL->config->get('collapsed_abooks',''), '&'.rawurlencode($args['source']).'&') !== false;
        $args['out'] .= html::div('treetoggle ' . ($is_collapsed ? 'collapsed' : 'expanded'), '&nbsp;');
        $jsdata = array();
        foreach ($groups as $group) {
            $groups_html .= sprintf($line_templ,
                rcube_utils::html_identifier('G' . $args['source'] . $group['ID'], true),
@@ -297,7 +294,7 @@
    }
    $args['out'] .= html::tag('ul',
      array('class' => 'groups', 'style' => ($is_collapsed ? "display:none;" : null)),
      array('class' => 'groups', 'style' => ($is_collapsed || empty($groups) ? "display:none;" : null)),
      $groups_html);
    return $args;
program/steps/addressbook/import.inc
@@ -88,7 +88,7 @@
  $content = html::p(null, rcube_label(array(
      'name' => 'importconfirm',
      'nr' => $IMORT_STATS->inserted,
      'nr' => $IMPORT_STATS->inserted,
      'vars' => $vars,
    )) . ($IMPORT_STATS->names ? ':' : '.'));
@@ -98,7 +98,7 @@
  if ($IMPORT_STATS->skipped) {
      $content .= html::p(null, rcube_label(array(
          'name' => 'importconfirmskipped',
          'nr' => $IMORT_STATS->skipped,
          'nr' => $IMPORT_STATS->skipped,
          'vars' => $vars,
        )) . ':');
      $content .= html::p('em', join(', ', array_map('Q', $IMPORT_STATS->skipped_names)));
program/steps/addressbook/show.inc
@@ -101,8 +101,6 @@
        return false;
    }
    $microformats = array('name' => 'fn', 'email' => 'email');
    $form = array(
        'head' => array(  // section 'head' is magic!
            'content' => array(
@@ -177,7 +175,7 @@
}
function rcmail_render_email_value($email, $col)
function rcmail_render_email_value($email)
{
    return html::a(array(
        'href' => 'mailto:' . $email,
@@ -188,7 +186,7 @@
}
function rcmail_render_url_value($url, $col)
function rcmail_render_url_value($url)
{
    $prefix = preg_match('!^(http|ftp)s?://!', $url) ? '' : 'http://';
    return html::a(array(
@@ -223,7 +221,7 @@
    }
    $hiddenfields = new html_hiddenfield(array('name' => '_source', 'value' => get_input_value('_source', RCUBE_INPUT_GPC)));
    $hiddenfields->add(array('name' => '_cid', 'value' => $record['ID']));
    $hiddenfields->add(array('name' => '_cid', 'value' => $contact_id));
    $form_start = $RCMAIL->output->request_form(array(
        'name' => "form", 'method' => "post",
program/steps/mail/autocomplete.inc
@@ -102,7 +102,7 @@
    // also list matching contact groups
    if ($abook->groups && count($contacts) < $MAXNUM) {
      foreach ($abook->list_groups($search) as $group) {
      foreach ($abook->list_groups($search, $mode) as $group) {
        $abook->reset();
        $abook->set_group($group['ID']);
        $group_prop = $abook->get_group($group['ID']);
program/steps/mail/compose.inc
@@ -327,6 +327,19 @@
        $fvalue .= $v;
      if ($v = $MESSAGE->headers->cc)
        $fvalue .= (!empty($fvalue) ? $separator : '') . $v;
      if ($v = $MESSAGE->headers->get('Sender', false))
        $fvalue .= (!empty($fvalue) ? $separator : '') . $v;
      // When To: and Reply-To: are the same we add From: address to the list (#1489037)
      if ($v = $MESSAGE->headers->from) {
        $from    = rcube_mime::decode_address_list($v, null, false, $MESSAGE->headers->charset, true);
        $to      = rcube_mime::decode_address_list($MESSAGE->headers->to, null, false, $MESSAGE->headers->charset, true);
        $replyto = rcube_mime::decode_address_list($MESSAGE->headers->replyto, null, false, $MESSAGE->headers->charset, true);
        if (count($replyto) && !count(array_diff($to, $replyto)) && count(array_diff($from, $to))) {
          $fvalue .= (!empty($fvalue) ? $separator : '') . $v;
        }
      }
    }
  }
  else if (in_array($compose_mode, array(RCUBE_COMPOSE_DRAFT, RCUBE_COMPOSE_EDIT))) {
@@ -386,7 +399,7 @@
{
  global $MESSAGE;
  list($form_start, $form_end) = get_form_tags($attrib);
  list($form_start,) = get_form_tags($attrib);
  $out  = '';
  $part = strtolower($attrib['part']);
@@ -450,7 +463,7 @@
function rcmail_compose_header_from($attrib)
{
  global $MESSAGE, $OUTPUT, $RCMAIL, $compose_mode;
  global $MESSAGE, $OUTPUT, $RCMAIL, $COMPOSE, $compose_mode;
  // pass the following attributes to the form class
  $field_attrib = array('name' => '_from');
@@ -553,7 +566,7 @@
function rcmail_prepare_message_body()
{
  global $RCMAIL, $MESSAGE, $COMPOSE, $compose_mode, $LINE_LENGTH, $HTML_MODE;
  global $RCMAIL, $MESSAGE, $COMPOSE, $compose_mode, $HTML_MODE;
  // use posted message body
  if (!empty($_POST['_message'])) {
@@ -626,7 +639,7 @@
function rcmail_compose_part_body($part, $isHtml = false)
{
    global $RCMAIL, $MESSAGE, $compose_mode;
    global $RCMAIL, $MESSAGE, $LINE_LENGTH, $compose_mode;
    // Check if we have enough memory to handle the message in it
    // #1487424: we need up to 10x more memory than the body
@@ -702,7 +715,7 @@
function rcmail_compose_body($attrib)
{
  global $RCMAIL, $CONFIG, $OUTPUT, $MESSAGE, $compose_mode, $LINE_LENGTH, $HTML_MODE, $MESSAGE_BODY;
  global $RCMAIL, $CONFIG, $OUTPUT, $MESSAGE, $compose_mode, $HTML_MODE, $MESSAGE_BODY;
  list($form_start, $form_end) = get_form_tags($attrib);
  unset($attrib['form']);
@@ -887,7 +900,6 @@
    $cid_map = rcmail_write_compose_attachments($MESSAGE, $bodyIsHtml);
  $date    = format_date($MESSAGE->headers->date, $RCMAIL->config->get('date_long'));
  $charset = $RCMAIL->output->get_charset();
  if (!$bodyIsHtml) {
    $prefix = "\n\n\n-------- " . rcube_label('originalmessage') . " --------\n";
@@ -941,7 +953,7 @@
function rcmail_create_draft_body($body, $bodyIsHtml)
{
  global $MESSAGE, $OUTPUT, $COMPOSE;
  global $MESSAGE, $COMPOSE;
  /**
   * add attachments
@@ -989,7 +1001,7 @@
  global $RCMAIL, $COMPOSE, $compose_mode;
  $loaded_attachments = array();
  foreach ((array)$COMPOSE['attachments'] as $id => $attachment) {
  foreach ((array)$COMPOSE['attachments'] as $attachment) {
      $loaded_attachments[$attachment['name'] . $attachment['mimetype']] = $attachment;
  }
@@ -1076,7 +1088,7 @@
  $names     = array();
  $loaded_attachments = array();
  foreach ((array)$COMPOSE['attachments'] as $id => $attachment) {
  foreach ((array)$COMPOSE['attachments'] as $attachment) {
      $loaded_attachments[$attachment['name'] . $attachment['mimetype']] = $attachment;
  }
@@ -1211,10 +1223,11 @@
  // handle attachments in memory
  $data = file_get_contents($path);
  $name = rcmail_basename($path);
  $attachment = array(
    'group' => $COMPOSE['id'],
    'name' => rcmail_basename($path),
    'name' => $name,
    'mimetype' => $mimetype ? $mimetype : rc_mime_content_type($path, $name),
    'data' => $data,
    'size' => strlen($data),
@@ -1484,7 +1497,7 @@
  $select->add(Q(rcube_label('plaintoggle')), 'plain');
  return $select->show($useHtml ? 'html' : 'plain');
/*
  foreach ($choices as $value => $text) {
    $attrib['id'] = '_' . $value;
    $attrib['value'] = $value;
@@ -1492,6 +1505,7 @@
  }
  return $selector;
*/
}
program/steps/mail/func.inc
@@ -224,7 +224,7 @@
  if (!in_array('threads', $a_show_cols))
    array_unshift($a_show_cols, 'threads');
  $skin_path = $_SESSION['skin_path'] = $CONFIG['skin_path'];
  $_SESSION['skin_path'] = $CONFIG['skin_path'];
  // set client env
  $OUTPUT->add_gui_object('messagelist', $attrib['id']);
@@ -236,15 +236,13 @@
  $OUTPUT->include_script('list.js');
  $thead = '';
  $table = new html_table($attrib);
  if (!$attrib['noheader']) {
  foreach (rcmail_message_list_head($attrib, $a_show_cols) as $cell)
    $thead .= html::tag('td', array('class' => $cell['className'], 'id' => $cell['id']), $cell['html']);
      $table->add_header(array('class' => $cell['className'], 'id' => $cell['id']), $cell['html']);
  }
  return html::tag('table',
    $attrib,
    html::tag('thead', null, html::tag('tr', null, $thead)) .
      html::tag('tbody', null, ''),
        array('style', 'class', 'id', 'cellpadding', 'cellspacing', 'border', 'summary'));
  return $table->show();
}
@@ -291,7 +289,7 @@
  $thead = $head_replace ? rcmail_message_list_head($_SESSION['list_attrib'], $a_show_cols) : NULL;
  // get name of smart From/To column in folder context
  if (($f = array_search('fromto', $a_show_cols)) !== false) {
  if (array_search('fromto', $a_show_cols) !== false) {
    $smart_col = rcmail_message_list_smart_column_name();
  }
@@ -307,7 +305,7 @@
  }
  // loop through message headers
  foreach ($a_headers as $n => $header) {
  foreach ($a_headers as $header) {
    if (empty($header))
      continue;
@@ -381,7 +379,6 @@
  global $RCMAIL;
  $skin_path = $_SESSION['skin_path'];
  $image_tag = html::img(array('src' => "%s%s", 'alt' => "%s"));
  // check to see if we have some settings for sorting
  $sort_col   = $_SESSION['sort_col'];
@@ -417,7 +414,7 @@
  $cells = array();
  // get name of smart From/To column in folder context
  if (($f = array_search('fromto', $a_show_cols)) !== false) {
  if (array_search('fromto', $a_show_cols) !== false) {
    $smart_col = rcmail_message_list_smart_column_name();
  }
@@ -897,7 +894,7 @@
 */
function rcmail_message_headers($attrib, $headers=null)
{
  global $OUTPUT, $MESSAGE, $PRINT_MODE, $RCMAIL;
  global $MESSAGE, $PRINT_MODE, $RCMAIL;
  static $sa_attrib;
  // keep header table attrib
@@ -1082,7 +1079,7 @@
      $header_attrib[$regs[1]] = $value;
  if (!empty($MESSAGE->parts)) {
    foreach ($MESSAGE->parts as $i => $part) {
    foreach ($MESSAGE->parts as $part) {
      if ($part->type == 'headers') {
        $out .= html::div('message-partheaders', rcmail_message_headers(sizeof($header_attrib) ? $header_attrib : null, $part->headers));
      }
@@ -1441,6 +1438,7 @@
  $j = 0;
  $out = '';
  $allvalues = array();
  $show_email = $RCMAIL->config->get('message_show_email');
  if ($addicon && !isset($_SESSION['writeable_abook'])) {
    $_SESSION['writeable_abook'] = $RCMAIL->get_address_sources(true) ? true : false;
@@ -1453,7 +1451,7 @@
    $string = $part['string'];
    // phishing email prevention (#1488981), e.g. "valid@email.addr <phishing@email.addr>"
    if ($name && $name != $mailto && strpos($name, '@')) {
    if (!$show_email && $name && $name != $mailto && strpos($name, '@')) {
      $name = '';
    }
@@ -1471,13 +1469,21 @@
    }
    else if (check_email($part['mailto'], false)) {
      if ($linked) {
        $address = html::a(array(
        $attrs = array(
            'href' => 'mailto:'.$mailto,
            'onclick' => sprintf("return %s.command('compose','%s',this)", JS_OBJECT_NAME, JQ($mailto)),
            'title' => $mailto,
            'class' => "rcmContactAddress",
          ),
        Q($name ? $name : $mailto));
        );
        if ($show_email && $name && $mailto) {
          $content = Q($name ? sprintf('%s <%s>', $name, $mailto) : $mailto);
        }
        else {
          $content = Q($name ? $name : $mailto);
          $attrs['title'] = $mailto;
        }
        $address = html::a($attrs, $content);
      }
      else {
        $address = html::span(array('title' => $mailto, 'class' => "rcmContactAddress"),
@@ -1730,8 +1736,7 @@
    $sent = rcmail_deliver_message($compose, $identity['email'], $mailto, $smtp_error, $body_file, $options);
    if ($sent)
    {
    if ($sent) {
      $RCMAIL->storage->set_flag($message->uid, 'MDNSENT');
      return true;
    }
program/steps/mail/show.inc
@@ -228,11 +228,11 @@
function rcmail_message_buttons()
{
  global $MESSAGE, $RCMAIL, $CONFIG;
  global $RCMAIL;
  $mbox  = $RCMAIL->storage->get_folder();
  $delim = $RCMAIL->storage->get_hierarchy_delimiter();
  $dbox  = $CONFIG['drafts_mbox'];
  $dbox  = $RCMAIL->config->get('drafts_mbox');
  // the message is not a draft
  if ($mbox != $dbox && strpos($mbox, $dbox.$delim) !== 0) {
program/steps/settings/edit_prefs.inc
@@ -40,7 +40,7 @@
  $out = $form_start;
  foreach ($SECTIONS[$CURR_SECTION]['blocks'] as $idx => $block) {
  foreach ($SECTIONS[$CURR_SECTION]['blocks'] as $block) {
    if (!empty($block['options'])) {
      $table = new html_table(array('cols' => 2));
program/steps/settings/folders.inc
@@ -283,7 +283,6 @@
        $noselect   = false;
        $classes    = array($i%2 ? 'even' : 'odd');
        $folder_js      = Q($folder['id']);
        $folder_utf8    = rcube_charset_convert($folder['id'], 'UTF7-IMAP');
        $display_folder = str_repeat('&nbsp;&nbsp;&nbsp;&nbsp;', $folder['level'])
            . Q($protected ? rcmail_localize_foldername($folder['id']) : $folder['name']);
@@ -394,7 +393,7 @@
        $a_threaded = (array) $RCMAIL->config->get('message_threading', array());
        $oldprefix  = '/^' . preg_quote($oldname . $delimiter, '/') . '/';
        foreach ($a_threaded as $key => $val) {
        foreach (array_keys($a_threaded) as $key) {
            if ($key == $oldname) {
                unset($a_threaded[$key]);
                $a_threaded[$newname] = true;
program/steps/settings/func.inc
@@ -418,6 +418,17 @@
      );
    }
    // show checkbox to show email instead of name
    if (!isset($no_override['message_show_email'])) {
      $field_id = 'rcmfd_message_show_email';
      $input_msgshowemail = new html_checkbox(array('name' => '_message_show_email', 'id' => $field_id, 'value' => 1));
      $blocks['main']['options']['message_show_email'] = array(
        'title' => html::label($field_id, Q(rcube_label('showemail'))),
        'content' => $input_msgshowemail->show($config['message_show_email']?1:0),
      );
    }
    // show checkbox for HTML/plaintext messages
    if (!isset($no_override['prefer_html'])) {
      $field_id = 'rcmfd_htmlmsg';
program/steps/settings/save_prefs.inc
@@ -60,6 +60,7 @@
  case 'mailview':
    $a_user_prefs = array(
      'message_extwin'  => intval($_POST['_message_extwin']),
      'message_show_email' => isset($_POST['_message_show_email']) ? TRUE : FALSE,
      'prefer_html'     => isset($_POST['_prefer_html']) ? TRUE : FALSE,
      'inline_images'   => isset($_POST['_inline_images']) ? TRUE : FALSE,
      'show_images'     => isset($_POST['_show_images']) ? intval($_POST['_show_images']) : 0,
program/steps/utils/spell.inc
@@ -42,6 +42,13 @@
    $result = $spellchecker->get_xml();
}
if ($err = $spellchecker->error()) {
    rcube::raise_error(array('code' => 500, 'type' => 'php',
        'file' => __FILE__, 'line' => __LINE__,
        'message' => sprintf("Spell check engine error: " . $err)),
        true, false);
}
// set response length
header("Content-Length: " . strlen($result));
program/steps/utils/spell_html.inc
@@ -46,6 +46,11 @@
}
if ($error = $spellchecker->error()) {
    rcube::raise_error(array('code' => 500, 'type' => 'php',
        'file' => __FILE__, 'line' => __LINE__,
        'message' => sprintf("Spell check engine error: " . $error)),
        true, false);
    echo '{"error":{"errstr":"' . addslashes($error) . '","errfile":"","errline":null,"errcontext":"","level":"FATAL"}}';
    exit;
}
tests/Framework/Mime.php
@@ -185,6 +185,10 @@
                array("----------------------------------------------------------------------------------------\nabc                        def123456789012345", 76),
                "----------------------------------------------------------------------------------------\nabc                        def123456789012345",
            ),
            array(
                array("-------\nabc def", 5),
                "-------\nabc\ndef",
            ),
        );
        foreach ($samples as $sample) {
tests/Framework/VCard.php
@@ -65,6 +65,20 @@
        $this->assertEquals("prefix", $vcard['prefix'], "Decode backslash character");
    }
    /**
     * Backslash parsing test (#1489085)
     */
    function test_parse_five()
    {
        $vcard = "BEGIN:VCARD\nVERSION:3.0\nN:last\\\\\\a;fir\\nst\nURL:http\\://domain.tld\nEND:VCARD";
        $vcard = new rcube_vcard($vcard, null);
        $vcard = $vcard->get_assoc();
        $this->assertEquals("last\\a", $vcard['surname'], "Decode dummy backslash character");
        $this->assertEquals("fir\nst", $vcard['firstname'], "Decode backslash character");
        $this->assertEquals("http://domain.tld", $vcard['website:other'][0], "Decode dummy backslash character");
    }
    function test_import()
    {
        $input = file_get_contents($this->_srcpath('apple.vcf'));
tests/src/Csv2vcard/tb_plain.csv
@@ -1,2 +1,2 @@
First Name,Last Name,Display Name,Nickname,Primary Email,Secondary Email,Screen Name,Work Phone,Home Phone,Fax Number,Pager Number,Mobile Number,Home Address,Home Address 2,Home City,Home State,Home ZipCode,Home Country,Work Address,Work Address 2,Work City,Work State,Work ZipCode,Work Country,Job Title,Department,Organization,Web Page 1,Web Page 2,Birth Year,Birth Month,Birth Day,Custom 1,Custom 2,Custom 3,Custom 4,Notes,
Firstname,Lastname,Displayname,Nick,test@domain.tld,next@domain.tld,,phone work,phone home,fax,pager,mobile,Priv address,,City,region,xx-xxx,USA,Addr work,,city,region,33-333,Poland,title,department,Organization,http://page.com,http://webpage.tld,1970,11,15,,,,,,
Firstname,Lastname,Displayname,Nick,test@domain.tld,next@domain.tld,,phone work,phone home,fax,pager,mobile,Priv address,,City,region,xx-xxx,USA,Addr work,,Wcity,Wstate,33-333,Poland,title,department,Organization,http://page.com,http://webpage.tld,1970,11,15,,,,,,
tests/src/Csv2vcard/tb_plain.vcf
@@ -16,5 +16,5 @@
URL;TYPE=other:http://webpage.tld
BDAY;VALUE=date:1970-11-15
ADR;TYPE=home:;;Priv address;City;region;xx-xxx;USA
ADR;TYPE=work:;;Addr work;;;33-333;Poland
ADR;TYPE=work:;;Addr work;Wcity;Wstate;33-333;Poland
END:VCARD
tests/src/format-flowed-unfolded.txt
@@ -5,7 +5,7 @@
On XX.YY.YYYY Y:YY, Somebody wrote:
> This part is a reply wihtout any flowing lines. rcube_mime::unfold_flowed()
> has to be careful with empty quoted lines because they might end with a
>> has to be careful with empty quoted lines because they might end with a
> space but still shouldn't be considered as flowed!
> The above empty line should persist after unfolding.
tests/src/format-flowed.txt
@@ -7,7 +7,7 @@
On XX.YY.YYYY Y:YY, Somebody wrote:
> This part is a reply wihtout any flowing lines. rcube_mime::unfold_flowed()
> has to be careful with empty quoted lines because they might end with a
>> has to be careful with empty quoted lines because they might end with a
> space but still shouldn't be considered as flowed!
> The above empty line should persist after unfolding.