thomascube
2005-12-03 1cded85790206afe084e1baff371c543711b2b18
Re-design of caching (new database table added\!); some bugfixes; Postgres support


5 files added
43 files modified
3287 ■■■■■ changed files
CHANGELOG 126 ●●●● patch | view | raw | blame | history
INSTALL 20 ●●●●● patch | view | raw | blame | history
SQL/mysql.initial.sql 38 ●●●● patch | view | raw | blame | history
SQL/mysql.update.sql 35 ●●●●● patch | view | raw | blame | history
SQL/postgres.initial.sql 354 ●●●● patch | view | raw | blame | history
SQL/sqlite.initial.sql 54 ●●●●● patch | view | raw | blame | history
UPGRADING 30 ●●●●● patch | view | raw | blame | history
config/db.inc.php.dist 18 ●●●●● patch | view | raw | blame | history
config/main.inc.php.dist 2 ●●● patch | view | raw | blame | history
index.php 17 ●●●● patch | view | raw | blame | history
program/include/cache.inc 5 ●●●●● patch | view | raw | blame | history
program/include/main.inc 312 ●●●●● patch | view | raw | blame | history
program/include/rcube_db.inc 469 ●●●● patch | view | raw | blame | history
program/include/rcube_imap.inc 780 ●●●● patch | view | raw | blame | history
program/include/rcube_mdb2.inc 16 ●●●●● patch | view | raw | blame | history
program/include/rcube_shared.inc 109 ●●●● patch | view | raw | blame | history
program/include/rcube_sqlite.inc 71 ●●●●● patch | view | raw | blame | history
program/include/session.inc 16 ●●●● patch | view | raw | blame | history
program/js/app.js 99 ●●●● patch | view | raw | blame | history
program/localization/de/labels.inc 4 ●●●● patch | view | raw | blame | history
program/localization/de/messages.inc 2 ●●●●● patch | view | raw | blame | history
program/localization/ee/labels.inc 182 ●●●●● patch | view | raw | blame | history
program/localization/ee/messages.inc 80 ●●●●● patch | view | raw | blame | history
program/localization/en/labels.inc 4 ●●●● patch | view | raw | blame | history
program/localization/en/messages.inc 8 ●●●● patch | view | raw | blame | history
program/localization/en_GB/labels.inc 3 ●●●● patch | view | raw | blame | history
program/localization/index.inc 1 ●●●● patch | view | raw | blame | history
program/steps/addressbook/delete.inc 6 ●●●● patch | view | raw | blame | history
program/steps/addressbook/edit.inc 2 ●●● patch | view | raw | blame | history
program/steps/addressbook/func.inc 6 ●●●● patch | view | raw | blame | history
program/steps/addressbook/list.inc 4 ●●●● patch | view | raw | blame | history
program/steps/addressbook/save.inc 12 ●●●● patch | view | raw | blame | history
program/steps/addressbook/show.inc 2 ●●● patch | view | raw | blame | history
program/steps/mail/addcontact.inc 8 ●●●● patch | view | raw | blame | history
program/steps/mail/compose.inc 180 ●●●● patch | view | raw | blame | history
program/steps/mail/func.inc 121 ●●●● patch | view | raw | blame | history
program/steps/mail/sendmail.inc 15 ●●●●● patch | view | raw | blame | history
program/steps/settings/delete_identity.inc 2 ●●● patch | view | raw | blame | history
program/steps/settings/edit_identity.inc 5 ●●●●● patch | view | raw | blame | history
program/steps/settings/func.inc 4 ●●●● patch | view | raw | blame | history
program/steps/settings/manage_folders.inc 15 ●●●● patch | view | raw | blame | history
program/steps/settings/save_identity.inc 12 ●●●● patch | view | raw | blame | history
skins/default/images/sort_asc.gif patch | view | raw | blame | history
skins/default/images/sort_desc.gif patch | view | raw | blame | history
skins/default/mail.css 27 ●●●● patch | view | raw | blame | history
skins/default/pngbehavior.htc 2 ●●● patch | view | raw | blame | history
skins/default/templates/compose.html 5 ●●●●● patch | view | raw | blame | history
skins/default/templates/mail.html 4 ●●● patch | view | raw | blame | history
CHANGELOG
@@ -1,68 +1,9 @@
CHANGELOG RoundCube Webmail
---------------------------
2005/08/11
2005/12/03
----------
- Write list header to client even if list is empty
- Add functions "select all", "select none" to message list
- Improved filter for HTML messages to remove potentially malicious tags (script, iframe, object) and event handlers.
- Buttons for next/previous message in view mode
- Add new created contact to list and show confirmation status
- Added folder management (subscribe/create/delete)
- Log message sending (SMTP log)
- Grant access for Camino browser
- Added German translation
2005/08/20
----------
- Improved cacheing of mailbox messagecount
- Fixed javascript bug when creating a new message folder
- Fixed javascript bugs #1260990 and #1260992: folder selection
- Make Trash folder configurable
- Auto create folders Inbox, Sent and Trash (if configured)
- Support for IMAP root folder
- Added support fot text/enriched messages
- Make list of special mailboxes configurable
2005/10/07
----------
- Added French, Italian, Spanish, Danish, Dutch translation
- Clarified license (Bug #1305966)
- Fixed PHP warnings (Bug #1299403)
- Fixed english translation (Bug #1295406)
- Fixed bug #1290833: Last character of email not seen
- Fixed bug #1292199 when creating new user
- Allow more browsers (Bug #1285101)
- Added setting for showing pretty dates
- Added support for SQLite database
- Make use of message caching configurable
- Also add attachments when forwarding a message
- Long folder names will not flow over message list (Bug #1267232)
- Show nested mailboxes hieracically
- Enable IMAPS by host
2005/10/20
----------
- Added Swedish, Latvian, Portuguese and Catalan translation
- Make SMTP auth method configurable
- Make mailboxlist scrollable (Bug #1326372)
- Fixed SSL support
- Improved support for Courier IMAP (root folder and delimiter issues)
- Moved taskbar from bottom to top
- Added 'session_lifetime' parameter
- Fixed wrong unread count when deleting message (Bug #1332434)
- Srip tags when creating a new folder (Bug #1332084)
- Translate HTML tags in message headers (Bug #1330134)
- Correction in German translation (Bug #1329434)
- Display folder names with special chars correctly (Bug #1330157)
2005/11/18
----------
- Added Finnish, Romanian, Polish, Czech, British, Norwegian, Greek, Russian and Chinese translation
- Added Finnish, Romanian, Polish, Czech, British, Norwegian, Greek, Russian, Estonian and Chinese translation
- Get IMAP server capabilities in array
- Check for NAMESPACE capability before sending command
- Set default user language from config 'locale_string'
@@ -85,5 +26,68 @@
- Decode attachment file names
- Make delimiter for message headers configurable
- Add generic footer to sent messages
- Choose the rigt identity when replying
- Remove signature when replying (Request #1333167)
- Signatures for each identity
- Select charset when composing message
- Complete re-design of the caching mechanism
2005/08/11
----------
- Write list header to client even if list is empty
- Add functions "select all", "select none" to message list
- Improved filter for HTML messages to remove potentially malicious tags (script, iframe, object) and event handlers.
- Buttons for next/previous message in view mode
- Add new created contact to list and show confirmation status
- Added folder management (subscribe/create/delete)
- Log message sending (SMTP log)
- Grant access for Camino browser
- Added German translation
2005/10/20
----------
- Added Swedish, Latvian, Portuguese and Catalan translation
- Make SMTP auth method configurable
- Make mailboxlist scrollable (Bug #1326372)
- Fixed SSL support
- Improved support for Courier IMAP (root folder and delimiter issues)
- Moved taskbar from bottom to top
- Added 'session_lifetime' parameter
- Fixed wrong unread count when deleting message (Bug #1332434)
- Srip tags when creating a new folder (Bug #1332084)
- Translate HTML tags in message headers (Bug #1330134)
- Correction in German translation (Bug #1329434)
- Display folder names with special chars correctly (Bug #1330157)
2005/10/07
----------
- Added French, Italian, Spanish, Danish, Dutch translation
- Clarified license (Bug #1305966)
- Fixed PHP warnings (Bug #1299403)
- Fixed english translation (Bug #1295406)
- Fixed bug #1290833: Last character of email not seen
- Fixed bug #1292199 when creating new user
- Allow more browsers (Bug #1285101)
- Added setting for showing pretty dates
- Added support for SQLite database
- Make use of message caching configurable
- Also add attachments when forwarding a message
- Long folder names will not flow over message list (Bug #1267232)
- Show nested mailboxes hieracically
- Enable IMAPS by host
2005/08/20
----------
- Improved cacheing of mailbox messagecount
- Fixed javascript bug when creating a new message folder
- Fixed javascript bugs #1260990 and #1260992: folder selection
- Make Trash folder configurable
- Auto create folders Inbox, Sent and Trash (if configured)
- Support for IMAP root folder
- Added support fot text/enriched messages
- Make list of special mailboxes configurable
INSTALL
@@ -31,6 +31,7 @@
> quit
# mysql roundcubemail < SQL/mysql.initial.sql
* SQLite
--------
Sqlite requires specifically php5 (sqlite in php4 currently doesn't
@@ -44,6 +45,25 @@
webserver can write to the file.
* PostgreSQL
------------
To use RoundCube with PostgreSQL support you have to follow the next
simple steps, which have to be done with the postgres system user (or
which ever is the database superuser):
$ createuser roundcubemail
$ createdb -O roundcubemail roundcubemail
$ psql roundcubemail
roundcubemail =# ALTER USER roundcube WITH PASSWORD 'the_new_password';
roundcubemail =# \c - roundcubemail
roundcubemail => \i SQL/postgres.initial.sql
All this has been tested with PostgreSQL 8.0.x and 7.4.x. Older
versions don't have a -O option for the createdb, so if you are
using that version you'll have to change ownership of the DB later.
UPGRADING
=========
If you already have a previous version of RoundCube installed,
SQL/mysql.initial.sql
@@ -11,7 +11,7 @@
CREATE TABLE `cache` (
  `cache_id` int(10) unsigned NOT NULL auto_increment,
  `user_id` int(10) unsigned NOT NULL default '0',
  `session_id` varchar(32) default NULL,
  `session_id` varchar(40) default NULL,
  `cache_key` varchar(128) NOT NULL default '',
  `created` datetime NOT NULL default '0000-00-00 00:00:00',
  `data` longtext NOT NULL,
@@ -31,7 +31,7 @@
  `contact_id` int(10) unsigned NOT NULL auto_increment,
  `user_id` int(10) unsigned NOT NULL default '0',
  `changed` datetime NOT NULL default '0000-00-00 00:00:00',
  `del` enum('0','1') NOT NULL default '0',
  `del` tinyint(1) NOT NULL default '0',
  `name` varchar(128) NOT NULL default '',
  `email` varchar(128) NOT NULL default '',
  `firstname` varchar(128) NOT NULL default '',
@@ -50,8 +50,8 @@
CREATE TABLE `identities` (
  `identity_id` int(10) unsigned NOT NULL auto_increment,
  `user_id` int(10) unsigned NOT NULL default '0',
  `del` enum('0','1') NOT NULL default '0',
  `default` enum('0','1') NOT NULL default '0',
  `del` tinyint(1) NOT NULL default '0',
  `standard` tinyint(1) NOT NULL default '0',
  `name` varchar(128) NOT NULL default '',
  `organization` varchar(128) NOT NULL default '',
  `email` varchar(128) NOT NULL default '',
@@ -94,3 +94,33 @@
  `preferences` text NOT NULL,
  PRIMARY KEY  (`user_id`)
) TYPE=MyISAM;
-- --------------------------------------------------------
--
-- Table structure for table `messages`
--
CREATE TABLE `messages` (
  `message_id` int(11) unsigned NOT NULL auto_increment,
  `user_id` int(11) unsigned NOT NULL default '0',
  `del` tinyint(1) NOT NULL default '0',
  `cache_key` varchar(128) NOT NULL default '',
  `idx` int(11) unsigned NOT NULL default '0',
  `uid` int(11) unsigned NOT NULL default '0',
  `subject` varchar(255) NOT NULL default '',
  `from` varchar(255) NOT NULL default '',
  `to` varchar(255) NOT NULL default '',
  `cc` varchar(255) NOT NULL default '',
  `date` datetime NOT NULL default '0000-00-00 00:00:00',
  `size` int(11) unsigned NOT NULL default '0',
  `headers` text NOT NULL,
  `body` longtext,
  PRIMARY KEY  (`message_id`),
  KEY `user_id` (`user_id`),
  KEY `cache_key` (`cache_key`),
  KEY `idx` (`idx`),
  KEY `uid` (`uid`)
) TYPE=MyISAM;
SQL/mysql.update.sql
@@ -11,4 +11,39 @@
-- Version 0.1-20051021
ALTER TABLE `session` CHANGE `sess_id` `sess_id` VARCHAR(40) NOT NULL;
ALTER TABLE `contacts` CHANGE `del` `del` TINYINT(1) NOT NULL;
ALTER TABLE `contacts` ADD `changed` DATETIME NOT NULL AFTER `user_id`;
UPDATE `contacts`  SET `del`=0 WHERE `del`=1;
UPDATE `contacts`  SET `del`=1 WHERE `del`=2;
ALTER TABLE `identities` CHANGE `default` `standard` TINYINT(1) NOT NULL;
ALTER TABLE `identities` CHANGE `del` `del` TINYINT(1) NOT NULL;
UPDATE `identities`  SET `del`=0 WHERE `del`=1;
UPDATE `identities`  SET `del`=1 WHERE `del`=2;
UPDATE `identities`  SET `standard`=0 WHERE `standard`=1;
UPDATE `identities`  SET `standard`=1 WHERE `standard`=2;
CREATE TABLE `messages` (
  `message_id` int(11) unsigned NOT NULL auto_increment,
  `user_id` int(11) unsigned NOT NULL default '0',
  `del` tinyint(1) NOT NULL default '0',
  `cache_key` varchar(128) NOT NULL default '',
  `idx` int(11) unsigned NOT NULL default '0',
  `uid` int(11) unsigned NOT NULL default '0',
  `subject` varchar(255) NOT NULL default '',
  `from` varchar(255) NOT NULL default '',
  `to` varchar(255) NOT NULL default '',
  `cc` varchar(255) NOT NULL default '',
  `date` datetime NOT NULL default '0000-00-00 00:00:00',
  `size` int(11) unsigned NOT NULL default '0',
  `headers` text NOT NULL,
  `body` longtext,
  PRIMARY KEY  (`message_id`),
  KEY `user_id` (`user_id`),
  KEY `cache_key` (`cache_key`),
  KEY `idx` (`idx`),
  KEY `uid` (`uid`)
) TYPE=MyISAM;
SQL/postgres.initial.sql
@@ -1,146 +1,5 @@
--
-- PostgreSQL database dump
--
SET client_encoding = 'UNICODE';
SET check_function_bodies = false;
SET search_path = public, pg_catalog;
ALTER TABLE ONLY public.identities DROP CONSTRAINT "$1";
ALTER TABLE ONLY public.contacts DROP CONSTRAINT "$1";
ALTER TABLE ONLY public."cache" DROP CONSTRAINT "$2";
ALTER TABLE ONLY public."cache" DROP CONSTRAINT "$1";
ALTER TABLE ONLY public.users DROP CONSTRAINT users_pkey;
ALTER TABLE ONLY public."session" DROP CONSTRAINT session_pkey;
ALTER TABLE ONLY public.identities DROP CONSTRAINT identities_pkey;
ALTER TABLE ONLY public.contacts DROP CONSTRAINT contacts_pkey;
ALTER TABLE ONLY public."cache" DROP CONSTRAINT cache_pkey;
DROP TABLE public.users;
DROP TABLE public."session";
DROP TABLE public.identities;
DROP TABLE public.contacts;
DROP TABLE public."cache";
DROP SEQUENCE public.user_ids;
DROP SEQUENCE public.identity_ids;
DROP SEQUENCE public.contact_ids;
DROP SEQUENCE public.cache_ids;
--
-- TOC entry 4 (OID 15282470)
-- Name: cache_ids; Type: SEQUENCE; Schema: public; Owner: postgres
--
CREATE SEQUENCE cache_ids
    INCREMENT BY 1
    NO MAXVALUE
    NO MINVALUE
    CACHE 1;
--
-- TOC entry 5 (OID 15282472)
-- Name: contact_ids; Type: SEQUENCE; Schema: public; Owner: postgres
--
CREATE SEQUENCE contact_ids
    START WITH 1
    INCREMENT BY 1
    NO MAXVALUE
    NO MINVALUE
    CACHE 1;
--
-- TOC entry 6 (OID 15282474)
-- Name: identity_ids; Type: SEQUENCE; Schema: public; Owner: postgres
--
CREATE SEQUENCE identity_ids
    START WITH 1
    INCREMENT BY 1
    NO MAXVALUE
    NO MINVALUE
    CACHE 1;
--
-- TOC entry 7 (OID 15282476)
-- Name: user_ids; Type: SEQUENCE; Schema: public; Owner: postgres
--
CREATE SEQUENCE user_ids
    INCREMENT BY 1
    NO MAXVALUE
    NO MINVALUE
    CACHE 1;
--
-- TOC entry 8 (OID 15282478)
-- Name: cache; Type: TABLE; Schema: public; Owner: postgres
--
CREATE TABLE "cache" (
    cache_id integer DEFAULT nextval('cache_ids'::text) NOT NULL,
    user_id integer DEFAULT 0 NOT NULL,
    session_id character varying(32),
    cache_key character varying(128) DEFAULT ''::character varying NOT NULL,
    created timestamp with time zone DEFAULT now() NOT NULL,
    data text NOT NULL
);
--
-- TOC entry 10 (OID 15282486)
-- Name: contacts; Type: TABLE; Schema: public; Owner: postgres
--
CREATE TABLE contacts (
    contact_id integer DEFAULT nextval('contact_ids'::text) NOT NULL,
    user_id integer DEFAULT 0 NOT NULL,
    del boolean DEFAULT false NOT NULL,
    name character varying(128) DEFAULT ''::character varying NOT NULL,
    email character varying(128) DEFAULT ''::character varying NOT NULL,
    firstname character varying(128) DEFAULT ''::character varying NOT NULL,
    surname character varying(128) DEFAULT ''::character varying NOT NULL,
    vcard text NOT NULL
);
--
-- TOC entry 11 (OID 15282494)
-- Name: identities; Type: TABLE; Schema: public; Owner: postgres
--
CREATE TABLE identities (
    identity_id integer DEFAULT nextval('identity_ids'::text) NOT NULL,
    user_id integer DEFAULT 0 NOT NULL,
    del boolean DEFAULT false NOT NULL,
    "default" boolean DEFAULT false NOT NULL,
    name character varying(128) NOT NULL,
    organization character varying(128),
    email character varying(128) NOT NULL,
    "reply-to" character varying(128),
    bcc character varying(128),
    signature text
);
--
-- TOC entry 12 (OID 15282503)
-- Name: session; Type: TABLE; Schema: public; Owner: postgres
--
CREATE TABLE "session" (
    sess_id character varying(32) DEFAULT ''::character varying NOT NULL,
    created timestamp with time zone DEFAULT now() NOT NULL,
    changed timestamp with time zone DEFAULT now() NOT NULL,
    ip character varying(16) NOT NULL,
    vars text NOT NULL
);
--
-- TOC entry 13 (OID 15282510)
-- Table "users"
-- Name: users; Type: TABLE; Schema: public; Owner: postgres
--
@@ -156,100 +15,209 @@
);
--
-- TOC entry 14 (OID 15282518)
-- Name: cache_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
-- Table "session"
-- Name: session; Type: TABLE; Schema: public; Owner: postgres
--
CREATE TABLE "session" (
    sess_id character varying(40) DEFAULT ''::character varying NOT NULL,
    created timestamp with time zone DEFAULT now() NOT NULL,
    changed timestamp with time zone DEFAULT now() NOT NULL,
    ip character varying(16) NOT NULL,
    vars text NOT NULL
);
--
-- Table "identities"
-- Name: identities; Type: TABLE; Schema: public; Owner: postgres
--
CREATE TABLE identities (
    identity_id integer DEFAULT nextval('identity_ids'::text) NOT NULL,
    user_id integer DEFAULT 0 NOT NULL,
    del integer DEFAULT 0 NOT NULL,
    standard integer DEFAULT 0 NOT NULL,
    name character varying(128) NOT NULL,
    organization character varying(128),
    email character varying(128) NOT NULL,
    "reply-to" character varying(128),
    bcc character varying(128),
    signature text
);
--
-- Table "contacts"
-- Name: contacts; Type: TABLE; Schema: public; Owner: postgres
--
CREATE TABLE contacts (
    contact_id integer DEFAULT nextval('contact_ids'::text) NOT NULL,
    user_id integer DEFAULT 0 NOT NULL,
    changed timestamp with time zone DEFAULT now() NOT NULL,
    del integer DEFAULT 0 NOT NULL,
    name character varying(128) DEFAULT ''::character varying NOT NULL,
    email character varying(128) DEFAULT ''::character varying NOT NULL,
    firstname character varying(128) DEFAULT ''::character varying NOT NULL,
    surname character varying(128) DEFAULT ''::character varying NOT NULL,
    vcard text
);
--
-- Table "cache"
-- Name: cache; Type: TABLE; Schema: public; Owner: postgres
--
CREATE TABLE "cache" (
    cache_id integer DEFAULT nextval('cache_ids'::text) NOT NULL,
    user_id integer DEFAULT 0 NOT NULL,
    session_id character varying(40),
    cache_key character varying(128) DEFAULT ''::character varying NOT NULL,
    created timestamp with time zone DEFAULT now() NOT NULL,
    data text NOT NULL
);
--
-- Table "messages"
-- Name: messages; Type: TABLE; Schema: public; Owner: postgres
--
CREATE TABLE "messages" (
    message_id integer DEFAULT nextval('message_ids'::text) NOT NULL,
    user_id integer DEFAULT 0 NOT NULL,
    del integer DEFAULT 0 NOT NULL,
    cache_key character varying(128) DEFAULT ''::character varying NOT NULL,
    idx integer DEFAULT 0 NOT NULL,
    uid integer DEFAULT 0 NOT NULL,
    subject character varying(128) DEFAULT ''::character varying NOT NULL,
    "from" character varying(128) DEFAULT ''::character varying NOT NULL,
    "to" character varying(128) DEFAULT ''::character varying NOT NULL,
    cc character varying(128) DEFAULT ''::character varying NOT NULL,
    date timestamp with time zone NOT NULL,
    size integer DEFAULT 0 NOT NULL,
    headers text NOT NULL,
    body text
);
--
-- Add primary keys
--
ALTER TABLE ONLY "cache"
    ADD CONSTRAINT cache_pkey PRIMARY KEY (cache_id);
--
-- TOC entry 15 (OID 15282520)
-- Name: contacts_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY contacts
ALTER TABLE ONLY "contacts"
    ADD CONSTRAINT contacts_pkey PRIMARY KEY (contact_id);
--
-- TOC entry 16 (OID 15282522)
-- Name: identities_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY identities
    ADD CONSTRAINT identities_pkey PRIMARY KEY (identity_id);
--
-- TOC entry 17 (OID 15282524)
-- Name: session_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY "session"
    ADD CONSTRAINT session_pkey PRIMARY KEY (sess_id);
--
-- TOC entry 18 (OID 15282526)
-- Name: users_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY users
ALTER TABLE ONLY "users"
    ADD CONSTRAINT users_pkey PRIMARY KEY (user_id);
ALTER TABLE ONLY "messages"
    ADD CONSTRAINT messages_pkey PRIMARY KEY (message_id);
--
-- TOC entry 19 (OID 15282528)
-- Name: $1; Type: FK CONSTRAINT; Schema: public; Owner: postgres
-- Reference keys
--
ALTER TABLE ONLY "cache"
    ADD CONSTRAINT "$1" FOREIGN KEY (user_id) REFERENCES users(user_id);
--
-- TOC entry 20 (OID 15282532)
-- Name: $2; Type: FK CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY "cache"
    ADD CONSTRAINT "$2" FOREIGN KEY (session_id) REFERENCES "session"(sess_id);
--
-- TOC entry 21 (OID 15282536)
-- Name: $1; Type: FK CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY "contacts"
    ADD CONSTRAINT "$1" FOREIGN KEY (user_id) REFERENCES users(user_id);
ALTER TABLE ONLY contacts
ALTER TABLE ONLY "identities"
    ADD CONSTRAINT "$1" FOREIGN KEY (user_id) REFERENCES users(user_id);
ALTER TABLE ONLY "messages"
    ADD CONSTRAINT "$1" FOREIGN KEY (user_id) REFERENCES users(user_id);
--
-- TOC entry 22 (OID 15282540)
-- Name: $1; Type: FK CONSTRAINT; Schema: public; Owner: postgres
-- Sequence "cache_ids"
-- Name: cache_ids; Type: SEQUENCE; Schema: public; Owner: postgres
--
ALTER TABLE ONLY identities
    ADD CONSTRAINT "$1" FOREIGN KEY (user_id) REFERENCES users(user_id);
CREATE SEQUENCE cache_ids
    INCREMENT BY 1
    NO MAXVALUE
    NO MINVALUE
    CACHE 1;
SET SESSION AUTHORIZATION 'postgres';
--
-- TOC entry 3 (OID 15282469)
-- Name: SCHEMA public; Type: COMMENT; Schema: -; Owner: postgres
-- Sequence "contact_ids"
-- Name: contact_ids; Type: SEQUENCE; Schema: public; Owner: postgres
--
COMMENT ON SCHEMA public IS 'Standard public schema';
CREATE SEQUENCE contact_ids
    START WITH 1
    INCREMENT BY 1
    NO MAXVALUE
    NO MINVALUE
    CACHE 1;
SET SESSION AUTHORIZATION 'postgres';
--
-- TOC entry 9 (OID 15282478)
-- Name: TABLE "cache"; Type: COMMENT; Schema: public; Owner: postgres
--
-- Sequence "identity_ids"
-- Name: identity_ids; Type: SEQUENCE; Schema: public; Owner: postgres
--
CREATE SEQUENCE identity_ids
    START WITH 1
    INCREMENT BY 1
    NO MAXVALUE
    NO MINVALUE
    CACHE 1;
--
-- Sequence "user_ids"
-- Name: user_ids; Type: SEQUENCE; Schema: public; Owner: postgres
--
CREATE SEQUENCE user_ids
    INCREMENT BY 1
    NO MAXVALUE
    NO MINVALUE
    CACHE 1;
--
-- Sequence "message_ids"
-- Name: message_ids; Type: SEQUENCE; Schema: public; Owner: postgres
--
CREATE SEQUENCE message_ids
    INCREMENT BY 1
    NO MAXVALUE
    NO MINVALUE
    CACHE 1;
SQL/sqlite.initial.sql
@@ -11,7 +11,7 @@
CREATE TABLE cache (
  cache_id integer NOT NULL PRIMARY KEY,
  user_id integer NOT NULL default 0,
  session_id varchar(32) default NULL,
  session_id varchar(40) default NULL,
  cache_key varchar(128) NOT NULL default '',
  created datetime NOT NULL default '0000-00-00 00:00:00',
  data longtext NOT NULL
@@ -20,6 +20,7 @@
CREATE INDEX ix_cache_user_id ON cache(user_id);
CREATE INDEX ix_cache_cache_key ON cache(cache_key);
CREATE INDEX ix_cache_session_id ON cache(session_id);
-- --------------------------------------------------------
@@ -30,7 +31,8 @@
CREATE TABLE contacts (
  contact_id integer NOT NULL PRIMARY KEY,
  user_id integer NOT NULL default '0',
  del integer NOT NULL default '0',
  created datetime NOT NULL default '0000-00-00 00:00:00',
  del tinyint NOT NULL default '0',
  name varchar(128) NOT NULL default '',
  email varchar(128) NOT NULL default '',
  firstname varchar(128) NOT NULL default '',
@@ -49,10 +51,10 @@
CREATE TABLE identities (
  identity_id integer NOT NULL PRIMARY KEY,
  user_id integer NOT NULL default '0',
  del integer NOT NULL default '0',
  "default" integer NOT NULL default '0',
  del tinyint NOT NULL default '0',
  standard tinyint NOT NULL default '0',
  name varchar(128) NOT NULL default '',
  organization varchar(128) NOT NULL default '',
  organization varchar(128) default '',
  email varchar(128) NOT NULL default '',
  "reply-to" varchar(128) NOT NULL default '',
  bcc varchar(128) NOT NULL default '',
@@ -78,3 +80,45 @@
  language varchar(5) NOT NULL default 'en',
  preferences text NOT NULL default ''
);
-- --------------------------------------------------------
--
-- Table structure for table session
--
CREATE TABLE session (
  sess_id varchar(40) NOT NULL PRIMARY KEY,
  created datetime NOT NULL default '0000-00-00 00:00:00',
  changed datetime NOT NULL default '0000-00-00 00:00:00',
  ip varchar(15) NOT NULL default '',
  vars text NOT NULL
);
-- --------------------------------------------------------
--
-- Table structure for table messages
--
CREATE TABLE messages (
  message_id integer NOT NULL PRIMARY KEY,
  user_id integer NOT NULL default '0',
  del tinyint NOT NULL default '0',
  cache_key varchar(128) NOT NULL default '',
  idx integer NOT NULL default '0',
  uid integer NOT NULL default '0',
  subject varchar(255) NOT NULL default '',
  "from" varchar(255) NOT NULL default '',
  "to" varchar(255) NOT NULL default '',
  cc varchar(255) NOT NULL default '',
  date datetime NOT NULL default '0000-00-00 00:00:00',
  size integer NOT NULL default '0',
  headers text NOT NULL,
  body text
);
CREATE INDEX ix_messages_user_id ON messages(user_id);
CREATE INDEX ix_messages_cache_key ON messages(cache_key);
UPGRADING
@@ -10,7 +10,7 @@
- replace index.php
- replace all files in folder /program/
- replace all files in folder /skins/default/
- rund SQL queries in order to update the database
- run all commands in SQL/*.update.sql or re-initalize database with *.initial.sql
- add these line to /config/main.inc.php
  $rcmail_config['trash_mbox'] = 'Trash';
  $rcmail_config['default_imap_folders'] = array('INBOX', 'Drafts', 'Sent', 'Junk', 'Trash');
@@ -22,9 +22,12 @@
  $rcmail_config['message_sort_col'] = 'date';
  $rcmail_config['message_sort_order'] = 'DESC';
  $rcmail_config['log_dir'] = 'logs/';
  $rcmail_config['temp_dir'] = 'temp/';
- replace database properties (db_type, db_host, db_user, db_pass, $d_name)
  in /config/db.inc.php with the following line:
  $rcmail_config['db_dsnw'] = 'mysql://roundcube:pass@localhost/roundcubemail';
- add these lines to /config/db.inc.php
  $rcmail_config['db_max_length'] = 512000;
from version 0.1-20050820
@@ -32,7 +35,7 @@
- replace index.php
- replace all files in folder /program/
- replace all files in folder /skins/default/
- rund SQL queries in order to update the database
- run all commands in SQL/*.update.sql or re-initalize database with *.initial.sql
- add these line to /config/main.inc.php
  $rcmail_config['prettydate'] = TRUE;
  $rcmail_config['smtp_port'] = 25;
@@ -41,9 +44,12 @@
  $rcmail_config['message_sort_col'] = 'date';
  $rcmail_config['message_sort_order'] = 'DESC';
  $rcmail_config['log_dir'] = 'logs/';
  $rcmail_config['temp_dir'] = 'temp/';
- replace database properties (db_type, db_host, db_user, db_pass, $d_name)
  in /config/db.inc.php with the following line:
  $rcmail_config['db_dsnw'] = 'mysql://roundcube:pass@localhost/roundcubemail';
- add these lines to /config/db.inc.php
  $rcmail_config['db_max_length'] = 512000;
from version 0.1-20051007
@@ -51,20 +57,38 @@
- replace index.php
- replace all files in folder /program/
- replace all files in folder /skins/default/
- run all commands in SQL/*.update.sql or re-initalize database with *.initial.sql
- add these lines to /config/main.inc.php
  $rcmail_config['smtp_auth_type'] = '';  // if you need to specify an auth method for SMTP
  $rcmail_config['session_lifetime'] = 20;  // to specify the session lifetime in minutes
  $rcmail_config['message_sort_col'] = 'date';
  $rcmail_config['message_sort_order'] = 'DESC';
  $rcmail_config['log_dir'] = 'logs/';
  $rcmail_config['temp_dir'] = 'temp/';
- add these lines to /config/db.inc.php
  $rcmail_config['db_max_length'] = 512000;
  $rcmail_config['db_sequence_user_ids'] = 'user_ids';
  $rcmail_config['db_sequence_identity_ids'] = 'identity_ids';
  $rcmail_config['db_sequence_contact_ids'] = 'contact_ids';
  $rcmail_config['db_sequence_cache_ids'] = 'cache_ids';
  $rcmail_config['db_sequence_message_ids'] = 'message_ids';
from version 0.1-20051021
----------------------------------------
- replace index.php
- replace all files in folder /program/
- replace all files in folder /skins/default/
- run all commands in SQL/*.update.sql or re-initalize database with *.initial.sql
- add these lines to /config/main.inc.php
  $rcmail_config['message_sort_col'] = 'date';
  $rcmail_config['message_sort_order'] = 'DESC';
  $rcmail_config['log_dir'] = 'logs/';
  $rcmail_config['temp_dir'] = 'temp/';
- add these lines to /config/db.inc.php
  $rcmail_config['db_max_length'] = 512000;
  $rcmail_config['db_sequence_user_ids'] = 'user_ids';
  $rcmail_config['db_sequence_identity_ids'] = 'identity_ids';
  $rcmail_config['db_sequence_contact_ids'] = 'contact_ids';
  $rcmail_config['db_sequence_cache_ids'] = 'cache_ids';
  $rcmail_config['db_sequence_message_ids'] = 'message_ids';
config/db.inc.php.dist
@@ -19,6 +19,7 @@
// currentyl suported db_providers: mysql, sqlite
$rcmail_config['db_dsnw'] = 'mysql://roundcube:pass@localhost/roundcubemail';
// postgres example: 'pgsql://roundcube:pass@localhost/roundcubemail';
// sqlite example: 'sqlite://./sqlite.db?mode=0646';
// PEAR database DSN for read only operations (if empty write database will be used)
@@ -27,6 +28,9 @@
// database backend to use (only db or mdb2 are supported)
$rcmail_config['db_backend'] = 'db';
// maximum length of a query in bytes
$rcmail_config['db_max_length'] = 512000;  // 500K
// you can define specific table names used to store webmail data
$rcmail_config['db_table_users'] = 'users';
@@ -39,6 +43,20 @@
$rcmail_config['db_table_cache'] = 'cache';
$rcmail_config['db_table_messages'] = 'messages';
// you can define specific sequence names used in PostgreSQL
$rcmail_config['db_sequence_users'] = 'user_ids';
$rcmail_config['db_sequence_identities'] = 'identity_ids';
$rcmail_config['db_sequence_contacts'] = 'contact_ids';
$rcmail_config['db_sequence_cache'] = 'cache_ids';
$rcmail_config['db_sequence_messages'] = 'message_ids';
// end db config file
?>
config/main.inc.php.dist
@@ -88,7 +88,7 @@
$rcmail_config['date_long'] = 'd.m.Y H:i';
// add this user-agent to message headers when sending
$rcmail_config['useragent'] = 'RoundCube Webmail/0.1-20051021';
$rcmail_config['useragent'] = 'RoundCube Webmail/0.1b';
// only list folders within this path
$rcmail_config['imap_root'] = '';
index.php
@@ -62,7 +62,7 @@
// increase maximum execution time for php scripts
// (does not work in safe mode)
@set_time_limit('120');
@set_time_limit(120);
// include base files
require_once('include/rcube_shared.inc');
@@ -199,6 +199,13 @@
  }
// handle keep-alive signal
if ($_action=='keep-alive')
  {
  rcube_remote_response('');
  exit;
  }
// include task specific files
if ($_task=='mail')
@@ -286,14 +293,6 @@
  if ($_action=='folders' || $_action=='subscribe' || $_action=='unsubscribe' || $_action=='create-folder' || $_action=='delete-folder')
    include('program/steps/settings/manage_folders.inc');
  }
// handle keep-alive signal
if ($_action=='keep-alive')
  {
  rcube_remote_response('');
  exit;
  }
program/include/cache.inc
@@ -43,8 +43,8 @@
  return $data;
  }
function rcube_write_cache($key, $data, $session_cache=FALSE)
  {
  global $DB, $CACHE_KEYS, $sess_id;
@@ -89,7 +89,6 @@
                $data);
    }
  }
function rcube_clear_cache($key)
program/include/main.inc
@@ -69,7 +69,7 @@
  // we can use the database for storing session data
  // session queries do not work with MDB2
  if ($CONFIG['db_backend']!='mdb2' && is_object($DB) && $DB->db_provider!='sqlite')
  if ($CONFIG['db_backend']!='mdb2' && is_object($DB) /* && $DB->db_provider!='sqlite' */)
    include_once('include/session.inc');
@@ -143,9 +143,9 @@
// create IMAP object and connect to server
function rcmail_imap_init($connect=FALSE)
  {
  global $CONFIG, $IMAP;
  global $CONFIG, $DB, $IMAP;
  $IMAP = new rcube_imap();
  $IMAP = new rcube_imap($DB);
  // connect with stored session data
  if ($connect)
@@ -227,6 +227,22 @@
  }
// return correct name for a specific database sequence
// (used for Postres only)
function get_sequence_name($sequence)
  {
  global $CONFIG;
  // return table name if configured
  $config_key = 'db_sequence_'.$sequence;
  if (strlen($CONFIG[$config_key]))
    return $CONFIG[$config_key];
  return $table;
  }
// init output object for GUI and add common scripts
function load_gui()
@@ -380,15 +396,15 @@
              $host,
              $_SESSION['user_lang']);
  if ($user_id = $DB->insert_id('user_ids'))
  if ($user_id = $DB->insert_id(get_sequence_name('users')))
    {
    $user_email = strstr($user, '@') ? $user : sprintf('%s@%s', $user, $host);
    $user_name = $user!=$user_email ? $user : '';
    
    // also create a new identity record
    $DB->query("INSERT INTO ".get_table_name('identities')."
                (user_id, `default`, name, email)
                VALUES (?, '1', ?, ?)",
                (user_id, del, standard, name, email)
                VALUES (?, 0, 1, ?, ?)",
                $user_id,
                $user_name,
                $user_email);
@@ -479,7 +495,6 @@
function rcube_remote_response($js_code)
  {
  send_nocacheing_headers();
  //header('Content-Type: text/javascript');
  header('Content-Type: application/x-javascript');
  print '/** remote response ['.date('d/M/Y h:i:s O')."] **/\n";
@@ -527,6 +542,117 @@
                                $JS_OBJECT_NAME,
                                $name,
                                rep_specialchars_output(rcube_label($name), 'js')));  
  }
// remove temp files of a session
function rcmail_clear_session_temp($sess_id)
  {
  global $CONFIG;
  $temp_dir = $CONFIG['temp_dir'].(!eregi('\/$', $CONFIG['temp_dir']) ? '/' : '');
  $cache_dir = $temp_dir.$sess_id;
  if (is_dir($cache_dir))
    {
    clear_directory($cache_dir);
    rmdir($cache_dir);
    }
  }
// replace specials characters to a specific encoding type
function rep_specialchars_output($str, $enctype='', $mode='', $newlines=TRUE)
  {
  global $OUTPUT_TYPE, $CHARSET;
  static $html_encode_arr, $js_rep_table, $rtf_rep_table, $xml_rep_table;
  if (!$enctype)
    $enctype = $GLOBALS['OUTPUT_TYPE'];
  // convert nbsps back to normal spaces if not html
  if ($enctype!='html')
    $str = str_replace(chr(160), ' ', $str);
  // encode for plaintext
  if ($enctype=='text')
    return str_replace("\r\n", "\n", $mode=='remove' ? strip_tags($str) : $str);
  // encode for HTML output
  if ($enctype=='html')
    {
    if (!$html_encode_arr)
      {
      if ($CHARSET=='ISO-8859-1')
        {
        $html_encode_arr = get_html_translation_table(HTML_ENTITIES);
        $html_encode_arr[chr(128)] = '&euro;';
        }
      else
        $html_encode_arr = get_html_translation_table(HTML_SPECIALCHARS);
      unset($html_encode_arr['?']);
      unset($html_encode_arr['&']);
      }
    $ltpos = strpos($str, '<');
    $encode_arr = $html_encode_arr;
    // don't replace quotes and html tags
    if (($mode=='show' || $mode=='') && $ltpos!==false && strpos($str, '>', $ltpos)!==false)
      {
      unset($encode_arr['"']);
      unset($encode_arr['<']);
      unset($encode_arr['>']);
      }
    else if ($mode=='remove')
      $str = strip_tags($str);
    $out = strtr($str, $encode_arr);
    return $newlines ? nl2br($out) : $out;
    }
  if ($enctype=='url')
    return rawurlencode($str);
  // if the replace tables for RTF, XML and JS are not yet defined
  if (!$js_rep_table)
    {
    $js_rep_table = $rtf_rep_table = $xml_rep_table = array();
    for ($c=160; $c<256; $c++)  // can be increased to support more charsets
      {
      $hex = dechex($c);
      $rtf_rep_table[Chr($c)] = "\\'$hex";
      $xml_rep_table[Chr($c)] = "&#$c;";
      if ($CHARSET=='ISO-8859-1')
        $js_rep_table[Chr($c)] = sprintf("\u%s%s", str_repeat('0', 4-strlen($hex)), $hex);
      }
    $js_rep_table['"'] = sprintf("\u%s%s", str_repeat('0', 4-strlen(dechex(34))), dechex(34));
    $xml_rep_table['"'] = '&quot;';
    }
  // encode for RTF
  if ($enctype=='xml')
    return strtr($str, $xml_rep_table);
  // encode for javascript use
  if ($enctype=='js')
    return preg_replace(array("/\r\n/", '/"/', "/([^\\\])'/"), array('\n', '\"', "$1\'"), strtr($str, $js_rep_table));
  // encode for RTF
  if ($enctype=='rtf')
    return preg_replace("/\r\n/", "\par ", strtr($str, $rtf_rep_table));
  // no encoding given -> return original string
  return $str;
  }
@@ -653,104 +779,53 @@
    case 'object':
      $object = strtolower($attrib['name']);
      $object_handlers = array(
        // MAIL
        'mailboxlist' => 'rcmail_mailbox_list',
        'messages' => 'rcmail_message_list',
        'messagecountdisplay' => 'rcmail_messagecount_display',
        'messageheaders' => 'rcmail_message_headers',
        'messagebody' => 'rcmail_message_body',
        'messageattachments' => 'rcmail_message_attachments',
        'blockedobjects' => 'rcmail_remote_objects_msg',
        'messagecontentframe' => 'rcmail_messagecontent_frame',
        'messagepartframe' => 'rcmail_message_part_frame',
        'messagepartcontrols' => 'rcmail_message_part_controls',
        'composeheaders' => 'rcmail_compose_headers',
        'composesubject' => 'rcmail_compose_subject',
        'composebody' => 'rcmail_compose_body',
        'composeattachmentlist' => 'rcmail_compose_attachment_list',
        'composeattachmentform' => 'rcmail_compose_attachment_form',
        'composeattachment' => 'rcmail_compose_attachment_field',
        'priorityselector' => 'rcmail_priority_selector',
        'charsetselector' => 'rcmail_charset_selector',
        // ADDRESS BOOK
        'addresslist' => 'rcmail_contacts_list',
        'addressframe' => 'rcmail_contact_frame',
        'recordscountdisplay' => 'rcmail_rowcount_display',
        'contactdetails' => 'rcmail_contact_details',
        'contacteditform' => 'rcmail_contact_editform',
        // USER SETTINGS
        'userprefs' => 'rcmail_user_prefs_form',
        'itentitieslist' => 'rcmail_identities_list',
        'identityframe' => 'rcmail_identity_frame',
        'identityform' => 'rcube_identity_form',
        'foldersubscription' => 'rcube_subscription_form',
        'createfolder' => 'rcube_create_folder_form',
        'composebody' => 'rcmail_compose_body'
      );
      if ($object=='loginform')
        return rcmail_login_form($attrib);
      else if ($object=='message')
        return rcmail_message_container($attrib);
      // MAIL
      else if ($object=='mailboxlist' && function_exists('rcmail_mailbox_list'))
        return rcmail_mailbox_list($attrib);
      else if ($object=='messages' && function_exists('rcmail_message_list'))
        return rcmail_message_list($attrib);
      else if ($object=='messagecountdisplay' && function_exists('rcmail_messagecount_display'))
        return rcmail_messagecount_display($attrib);
      else if ($object=='messageheaders' && function_exists('rcmail_message_headers'))
        return rcmail_message_headers($attrib);
      else if ($object=='messageattachments' && function_exists('rcmail_message_attachments'))
        return rcmail_message_attachments($attrib);
      else if ($object=='messagebody' && function_exists('rcmail_message_body'))
        return rcmail_message_body($attrib);
      else if ($object=='blockedobjects' && function_exists('rcmail_remote_objects_msg'))
        return rcmail_remote_objects_msg($attrib);
      else if ($object=='messagecontentframe' && function_exists('rcmail_messagecontent_frame'))
        return rcmail_messagecontent_frame($attrib);
      else if ($object=='messagepartframe' && function_exists('rcmail_message_part_frame'))
        return rcmail_message_part_frame($attrib);
      else if ($object=='messagepartcontrols' && function_exists('rcmail_message_part_controls'))
        return rcmail_message_part_controls($attrib);
      else if ($object=='composeheaders' && function_exists('rcmail_compose_headers'))
        return rcmail_compose_headers($attrib);
      else if ($object=='composesubject' && function_exists('rcmail_compose_subject'))
        return rcmail_compose_subject($attrib);
      else if ($object=='composebody' && function_exists('rcmail_compose_body'))
        return rcmail_compose_body($attrib);
      else if ($object=='composeattachmentlist' && function_exists('rcmail_compose_attachment_list'))
        return rcmail_compose_attachment_list($attrib);
      else if ($object=='composeattachmentform' && function_exists('rcmail_compose_attachment_form'))
        return rcmail_compose_attachment_form($attrib);
      else if ($object=='composeattachment' && function_exists('rcmail_compose_attachment_field'))
        return rcmail_compose_attachment_field($attrib);
      else if ($object=='priorityselector' && function_exists('rcmail_priority_selector'))
        return rcmail_priority_selector($attrib);
      else if ($object=='priorityselector' && function_exists('rcmail_priority_selector'))
        return rcmail_priority_selector($attrib);
      // ADDRESS BOOK
      else if ($object=='addresslist' && function_exists('rcmail_contacts_list'))
        return rcmail_contacts_list($attrib);
      else if ($object=='addressframe' && function_exists('rcmail_contact_frame'))
        return rcmail_contact_frame($attrib);
      else if ($object=='recordscountdisplay' && function_exists('rcmail_rowcount_display'))
        return rcmail_rowcount_display($attrib);
      else if ($object=='contactdetails' && function_exists('rcmail_contact_details'))
        return rcmail_contact_details($attrib);
      else if ($object=='contacteditform' && function_exists('rcmail_contact_editform'))
        return rcmail_contact_editform($attrib);
      // USER SETTINGS
      else if ($object=='userprefs' && function_exists('rcmail_user_prefs_form'))
        return rcmail_user_prefs_form($attrib);
      else if ($object=='itentitieslist' && function_exists('rcmail_identities_list'))
        return rcmail_identities_list($attrib);
      else if ($object=='identityframe' && function_exists('rcmail_identity_frame'))
        return rcmail_identity_frame($attrib);
      else if ($object=='identityform' && function_exists('rcube_identity_form'))
        return rcube_identity_form($attrib);
      else if ($object=='foldersubscription' && function_exists('rcube_subscription_form'))
        return rcube_subscription_form($attrib);
      else if ($object=='createfolder' && function_exists('rcube_create_folder_form'))
        return rcube_create_folder_form($attrib);
      // execute object handler function
      else if ($object_handlers[$object] && function_exists($object_handlers[$object]))
        return call_user_func($object_handlers[$object], $attrib);
      else if ($object=='pagetitle')
        {
@@ -878,7 +953,7 @@
  // generate image tag
  if ($attrib['type']=='image')
    {
    $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id', 'width', 'height', 'border', 'hspace', 'vspace', 'alt'));
    $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id', 'width', 'height', 'border', 'hspace', 'vspace', 'align', 'alt'));
    $img_tag = sprintf('<img src="%%s"%s />', $attrib_str);
    $btn_content = sprintf($img_tag, $skin_path.$attrib['image']);
    if ($attrib['label'])
@@ -1161,4 +1236,39 @@
  }
function rcmail_charset_selector($attrib)
  {
  // pass the following attributes to the form class
  $field_attrib = array('name' => '_charset');
  foreach ($attrib as $attr => $value)
    if (in_array($attr, array('id', 'class', 'style', 'size', 'tabindex')))
      $field_attrib[$attr] = $value;
  $charsets = array(
    'US-ASCII'     => 'ASCII (English)',
    'X-EUC-JP'     => 'EUC-JP (Japanese)',
    'EUC-KR'       => 'EUC-KR (Korean)',
    'BIG5'         => 'BIG5 (Chinese)',
    'GB2312'       => 'GB2312 (Chinese)',
    'ISO-8859-1'   => 'ISO-8859-1 (Latin-1)',
    'ISO-8859-2'   => 'ISO-8895-2 (Central European)',
    'ISO-8859-7'   => 'ISO-8859-7 (Greek)',
    'ISO-8859-9'   => 'ISO-8859-9 (Turkish)',
    'Windows-1251' => 'Windows-1251 (Cyrillic)',
    'Windows-1252' => 'Windows-1252 (Western)',
    'Windows-1255' => 'Windows-1255 (Hebrew)',
    'Windows-1256' => 'Windows-1256 (Arabic)',
    'Windows-1257' => 'Windows-1257 (Baltic)',
    'UTF-8'        => 'UTF-8'
    );
  $select = new select($field_attrib);
  $select->add(array_values($charsets), array_keys($charsets));
  $set = $_POST['_charset'] ? $_POST['_charset'] : $GLOBALS['CHARSET'];
  return $select->show($set);
  }
?>
program/include/rcube_db.inc
@@ -23,299 +23,332 @@
require_once('DB.php');
class rcube_db
{
    var $db_dsnw;               // DSN for write operations
    var $db_dsnr;               // DSN for read operations
    var $db_connected=false;    // Already connected ?
    var $db_mode='';            // Connection mode
    var $db_handle=0;           // Connection handle
  {
  var $db_dsnw;               // DSN for write operations
  var $db_dsnr;               // DSN for read operations
  var $db_connected = false;  // Already connected ?
  var $db_mode = '';          // Connection mode
  var $db_handle = 0;         // Connection handle
    var $a_query_results = array('dummy');
    var $last_res_id = 0;
  var $a_query_results = array('dummy');
  var $last_res_id = 0;
    // PHP 5 constructor
    function __construct($db_dsnw,$db_dsnr='')
  // PHP 5 constructor
  function __construct($db_dsnw,$db_dsnr='')
    {
        if ($db_dsnr=='') $db_dsnr=$db_dsnw;
    if ($db_dsnr=='')
      $db_dsnr=$db_dsnw;
        
        $this->db_dsnw = $db_dsnw;
        $this->db_dsnr = $db_dsnr;
    $this->db_dsnw = $db_dsnw;
    $this->db_dsnr = $db_dsnr;
        
        $dsn_array = DB::parseDSN($db_dsnw);
        $this->db_provider = $dsn_array['phptype'];
    $dsn_array = DB::parseDSN($db_dsnw);
    $this->db_provider = $dsn_array['phptype'];
    }
    // PHP 4 compatibility
    function rcube_db($db_dsnw,$db_dsnr='')
  // PHP 4 compatibility
  function rcube_db($db_dsnw,$db_dsnr='')
    {
        $this->__construct($db_dsnw,$db_dsnr);
    $this->__construct($db_dsnw,$db_dsnr);
    }
    // Connect to specific database
    function dsn_connect($dsn)
  // Connect to specific database
  function dsn_connect($dsn)
    {
        // Use persistent connections if available
        $dbh = DB::connect($dsn, array('persistent' => $true));
    // Use persistent connections if available
    $dbh = DB::connect($dsn, array('persistent' => TRUE));
        
        if (DB::isError($dbh))
            raise_error(array('code' => 500,
    if (DB::isError($dbh))
      raise_error(array('code' => 500,
                        'type' => 'db',
                        'line' => __LINE__,
                        'file' => __FILE__,
                        'message' => $dbh->getMessage()), TRUE, FALSE);
        else if ($this->db_provider=='sqlite')
        {
            $dsn_array = DB::parseDSN($dsn);
            if (!filesize($dsn_array['database']) && !empty($this->sqlite_initials))
                $this->_sqlite_create_database($dbh, $this->sqlite_initials);
        }
    else if ($this->db_provider=='sqlite')
      {
      $dsn_array = DB::parseDSN($dsn);
      if (!filesize($dsn_array['database']) && !empty($this->sqlite_initials))
        $this->_sqlite_create_database($dbh, $this->sqlite_initials);
      }
        
        return $dbh;
    return $dbh;
    }
    // Connect to appropiate databse
    function db_connect ($mode)
    {
        $this->db_mode = $mode;
        // Already connected
        if ($this->db_connected)
            {
            // no replication, current connection is ok
            if ($this->db_dsnw==$this->db_dsnr) return;
  // Connect to appropiate databse
  function db_connect ($mode)
    {
    $this->db_mode = $mode;
    // Already connected
    if ($this->db_connected)
      {
      // no replication, current connection is ok
      if ($this->db_dsnw==$this->db_dsnr)
        return;
            
            // connected to master, current connection is ok
            if ($this->db_mode=='w') return;
      // connected to master, current connection is ok
      if ($this->db_mode=='w')
        return;
            // Same mode, current connection is ok
            if ($this->db_mode==$mode) return;
            }
      // Same mode, current connection is ok
      if ($this->db_mode==$mode)
        return;
      }
            
        if ($mode=='r')
            $dsn=$this->db_dsnr;
        else
            $dsn=$this->db_dsnw;
    if ($mode=='r')
      $dsn = $this->db_dsnr;
    else
      $dsn = $this->db_dsnw;
        $this->db_handle = $this->dsn_connect($dsn);
        $this->db_connected = true;
    $this->db_handle = $this->dsn_connect($dsn);
    $this->db_connected = true;
    }
    // Query database
    function query()
  // Query database
  function query()
    {
        $params = func_get_args();
        $query = array_shift($params);
    $params = func_get_args();
    $query = array_shift($params);
    return $this->_query($query, 0, 0, $params);
    }
  // Query with limits
  function limitquery()
    {
    $params = func_get_args();
    $query = array_shift($params);
    $offset = array_shift($params);
    $numrows = array_shift($params);
        
        return $this->_query($query, 0, 0, $params);
    return $this->_query($query, $offset, $numrows, $params);
    }
    function limitquery()
  function _query($query, $offset, $numrows, $params)
    {
        $params = func_get_args();
        $query = array_shift($params);
        $offset = array_shift($params);
        $numrows = array_shift($params);
        return $this->_query($query, $offset, $numrows, $params);
    // Read or write ?
    if (strtolower(trim(substr($query,0,6)))=='select')
      $mode='r';
    else
      $mode='w';
    $this->db_connect($mode);
    if ($this->db_provider == 'sqlite')
      $this->_sqlite_prepare();
    if ($numrows || $offset)
      $result = $this->db_handle->limitQuery($query,$offset,$numrows,$params);
    else
      $result = $this->db_handle->query($query, $params);
    // add result, even if it's an error
    return $this->_add_result($result);
    }
    function _query($query, $offset, $numrows, $params)
  function num_rows($res_id=NULL)
    {
        // Read or write ?
        if (strtolower(trim(substr($query,0,6)))=='select')
            $mode='r';
        else
            $mode='w';
        $this->db_connect($mode);
    if (!$this->db_handle)
      return FALSE;
        if ($this->db_provider == 'sqlite')
            $query = $this->_sqlite_prepare_query($query);
        if ($numrows || $offset)
            {
            $result = $this->db_handle->limitQuery($query,$offset,$numrows,$params);
            }
        else
            $result = $this->db_handle->query($query, $params);
    if ($result = $this->_get_result($res_id))
      return $result->numRows();
    else
      return FALSE;
    }
  function affected_rows($res_id=NULL)
    {
    if (!$this->db_handle)
      return FALSE;
    return $this->db_handle->affectedRows();
    }
  function insert_id($sequence = '')
    {
    if (!$this->db_handle || $this->db_mode=='r')
      return FALSE;
    switch($this->db_provider)
      {
      case 'pgsql':
        // PostgreSQL uses sequences
        $result =& $this->db_handle->getOne("SELECT CURRVAL('$sequence')");
        if (DB::isError($result))
            {
            raise_error(array('code' => 500,
                              'type' => 'db',
                              'line' => __LINE__,
                              'file' => __FILE__,
                              'message' => $result->getMessage().'; QUERY: '.$query), TRUE, FALSE);
             return false;
            }
          raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__,
                            'message' => $result->getMessage()), TRUE, FALSE);
        return $this->_add_result($result, $query);
    }
    function num_rows($res_id=NULL)
    {
        if (!$this->db_handle)
            return FALSE;
        $result = $this->_get_result($res_id);
        if ($result)
              return $result->numRows();
        else
              return FALSE;
    }
    function affected_rows($res_id=NULL)
    {
        if (!$this->db_handle)
            return FALSE;
        return $this->db_handle->affectedRows();
    }
    function insert_id($sequence = '')
    {
        if (!$this->db_handle || $this->db_mode=='r')
            return FALSE;
        switch($this->db_provider)
        {
            case 'pgsql':
                // PostgreSQL uses sequences
                $result =& $this->db_handle->getOne("SELECT CURRVAL('$sequence')");
                if (DB::isError($result)) {
                    raise_error( array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__,
                    'message' => $result->getMessage()), TRUE, TRUE);
                }
                return $result;
        return $result;
                
            case 'mysql': // This is unfortuneate
                return mysql_insert_id($this->db_handle->connection);
      case 'mysql': // This is unfortuneate
        return mysql_insert_id($this->db_handle->connection);
            case 'mysqli':
                return mysqli_insert_id($this->db_handle->connection);
      case 'mysqli':
        return mysqli_insert_id($this->db_handle->connection);
    
            case 'sqlite':
                return sqlite_last_insert_rowid($this->db_handle->connection);
      case 'sqlite':
        return sqlite_last_insert_rowid($this->db_handle->connection);
            default:
                die("portability issue with this database, please have the developer fix");
        }
      default:
        die("portability issue with this database, please have the developer fix");
      }
    }
    function fetch_assoc($res_id=NULL)
  function fetch_assoc($res_id=NULL)
    {
        $result = $this->_get_result($res_id);
    $result = $this->_get_result($res_id);
        if (DB::isError($result))
        {
            raise_error( array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__,
                         'message' => $this->db_link->getMessage()), TRUE, FALSE);
            return FALSE;
        }
    if (DB::isError($result))
      {
      raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__,
                        'message' => $this->db_link->getMessage()), TRUE, FALSE);
      return FALSE;
      }
                         
        return $result->fetchRow(DB_FETCHMODE_ASSOC);
    return $result->fetchRow(DB_FETCHMODE_ASSOC);
    }
    function quote($input, $type=null)
  function quote($input, $type=null)
    {
        if (!$this->db_handle)
            $this->db_connect('r');
    if (!$this->db_handle)
      $this->db_connect('r');
        return $this->db_handle->quote($input);
    return $this->db_handle->quote($input);
    }
    
    function quoteIdentifier($str)
  function quoteIdentifier($str)
    {
        if (!$this->db_handle)
            $this->db_connect('r');
    if (!$this->db_handle)
      $this->db_connect('r');
            
        return $this->db_handle->quoteIdentifier($str);
    }
    function quote_identifier($str)
    {
        return $this->quoteIdentifier($str);
    return $this->db_handle->quoteIdentifier($str);
    }
    function unixtimestamp($field)
    {
        switch($this->db_provider)
            {
            case 'pgsql':
                return "EXTRACT (EPOCH FROM $field)";
                break;
            default:
                return "UNIX_TIMESTAMP($field)";
            }
    }
    function _add_result($res, $query)
  function quote_identifier($str)
    {
        // sql error occured
        if (DB::isError($res))
        {
            raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__, 'message' => $res->getMessage() . " Query: " . substr(preg_replace('/[\r\n]+\s*/', ' ', $query), 0, 1024)), TRUE, FALSE);
             return FALSE;
        }
        else
        {
            $res_id = sizeof($this->a_query_results);
            $this->a_query_results[$res_id] = $res;
            $this->last_res_id = $res_id;
            return $res_id;
        }
    return $this->quoteIdentifier($str);
    }
    function _get_result($res_id)
  function unixtimestamp($field)
    {
        if ($res_id==NULL)
            $res_id = $this->last_res_id;
    switch($this->db_provider)
      {
      case 'pgsql':
        return "EXTRACT (EPOCH FROM $field)";
        break;
      default:
        return "UNIX_TIMESTAMP($field)";
      }
    }
  function fromunixtime($timestamp)
    {
    switch($this->db_provider)
      {
      case 'mysqli':
      case 'mysql':
      case 'sqlite':
        return "FROM_UNIXTIME($timestamp)";
      default:
        return date("'Y-m-d H:i:s'", $timestamp);
      }
    }
  function _add_result($res)
    {
    // sql error occured
    if (DB::isError($res))
      {
      raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__,
                        'message' => $res->getMessage() . " Query: " . substr(preg_replace('/[\r\n]+\s*/', ' ', $res->userinfo), 0, 1024)), TRUE, FALSE);
      return FALSE;
      }
    else
      {
      $res_id = sizeof($this->a_query_results);
      $this->a_query_results[$res_id] = $res;
      $this->last_res_id = $res_id;
      return $res_id;
      }
    }
  function _get_result($res_id)
    {
    if ($res_id==NULL)
      $res_id = $this->last_res_id;
    
        if ($res_id && isset($this->a_query_results[$res_id]))
            return $this->a_query_results[$res_id];
        else
        return FALSE;
     if ($res_id && isset($this->a_query_results[$res_id]))
       return $this->a_query_results[$res_id];
     else
       return FALSE;
    }
    // create a sqlite database from a file
    function _sqlite_create_database($dbh, $fileName)
  // create a sqlite database from a file
  function _sqlite_create_database($dbh, $fileName)
    {
        if (empty($fileName) || !is_string($fileName))
            return ;
    if (empty($fileName) || !is_string($fileName))
      return ;
        $data = '';
        if ($fd = fopen($fileName, 'r'))
          {
          $data = fread($fd, filesize($fileName));
          fclose($fd);
          }
    $data = '';
    if ($fd = fopen($fileName, 'r'))
      {
      $data = fread($fd, filesize($fileName));
      fclose($fd);
      }
        if (strlen($data))
          sqlite_exec($dbh->connection, $data);
    if (strlen($data))
      sqlite_exec($dbh->connection, $data);
    }
    // transform a query so that it is sqlite2 compliant
    function _sqlite_prepare_query($query)
  function _sqlite_prepare()
    {
        if (!is_string($query))
            return ($query);
        $search = array('/NOW\(\)/i', '/`/');
        $replace = array("datetime('now')", '"');
        $query = preg_replace($search, $replace, $query);
    include_once('include/rcube_sqlite.inc');
        return ($query);
    // we emulate via callback some missing MySQL function
    sqlite_create_function($this->db_handle->connection, "from_unixtime", "rcube_sqlite_from_unixtime");
    sqlite_create_function($this->db_handle->connection, "unix_timestamp", "rcube_sqlite_unix_timestamp");
    sqlite_create_function($this->db_handle->connection, "now", "rcube_sqlite_now");
    sqlite_create_function($this->db_handle->connection, "md5", "rcube_sqlite_md5");
    }
}
/*
  // transform a query so that it is sqlite2 compliant
  function _sqlite_prepare_query($query)
    {
    if (!is_string($query))
      return ($query);
    $search = array('/NOW\(\)/i', '/`/');
    $replace = array("datetime('now')", '"');
    $query = preg_replace($search, $replace, $query);
    return ($query);
    }
*/
  }  // end class rcube_db
?>
program/include/rcube_imap.inc
@@ -28,6 +28,7 @@
class rcube_imap
  {
  var $db;
  var $conn;
  var $root_ns = '';
  var $root_dir = '';
@@ -38,21 +39,23 @@
  var $caching_enabled = FALSE;
  var $default_folders = array('inbox', 'drafts', 'sent', 'junk', 'trash');
  var $cache = array();
  var $cache_keys = array();
  var $cache_changes = array();  
  var $uid_id_map = array();
  var $msg_headers = array();
  var $capabilities = array();
  // PHP 5 constructor
  function __construct()
  function __construct($db_conn)
    {
    $this->db = $db_conn;
    }
  // PHP 4 compatibility
  function rcube_imap()
  function rcube_imap($db_conn)
    {
    $this->__construct();
    $this->__construct($db_conn);
    }
@@ -95,6 +98,7 @@
    // get account namespace
    if ($this->conn)
      {
      $this->_parse_capability($this->conn->capability);
      iil_C_NameSpace($this->conn);
      
      if (!empty($this->conn->delimiter))
@@ -182,6 +186,13 @@
  function get_mailbox_name()
    {
    return $this->conn ? $this->_mod_mailbox($this->mailbox, 'out') : '';
    }
  function get_capability($cap)
    {
    $cap = strtoupper($cap);
    return $this->capabilities[$cap];
    }
@@ -298,200 +309,131 @@
  // private method for listing message header
  // by DrSlump <drslump@drslump.biz>
  function __list_headers($mailbox='', $page=NULL, $sort_field='date', $sort_order='DESC')
    {
    $a_out = array();
    $cached_count = 0;
    if (!strlen($mailbox))
      return $a_out;
    $mbox_count = $this->_messagecount($mailbox /*, 'ALL', TRUE*/);
    $revalidate = false;
    if ($mbox_count)
      {
      // get cached headers
      $a_out = $this->get_cache($mailbox.'.msg');
      $a_out = is_array($a_out) ? $a_out : array(); // make sure we get an array
      $cached_count = count($a_out);
      $a_new = array();
      $revalidate = true; // revalidate by default
      // if the cache count is greater then there have been changes for sure
      if ($cached_count <= $mbox_count)
        {
        $from = $cached_count?$cached_count:1;
        //get new headers (at least one is returned)
        $a_temp = iil_C_FetchHeaders($this->conn, $mailbox, $from . ':' . $mbox_count);
        $duplicated = $cached_count?true:false;
        foreach ($a_temp as $hdr)
          {
          //skip the first one if duplicated
          if ($duplicated)
            {
            //check for changes using the UID
            $lastCacheHdr = end($a_out);
            if ($hdr->uid === $lastCacheHdr->uid)
              $revalidate = false;
            $duplicated = false;
            continue;
            }
          //skip deleted ones
          if (! $hdr->deleted)
            $a_new[ $hdr->uid ] = $hdr;
          }
        }
      //revalidate cache if needed
      $to = $mbox_count - count($a_new);
      if ($revalidate && $to !== 0)    //we'll need to reindex the array so we have to make a copy
        {
        $a_dirty = $a_out;
        $a_out = array();
        $a_buffers = array();
        //fetch chunks of 20 headers
        $step = 20;
        $found = false;
        //fetch headers in blocks starting from new to old
        do {
          $from = $to-$step;
          if ($from < 1) $from = 1;
          //store the block in a temporal buffer
          $a_buffers[$from] = iil_C_FetchHeaders($this->conn, $mailbox, $from . ':' . $to);
          //compare the fetched headers with the ones in the cache
          $idx = 0;
          foreach ($a_buffers[$from] as $k=>$hdr)
            {
            //if it's different the comparison ends
            if (!isset($a_dirty[$hdr->uid]) || $a_dirty[$hdr->uid]->id !== $hdr->id)
              break;
            //if we arrive here then we know that the older messages in cache are ok
            $found = $hdr->id;
            $idx++;
            }
          //remove from the buffer the headers which are already cached
          if ($found)
            $a_buffers[$from] = array_splice($a_buffers[$from], 0, $idx );
          $to = $from-1;
          }
        while ($found===false && $from > 1);
        //just keep the headers we are certain that didn't change in the cache
        if ($found !== false)
          {
          foreach ($a_dirty as $hdr)
            {
            if ($hdr->id > $found) break;
            $a_out[$hdr->uid] = $hdr;
            }
          }
        //we builded the block buffers from new to older, we process them in reverse order
        ksort($a_buffers, SORT_NUMERIC);
        foreach ($a_buffers as $a_buff)
          {
          foreach ($a_buff as $hdr)
            {
            if (! $hdr->deleted)
              $a_out[$hdr->uid] = $hdr;
            }
          }
        }
      //array_merge() would reindex the keys, so we use this 'hack'
      $a_out += $a_new;
      }
    //write headers list to cache if needed
    if ($revalidate || count($a_out)!=$cached_count) {
      $this->update_cache($mailbox.'.msg', $a_out);
     }
    //sort headers by a specific col
    $a_out = iil_SortHeaders( $a_out, $sort_field, $sort_order );
    // return complete list of messages
    if (strtolower($page)=='all')
      return $a_out;
    $start_msg = ($this->list_page-1) * $this->page_size;
    return array_slice($a_out, $start_msg, $this->page_size);
    }
  // original function; replaced 2005/10/18
  // private method for listing message header
  function _list_headers($mailbox='', $page=NULL, $sort_field='date', $sort_order='DESC')
    {
    $max = $this->_messagecount($mailbox);
    if (!strlen($mailbox))
      return array();
    // get cached headers
    $a_msg_headers = $this->get_cache($mailbox.'.msg');
    // retrieve headers from IMAP
    if (!is_array($a_msg_headers) || sizeof($a_msg_headers) != $max)
    $max = $this->_messagecount($mailbox);
    $start_msg = ($this->list_page-1) * $this->page_size;
    if ($page=='all')
      {
      $a_header_index = iil_C_FetchHeaders($this->conn, $mailbox, "1:$max");
      $a_msg_headers = array();
      if (!empty($a_header_index))
        foreach ($a_header_index as $i => $headers)
          if (!$headers->deleted)
            $a_msg_headers[$headers->uid] = $headers;
      $begin = 0;
      $end = $max;
      }
    else if ($sort_order=='DESC')
      {
      $begin = $max - $this->page_size - $start_msg;
      $end =   $max - $start_msg;
      }
    else
      $headers_cached = TRUE;
    if (!is_array($a_msg_headers))
        return array();
    // sort headers by a specific col
    $a_headers = iil_SortHeaders($a_msg_headers, $sort_field, $sort_order);
    $headers_count = count($a_headers);
    // free memory
    unset($a_msg_headers);
    // write headers list to cache
    if (!$headers_cached)
      $this->update_cache($mailbox.'.msg', $a_headers);
    // update message count cache
    $a_mailbox_cache = $this->get_cache('messagecount');
    if (isset($a_mailbox_cache[$mailbox]['ALL']) && $a_mailbox_cache[$mailbox]['ALL'] != $headers_count)
      {
      $a_mailbox_cache[$mailbox]['ALL'] = (int)$headers_count;
      $this->update_cache('messagecount', $a_mailbox_cache);
      $begin = $start_msg;
      $end =   $start_msg + $this->page_size;
      }
    if (empty($a_headers))
        return array();
    // return complete list of messages
    if (strtolower($page)=='all')
      return $a_headers;
    if ($begin < 0) $begin = 0;
    if ($end < 0) $end = $max;
    if ($end > $max) $end = $max;
    $start_msg = ($this->list_page-1) * $this->page_size;
    return array_slice($a_headers, $start_msg, $this->page_size);
//console("fetch headers $start_msg to ".($start_msg+$this->page_size)." (msg $begin to $end)");
    $headers_sorted = FALSE;
    $cache_key = $mailbox.'.msg';
    $cache_status = $this->check_cache_status($mailbox, $cache_key);
//console("Cache status = $cache_status");
    // cache is OK, we can get all messages from local cache
    if ($cache_status>0)
      {
      $a_msg_headers = $this->get_message_cache($cache_key, $start_msg, $start_msg+$this->page_size, $sort_field, $sort_order);
      $headers_sorted = TRUE;
      }
    else
      {
      // retrieve headers from IMAP
      if ($this->get_capability('sort') && ($msg_index = iil_C_Sort($this->conn, $mailbox, $sort_field)))
        {
//console("$mailbox: ".count($msg_index));
        $msgs = $msg_index[$begin];
        for ($i=$begin; $i < $end; $i++)
          {
          if ($sort_order == 'DESC')
            $msgs = $msg_index[$i].','.$msgs;
          else
            $msgs = $msgs.','.$msg_index[$i];
          }
        $sorted = TRUE;
        }
      else
        {
        $msgs = sprintf("%d:%d", $begin+1, $end);
        $sorted = FALSE;
        }
      // cache is dirty, sync it
      if ($this->caching_enabled && $cache_status==-1)
        {
        $this->sync_header_index($mailbox);
        return $this->_list_headers($mailbox, $page, $sort_field, $sort_order);
        }
      // cache is incomplete
      $cache_index = $this->get_message_cache_index($cache_key);
      // fetch reuested headers from server
      $a_header_index = iil_C_FetchHeaders($this->conn, $mailbox, $msgs);
      $a_msg_headers = array();
      if (!empty($a_header_index))
        {
        foreach ($a_header_index as $i => $headers)
          {
          if ($headers->deleted)
            {
            // delete from cache
            if ($cache_index[$headers->id] && $cache_index[$headers->id] == $headers->uid)
              $this->remove_message_cache($cache_key, $headers->id);
            continue;
            }
          // add message to cache
          if ($this->caching_enabled && $cache_index[$headers->id] != $headers->uid)
            $this->add_message_cache($cache_key, $headers->id, $headers);
          $a_msg_headers[$headers->uid] = $headers;
          }
        }
      // delete cached messages with a higher index than $max
      $this->clear_message_cache($cache_key, $max);
      // kick child process to sync cache
      }
    // return empty array if no messages found
    if (!is_array($a_msg_headers) || empty($a_msg_headers))
        return array();
    // if not already sorted
    if (!$headers_sorted)
      $a_msg_headers = iil_SortHeaders($a_msg_headers, $sort_field, $sort_order);
    return array_values($a_msg_headers);
    }
  // return sorted array of message UIDs
  function message_index($mbox='', $sort_field='date', $sort_order='DESC')
@@ -510,9 +452,54 @@
    }
  function sync_header_index($mbox=NULL)
  function sync_header_index($mailbox)
    {
    $cache_key = $mailbox.'.msg';
    $cache_index = $this->get_message_cache_index($cache_key);
    $msg_count = $this->_messagecount($mailbox);
    // fetch complete message index
    $a_message_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:$msg_count", 'UID');
    foreach ($a_message_index as $id => $uid)
      {
      // message in cache at correct position
      if ($cache_index[$id] == $uid)
        {
// console("$id / $uid: OK");
        unset($cache_index[$id]);
        continue;
        }
      // message in cache but in wrong position
      if (in_array((string)$uid, $cache_index, TRUE))
        {
// console("$id / $uid: Moved");
        unset($cache_index[$id]);
        }
      // other message at this position
      if (isset($cache_index[$id]))
        {
// console("$id / $uid: Delete");
        $this->remove_message_cache($cache_key, $id);
        unset($cache_index[$id]);
        }
// console("$id / $uid: Add");
      // fetch complete headers and add to cache
      $headers = iil_C_FetchHeader($this->conn, $mailbox, $id);
      $this->add_message_cache($cache_key, $headers->id, $headers);
      }
    // those ids that are still in cache_index have been deleted
    if (!empty($cache_index))
      {
      foreach ($cache_index as $id => $uid)
        $this->remove_message_cache($cache_key, $id);
      }
    }
@@ -527,22 +514,19 @@
  function get_headers($uid, $mbox=NULL)
    {
    $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
    // get cached headers
    $a_msg_headers = $this->get_cache($mailbox.'.msg');
    // return cached header
    if ($a_msg_headers[$uid])
      return $a_msg_headers[$uid];
    if ($headers = $this->get_cached_message($mailbox.'.msg', $uid))
      return $headers;
    $msg_id = $this->_uid2id($uid);
    $header = iil_C_FetchHeader($this->conn, $mailbox, $msg_id);
    $headers = iil_C_FetchHeader($this->conn, $mailbox, $msg_id);
    // write headers cache
    $a_msg_headers[$uid] = $header;
    $this->update_cache($mailbox.'.msg', $a_msg_headers);
    if ($headers)
      $this->add_message_cache($mailbox.'.msg', $msg_id, $headers);
    return $header;
    return $headers;
    }
@@ -595,19 +579,20 @@
    // reload message headers if cached
    $cache_key = $this->mailbox.'.msg';
    if ($this->caching_enabled && $result && ($a_cached_headers = $this->get_cache($cache_key)))
    if ($this->caching_enabled)
      {
      // close and re-open connection
      $this->reconnect();
      foreach ($uids as $uid)
      foreach ($msg_ids as $id)
        {
        if (isset($a_cached_headers[$uid]))
        if ($cached_headers = $this->get_cached_message($cache_key, $id))
          {
          unset($this->cache[$cache_key][$uid]);
          $this->get_headers($uid);
          $this->remove_message_cache($cache_key, $id);
          //$this->get_headers($uid);
          }
        }
      // close and re-open connection
      // this prevents connection problems with Courier
      $this->reconnect();
      }
    // set nr of messages that were flaged
@@ -633,7 +618,7 @@
    // make shure mailbox exists
    if (in_array($mailbox, $this->_list_mailboxes()))
      $saved = iil_C_Append($this->conn, $mailbox, $message);
    if ($saved)
      {
      // increase messagecount of the target mailbox
@@ -672,20 +657,24 @@
    // really deleted from the source mailbox
    if ($moved)
      {
      $this->expunge($from_mbox, FALSE);
      $this->clear_cache($to_mbox.'.msg');
      $this->_expunge($from_mbox, FALSE);
      $this->_clear_messagecount($from_mbox);
      $this->_clear_messagecount($to_mbox);
      }
    // update cached message headers
    $cache_key = $from_mbox.'.msg';
    if ($moved && ($a_cached_headers = $this->get_cache($cache_key)))
    if ($moved && ($a_cache_index = $this->get_message_cache_index($cache_key)))
      {
      $start_index = 100000;
      foreach ($a_uids as $uid)
        unset($a_cached_headers[$uid]);
        {
        $index = array_search($uid, $a_cache_index);
        $start_index = min($index, $start_index);
        }
      $this->update_cache($cache_key, $a_cached_headers);
      // clear cache from the lowest index on
      $this->clear_message_cache($cache_key, $start_index);
      }
    return $moved;
@@ -716,17 +705,23 @@
    // really deleted from the mailbox
    if ($deleted)
      {
      $this->expunge($mailbox, FALSE);
      $this->_expunge($mailbox, FALSE);
      $this->_clear_messagecount($mailbox);
      }
    // remove deleted messages from cache
    if ($deleted && ($a_cached_headers = $this->get_cache($mailbox.'.msg')))
    $cache_key = $mailbox.'.msg';
    if ($deleted && ($a_cache_index = $this->get_message_cache_index($cache_key)))
      {
      $start_index = 100000;
      foreach ($a_uids as $uid)
        unset($a_cached_headers[$uid]);
        {
        $index = array_search($uid, $a_cache_index);
        $start_index = min($index, $start_index);
        }
      $this->update_cache($mailbox.'.msg', $a_cached_headers);
      // clear cache from the lowest index on
      $this->clear_message_cache($cache_key, $start_index);
      }
    return $deleted;
@@ -740,7 +735,10 @@
    $msg_count = $this->_messagecount($mailbox, 'ALL');
    
    if ($msg_count>0)
      {
      $this->clear_message_cache($mailbox.'.msg');
      return iil_C_ClearFolder($this->conn, $mailbox);
      }
    else
      return 0;
    }
@@ -750,18 +748,23 @@
  function expunge($mbox='', $clear_cache=TRUE)
    {
    $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
    return $this->_expunge($mailbox, $clear_cache);
    }
  // send IMAP expunge command and clear cache
  function _expunge($mailbox, $clear_cache=TRUE)
    {
    $result = iil_C_Expunge($this->conn, $mailbox);
    if ($result>=0 && $clear_cache)
      {
      $this->clear_cache($mailbox.'.msg');
      //$this->clear_message_cache($mailbox.'.msg');
      $this->_clear_messagecount($mailbox);
      }
      
    return $result;
    }
  /* --------------------------------
@@ -824,13 +827,18 @@
  function create_mailbox($name, $subscribe=FALSE)
    {
    $result = FALSE;
    // replace backslashes
    $name = preg_replace('/[\\\]+/', '-', $name);
    $name_enc = UTF7EncodeString($name);
    // reduce mailbox name to 100 chars
    $name_enc = substr($name_enc, 0, 100);
    $abs_name = $this->_mod_mailbox($name_enc);
    $a_mailbox_cache = $this->get_cache('mailboxes');
    //if (strlen($this->root_ns))
    //  $abs_name = $this->root_ns.$abs_name;
    if (strlen($abs_name) && (!is_array($a_mailbox_cache) || !in_array($abs_name, $a_mailbox_cache)))
      $result = iil_C_CreateFolder($this->conn, $abs_name);
@@ -875,37 +883,41 @@
    // clear mailboxlist cache
    if ($deleted)
      {
      $this->clear_message_cache($mailbox.'.msg');
      $this->clear_cache('mailboxes');
      }
    return $updated;
    return $deleted;
    }
  /* --------------------------------
   *   internal caching functions
   *   internal caching methods
   * --------------------------------*/
  function set_caching($set)
    {
    if ($set && function_exists('rcube_read_cache'))
    if ($set && is_object($this->db))
      $this->caching_enabled = TRUE;
    else
      $this->caching_enabled = FALSE;
    }
  function get_cache($key)
    {
    // read cache
    if (!isset($this->cache[$key]) && $this->caching_enabled)
      {
      $cache_data = rcube_read_cache('IMAP.'.$key);
      $cache_data = $this->_read_cache_record('IMAP.'.$key);
      $this->cache[$key] = strlen($cache_data) ? unserialize($cache_data) : FALSE;
      }
    
    return $this->cache[$key];
    return $this->cache[$key];
    }
@@ -924,7 +936,7 @@
      foreach ($this->cache as $key => $data)
        {
        if ($this->cache_changes[$key])
          rcube_write_cache('IMAP.'.$key, serialize($data));
          $this->_write_cache_record('IMAP.'.$key, serialize($data));
        }
      }    
    }
@@ -935,7 +947,7 @@
    if ($key===NULL)
      {
      foreach ($this->cache as $key => $data)
        rcube_clear_cache('IMAP.'.$key);
        $this->_clear_cache_record('IMAP.'.$key);
      $this->cache = array();
      $this->cache_changed = FALSE;
@@ -943,7 +955,7 @@
      }
    else
      {
      rcube_clear_cache('IMAP.'.$key);
      $this->_clear_cache_record('IMAP.'.$key);
      $this->cache_changes[$key] = FALSE;
      unset($this->cache[$key]);
      }
@@ -951,8 +963,276 @@
  function _read_cache_record($key)
    {
    $cache_data = FALSE;
    if ($this->db)
      {
      // get cached data from DB
      $sql_result = $this->db->query(
        "SELECT cache_id, data
         FROM ".get_table_name('cache')."
         WHERE  user_id=?
         AND    cache_key=?",
        $_SESSION['user_id'],
        $key);
      if ($sql_arr = $this->db->fetch_assoc($sql_result))
        {
        $cache_data = $sql_arr['data'];
        $this->cache_keys[$key] = $sql_arr['cache_id'];
        }
      }
    return $cache_data;
    }
  function _write_cache_record($key, $data)
    {
    if (!$this->db)
      return FALSE;
    // check if we already have a cache entry for this key
    if (!isset($this->cache_keys[$key]))
      {
      $sql_result = $this->db->query(
        "SELECT cache_id
         FROM ".get_table_name('cache')."
         WHERE  user_id=?
         AND    cache_key=?",
        $_SESSION['user_id'],
        $key);
      if ($sql_arr = $this->db->fetch_assoc($sql_result))
        $this->cache_keys[$key] = $sql_arr['cache_id'];
      else
        $this->cache_keys[$key] = FALSE;
      }
    // update existing cache record
    if ($this->cache_keys[$key])
      {
      $this->db->query(
        "UPDATE ".get_table_name('cache')."
         SET    created=now(),
                data=?
         WHERE  user_id=?
         AND    cache_key=?",
        $data,
        $_SESSION['user_id'],
        $key);
      }
    // add new cache record
    else
      {
      $this->db->query(
        "INSERT INTO ".get_table_name('cache')."
         (created, user_id, cache_key, data)
         VALUES (now(), ?, ?, ?)",
        $_SESSION['user_id'],
        $key,
        $data);
      }
    }
  function _clear_cache_record($key)
    {
    $this->db->query(
      "DELETE FROM ".get_table_name('cache')."
       WHERE  user_id=?
       AND    cache_key=?",
      $_SESSION['user_id'],
      $key);
    }
  /* --------------------------------
   *   encoding/decoding functions
   *   message caching methods
   * --------------------------------*/
  // checks if the cache is up-to-date
  // return: -3 = off, -2 = incomplete, -1 = dirty
  function check_cache_status($mailbox, $cache_key)
    {
    if (!$this->caching_enabled)
      return -3;
    $cache_index = $this->get_message_cache_index($cache_key, TRUE);
    $msg_count = $this->_messagecount($mailbox);
    $cache_count = count($cache_index);
    // console("Cache check: $msg_count !== ".count($cache_index));
    if ($cache_count==$msg_count)
      {
      // get highest index
      $header = iil_C_FetchHeader($this->conn, $mailbox, "$msg_count");
      $cache_uid = array_pop($cache_index);
      // uids of highes message matches -> cache seems OK
      if ($cache_uid == $header->uid)
        return 1;
      // cache is dirty
      return -1;
      }
    // if cache count differs less that 10% report as dirty
    else if (abs($msg_count - $cache_count) < $msg_count/10)
      return -1;
    else
      return -2;
    }
  function get_message_cache($key, $from, $to, $sort_field, $sort_order)
    {
    $cache_key = "$key:$from:$to:$sort_field:$sort_order";
    $db_header_fields = array('idx', 'uid', 'subject', 'from', 'to', 'cc', 'date', 'size');
    if (!in_array($sort_field, $db_header_fields))
      $sort_field = 'idx';
    if ($this->caching_enabled && !isset($this->cache[$cache_key]))
      {
      $this->cache[$cache_key] = array();
      $sql_result = $this->db->limitquery(
        "SELECT idx, uid, headers
         FROM ".get_table_name('messages')."
         WHERE  user_id=?
         AND    cache_key=?
         ORDER BY ".$this->db->quoteIdentifier($sort_field)." ".
         strtoupper($sort_order),
        $from,
        $to-$from,
        $_SESSION['user_id'],
        $key);
      while ($sql_arr = $this->db->fetch_assoc($sql_result))
        {
        $uid = $sql_arr['uid'];
        $this->cache[$cache_key][$uid] = unserialize($sql_arr['headers']);
        }
      }
    return $this->cache[$cache_key];
    }
  function get_cached_message($key, $uid, $body=FALSE)
    {
    if (!$this->caching_enabled)
      return FALSE;
    $internal_key = '__single_msg';
    if ($this->caching_enabled && (!isset($this->cache[$internal_key][$uid]) || $body))
      {
      $sql_select = "idx, uid, headers";
      if ($body)
        $sql_select .= ", body";
      $sql_result = $this->db->query(
        "SELECT $sql_select
         FROM ".get_table_name('messages')."
         WHERE  user_id=?
         AND    cache_key=?
         AND    uid=?",
        $_SESSION['user_id'],
        $key,
        $uid);
      if ($sql_arr = $this->db->fetch_assoc($sql_result))
        {
        $headers = unserialize($sql_arr['headers']);
        if (is_object($headers) && !empty($sql_arr['body']))
          $headers->body = $sql_arr['body'];
        $this->cache[$internal_key][$uid] = $headers;
        }
      }
    return $this->cache[$internal_key][$uid];
    }
  function get_message_cache_index($key, $force=FALSE)
    {
    static $sa_message_index = array();
    if (!empty($sa_message_index[$key]) && !$force)
      return $sa_message_index[$key];
    $sa_message_index[$key] = array();
    $sql_result = $this->db->query(
      "SELECT idx, uid
       FROM ".get_table_name('messages')."
       WHERE  user_id=?
       AND    cache_key=?
       ORDER BY idx ASC",
      $_SESSION['user_id'],
      $key);
    while ($sql_arr = $this->db->fetch_assoc($sql_result))
      $sa_message_index[$key][$sql_arr['idx']] = $sql_arr['uid'];
    return $sa_message_index[$key];
    }
  function add_message_cache($key, $index, $headers)
    {
    $this->db->query(
      "INSERT INTO ".get_table_name('messages')."
       (user_id, del, cache_key, idx, uid, subject, ".$this->db->quoteIdentifier('from').", ".$this->db->quoteIdentifier('to').", cc, date, size, headers)
       VALUES (?, 0, ?, ?, ?, ?, ?, ?, ?, ".$this->db->fromunixtime($headers->timestamp).", ?, ?)",
      $_SESSION['user_id'],
      $key,
      $index,
      $headers->uid,
      $this->decode_header($headers->subject, TRUE),
      $this->decode_header($headers->from, TRUE),
      $this->decode_header($headers->to, TRUE),
      $this->decode_header($headers->cc, TRUE),
      $headers->size,
      serialize($headers));
    }
  function remove_message_cache($key, $index)
    {
    $this->db->query(
      "DELETE FROM ".get_table_name('messages')."
       WHERE  user_id=?
       AND    cache_key=?
       AND    idx=?",
      $_SESSION['user_id'],
      $key,
      $index);
    }
  function clear_message_cache($key, $start_index=1)
    {
    $this->db->query(
      "DELETE FROM ".get_table_name('messages')."
       WHERE  user_id=?
       AND    cache_key=?
       AND    idx>=?",
      $_SESSION['user_id'],
      $key,
      $start_index);
    }
  /* --------------------------------
   *   encoding/decoding methods
   * --------------------------------*/
  
@@ -986,9 +1266,15 @@
    }
  function decode_header($input)
  function decode_header($input, $remove_quotes=FALSE)
    {
    return $this->decode_mime_string($input);
    $str = $this->decode_mime_string($input);
    if ($str{0}=='"' && $remove_quotes)
      {
      $str = str_replace('"', '', $str);
      }
    return $str;
    }
    
    
@@ -1094,6 +1380,7 @@
    }
  /* --------------------------------
   *         private methods
   * --------------------------------*/
@@ -1149,6 +1436,33 @@
    }
  // parse string or array of server capabilities and put them in internal array
  function _parse_capability($caps)
    {
    if (!is_array($caps))
      $cap_arr = explode(' ', $caps);
    else
      $cap_arr = $caps;
    foreach ($cap_arr as $cap)
      {
      if ($cap=='CAPABILITY')
        continue;
      if (strpos($cap, '=')>0)
        {
        list($key, $value) = explode('=', $cap);
        if (!is_array($this->capabilities[$key]))
          $this->capabilities[$key] = array();
        $this->capabilities[$key][] = $value;
        }
      else
        $this->capabilities[$cap] = TRUE;
      }
    }
  // subscribe/unsubscribe a list of mailboxes and update local cache
  function _change_subscription($a_mboxes, $mode)
    {
program/include/rcube_mdb2.inc
@@ -238,6 +238,22 @@
    }
    function format_date($timestamp)
      {
        switch($this->db_provider)
            {
            case 'mysqli':
            case 'mysql':
                return "FROM_UNIXTIME($timestamp)";
                break;
            case 'sqlite':
                return "datetime('$timestamp')";
                break;
            default:
                return date("Y-m-d H:i:s", $timestamp);
            }
      }
    function _add_result($res, $query)
    {
        // sql error occured
program/include/rcube_shared.inc
@@ -1185,99 +1185,6 @@
  }
// replace specials characters to a specific encoding type
function rep_specialchars_output($str, $enctype='', $mode='', $newlines=TRUE)
  {
  global $OUTPUT_TYPE, $CHARSET;
  static $html_encode_arr, $js_rep_table, $rtf_rep_table, $xml_rep_table;
  if (!$enctype)
    $enctype = $GLOBALS['OUTPUT_TYPE'];
  // convert nbsps back to normal spaces if not html
  if ($enctype!='html')
    $str = str_replace(chr(160), ' ', $str);
  // encode for plaintext
  if ($enctype=='text')
    return str_replace("\r\n", "\n", $mode=='remove' ? strip_tags($str) : $str);
  // encode for HTML output
  if ($enctype=='html')
    {
    if (!$html_encode_arr)
      {
      if ($CHARSET=='ISO-8859-1')
        {
        $html_encode_arr = get_html_translation_table(HTML_ENTITIES);
        $html_encode_arr[chr(128)] = '&euro;';
        }
      else
        $html_encode_arr = get_html_translation_table(HTML_SPECIALCHARS);
      unset($html_encode_arr['?']);
      unset($html_encode_arr['&']);
      }
    $ltpos = strpos($str, '<');
    $encode_arr = $html_encode_arr;
    // don't replace quotes and html tags
    if (($mode=='show' || $mode=='') && $ltpos!==false && strpos($str, '>', $ltpos)!==false)
      {
      unset($encode_arr['"']);
      unset($encode_arr['<']);
      unset($encode_arr['>']);
      }
    else if ($mode=='remove')
      $str = strip_tags($str);
    $out = strtr($str, $encode_arr);
    return $newlines ? nl2br($out) : $out;
    }
  if ($enctype=='url')
    return rawurlencode($str);
  // if the replace tables for RTF, XML and JS are not yet defined
  if (!$js_rep_table)
    {
    $js_rep_table = $rtf_rep_table = $xml_rep_table = array();
    for ($c=160; $c<256; $c++)  // can be increased to support more charsets
      {
      $hex = dechex($c);
      $rtf_rep_table[Chr($c)] = "\\'$hex";
      $xml_rep_table[Chr($c)] = "&#$c;";
      if ($CHARSET=='ISO-8859-1')
        $js_rep_table[Chr($c)] = sprintf("\u%s%s", str_repeat('0', 4-strlen($hex)), $hex);
      }
    $js_rep_table['"'] = sprintf("\u%s%s", str_repeat('0', 4-strlen(dechex(34))), dechex(34));
    $xml_rep_table['"'] = '&quot;';
    }
  // encode for RTF
  if ($enctype=='xml')
    return strtr($str, $xml_rep_table);
  // encode for javascript use
  if ($enctype=='js')
    return preg_replace(array("/\r\n/", '/"/', "/([^\\\])'/"), array('\n', '\"', "$1\'"), strtr($str, $js_rep_table));
  // encode for RTF
  if ($enctype=='rtf')
    return preg_replace("/\r\n/", "\par ", strtr($str, $rtf_rep_table));
  // no encoding given -> return original string
  return $str;
  }
function decode_specialchars($input, $charset='')
  {
@@ -1462,7 +1369,21 @@
  return $str;
  }
// delete all files within a folder
function clear_directory($dir_path)
  {
  $dir = @opendir($dir_path);
  if(!$dir) return FALSE;
  while ($file = readdir($dir))
    if (strlen($file)>2)
      unlink("$dir_path/$file");
  closedir($dir);
  return TRUE;
  }
?>
program/include/rcube_sqlite.inc
New file
@@ -0,0 +1,71 @@
<?php
/*
 +-----------------------------------------------------------------------+
 | program/include/rcube_sqlite.inc                                      |
 |                                                                       |
 | This file is part of the RoundCube Webmail client                     |
 | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
 | Licensed under the GNU GPL                                            |
 |                                                                       |
 | PURPOSE:                                                              |
 |   Provide callback functions for sqlite that will emulate             |
 |   sone MySQL functions                                                |
 |                                                                       |
 +-----------------------------------------------------------------------+
 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
 +-----------------------------------------------------------------------+
 $Id$
*/
function rcube_sqlite_from_unixtime($timestamp)
  {
    $timestamp = trim($timestamp);
    if (!preg_match("/^[0-9]+$/is", $timestamp))
      $ret = strtotime($timestamp);
    else
      $ret = $timestamp;
    $ret = date("Y-m-d H:i:s", $ret);
    rcube_sqlite_debug("FROM_UNIXTIME ($timestamp) = $ret");
    return $ret;
  }
function rcube_sqlite_unix_timestamp($timestamp="")
  {
    $timestamp = trim($timestamp);
    if (!$timestamp)
      $ret = time();
    else if (!preg_match("/^[0-9]+$/is", $timestamp))
      $ret = strtotime($timestamp);
    else
      $ret = $timestamp;
    rcube_sqlite_debug("UNIX_TIMESTAMP ($timestamp) = $ret");
    return $ret;
  }
function rcube_sqlite_now()
  {
    rcube_sqlite_debug("NOW() = ".date("Y-m-d H:i:s"));
    return date("Y-m-d H:i:s");
  }
function rcube_sqlite_md5($str)
  {
    return md5($str);
  }
function rcube_sqlite_debug($str)
  {
    //console($str);
  }
?>
program/include/session.inc
@@ -45,7 +45,7 @@
  if ($sql_arr = $DB->fetch_assoc($sql_result))
    {
    $SESS_CHANGED = $sql_arr['changed'];
    $SESS_CHANGED = mktime(); //$sql_arr['changed'];
    if (strlen($sql_arr['vars']))
      return $sql_arr['vars'];
@@ -59,7 +59,7 @@
function sess_write($key, $vars)
  {
  global $DB;
  $sql_result = $DB->query("SELECT 1
                            FROM ".get_table_name('session')."
                            WHERE  sess_id=?",
@@ -83,6 +83,8 @@
                $key,
                $vars,
                $_SERVER['REMOTE_ADDR']);
    }
  return TRUE;
@@ -102,7 +104,9 @@
  $DB->query("DELETE FROM ".get_table_name('session')."
              WHERE sess_id=?",
              $key);
  rcmail_clear_session_temp($key);
  return TRUE;
  }
@@ -115,7 +119,7 @@
  // get all expired sessions  
  $sql_result = $DB->query("SELECT sess_id
                            FROM ".get_table_name('session')."
                            WHERE ".$DB->unixtimestamp('now()')."-".$DB->unixtimestamp('created')." > ?",
                            WHERE ".$DB->unixtimestamp('now()')."-".$DB->unixtimestamp('changed')." > ?",
                            $maxlifetime);
                                   
  $a_exp_sessions = array();
@@ -134,6 +138,10 @@
                WHERE sess_id IN ('".join("','", $a_exp_sessions)."')");
    }
  // remove session specific temp dirs
  foreach ($a_exp_sessions as $key)
    rcmail_clear_session_temp($key);
  return TRUE;
  }
program/js/app.js
@@ -210,7 +210,7 @@
    this.enable_command('logout', true);
    // disable browser's contextmenus
    //document.oncontextmenu = function(){ return false; }
    // document.oncontextmenu = function(){ return false; }
    // flag object as complete
    this.loaded = true;
@@ -286,6 +286,7 @@
      return false;
    
    //this.messageform = this.gui_objects.messageform;
    var input_from = rcube_find_object('_from');
    var input_to = rcube_find_object('_to');
    var input_cc = rcube_find_object('_cc');
    var input_bcc = rcube_find_object('_bcc');
@@ -300,6 +301,10 @@
      this.init_address_input_events(input_cc);
    if (input_bcc)
      this.init_address_input_events(input_bcc);
    // add signature according to selected identity
    if (input_from && input_from.type=='select-one')
      this.change_identity(input_from);
    if (input_to && input_to.value=='')
      input_to.focus();
@@ -461,8 +466,17 @@
        // get the type of sorting
        var a_sort = props.split('_');
        var sort_col = a_sort[0];
        var sort_order = a_sort[1].toUpperCase();
        var sort_order = a_sort[1] ? a_sort[1].toUpperCase() : null;
        var header;
        // no sort order specified: toggle
        if (sort_order==null)
          {
          if (this.env.sort_col==sort_col)
            sort_order = this.env.sort_order=='ASC' ? 'DESC' : 'ASC';
          else
            sort_order = this.env.sort_order;
          }
        
        if (this.env.sort_col==sort_col && this.env.sort_order==sort_order)
          break;
@@ -478,7 +492,7 @@
        this.env.sort_order = sort_order;
        // reload message list
        this.list_mailbox('', '', props);
        this.list_mailbox('', '', sort_col+'_'+sort_order);
        break;
      case 'nextpage':
@@ -805,7 +819,7 @@
        break;
      case 'delete-folder':
        if (confirm('Do you really want to delete this folder?'))
        if (confirm(this.get_label('deletefolderconfirm')))
          this.delete_folder(props);
        break;
@@ -933,8 +947,8 @@
    if (!this.in_selection_before)
      {
      var shift = this.check_shiftkey(e);
      this.select(id, shift);
      var ctrl = this.check_ctrlkey(e);
      this.select(id, ctrl);
      }
    
    if (this.selection.length)
@@ -951,7 +965,7 @@
  // onmouseup-handler of message list row
  this.click_row = function(e, id)
    {
    var shift = this.check_shiftkey(e);
    var ctrl = this.check_ctrlkey(e);
    
    // don't do anything (another action processed before)
    if (this.dont_select)
@@ -961,13 +975,13 @@
      }
    
    if (!this.drag_active && this.in_selection_before==id)
      this.select(id, (shift && this.task!='settings'));
      this.select(id, (ctrl && this.task!='settings'));
    
    this.drag_start = false;
    this.in_selection_before = false;
        
    // row was double clicked
    if (this.task=='mail' && this.list_rows && this.list_rows[id].clicked && !shift)
    if (this.task=='mail' && this.list_rows && this.list_rows[id].clicked && !ctrl)
      {
      this.show_message(id);
      return false;
@@ -1332,6 +1346,38 @@
  /*********************************************************/
  /*********        message compose methods        *********/
  /*********************************************************/
  this.change_identity = function(obj)
    {
    if (!obj || !obj.options)
      return false;
    var id = obj.options[obj.selectedIndex].value;
    var input_message = rcube_find_object('_message');
    var message = input_message ? input_message.value : '';
    // remove the 'old' signature
    if (this.env.identity && this.env.signatures && this.env.signatures[this.env.identity])
      {
      var sig = this.env.signatures[this.env.identity];
      if (p = message.lastIndexOf(sig))
        message = message.substring(0, p-1) + message.substring(p+sig.length, message.length);
      }
    // add the new signature string
    if (this.env.signatures && this.env.signatures[id])
      {
      var sig = this.env.signatures[id];
      message += '\n'+sig;
      }
    if (input_message && message)
      input_message.value = message;
    this.env.identity = id;
    };
  this.show_attachment_form = function(a)
@@ -1854,16 +1900,20 @@
    {
    if (folder)
      {
      for (var id in this.env.subscriptionrows)
        if (this.env.subscriptionrows[id]==folder)
          break;
      var row;
      if (id && (row = document.getElementById(id)))
        row.style.display = 'none';
      this.http_request('delete-folder', '_mboxes='+escape(folder));
      }
    };
  this.remove_folder_row = function(folder)
    {
    for (var id in this.env.subscriptionrows)
      if (this.env.subscriptionrows[id]==folder)
        break;
    var row;
    if (id && (row = document.getElementById(id)))
      row.style.display = 'none';
    };
@@ -2491,6 +2541,21 @@
      return false;
    }
  // check if Shift-key is pressed on event
  this.check_ctrlkey = function(e)
    {
    if(!e && window.event)
      e = window.event;
    if(bw.linux && bw.ns4 && e.modifiers)
      return true;
   else if (bw.mac)
       return this.check_shiftkey(e);
    else if((bw.ns4 && e.modifiers & Event.CTRL_MASK) || (e && e.ctrlKey))
      return true;
    else
      return false;
    }
  this.get_mouse_pos = function(e)
    {
program/localization/de/labels.inc
@@ -109,6 +109,7 @@
$labels['compose']  = 'Neue Nachricht verfassen';
$labels['sendmessage']  = 'Nachricht jetzt senden';
$labels['addattachment']  = 'Datei anfügen';
$labels['charset']  = 'Zeichensatz';
$labels['attachments'] = 'Anhänge';
$labels['upload'] = 'Hochladen';
@@ -121,7 +122,6 @@
$labels['highest'] = 'Höchste';
$labels['nosubject']  = '(kein Betreff)';
$labels['showimages'] = 'Bilder anzeigen';
@@ -165,7 +165,7 @@
$labels['language']  = 'Sprache';
$labels['timezone']  = 'Zeitzone';
$labels['pagesize']  = 'Einträge pro Seite';
$labels['signature'] = 'Signatur';
$labels['folders']  = 'Ordner';
$labels['foldername']  = 'Ordnername';
program/localization/de/messages.inc
@@ -54,6 +54,8 @@
$messages['errorsaving'] = 'Beim Speichern ist ein Fehler aufgetreten';
$messages['deletefolderconfirm']  = 'Wollen Sie diesen Ordner wirklich löschen?';
$messages['formincomplete']    = 'Das Formular wurde nicht vollständig ausgefüllt';
$messages['noemailwarning']    = 'Bitte geben Sie eine gültige E-Mail-Adresse ein';
program/localization/ee/labels.inc
New file
@@ -0,0 +1,182 @@
<?php
/*
 +-----------------------------------------------------------------------+
 | language/ee/labels.inc                                                |
 |                                                                       |
 | Language file of the RoundCube Webmail client                         |
 | Copyright (C) 2005, RoundQube Dev. - Switzerland                      |
 | Licensed under the GNU GPL                                            |
 |                                                                       |
 +-----------------------------------------------------------------------+
 | Author: Ãœllar Pajus <yllar.pajus@gmail.com>                           |
 +-----------------------------------------------------------------------+
 $Id$
*/
$labels = array();
// login page
$labels['username']  = 'Kasutajanimi';
$labels['password']  = 'Parool';
$labels['server']    = 'Server';
$labels['login']     = 'Logi sisse';
// taskbar
$labels['logout']   = 'Logi välja';
$labels['mail']     = 'Postkast';
$labels['settings'] = 'Seaded';
$labels['addressbook'] = 'Aadressiraamat';
// mailbox names
$labels['inbox']  = 'Sissetulevad';
$labels['sent']   = 'Saadetud';
$labels['trash']  = 'Prügikast';
$labels['drafts'] = 'Ootel';
$labels['junk']   = 'Rämps';
// message listing
$labels['subject'] = 'Pealkiri';
$labels['from']    = 'Saatja';
$labels['to']      = 'Saaja';
$labels['cc']      = 'Koopia';
$labels['bcc']     = 'Bcc';
$labels['replyto'] = 'Vastus aadressile';
$labels['date']    = 'Kuupäev';
$labels['size']    = 'Suurus';
$labels['priority'] = 'Tähtsus';
$labels['organization'] = 'Organisatsioon';
// aliases
$labels['reply-to'] = $labels['replyto'];
$labels['mailboxlist'] = 'Kaustad';
$labels['messagesfromto'] = 'Kirjed $from kuni $to, kokku $count';
$labels['messagenrof'] = 'Kiri $nr, kokku $count';
$labels['moveto']   = 'liiguta kausta...';
$labels['download'] = 'lae arvutisse';
$labels['filename'] = 'Faili nimi';
$labels['filesize'] = 'Faili suurus';
$labels['preferhtml'] = 'Eelista HTMLi';
$labels['htmlmessage'] = 'HTML kirjad';
$labels['prettydate'] = 'Kenad kuupäevad';
$labels['addtoaddressbook'] = 'Lisa aadressiraamatusse';
// weekdays short
$labels['sun'] = 'P';
$labels['mon'] = 'E';
$labels['tue'] = 'T';
$labels['wed'] = 'K';
$labels['thu'] = 'N';
$labels['fri'] = 'R';
$labels['sat'] = 'L';
// weekdays long
$labels['sunday']    = 'Pühapäev';
$labels['monday']    = 'Esmaspäev';
$labels['tuesday']   = 'Teisipäev';
$labels['wednesday'] = 'Kolmapäev';
$labels['thursday']  = 'Neljapäev';
$labels['friday']    = 'Reede';
$labels['saturday']  = 'Laupäev';
$labels['today'] = 'Täna';
// toolbar buttons
$labels['writenewmessage']  = 'Kirjuta uus kiri';
$labels['replytomessage']   = 'Vasta kirjale';
$labels['replytoallmessage'] = 'Vasta saatjale ja teistele kirja saanutele';
$labels['forwardmessage']   = 'Edasta see kiri';
$labels['deletemessage']    = 'Liiguta kiri prügikasti';
$labels['printmessage']     = 'Trüki kiri';
$labels['previousmessages'] = 'Näita eelmisi kirju';
$labels['nextmessages']     = 'Näita järgmisi kirju';
$labels['backtolist']       = 'Tagasi kirjade nimekirja';
$labels['viewsource']       = 'Näita lähtekoodi';
$labels['select'] = 'Vali';
$labels['all'] = 'kõik';
$labels['none'] = 'mitte midagi';
$labels['unread'] = 'mitte loetud';
// message compose
$labels['compose']  = 'Koosta kiri';
$labels['sendmessage']  = 'Saada kiri kohe';
$labels['addattachment']  = 'Lisa fail';
$labels['attachments'] = 'Manused';
$labels['upload'] = 'Kinnita manus';
$labels['close']  = 'Sulge';
$labels['low']     = 'Madal';
$labels['lowest']  = 'Madalaim';
$labels['normal']  = 'Tavaline';
$labels['high']    = 'Kõrge';
$labels['highest'] = 'Kõrgeim';
$labels['nosubject']  = '(teema puudub)';
$labels['showimages'] = 'Näita pilte';
// address boook
$labels['name']      = 'Näidatav nimi';
$labels['firstname'] = 'Eesnimi';
$labels['surname']   = 'Perekonnanimi';
$labels['email']     = 'E-Mail';
$labels['addcontact'] = 'Lisa uus kontakt';
$labels['editcontact'] = 'Muuda kontakti';
$labels['edit']   = 'Muuda';
$labels['cancel'] = 'Katkesta';
$labels['save']   = 'Salvesta';
$labels['delete'] = 'Kustuta';
$labels['newcontact']     = 'Loo uus sissekanne';
$labels['deletecontact']  = 'Kustuta märgistatud kontaktid';
$labels['composeto']      = 'Kirjuta kiri';
$labels['contactsfromto'] = 'Kirjed $from kuni $to, kokku $count';
$labels['print']          = 'Trüki';
$labels['export']         = 'Ekspordi';
// settings
$labels['settingsfor']  = 'Kasutajaeelistused kontole';
$labels['preferences']  = 'Eelistused';
$labels['userpreferences']  = 'Kasutaja eelistused';
$labels['editpreferences']  = 'Muuda kasutaja eelistusi';
$labels['identities']  = 'Identiteedid';
$labels['manageidentities']  = 'Halda selle konto identiteete';
$labels['newidentity']  = 'Uus identiteet';
$labels['newitem']  = 'Uus sissekanne';
$labels['edititem']  = 'Muuda sissekannet';
$labels['setdefault']  = 'Muuda vaikeseadeks';
$labels['language']  = 'Keel';
$labels['timezone']  = 'Ajatsoon';
$labels['pagesize']  = 'Ridu lehe kohta';
$labels['folders']  = 'Kaustad';
$labels['foldername']  = 'Kausta nimi';
$labels['subscribed']  = 'Näitan';
$labels['create']  = 'Loo';
$labels['createfolder']  = 'Loo uus kaust';
$labels['deletefolder']  = 'Kustuta kaust';
$labels['managefolders']  = 'Manage folders';
$labels['sortby'] = 'Järjesta';
$labels['sortasc']  = 'Järjesta kasvavalt';
$labels['sortdesc'] = 'Järjesta kahanevalt';
?>
program/localization/ee/messages.inc
New file
@@ -0,0 +1,80 @@
<?php
/*
 +-----------------------------------------------------------------------+
 | language/ee/messages.inc                                              |
 |                                                                       |
 | Language file of the RoundCube Webmail client                         |
 | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
 | Licensed under the GNU GPL                                            |
 |                                                                       |
 +-----------------------------------------------------------------------+
 | Author: Ãœllar Pajus <yllar.pajus@gmail.com>                           |
 +-----------------------------------------------------------------------+
 $Id$
*/
$messages = array();
$messages['loginfailed']  = 'Sisselogimine ebaõnnestus';
$messages['cookiesdisabled'] = 'Sinu veebilehitseja ei võta präänikuid vastu';
$messages['sessionerror'] = 'Sinu sessioon on aegunud või vigane';
$messages['imaperror'] = 'Ei Ãµnnestunud IMAP serveriga Ã¼hendust luua';
$messages['nomessagesfound'] = 'Postkast paistab tühi olevat';
$messages['loggedout'] = 'Sinu sessioon on edukalt lõpetatud. Nägemiseni!';
$messages['mailboxempty'] = 'Postkast on tühi';
$messages['loading'] = 'Laen...';
$messages['loadingdata'] = 'Laen andmeid...';
$messages['sendingmessage'] = 'Saadan kirja...';
$messages['messagesent'] = 'Kiri edukalt saadetud';
$messages['successfullysaved'] = 'Edukalt salvestatud';
$messages['addedsuccessfully'] = 'Kontakt lisati edukalt aadressiraamatusse';
$messages['contactexists'] = 'Sama e-maili aadressiga kontakt on juba olemas';
$messages['blockedimages'] = 'Sinu privaatsuse kaitsmiseks on selles kirjas välised pildid blokeeritud.';
$messages['encryptedmessage'] = 'See on krüpteeritud kiri ja kahjuks pole seda võimalik näidata. Andestust!';
$messages['nocontactsfound'] = 'Ei leidnud Ã¼htegi kontakti';
$messages['sendingfailed'] = 'Kirja saatmine ebaõnnestus';
$messages['errorsaving'] = 'Salvestamie ajal ilmnes viga';
$messages['errormoving'] = 'Ei suutnud seda kirja liigutada';
$messages['errordeleting'] = 'Ei suutnud seda kirja kustutada';
$messages['errordeleting'] = 'Ei suutnud seda kirja kustutada';
$messages['formincomplete']    = 'Vormi kõik väljad ei ole täidetud';
$messages['noemailwarning']    = 'Palun sisesta toimiv e-maili aadress';
$messages['nonamewarning']     = 'Palun sisesta nimi';
$messages['nopagesizewarning'] = 'Palun sisesta lehekülje suurus';
$messages['norecipientwarning'] = 'Palun sisesta vähemalt Ã¼ks kirjasaaja';
$messages['nosubjectwarning']  = 'Väli "Pealkiri" on tühi. Soovid selle Ã¤ra täita ?';
$messages['nobodywarning'] = 'Saadan selle kirja ilma tekstita ?';
?>
program/localization/en/labels.inc
@@ -109,6 +109,7 @@
$labels['compose']  = 'Compose a message';
$labels['sendmessage']  = 'Send the message now';
$labels['addattachment']  = 'Attach a file';
$labels['charset']  = 'Charset';
$labels['attachments'] = 'Attachments';
$labels['upload'] = 'Upload';
@@ -121,7 +122,6 @@
$labels['highest'] = 'Highest';
$labels['nosubject']  = '(no subject)';
$labels['showimages'] = 'Display images';
@@ -165,7 +165,7 @@
$labels['language']  = 'Language';
$labels['timezone']  = 'Time zone';
$labels['pagesize']  = 'Rows per page';
$labels['signature'] = 'Signature';
$labels['folders']  = 'Folders';
$labels['foldername']  = 'Folder name';
program/localization/en/messages.inc
@@ -60,13 +60,13 @@
$messages['errordeleting'] = 'Could not delete the message';
$messages['errordeleting'] = 'Could not delete the message';
$messages['deletefolderconfirm']  = 'Do you really want to delete this folder?';
$messages['formincomplete']    = 'The form was not completely filled out';
$messages['formincomplete'] = 'The form was not completely filled out';
$messages['noemailwarning']    = 'Please enter a valid email address';
$messages['noemailwarning'] = 'Please enter a valid email address';
$messages['nonamewarning']     = 'Please enter a name';
$messages['nonamewarning']  = 'Please enter a name';
$messages['nopagesizewarning'] = 'Please enter a page size';
program/localization/en_GB/labels.inc
@@ -120,6 +120,7 @@
$labels['high']    = 'High';
$labels['highest'] = 'Highest';
$labels['nosubject']  = '(no subject)';
$labels['showimages'] = 'Display images';
@@ -163,7 +164,7 @@
$labels['language']  = 'Language';
$labels['timezone']  = 'Time zone';
$labels['pagesize']  = 'Rows per page';
$labels['signature'] = 'Signature';
$labels['folders']  = 'Folders';
$labels['foldername']  = 'Folder name';
program/localization/index.inc
@@ -32,6 +32,7 @@
    'da'    => 'Dansk',
     'de'    => 'Deutsch',
     'es'    => 'Espa&ntilde;ol',
     'ee'    => 'Estonian',
    'fr'    => 'Fran&ccedil;ais', 
    'ga'    => 'Galician',
    'el'    => 'Greek',
program/steps/addressbook/delete.inc
@@ -24,7 +24,7 @@
if ($_GET['_cid'])
  {
  $DB->query("UPDATE ".get_table_name('contacts')."
              SET    del='1'
              SET    del=1
              WHERE  user_id=?
              AND    contact_id IN (".$_GET['_cid'].")",
              $_SESSION['user_id']);
@@ -40,7 +40,7 @@
  // count contacts for this user
  $sql_result = $DB->query("SELECT COUNT(contact_id) AS rows
                            FROM ".get_table_name('contacts')."
                            WHERE  del<>'1'
                            WHERE  del<>1
                            AND    user_id=?",
                            $_SESSION['user_id']);
                                   
@@ -60,7 +60,7 @@
    // get contacts from DB
    $sql_result = $DB->limitquery("SELECT * FROM ".get_table_name('contacts')."
                                   WHERE  del<>'1'
                                   WHERE  del<>1
                                   AND    user_id=?
                                   ORDER BY name",
                                   $start_row,
program/steps/addressbook/edit.inc
@@ -26,7 +26,7 @@
  $DB->query("SELECT * FROM ".get_table_name('contacts')."
             WHERE  contact_id=?
             AND    user_id=?
             AND    del<>'1'",
             AND    del<>1",
             $cid,
             $_SESSION['user_id']);
  
program/steps/addressbook/func.inc
@@ -43,7 +43,7 @@
  // count contacts for this user
  $sql_result = $DB->query("SELECT COUNT(contact_id) AS rows
                            FROM ".get_table_name('contacts')."
                            WHERE  del<>'1'
                            WHERE  del<>1
                            AND    user_id=?",
                            $_SESSION['user_id']);
@@ -56,7 +56,7 @@
    // get contacts from DB
    $sql_result = $DB->limitquery("SELECT * FROM ".get_table_name('contacts')."
                                   WHERE  del<>'1'
                                   WHERE  del<>1
                                   AND    user_id= ?
                                   ORDER BY name",
                                   $start_row,
@@ -173,7 +173,7 @@
  if ($max===NULL)
    {
    $sql_result = $DB->query("SELECT 1 FROM ".get_table_name('contacts')."
                              WHERE  del<>'1'
                              WHERE  del<>1
                              AND    user_id=?",
                              $_SESSION['user_id']);
program/steps/addressbook/list.inc
@@ -24,7 +24,7 @@
// count contacts for this user
$sql_result = $DB->query("SELECT COUNT(contact_id) AS rows
                          FROM ".get_table_name('contacts')."
                          WHERE  del<>'1'
                          WHERE  del<>1
                          AND    user_id=?",
                          $_SESSION['user_id']);
                                   
@@ -40,7 +40,7 @@
// get contacts from DB
$sql_result = $DB->limitquery("SELECT * FROM ".get_table_name('contacts')."
                               WHERE  del<>'1'
                               WHERE  del<>1
                               AND    user_id=?
                               ORDER BY name",
                               $start_row,
program/steps/addressbook/save.inc
@@ -52,7 +52,7 @@
                SET    changed=now(), ".join(', ', $a_write_sql)."
                WHERE  contact_id=?
                AND    user_id=?
                AND    del<>'1'",
                AND    del<>1",
                $_POST['_cid'],
                $_SESSION['user_id']);
                       
@@ -73,7 +73,7 @@
      $sql_result = $DB->query("SELECT * FROM ".get_table_name('contacts')."
                                WHERE  contact_id=?
                                AND    user_id=?
                                AND    del<>'1'",
                                AND    del<>1",
                               $_POST['_cid'],
                               $_SESSION['user_id']);
                         
@@ -109,7 +109,7 @@
  $sql_result = $DB->query("SELECT 1 FROM ".get_table_name('contacts')."
                            WHERE  user_id=?
                            AND    email=?
                            AND    del<>'1'",
                            AND    del<>1",
                           $_SESSION['user_id'],
                           $_POST['_email']);
@@ -134,11 +134,11 @@
  if (sizeof($a_insert_cols))
    {
    $DB->query("INSERT INTO ".get_table_name('contacts')."
                (user_id, changed, ".join(', ', $a_insert_cols).")
                VALUES (?, now(), ".join(', ', $a_insert_values).")",
                (user_id, changed, del, ".join(', ', $a_insert_cols).")
                VALUES (?, now(), 0, ".join(', ', $a_insert_values).")",
                $_SESSION['user_id']);
                       
    $insert_id = $DB->insert_id();
    $insert_id = $DB->insert_id(get_sequence_name('contacts'));
    }
    
  if ($insert_id)
program/steps/addressbook/show.inc
@@ -26,7 +26,7 @@
  $DB->query("SELECT * FROM ".get_table_name('contacts')."
              WHERE  contact_id=?
              AND    user_id=?
              AND    del<>'1'",
              AND    del<>1",
              $cid,
              $_SESSION['user_id']);
  
program/steps/mail/addcontact.inc
@@ -32,7 +32,7 @@
      $sql_result = $DB->query("SELECT 1 FROM ".get_table_name('contacts')."
                                WHERE  user_id=?
                                AND    email=?
                                AND    del<>'1'",
                                AND    del<>1",
                                $_SESSION['user_id'],$contact['mailto']);
    // contact entry with this mail address exists
@@ -42,13 +42,13 @@
    else if ($contact['mailto'])
      {
      $DB->query("INSERT INTO ".get_table_name('contacts')."
                  (user_id, changed, name, email)
                  VALUES (?, now(), ?, ?)",
                  (user_id, changed, del, name, email)
                  VALUES (?, now(), 0, ?, ?)",
                  $_SESSION['user_id'],
                  $contact['name'],
                  $contact['mailto']);
      $added = $DB->insert_id();
      $added = $DB->insert_id(get_sequence_name('contacts'));
      }
    }
program/steps/mail/compose.inc
@@ -88,50 +88,20 @@
  switch ($part)
    {
    case 'from':
      // pass the following attributes to the form class
      $field_attrib = array('name' => '_from');
      foreach ($attrib as $attr => $value)
        if (in_array($attr, array('id', 'class', 'style', 'size', 'tabindex')))
          $field_attrib[$attr] = $value;
      // get this user's identities
      $sql_result = $DB->query("SELECT identity_id, name, email
                                FROM   ".get_table_name('identities')." WHERE  user_id=?
                                AND    del<>'1'
                                ORDER BY ".$DB->quoteIdentifier('default')." DESC, name ASC",
                                $_SESSION['user_id']);
      if ($DB->num_rows($sql_result))
        {
        $select_from = new select($field_attrib);
        while ($sql_arr = $DB->fetch_assoc($sql_result))
          $select_from->add(format_email_recipient($sql_arr['email'], $sql_arr['name']), $sql_arr['identity_id']);
        $out = $select_from->show($_POST['_from']);
        }
      else
        {
        $input_from = new textfield($field_attrib);
        $out = $input_from->show($_POST['_from']);
        }
      if ($form_start)
        $out = $form_start.$out;
      return $out;
      return rcmail_compose_header_from($attrib);
    case 'to':
      $fname = '_to';
      $header = 'to';
      // we have contact id's as get parameters
      if (!empty($_GET['_to']) && preg_match('/[0-9]+,?/', $_GET['_to']))
      if (!empty($_GET['_to']) && preg_match('/^([0-9]+,?)+$/', $_GET['_to']))
        {
        $a_recipients = array();
        $sql_result = $DB->query("SELECT name, email
                                  FROM ".get_table_name('contacts')." WHERE user_id=?
                                  AND    del<>'1'
                                  FROM ".get_table_name('contacts')."
                                  WHERE user_id=?
                                  AND    del<>1
                                  AND    contact_id IN (".$_GET['_to'].")",
                                  $_SESSION['user_id']);
                                         
@@ -229,71 +199,91 @@
  }
/*function rcube_compose_headers($attrib)
function rcmail_compose_header_from($attrib)
  {
  global $CONFIG, $OUTPUT;
  global $IMAP, $REPLY_MESSAGE, $DB, $OUTPUT, $JS_OBJECT_NAME;
  // pass the following attributes to the form class
  $field_attrib = array('name' => '_from');
  foreach ($attrib as $attr => $value)
    if (in_array($attr, array('id', 'class', 'style', 'size', 'tabindex')))
      $field_attrib[$attr] = $value;
  list($form_start, $form_end) = get_form_tags($attrib);
  // extract all recipients of the reply-message
  $a_recipients = array();
  if ($REPLY_MESSAGE && is_object($REPLY_MESSAGE['headers']))
    {
    $a_to = $IMAP->decode_address_list($REPLY_MESSAGE['headers']->to);
    foreach ($a_to as $addr)
      {
      if (!empty($addr['mailto']))
        $a_recipients[] = $addr['mailto'];
      }
  // allow the following attributes to be added to the headers table
  $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id', 'cellpadding', 'cellspacing', 'border'));
  $labels = array();
  $labels['from'] = rcube_label('from');
  $labels['to'] = rcube_label('to');
  $labels['cc'] = rcube_label('cc');
  $labels['bcc'] = rcube_label('bcc');
  $labels['replyto'] = rcube_label('replyto');
    if (!empty($REPLY_MESSAGE['headers']->cc))
      {
      $a_cc = $IMAP->decode_address_list($REPLY_MESSAGE['headers']->cc);
      foreach ($a_cc as $addr)
        {
        if (!empty($addr['mailto']))
          $a_recipients[] = $addr['mailto'];
        }
      }
    }
  $input_from = new textfield(array('name' => '_from', 'size' => 30));
  $input_to = new textfield(array('name' => '_to', 'size' => 30));
  $input_cc = new textfield(array('name' => '_cc', 'size' => 30));
  $input_bcc = new textfield(array('name' => '_bcc', 'size' => 30));
  $input_replyto = new textfield(array('name' => '_replyto', 'size' => 30));
  // get this user's identities
  $sql_result = $DB->query("SELECT identity_id, name, email, signature
                            FROM   ".get_table_name('identities')."
                            WHERE user_id=?
                            AND    del<>1
                            ORDER BY ".$DB->quoteIdentifier('standard')." DESC, name ASC",
                           $_SESSION['user_id']);
  if ($DB->num_rows($sql_result))
    {
    $from_id = 0;
    $a_signatures = array();
    $field_attrib['onchange'] = "$JS_OBJECT_NAME.change_identity(this)";
    $select_from = new select($field_attrib);
    while ($sql_arr = $DB->fetch_assoc($sql_result))
      {
      $select_from->add(format_email_recipient($sql_arr['email'], $sql_arr['name']), $sql_arr['identity_id']);
  $fields = array();
  $fields['from'] = $input_from->show($_POST['_from']);
  $fields['to'] = $input_to->show($_POST['_to']);
  $fields['cc'] = $input_cc->show($_POST['_cc']);
  $fields['bcc'] = $input_bcc->show($_POST['_bcc']);
  $fields['replyto'] = $input_replyto->show($_POST['_replyto']);
      // add signature to array
      if (!empty($sql_arr['signature']))
        $a_signatures[$sql_arr['identity_id']] = $sql_arr['signature'];
      // set identity if it's one of the reply-message recipients
      if (in_array($sql_arr['email'], $a_recipients))
        $from_id = $sql_arr['identity_id'];
      }
    // overwrite identity selection with post parameter
    if (isset($_POST['_from']))
      $from_id = $_POST['_from'];
  $out = <<<EOF
$form_start
<table$attrib_str><tr>
    $out = $select_from->show($from_id);
<td class="title">$labels[from]</td>
<td>$fields[from]</td>
    // add signatures to client
    $OUTPUT->add_script(sprintf("%s.set_env('signatures', %s);", $JS_OBJECT_NAME, array2js($a_signatures)));
    }
  else
    {
    $input_from = new textfield($field_attrib);
    $out = $input_from->show($_POST['_from']);
    }
  if ($form_start)
    $out = $form_start.$out;
</tr><tr>
<td class="title">$labels[to]</td>
<td>$fields[to]</td>
</tr><tr>
<td class="title">$labels[cc]</td>
<td>$fields[cc]</td>
</tr><tr>
<td class="title">$labels[bcc]</td>
<td>$fields[bcc]</td>
</tr><tr>
<td class="title">$labels[replyto]</td>
<td>$fields[replyto]</td>
</tr></table>
$form_end
EOF;
  return $out;
  return $out;
  }
*/
function rcmail_compose_body($attrib)
  {
@@ -360,6 +350,14 @@
  $pefix = sprintf("\n\n\nOn %s, %s wrote:\n",
           $REPLY_MESSAGE['headers']->date,
           $IMAP->decode_header($REPLY_MESSAGE['headers']->from));
  // try to remove the signature
  if ($sp = strrpos($body, '--'))
    {
    if ($body{$sp+3}==' ' || $body{$sp+3}=="\n" || $body{$sp+3}=="\r")
      $body = substr($body, 0, $sp-1);
    }
  return $pefix.$body;
  }
@@ -596,7 +594,7 @@
$sql_result = $DB->query("SELECT name, email
                          FROM ".get_table_name('contacts')." WHERE  user_id=?
                          AND  del<>'1'",$_SESSION['user_id']);
                          AND  del<>1",$_SESSION['user_id']);
                                   
if ($DB->num_rows($sql_result))
  {        
program/steps/mail/func.inc
@@ -305,26 +305,41 @@
    // make sort links
    $sort = '';
    if (in_array($col, $a_sort_cols) && (!empty($attrib['sortdescbutton']) || !empty($attrib['sortascbutton'])))
    if ($IMAP->get_capability('sort') && in_array($col, $a_sort_cols))
      {
      $sort = '&nbsp;&nbsp;';
      // have buttons configured
      if (!empty($attrib['sortdescbutton']) || !empty($attrib['sortascbutton']))
        {
        $sort = '&nbsp;&nbsp;';
      // asc link
      if (!empty($attrib['sortascbutton']))
        {
        $sort .= rcube_button(array('command' => 'sort',
                                    'prop' => $col.'_ASC',
                                    'image' => $attrib['sortascbutton'],
                                    'title' => 'sortasc'));
        }
        // asc link
        if (!empty($attrib['sortascbutton']))
          {
          $sort .= rcube_button(array('command' => 'sort',
                                      'prop' => $col.'_ASC',
                                      'image' => $attrib['sortascbutton'],
                                      'align' => 'absmiddle',
                                      'title' => 'sortasc'));
          }
        
      // desc link
      if (!empty($attrib['sortdescbutton']))
        // desc link
        if (!empty($attrib['sortdescbutton']))
          {
          $sort .= rcube_button(array('command' => 'sort',
                                      'prop' => $col.'_DESC',
                                      'image' => $attrib['sortdescbutton'],
                                      'align' => 'absmiddle',
                                      'title' => 'sortdesc'));
          }
        }
      // just add a link tag to the header
      else
        {
        $sort .= rcube_button(array('command' => 'sort',
                                    'prop' => $col.'_DESC',
                                    'image' => $attrib['sortdescbutton'],
                                    'title' => 'sortdesc'));
        $col_name = sprintf('<a href="./#sort" onclick="return %s.command(\'sort\',\'%s\',this)" title="%s">%s</a>',
                            $JS_OBJECT_NAME,
                            $col,
                            rcube_label('sortby'),
                            $col_name);
        }
      }
      
@@ -1128,20 +1143,76 @@
// get source code of a specific message and cache it
function rcmail_message_source($uid)
  {
  global $IMAP, $DB;
  global $IMAP, $DB, $CONFIG;
  // get message ID if uid is given
  $headers = $IMAP->get_headers($uid);
  // get message ID if uid is given
  $cache_key = $IMAP->mailbox.'.msg';
  $cached = $IMAP->get_cached_message($cache_key, $uid, FALSE);
  // message is cached in database
  if ($cached && !empty($cached->body))
    return $cached->body;
  if (!$cached)
    $headers = $IMAP->get_headers($uid);
  else
    $headers = &$cached;
  $message_id = $headers->messageID;
  
  // get cached message source
  $msg_source = rcube_read_cache($message_id);
  $temp_dir = $CONFIG['temp_dir'].(!eregi('\/$', $CONFIG['temp_dir']) ? '/' : '');
  $cache_dir = $temp_dir.$_SESSION['client_id'];
  $cache_path = $cache_dir.'/'.$message_id;
  // get message from server and cache it
  if (!$msg_source)
  // message is cached in temp dir
  if (is_dir($cache_dir) && is_file($cache_path))
    {
    $msg_source = $IMAP->get_raw_body($uid);
    rcube_write_cache($message_id, $msg_source, TRUE);
    if ($fp = fopen($cache_path, 'r'))
      {
      $msg_source = fread($fp, filesize($cache_path));
      fclose($fp);
      return $msg_source;
      }
    }
  // get message from server
  $msg_source = $IMAP->get_raw_body($uid);
  // let's cache the message body within the database
  if ($CONFIG['enable_caching'] && $cached && ($CONFIG['db_max_length'] -300) > $headers->size)
    {
    $DB->query("UPDATE ".get_table_name('messages')."
                SET    body=?
                WHERE  user_id=?
                AND    cache_key=?
                AND    uid=?",
               $msg_source,
               $_SESSION['user_id'],
               $cache_key,
               $uid);
    return $msg_source;
    }
  // create dir for caching
  if (!is_dir($cache_dir))
    $dir = mkdir($cache_dir);
  else
    $dir = true;
  // attempt to write a file with the message body
  if ($dir && ($fp = fopen($cache_path, 'w')))
    {
    fwrite($fp, $msg_source);
    fclose($fp);
    }
  else
    {
    raise_error(array('code' => 403, 'type' => 'php', 'line' => __LINE__, 'file' => __FILE__,
                      'message' => "Failed to write to temp dir"), TRUE, FALSE);
    }
  return $msg_source;
program/steps/mail/sendmail.inc
@@ -46,7 +46,7 @@
                            FROM ".get_table_name('identities')."
                            WHERE  identity_id=?
                            AND    user_id=?
                            AND    del<>'1'",
                            AND    del<>1",
                            $id,$_SESSION['user_id']);
                                   
  if ($DB->num_rows($sql_result))
@@ -78,8 +78,8 @@
  $CHARSET = 'ISO-8859-1';
$mailto_regexp = array('/,\s*[\r\n]+/', '/[\r\n]+/', '/,\s*$/m');
$mailto_replace = array(' ', ', ', '');
$mailto_regexp = array('/[,;]\s*[\r\n]+/', '/[\r\n]+/', '/[,;]\s*$/m');
$mailto_replace = array(', ', ', ', '');
// repalce new lines and strip ending ', '
$mailto = preg_replace($mailto_regexp, $mailto_replace, stripslashes($_POST['_to']));
@@ -175,13 +175,16 @@
  foreach ($_FILES['_attachments']['tmp_name'] as $i => $filepath)
    $MAIL_MIME->addAttachment($filepath, $files['type'][$i], $files['name'][$i], TRUE);
$message_charset = isset($_POST['_charset']) ? $_POST['_charset'] : $CHARSET;
// encoding settings for mail composing
$message_param = array('text_encoding' => '7bit',
                       'html_encoding' => 'quoted-printable',
                       'head_encoding' => 'quoted-printable',
                       'head_charset'  => $CHARSET,
                       'html_charset'  => $CHARSET,
                       'text_charset'  => $CHARSET);
                       'head_charset'  => $message_charset,
                       'html_charset'  => $message_charset,
                       'text_charset'  => $message_charset);
// compose message body and get headers
$msg_body = $MAIL_MIME->get($message_param);
program/steps/settings/delete_identity.inc
@@ -24,7 +24,7 @@
if ($_GET['_iid'])
  {
  $DB->query("UPDATE ".get_table_name('identities')."
              SET    del='1'
              SET    del=1
              WHERE  user_id=?
              AND    identity_id IN (".$_GET['_iid'].")",
              $_SESSION['user_id']);
program/steps/settings/edit_identity.inc
@@ -25,7 +25,7 @@
  $DB->query("SELECT * FROM ".get_table_name('identities')."
              WHERE  identity_id=?
              AND    user_id=?
              AND    del<>'1'",
              AND    del<>1",
              $id,
              $_SESSION['user_id']);
  
@@ -63,7 +63,8 @@
                       'organization' => array('type' => 'text'),
                       'reply-to'     => array('type' => 'text', 'label' => 'replyto'),
                       'bcc'          => array('type' => 'text'),
                       'default'      => array('type' => 'checkbox', 'label' => 'setdefault'));
                       'signature'      => array('type' => 'textarea'),
                       'standard'     => array('type' => 'checkbox', 'label' => 'setdefault'));
  // a specific part is requested
program/steps/settings/func.inc
@@ -146,9 +146,9 @@
  // get contacts from DB
  $sql_result = $DB->query("SELECT * FROM ".get_table_name('identities')."
                            WHERE  del<>'1'
                            WHERE  del<>1
                            AND    user_id=?
                            ORDER BY ".$DB->quoteIdentifier('default')." DESC, name ASC",
                            ORDER BY standard DESC, name ASC",
                            $_SESSION['user_id']);
program/steps/settings/manage_folders.inc
@@ -69,10 +69,15 @@
else if ($_action=='delete-folder')
  {
  if (strlen($_GET['_mboxes']))
    $IMAP->delete_mailbox(explode(',', $_GET['_mboxes']));
    $deleted = $IMAP->delete_mailbox(explode(',', $_GET['_mboxes']));
  if ($_GET['_remote'])
    rcube_remote_response('// deleted');
  if ($_GET['_remote'] && $deleted)
    rcube_remote_response(sprintf("this.remove_folder_row('%s')", rep_specialchars_output($_GET['_mboxes'], 'js')));
  else if ($_GET['_remote'])
    {
    $commands = show_message('errorsaving', 'error');
    rcube_remote_response($commands);
    }
  }
@@ -174,5 +179,9 @@
  }
// add some labels to client
rcube_add_label('deletefolderconfirm');
parse_template('managefolders');
?>
program/steps/settings/save_identity.inc
@@ -19,7 +19,7 @@
*/
$a_save_cols = array('name', 'email', 'organization', 'reply-to', 'bcc', 'default');
$a_save_cols = array('name', 'email', 'organization', 'reply-to', 'bcc', 'standard', 'signature');
// check input
@@ -51,7 +51,7 @@
                SET ".join(', ', $a_write_sql)."
                WHERE  identity_id=?
                AND    user_id=?
                AND    del<>'1'",
                AND    del<>1",
                $_POST['_iid'],
                $_SESSION['user_id']);
                       
@@ -64,10 +64,10 @@
    // mark all other identities as 'not-default'
    $DB->query("UPDATE ".get_table_name('identities')."
                SET ".$DB->quoteIdentifier('default')."='0'
                SET ".$DB->quoteIdentifier('standard')."='0'
                WHERE  user_id=?
                AND    identity_id<>?
                AND    del<>'1'",
                AND    del<>1",
                $_SESSION['user_id'],
                $_POST['_iid']);
    
@@ -106,8 +106,8 @@
                (user_id, ".join(', ', $a_insert_cols).")
                VALUES (?, ".join(', ', $a_insert_values).")",
                $_SESSION['user_id']);
    $insert_id = $DB->insert_id();
    $insert_id = $DB->insert_id(get_sequence_name('identities'));
    }
    
  if ($insert_id)
skins/default/images/sort_asc.gif
skins/default/images/sort_desc.gif
skins/default/mail.css
@@ -75,9 +75,10 @@
  position: absolute;
  top: 60px;
  right: 40px;
  width: 200px;
  width: 220px;
  height: 20px;
  text-align: right;
  white-space: nowrap;
}
#messagecountbar span
@@ -330,6 +331,26 @@
#messagelist thead tr td.sortedDESC
{
  background-image: url(images/listheader_dark.gif); 
}
#messagelist thead tr td.sortedASC a
{
  background: url(images/sort_asc.gif) top right no-repeat;
}
#messagelist thead tr td.sortedDESC a
{
  background: url(images/sort_desc.gif) top right no-repeat;
}
#messagelist thead tr td a,
#messagelist thead tr td a:hover
{
  display: block;
  width: auto !important;
  width: 100%;
  color: #333333;
  text-decoration: none;
}
#messagelist tbody tr td
@@ -645,9 +666,10 @@
#compose-body
{
  margin-top: 10px;
  margin-bottom: 5px;
  width: 99% !important;
  width: 95%;
  height: 95%;
  height: 90%;
  min-height: 300px;
  font-size: 9pt;
  font-family: "Courier New", Courier, monospace;
@@ -701,5 +723,4 @@
{
  margin-top: 8px;
}
skins/default/pngbehavior.htc
@@ -40,7 +40,7 @@
      element.src = blankSrc;
      // set filter
      element.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" +
                                     src + "',sizingMethod='scale')";
                                     src + "',sizingMethod='crop')";
   }
   else {
      // remove filter
skins/default/templates/compose.html
@@ -87,8 +87,9 @@
</tr><tr>
<td style="width:100%; height:100%; vertical-align:top;">
<roundcube:object name="composeBody" id="compose-body" form="form" cols="80" rows="20" warp="virtual" tabindex="7" />
<td style="width:100%; height:95%; vertical-align:top;">
<roundcube:object name="composeBody" id="compose-body" form="form" cols="80" rows="20" warp="virtual" tabindex="7" /><br />
<roundcube:label name="charset" />:&nbsp;<roundcube:object name="charsetSelector" tabindex="8" />
</td>
</tr></table>
skins/default/templates/mail.html
@@ -37,9 +37,7 @@
  messageIcon="/images/icons/dot.png"
  unreadIcon="/images/icons/unread.png"
  repliedIcon="/images/icons/replied.png"
  attachmentIcon="/images/icons/attachment.png"
  sortDescButton="/images/buttons/up_arrow.png"
  sortAscButton="/images/buttons/down_arrow.png" />
  attachmentIcon="/images/icons/attachment.png" />
</div>
<div id="listcontrols">