diff options
author | thomascube <thomas@roundcube.net> | 2009-04-19 17:44:29 +0000 |
---|---|---|
committer | thomascube <thomas@roundcube.net> | 2009-04-19 17:44:29 +0000 |
commit | cc97ea0559af1a92a54dbcdf738ee4d95e67d3ff (patch) | |
tree | f9e18128e5a90abb06f079b09f8cd9ed92044faf /program/include | |
parent | fb253ee9a89e2da779d11058f1f0c63c314b2840 (diff) |
Merged branch devel-api (from r2208 to r2387) back into trunk (omitting some sample plugins)
Diffstat (limited to 'program/include')
-rw-r--r-- | program/include/html.php | 30 | ||||
-rwxr-xr-x | program/include/iniset.php | 2 | ||||
-rw-r--r-- | program/include/main.inc | 25 | ||||
-rw-r--r-- | program/include/rcmail.php | 44 | ||||
-rw-r--r-- | program/include/rcube_addressbook.php | 169 | ||||
-rw-r--r-- | program/include/rcube_config.php | 1 | ||||
-rw-r--r-- | program/include/rcube_contacts.php | 35 | ||||
-rw-r--r-- | program/include/rcube_html_page.php | 11 | ||||
-rw-r--r-- | program/include/rcube_imap.php | 31 | ||||
-rw-r--r-- | program/include/rcube_json_output.php | 26 | ||||
-rw-r--r-- | program/include/rcube_ldap.php | 2 | ||||
-rw-r--r-- | program/include/rcube_message.php | 3 | ||||
-rw-r--r-- | program/include/rcube_plugin.php | 196 | ||||
-rw-r--r-- | program/include/rcube_plugin_api.php | 312 | ||||
-rwxr-xr-x | program/include/rcube_template.php | 112 | ||||
-rw-r--r-- | program/include/rcube_user.php | 26 | ||||
-rw-r--r-- | program/include/rcube_vcard.php | 4 |
17 files changed, 875 insertions, 154 deletions
diff --git a/program/include/html.php b/program/include/html.php index 01ad41544..78e696cb6 100644 --- a/program/include/html.php +++ b/program/include/html.php @@ -33,7 +33,7 @@ class html protected $content; public static $common_attrib = array('id','class','style','title','align'); - public static $containers = array('iframe','div','span','p','h1','h2','h3','form','textarea','table','tr','th','td','style'); + public static $containers = array('iframe','div','span','p','h1','h2','h3','form','textarea','table','tr','th','td','style','script'); public static $lc_tags = true; /** @@ -599,6 +599,34 @@ class html_table extends html $this->header[] = $cell; } + /** + * Remove a column from a table + * Useful for plugins making alterations + * + * @param string $class + */ + public function remove_column($class) + { + // Remove the header + foreach($this->header as $index=>$header){ + if($header->attrib['class'] == $class){ + unset($this->header[$index]); + break; + } + } + + // Remove cells from rows + foreach($this->rows as $i=>$row){ + foreach($row->cells as $j=>$cell){ + if($cell->attrib['class'] == $class){ + unset($this->rows[$i]->cells[$j]); + break; + } + } + } + } + + /** * Jump to next row * diff --git a/program/include/iniset.php b/program/include/iniset.php index 234f9ebcb..46f3750ea 100755 --- a/program/include/iniset.php +++ b/program/include/iniset.php @@ -22,7 +22,7 @@ // application constants -define('RCMAIL_VERSION', '0.2-trunk'); +define('RCMAIL_VERSION', '0.3-trunk'); define('RCMAIL_CHARSET', 'UTF-8'); define('JS_OBJECT_NAME', 'rcmail'); diff --git a/program/include/main.inc b/program/include/main.inc index b22be1aca..b3d0dab2a 100644 --- a/program/include/main.inc +++ b/program/include/main.inc @@ -88,9 +88,9 @@ function get_sequence_name($sequence) * @return string Localized text * @see rcmail::gettext() */ -function rcube_label($p) +function rcube_label($p, $domain=null) { - return rcmail::get_instance()->gettext($p); + return rcmail::get_instance()->gettext($p, $domain); } @@ -302,12 +302,11 @@ function rcube_charset_convert($str, $from, $to=NULL) */ function rep_specialchars_output($str, $enctype='', $mode='', $newlines=TRUE) { - global $OUTPUT; static $html_encode_arr = false; static $js_rep_table = false; static $xml_rep_table = false; - $charset = $OUTPUT->get_charset(); + $charset = rcmail::get_instance()->config->get('charset', RCMAIL_CHARSET); $is_iso_8859_1 = false; if ($charset == 'ISO-8859-1') { $is_iso_8859_1 = true; @@ -692,11 +691,11 @@ function parse_attrib_string($str) preg_match_all('/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui', stripslashes($str), $regs, PREG_SET_ORDER); // convert attributes to an associative array (name => value) - if ($regs) - foreach ($regs as $attr) - { - $attrib[strtolower($attr[1])] = $attr[3] . $attr[4]; - } + if ($regs) { + foreach ($regs as $attr) { + $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]); + } + } return $attrib; } @@ -829,9 +828,13 @@ function format_email_recipient($email, $name='') */ function console() { + $args = func_get_args(); + if (class_exists('rcmail', false)) + rcmail::get_instance()->plugins->exec_hook('console', $args); + $msg = array(); - foreach (func_get_args() as $arg) - $msg[] = !is_string($arg) ? var_export($arg, true) : $arg; + foreach ($args as $arg) + $msg[] = !is_string($arg) ? var_export($arg, true) : $arg; if (!($GLOBALS['CONFIG']['debug_level'] & 4)) write_log('console', join(";\n", $msg)); diff --git a/program/include/rcmail.php b/program/include/rcmail.php index 9aad25b27..56fc2f5db 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -37,6 +37,7 @@ class rcmail public $db; public $imap; public $output; + public $plugins; public $task = 'mail'; public $action = ''; public $comm_path = './'; @@ -88,7 +89,7 @@ class rcmail $syslog_facility = $this->config->get('syslog_facility', LOG_USER); openlog($syslog_id, LOG_ODELAY, $syslog_facility); } - + // set task and action properties $this->set_task(strip_quotes(get_input_value('_task', RCUBE_INPUT_GPC))); $this->action = asciiwords(get_input_value('_action', RCUBE_INPUT_GPC)); @@ -131,6 +132,9 @@ class rcmail // create IMAP object if ($this->task == 'mail') $this->imap_init(); + + // create plugin API and load plugins + $this->plugins = rcube_plugin_api::get_instance(); } @@ -255,10 +259,19 @@ class rcmail $contacts = null; $ldap_config = (array)$this->config->get('ldap_public'); $abook_type = strtolower($this->config->get('address_book_type')); + + $plugin = $this->plugins->exec_hook('get_address_book', array('id' => $id, 'writeable' => $writeable)); - if ($id && $ldap_config[$id]) { + // plugin returned instance of a rcube_addressbook + if ($plugin['instance'] instanceof rcube_addressbook) { + $contacts = $plugin['instance']; + } + else if ($id && $ldap_config[$id]) { $contacts = new rcube_ldap($ldap_config[$id]); } + else if ($id === '0') { + $contacts = new rcube_contacts($this->db, $this->user->ID); + } else if ($abook_type == 'ldap') { // Use the first writable LDAP address book. foreach ($ldap_config as $id => $prop) { @@ -598,7 +611,7 @@ class rcmail * @param mixed Named parameters array or label name * @return string Localized text */ - public function gettext($attrib) + public function gettext($attrib, $domain=null) { // load localization files if not done yet if (empty($this->texts)) @@ -613,9 +626,12 @@ class rcmail $command_name = !empty($attrib['command']) ? $attrib['command'] : NULL; $alias = $attrib['name'] ? $attrib['name'] : ($command_name && $command_label_map[$command_name] ? $command_label_map[$command_name] : ''); - + + // check for text with domain + if ($domain && ($text_item = $this->texts[$domain.'.'.$alias])) + ; // text does not exist - if (!($text_item = $this->texts[$alias])) { + else if (!($text_item = $this->texts[$alias])) { /* raise_error(array( 'code' => 500, @@ -677,7 +693,7 @@ class rcmail * * @param string Language ID */ - public function load_language($lang = null) + public function load_language($lang = null, $add = array()) { $lang = $this->language_prop(($lang ? $lang : $_SESSION['language'])); @@ -707,6 +723,10 @@ class rcmail $_SESSION['language'] = $lang; } + + // append additional texts (from plugin) + if (is_array($add) && !empty($add)) + $this->texts += $add; } @@ -920,18 +940,20 @@ class rcmail { if (!is_array($p)) $p = array('_action' => @func_get_arg(0)); + + $task = $p['_task'] ? $p['_task'] : $p['task']; + if (!$task || !in_array($task, rcmail::$main_tasks)) + $task = $this->task; - if (!$p['task'] || !in_array($p['task'], rcmail::$main_tasks)) - $p['task'] = $this->task; - - $p['_task'] = $p['task']; + $p['_task'] = $task; unset($p['task']); $url = './'; $delm = '?'; - foreach (array_reverse($p) as $par => $val) + foreach (array_reverse($p) as $key => $val) { if (!empty($val)) { + $par = $key[0] == '_' ? $key : '_'.$key; $url .= $delm.urlencode($par).'='.urlencode($val); $delm = '&'; } diff --git a/program/include/rcube_addressbook.php b/program/include/rcube_addressbook.php new file mode 100644 index 000000000..9e970f213 --- /dev/null +++ b/program/include/rcube_addressbook.php @@ -0,0 +1,169 @@ +<?php + +/* + +-----------------------------------------------------------------------+ + | program/include/rcube_addressbook.php | + | | + | This file is part of the RoundCube Webmail client | + | Copyright (C) 2006-2009, RoundCube Dev. - Switzerland | + | Licensed under the GNU GPL | + | | + | PURPOSE: | + | Interface to the local address book database | + | | + +-----------------------------------------------------------------------+ + | Author: Thomas Bruederli <roundcube@gmail.com> | + +-----------------------------------------------------------------------+ + + $Id: $ + +*/ + + +/** + * Abstract skeleton of an address book/repository + * + * @package Addressbook + */ +abstract class rcube_addressbook +{ + /** public properties */ + var $primary_key; + var $readonly = true; + var $ready = false; + var $list_page = 1; + var $page_size = 10; + + /** + * Save a search string for future listings + * + * @param mixed Search params to use in listing method, obtained by get_search_set() + */ + abstract function set_search_set($filter); + + /** + * Getter for saved search properties + * + * @return mixed Search properties used by this class + */ + abstract function get_search_set(); + + /** + * Reset saved results and search parameters + */ + abstract function reset(); + + /** + * List the current set of contact records + * + * @param array List of cols to show + * @param int Only return this number of records, use negative values for tail + * @return array Indexed list of contact records, each a hash array + */ + abstract function list_records($cols=null, $subset=0); + + /** + * Search records + * + * @param array List of fields to search in + * @param string Search value + * @param boolean True if results are requested, False if count only + * @return Indexed list of contact records and 'count' value + */ + abstract function search($fields, $value, $strict=false, $select=true); + + /** + * Count number of available contacts in database + * + * @return object rcube_result_set Result set with values for 'count' and 'first' + */ + abstract function count(); + + /** + * Return the last result set + * + * @return object rcube_result_set Current result set or NULL if nothing selected yet + */ + abstract function get_result(); + + /** + * Get a specific contact record + * + * @param mixed record identifier(s) + * @param boolean True to return record as associative array, otherwise a result set is returned + * @return mixed Result object with all record fields or False if not found + */ + abstract function get_record($id, $assoc=false); + + /** + * Close connection to source + * Called on script shutdown + */ + function close() { } + + /** + * Set internal list page + * + * @param number Page number to list + * @access public + */ + function set_page($page) + { + $this->list_page = (int)$page; + } + + /** + * Set internal page size + * + * @param number Number of messages to display on one page + * @access public + */ + function set_pagesize($size) + { + $this->page_size = (int)$size; + } + + /** + * Create a new contact record + * + * @param array Assoziative array with save data + * @param boolean True to check for duplicates first + * @return The created record ID on success, False on error + */ + function insert($save_data, $check=false) + { + /* empty for read-only address books */ + } + + /** + * Update a specific contact record + * + * @param mixed Record identifier + * @param array Assoziative array with save data + * @return True on success, False on error + */ + function update($id, $save_cols) + { + /* empty for read-only address books */ + } + + /** + * Mark one or more contact records as deleted + * + * @param array Record identifiers + */ + function delete($ids) + { + /* empty for read-only address books */ + } + + /** + * Remove all records from the database + */ + function delete_all() + { + /* empty for read-only address books */ + } + +} +
\ No newline at end of file diff --git a/program/include/rcube_config.php b/program/include/rcube_config.php index 7be2b0d2c..1312a73de 100644 --- a/program/include/rcube_config.php +++ b/program/include/rcube_config.php @@ -74,6 +74,7 @@ class rcube_config // fix paths $this->prop['log_dir'] = $this->prop['log_dir'] ? unslashify($this->prop['log_dir']) : INSTALL_PATH . 'logs'; $this->prop['temp_dir'] = $this->prop['temp_dir'] ? unslashify($this->prop['temp_dir']) : INSTALL_PATH . 'temp'; + $this->prop['plugins_dir'] = $this->prop['plugins_dir'] ? unslashify($this->prop['plugins_dir']) : INSTALL_PATH . 'plugins'; // fix default imap folders encoding foreach (array('drafts_mbox', 'junk_mbox', 'sent_mbox', 'trash_mbox') as $folder) diff --git a/program/include/rcube_contacts.php b/program/include/rcube_contacts.php index 65d89ca2b..f440e5f8a 100644 --- a/program/include/rcube_contacts.php +++ b/program/include/rcube_contacts.php @@ -25,7 +25,7 @@ * * @package Addressbook */ -class rcube_contacts +class rcube_contacts extends rcube_addressbook { var $db = null; var $db_name = ''; @@ -60,30 +60,6 @@ class rcube_contacts /** - * Set internal list page - * - * @param number Page number to list - * @access public - */ - function set_page($page) - { - $this->list_page = (int)$page; - } - - - /** - * Set internal page size - * - * @param number Number of messages to display on one page - * @access public - */ - function set_pagesize($size) - { - $this->page_size = (int)$size; - } - - - /** * Save a search string for future listings * * @param string SQL params to use in listing method @@ -118,13 +94,6 @@ class rcube_contacts /** - * Close connection to source - * Called on script shutdown - */ - function close(){} - - - /** * List the current set of contact records * * @param array List of cols to show @@ -233,7 +202,7 @@ class rcube_contacts * * @return Result array or NULL if nothing selected yet */ - function get_result($as_res=true) + function get_result() { return $this->result; } diff --git a/program/include/rcube_html_page.php b/program/include/rcube_html_page.php index 78f6176bf..c83c6aeea 100644 --- a/program/include/rcube_html_page.php +++ b/program/include/rcube_html_page.php @@ -31,8 +31,8 @@ class rcube_html_page protected $scripts = array(); protected $charset = 'UTF-8'; - protected $script_tag_file = "<script type=\"text/javascript\" src=\"%s%s\"></script>\n"; - protected $script_tag = "<script type=\"text/javascript\">\n<!--\n%s\n\n//-->\n</script>\n"; + protected $script_tag_file = "<script type=\"text/javascript\" src=\"%s\"></script>\n"; + protected $script_tag = "<script type=\"text/javascript\">\n/* <![CDATA[ */\n%s\n/* ]]> */\n</script>"; protected $default_template = "<html>\n<head><title></title></head>\n<body></body>\n</html>"; protected $title = ''; @@ -53,6 +53,9 @@ class rcube_html_page public function include_script($file, $position='head') { static $sa_files = array(); + + if (!ereg('^https?://', $file) && $file[0] != '/') + $file = $this->scripts_path . $file; if (in_array($file, $sa_files)) { return; @@ -165,7 +168,7 @@ class rcube_html_page // definition of the code to be placed in the document header and footer if (is_array($this->script_files['head'])) { foreach ($this->script_files['head'] as $file) { - $__page_header .= sprintf($this->script_tag_file, $this->scripts_path, $file); + $__page_header .= sprintf($this->script_tag_file, $file); } } @@ -180,7 +183,7 @@ class rcube_html_page if (is_array($this->script_files['foot'])) { foreach ($this->script_files['foot'] as $file) { - $__page_footer .= sprintf($this->script_tag_file, $this->scripts_path, $file); + $__page_footer .= sprintf($this->script_tag_file, $file); } } diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php index da7c5bf88..e2b6c0d9a 100644 --- a/program/include/rcube_imap.php +++ b/program/include/rcube_imap.php @@ -55,6 +55,7 @@ class rcube_imap var $default_charset = 'ISO-8859-1'; var $default_folders = array('INBOX'); var $default_folders_lc = array('inbox'); + var $fetch_add_headers = ''; var $cache = array(); var $cache_keys = array(); var $cache_changes = array(); @@ -428,8 +429,16 @@ class rcube_imap if (is_array($a_mboxes)) return $a_mboxes; - // retrieve list of folders from IMAP server - $a_folders = iil_C_ListSubscribed($this->conn, $this->_mod_mailbox($root), $filter); + // Give plugins a chance to provide a list of mailboxes + $data = rcmail::get_instance()->plugins->exec_hook('list_mailboxes',array('root'=>$root,'filter'=>$filter)); + if (isset($data['folders'])) { + $a_folders = $data['folders']; + } + else{ + // retrieve list of folders from IMAP server + $a_folders = iil_C_ListSubscribed($this->conn, $this->_mod_mailbox($root), $filter); + } + if (!is_array($a_folders) || !sizeof($a_folders)) $a_folders = array(); @@ -775,7 +784,7 @@ class rcube_imap $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_header_index = iil_C_FetchHeaders($this->conn, $mailbox, $msgs, false, $this->fetch_add_headers); $deleted_count = 0; if (!empty($a_header_index)) @@ -829,14 +838,14 @@ class rcube_imap if ($this->sort_field && $this->search_sort_field != $this->sort_field) $this->search('', $this->search_string, $this->search_charset, $this->sort_field); - if ($this->sort_order == 'DESC') + if ($this->sort_order == 'DESC') $this->cache[$key] = array_reverse($this->search_set); - else - $this->cache[$key] = $this->search_set; + else + $this->cache[$key] = $this->search_set; } else { - $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, join(',', $this->search_set), $this->sort_field); + $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, join(',', $this->search_set), $this->sort_field, false); if ($this->sort_order=="ASC") asort($a_index); @@ -923,7 +932,7 @@ class rcube_imap // fetch complete headers and add to cache - $headers = iil_C_FetchHeader($this->conn, $mailbox, $id); + $headers = iil_C_FetchHeader($this->conn, $mailbox, $id, false, $this->fetch_add_headers); $this->add_message_cache($cache_key, $headers->id, $headers); } @@ -1062,7 +1071,7 @@ class rcube_imap if ($uid && ($headers = &$this->get_cached_message($mailbox.'.msg', $uid))) return $headers; - $headers = iil_C_FetchHeader($this->conn, $mailbox, $id, $is_uid, $bodystr); + $headers = iil_C_FetchHeader($this->conn, $mailbox, $id, $is_uid, $bodystr, $this->fetch_add_headers); // write headers cache if ($headers) @@ -2227,7 +2236,7 @@ class rcube_imap if ($cache_count==$msg_count) { // get highest index - $header = iil_C_FetchHeader($this->conn, $mailbox, "$msg_count"); + $header = iil_C_FetchHeader($this->conn, $mailbox, "$msg_count", false, $this->fetch_add_headers); $cache_uid = array_pop($cache_index); // uids of highest message matches -> cache seems OK @@ -2277,7 +2286,7 @@ class rcube_imap // featch headers if unserialize failed if (empty($this->cache[$cache_key][$uid])) - $this->cache[$cache_key][$uid] = iil_C_FetchHeader($this->conn, preg_replace('/.msg$/', '', $key), $uid, true); + $this->cache[$cache_key][$uid] = iil_C_FetchHeader($this->conn, preg_replace('/.msg$/', '', $key), $uid, true, $this->fetch_add_headers); } } diff --git a/program/include/rcube_json_output.php b/program/include/rcube_json_output.php index 0bd3a2bad..a14f4ae14 100644 --- a/program/include/rcube_json_output.php +++ b/program/include/rcube_json_output.php @@ -196,26 +196,33 @@ class rcube_json_output * @return void * @deprecated */ - public function remote_response($add='', $flush=false) + public function remote_response($add='') { static $s_header_sent = false; if (!$s_header_sent) { $s_header_sent = true; send_nocacheing_headers(); - header('Content-Type: application/x-javascript; charset=' . $this->get_charset()); + header('Content-Type: text/plain; charset=' . $this->get_charset()); print '/** ajax response ['.date('d/M/Y h:i:s O')."] **/\n"; } // unset default env vars unset($this->env['task'], $this->env['action'], $this->env['comm_path']); + $rcmail = rcmail::get_instance(); + $response = array('action' => $rcmail->action, 'unlock' => (bool)$_REQUEST['_unlock']); + + if (!empty($this->env)) + $response['env'] = $this->env; + + if (!empty($this->texts)) + $response['texts'] = $this->texts; + // send response code - echo $this->get_js_commands() . $add; + $response['exec'] = $this->get_js_commands() . $add; - // flush the output buffer - if ($flush) - flush(); + echo json_serialize($response); } @@ -227,14 +234,7 @@ class rcube_json_output private function get_js_commands() { $out = ''; - - if (sizeof($this->env)) - $out .= 'this.set_env('.json_serialize($this->env).");\n"; - foreach($this->texts as $name => $text) { - $out .= sprintf("this.add_label('%s', '%s');\n", $name, JQ($text)); - } - foreach ($this->commands as $i => $args) { $method = array_shift($args); foreach ($args as $i => $arg) { diff --git a/program/include/rcube_ldap.php b/program/include/rcube_ldap.php index 016fe94d5..544c7f744 100644 --- a/program/include/rcube_ldap.php +++ b/program/include/rcube_ldap.php @@ -24,7 +24,7 @@ * * @package Addressbook */ -class rcube_ldap +class rcube_ldap extends rcube_addressbook { var $conn; var $prop = array(); diff --git a/program/include/rcube_message.php b/program/include/rcube_message.php index ec3be4b00..5c03b2147 100644 --- a/program/include/rcube_message.php +++ b/program/include/rcube_message.php @@ -84,6 +84,9 @@ class rcube_message else { $this->body = $this->imap->get_body($uid); } + + // notify plugins and let them analyze this structured message object + $this->app->plugins->exec_hook('message_load', array('object' => $this)); } diff --git a/program/include/rcube_plugin.php b/program/include/rcube_plugin.php new file mode 100644 index 000000000..62f65a9e4 --- /dev/null +++ b/program/include/rcube_plugin.php @@ -0,0 +1,196 @@ +<?php + +/* + +-----------------------------------------------------------------------+ + | program/include/rcube_plugin.php | + | | + | This file is part of the RoundCube Webmail client | + | Copyright (C) 2008-2009, RoundCube Dev. - Switzerland | + | Licensed under the GNU GPL | + | | + | PURPOSE: | + | Abstract plugins interface/class | + | All plugins need to extend this class | + +-----------------------------------------------------------------------+ + | Author: Thomas Bruederli <roundcube@gmail.com> | + +-----------------------------------------------------------------------+ + + $Id: $ + +*/ + +/** + * Plugin interface class + * + * @package Core + */ +abstract class rcube_plugin +{ + public $ID; + public $api; + public $task; + protected $home; + protected $urlbase; + + /** + * Default constructor. + */ + public function __construct($api) + { + $this->ID = get_class($this); + $this->api = $api; + $this->home = $api->dir . DIRECTORY_SEPARATOR . $this->ID; + $this->urlbase = $api->url . $this->ID . '/'; + } + + /** + * Initialization method, needs to be implemented by the plugin itself + */ + abstract function init(); + + /** + * Register a callback function for a specific (server-side) hook + * + * @param string Hook name + * @param mixed Callback function as string or array with object reference and method name + */ + public function add_hook($hook, $callback) + { + $this->api->register_hook($hook, $callback); + } + + /** + * Load localized texts from the plugins dir + * + * @param string Directory to search in + * @param mixed Make texts also available on the client (array with list or true for all) + */ + public function add_texts($dir, $add2client = false) + { + $domain = $this->ID; + + $lang = $_SESSION['language']; + $locdir = slashify(realpath(slashify($this->home) . $dir)); + $texts = array(); + + foreach (array('en_US', $lang) as $lng) { + @include($locdir . $lng . '.inc'); + $texts = (array)$labels + (array)$messages + (array)$texts; + } + + // prepend domain to text keys and add to the application texts repository + if (!empty($texts)) { + $add = array(); + foreach ($texts as $key => $value) + $add[$domain.'.'.$key] = $value; + + $rcmail = rcmail::get_instance(); + $rcmail->load_language($lang, $add); + + // add labels to client + if ($add2client) { + $js_labels = is_array($add2client) ? array_map(array($this, 'label_map_callback'), $add2client) : array_keys($add); + $rcmail->output->add_label($js_labels); + } + } + } + + /** + * Wrapper for rcmail::gettext() adding the plugin ID as domain + * + * @return string Localized text + * @see rcmail::gettext() + */ + function gettext($p) + { + return rcmail::get_instance()->gettext($p, $this->ID); + } + + /** + * Register a handler for a specific client-request action + * + * The callback will be executed upon a request like /?_task=mail&_action=plugin.myaction + * + * @param string Action name (should be unique) + * @param mixed Callback function as string or array with object reference and method name + */ + public function register_action($action, $callback) + { + $this->api->register_action($action, $this->ID, $callback); + } + + /** + * Register a handler function for a template object + * + * When parsing a template for display, tags like <roundcube:object name="plugin.myobject" /> + * will be replaced by the return value if the registered callback function. + * + * @param string Object name (should be unique and start with 'plugin.') + * @param mixed Callback function as string or array with object reference and method name + */ + public function register_handler($name, $callback) + { + $this->api->register_handler($name, $this->ID, $callback); + } + + /** + * Make this javascipt file available on the client + * + * @param string File path; absolute or relative to the plugin directory + */ + public function include_script($fn) + { + $this->api->include_script($this->ressource_url($fn)); + } + + /** + * Make this stylesheet available on the client + * + * @param string File path; absolute or relative to the plugin directory + */ + public function include_stylesheet($fn) + { + $this->api->include_stylesheet($this->ressource_url($fn)); + } + + /** + * Append a button to a certain container + * + * @param array Hash array with named parameters (as used in skin templates) + * @param string Container name where the buttons should be added to + * @see rcube_remplate::button() + */ + public function add_button($p, $container) + { + if ($this->api->output->type == 'html') { + // fix relative paths + foreach (array('imagepas', 'imageact', 'imagesel') as $key) + if ($p[$key]) + $p[$key] = $this->api->url . $this->ressource_url($p[$key]); + + $this->api->add_content($this->api->output->button($p), $container); + } + } + + /** + * Make the given file name link into the plugin directory + */ + private function ressource_url($fn) + { + if ($fn[0] != '/' && !eregi('^https?://', $fn)) + return $this->ID . '/' . $fn; + else + return $fn; + } + + /** + * Callback function for array_map + */ + private function label_map_callback($key) + { + return $this->ID.'.'.$key; + } + + +} + diff --git a/program/include/rcube_plugin_api.php b/program/include/rcube_plugin_api.php new file mode 100644 index 000000000..4780f2e7e --- /dev/null +++ b/program/include/rcube_plugin_api.php @@ -0,0 +1,312 @@ +<?php + +/* + +-----------------------------------------------------------------------+ + | program/include/rcube_plugin_api.php | + | | + | This file is part of the RoundCube Webmail client | + | Copyright (C) 2008-2009, RoundCube Dev. - Switzerland | + | Licensed under the GNU GPL | + | | + | PURPOSE: | + | Plugins repository | + | | + +-----------------------------------------------------------------------+ + | Author: Thomas Bruederli <roundcube@gmail.com> | + +-----------------------------------------------------------------------+ + + $Id: $ + +*/ + +/** + * The plugin loader and global API + * + * @package Core + */ +class rcube_plugin_api +{ + static private $instance; + + public $dir; + public $url = 'plugins/'; + public $output; + + public $handlers = array(); + private $plugins = array(); + private $actions = array(); + private $actionmap = array(); + private $objectsmap = array(); + private $template_contents = array(); + + private $required_plugins = array('filesystem_attachments'); + + /** + * This implements the 'singleton' design pattern + * + * @return object rcube_plugin_api The one and only instance if this class + */ + static function get_instance() + { + if (!self::$instance) { + self::$instance = new rcube_plugin_api(); + } + + return self::$instance; + } + + + /** + * Private constructor + */ + private function __construct() + { + $rcmail = rcmail::get_instance(); + $this->dir = realpath($rcmail->config->get('plugins_dir')); + } + + + /** + * Load and init all enabled plugins + * + * This has to be done after rcmail::load_gui() or rcmail::init_json() + * was called because plugins need to have access to rcmail->output + */ + public function init() + { + $rcmail = rcmail::get_instance(); + $this->output = $rcmail->output; + + $plugins_dir = dir($this->dir); + $plugins_enabled = (array)$rcmail->config->get('plugins', array()); + + foreach ($plugins_enabled as $plugin_name) { + $fn = $plugins_dir->path . DIRECTORY_SEPARATOR . $plugin_name . DIRECTORY_SEPARATOR . $plugin_name . '.php'; + + if (file_exists($fn)) { + include($fn); + + // instantiate class if exists + if (class_exists($plugin_name, false)) { + $plugin = new $plugin_name($this); + // check inheritance and task specification + if (is_subclass_of($plugin, 'rcube_plugin') && (!$plugin->task || $plugin->task == $rcmail->task)) { + $plugin->init(); + $this->plugins[] = $plugin; + } + } + else { + raise_error(array('code' => 520, 'type' => 'php', 'message' => "No plugin class $plugin_name found in $fn"), true, false); + } + } + else { + raise_error(array('code' => 520, 'type' => 'php', 'message' => "Failed to load plugin file $fn"), true, false); + } + } + + // check existance of all required core plugins + foreach ($this->required_plugins as $plugin_name) { + $loaded = false; + foreach ($this->plugins as $plugin) { + if ($plugin instanceof $plugin_name) { + $loaded = true; + break; + } + } + + // load required core plugin if no derivate was found + if (!$loaded) { + $fn = $plugins_dir->path . DIRECTORY_SEPARATOR . $plugin_name . DIRECTORY_SEPARATOR . $plugin_name . '.php'; + if (file_exists($fn)) { + include($fn); + + if (class_exists($plugin_name, false)) { + $plugin = new $plugin_name($this); + // check inheritance + if (is_subclass_of($plugin, 'rcube_plugin')) { + $plugin->init(); + $this->plugins[] = $plugin; + $loaded = true; + } + } + } + } + + // trigger fatal error if still not loaded + if (!$loaded) { + raise_error(array('code' => 520, 'type' => 'php', 'message' => "Requried plugin $plugin_name was not loaded"), true, true); + } + } + + // register an internal hook + $this->register_hook('template_container', array($this, 'template_container_hook')); + + // maybe also register a shudown function which triggers shutdown functions of all plugin objects + + + // call imap_init right now + // (should actually be done in rcmail::imap_init() but plugins are not initialized then) + if ($rcmail->imap) { + $hook = $this->exec_hook('imap_init', array('fetch_headers' => $rcmail->imap->fetch_add_headers)); + if ($hook['fetch_headers']) + $rcmail->imap->fetch_add_headers = $hook['fetch_headers']; + } + } + + + /** + * Allows a plugin object to register a callback for a certain hook + * + * @param string Hook name + * @param mixed String with global function name or array($obj, 'methodname') + */ + public function register_hook($hook, $callback) + { + if (is_callable($callback)) + $this->handlers[$hook][] = $callback; + else + raise_error(array('code' => 521, 'type' => 'php', 'message' => "Invalid callback function for $hook"), true, false); + } + + + /** + * Triggers a plugin hook. + * This is called from the application and executes all registered handlers + * + * @param string Hook name + * @param array Named arguments (key->value pairs) + * @return array The (probably) altered hook arguments + */ + public function exec_hook($hook, $args = array()) + { + $args += array('abort' => false); + + foreach ((array)$this->handlers[$hook] as $callback) { + $ret = call_user_func($callback, $args); + if ($ret && is_array($ret)) + $args = $ret + $args; + + if ($args['abort']) + break; + } + + return $args; + } + + + /** + * Let a plugin register a handler for a specific request + * + * @param string Action name (_task=mail&_action=plugin.foo) + * @param string Plugin name that registers this action + * @param mixed Callback: string with global function name or array($obj, 'methodname') + */ + public function register_action($action, $owner, $callback) + { + // check action name + if (strpos($action, 'plugin.') !== 0) + $action = 'plugin.'.$action; + + // can register action only if it's not taken or registered by myself + if (!isset($this->actionmap[$action]) || $this->actionmap[$action] == $owner) { + $this->actions[$action] = $callback; + $this->actionmap[$action] = $owner; + } + else { + raise_error(array('code' => 523, 'type' => 'php', 'message' => "Cannot register action $action; already taken by another plugin"), true, false); + } + } + + + /** + * This method handles requests like _task=mail&_action=plugin.foo + * It executes the callback function that was registered with the given action. + * + * @param string Action name + */ + public function exec_action($action) + { + if (isset($this->actions[$action])) { + call_user_func($this->actions[$action]); + } + else { + raise_error(array('code' => 524, 'type' => 'php', 'message' => "No handler found for action $action"), true, true); + } + } + + + /** + * Register a handler function for template objects + * + * @param string Object name + * @param string Plugin name that registers this action + * @param mixed Callback: string with global function name or array($obj, 'methodname') + */ + public function register_handler($name, $owner, $callback) + { + // check name + if (strpos($name, 'plugin.') !== 0) + $name = 'plugin.'.$name; + + // can register handler only if it's not taken or registered by myself + if (!isset($this->objectsmap[$name]) || $this->objectsmap[$name] == $owner) { + $this->output->add_handler($name, $callback); + $this->objectsmap[$name] = $owner; + } + else { + raise_error(array('code' => 525, 'type' => 'php', 'message' => "Cannot register template handler $name; already taken by another plugin"), true, false); + } + } + + /** + * Include a plugin script file in the current HTML page + */ + public function include_script($fn) + { + if ($this->output->type == 'html') { + $src = $this->ressource_url($fn); + $this->output->add_header(html::tag('script', array('type' => "text/javascript", 'src' => $src))); + } + } + + /** + * Include a plugin stylesheet in the current HTML page + */ + public function include_stylesheet($fn) + { + if ($this->output->type == 'html') { + $src = $this->ressource_url($fn); + $this->output->add_header(html::tag('link', array('rel' => "stylesheet", 'type' => "text/css", 'href' => $src))); + } + } + + /** + * Save the given HTML content to be added to a template container + */ + public function add_content($html, $container) + { + $this->template_contents[$container] .= $html . "\n"; + } + + /** + * Callback for template_container hooks + */ + private function template_container_hook($attrib) + { + $container = $attrib['name']; + return array('content' => $this->template_contents[$container]); + } + + /** + * Make the given file name link into the plugins directory + */ + private function ressource_url($fn) + { + if ($fn[0] != '/' && !eregi('^https?://', $fn)) + return $this->url . $fn; + else + return $fn; + } + +} + diff --git a/program/include/rcube_template.php b/program/include/rcube_template.php index 1e732ca11..557509a14 100755 --- a/program/include/rcube_template.php +++ b/program/include/rcube_template.php @@ -66,11 +66,12 @@ class rcube_template extends rcube_html_page $javascript = 'var '.JS_OBJECT_NAME.' = new rcube_webmail();'; // don't wait for page onload. Call init at the bottom of the page (delayed) - $javascript_foot = "if (window.call_init)\n call_init('".JS_OBJECT_NAME."');"; + $javascript_foot = '$(document).ready(function(){ '.JS_OBJECT_NAME.'.init(); });'; $this->add_script($javascript, 'head_top'); $this->add_script($javascript_foot, 'foot'); $this->scripts_path = 'program/js/'; + $this->include_script('http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js'); $this->include_script('common.js'); $this->include_script('app.js'); @@ -208,8 +209,11 @@ class rcube_template extends rcube_html_page */ public function add_label() { - $arg_list = func_get_args(); - foreach ($arg_list as $i => $name) { + $args = func_get_args(); + if (count($args) == 1 && is_array($args[0])) + $args = $args[0]; + + foreach ($args as $name) { $this->command('add_label', $name, rcube_label($name)); } } @@ -375,9 +379,9 @@ class rcube_template extends rcube_html_page $parent = $this->framed || preg_match('/^parent\./', $method); $out .= sprintf( "%s.%s(%s);\n", - ($parent ? 'parent.' : '') . JS_OBJECT_NAME, - preg_replace('/^parent\./', '', $method), - implode(',', $args) + ($parent ? 'if(window.parent && parent.'.JS_OBJECT_NAME.') parent.' : '') . JS_OBJECT_NAME, + preg_replace('/^parent\./', '', $method), + implode(',', $args) ); } @@ -511,37 +515,21 @@ class rcube_template extends rcube_html_page */ private function parse_xml($input) { - return preg_replace_callback('/<roundcube:([-_a-z]+)\s+([^>]+)>/Ui', array($this, 'xml_command_callback'), $input); - } - - - /** - * This is a callback function for preg_replace_callback (see #1485286) - * It's only purpose is to reconfigure parameters for xml_command, so that the signature isn't disturbed - */ - private function xml_command_callback($matches) - { - $str_attrib = isset($matches[2]) ? $matches[2] : ''; - $add_attrib = isset($matches[3]) ? $matches[3] : array(); - - $command = $matches[1]; - //matches[0] is the entire matched portion of the string - - return $this->xml_command($command, $str_attrib, $add_attrib); + return preg_replace_callback('/<roundcube:([-_a-z]+)\s+([^>]+)>/Ui', array($this, 'xml_command'), $input); } /** - * Convert a xml command tag into real content + * Callback function for parsing an xml command tag + * and turn it into real html content * - * @param string Tag command: object,button,label, etc. - * @param string Attribute string + * @param array Matches array of preg_replace_callback * @return string Tag/Object content */ - private function xml_command($command, $str_attrib, $add_attrib = array()) + private function xml_command($matches) { - $command = strtolower($command); - $attrib = parse_attrib_string($str_attrib) + $add_attrib; + $command = strtolower($matches[1]); + $attrib = parse_attrib_string($matches[2]); // empty output if required condition is not met if (!empty($attrib['condition']) && !$this->check_condition($attrib['condition'])) { @@ -572,67 +560,70 @@ class rcube_template extends rcube_html_page $incl = $this->include_php($path); } else { - $incl = file_get_contents($path); - } + $incl = file_get_contents($path); + } return $this->parse_xml($incl); } break; case 'plugin.include': - //rcube::tfk_debug(var_export($this->config['skin_path'], true)); - $path = realpath($this->config['skin_path'].$attrib['file']); - if (!$path) { - //rcube::tfk_debug("Does not exist:"); - //rcube::tfk_debug($this->config['skin_path']); - //rcube::tfk_debug($attrib['file']); - //rcube::tfk_debug($path); - } - $incl = file_get_contents($path); - if ($incl) { - return $this->parse_xml($incl); + $hook = $this->app->plugins->exec_hook("template_plugin_include", $attrib); + return $hook['content']; + break; + + // define a container block + case 'container': + if ($attrib['name'] && $attrib['id']) { + $this->command('gui_container', $attrib['name'], $attrib['id']); + // let plugins insert some content here + $hook = $this->app->plugins->exec_hook("template_container", $attrib); + return $hook['content']; } break; // return code for a specific application object case 'object': $object = strtolower($attrib['name']); + $content = ''; // we are calling a class/method if (($handler = $this->object_handlers[$object]) && is_array($handler)) { if ((is_object($handler[0]) && method_exists($handler[0], $handler[1])) || (is_string($handler[0]) && class_exists($handler[0]))) - return call_user_func($handler, $attrib); + $content = call_user_func($handler, $attrib); } + // execute object handler function else if (function_exists($handler)) { - // execute object handler function - return call_user_func($handler, $attrib); + $content = call_user_func($handler, $attrib); } - - if ($object=='productname') { + else if ($object == 'productname') { $name = !empty($this->config['product_name']) ? $this->config['product_name'] : 'RoundCube Webmail'; - return Q($name); + $content = Q($name); } - if ($object=='version') { + else if ($object == 'version') { $ver = (string)RCMAIL_VERSION; if (is_file(INSTALL_PATH . '.svn/entries')) { if (preg_match('/Revision:\s(\d+)/', @shell_exec('svn info'), $regs)) $ver .= ' [SVN r'.$regs[1].']'; } - return $ver; + $content = Q($ver); } - if ($object=='steptitle') { - return Q($this->get_pagetitle()); + else if ($object == 'steptitle') { + $content = Q($this->get_pagetitle()); } - if ($object=='pagetitle') { + else if ($object == 'pagetitle') { $title = !empty($this->config['product_name']) ? $this->config['product_name'].' :: ' : ''; $title .= $this->get_pagetitle(); - return Q($title); + $content = Q($title); } - break; + + // exec plugin hooks for this template object + $hook = $this->app->plugins->exec_hook("template_object_$object", $attrib + array('content' => $content)); + return $hook['content']; // return code for a specified eval expression case 'exp': - $value = $this->parse_expression($attrib['expression']); + $value = $this->parse_expression($attrib['expression']); return eval("return Q($value);"); // return variable @@ -702,7 +693,7 @@ class rcube_template extends rcube_html_page static $s_button_count = 100; // these commands can be called directly via url - $a_static_commands = array('compose', 'list'); + $a_static_commands = array('compose', 'list', 'preferences', 'folders', 'identities'); if (!($attrib['command'] || $attrib['name'])) { return ''; @@ -941,11 +932,17 @@ class rcube_template extends rcube_html_page $default_host = $this->config['default_host']; $_SESSION['temp'] = true; + + // save original url + $url = get_input_value('_url', RCUBE_INPUT_POST); + if (empty($url) && !preg_match('/_action=logout/', $_SERVER['QUERY_STRING'])) + $url = $_SERVER['QUERY_STRING']; $input_user = new html_inputfield(array('name' => '_user', 'id' => 'rcmloginuser', 'size' => 30) + $attrib); $input_pass = new html_passwordfield(array('name' => '_pass', 'id' => 'rcmloginpwd', 'size' => 30) + $attrib); $input_action = new html_hiddenfield(array('name' => '_action', 'value' => 'login')); $input_tzone = new html_hiddenfield(array('name' => '_timezone', 'id' => 'rcmlogintz', 'value' => '_default_')); + $input_url = new html_hiddenfield(array('name' => '_url', 'id' => 'rcmloginurl', 'value' => $url)); $input_host = null; if (is_array($default_host)) { @@ -985,6 +982,7 @@ class rcube_template extends rcube_html_page $out = $input_action->show(); $out .= $input_tzone->show(); + $out .= $input_url->show(); $out .= $table->show(); // surround html output with a form tag diff --git a/program/include/rcube_user.php b/program/include/rcube_user.php index 17debeb7a..b68c56cfa 100644 --- a/program/include/rcube_user.php +++ b/program/include/rcube_user.php @@ -346,16 +346,22 @@ class rcube_user */ static function create($user, $host) { + $user_name = ''; $user_email = ''; $rcmail = rcmail::get_instance(); + + $data = $rcmail->plugins->exec_hook('create_user', array('user'=>$user, 'user_name'=>$user_name, 'user_email'=>$user_email)); + $user_name = $data['user_name']; + $user_email = $data['user_email']; + $dbh = $rcmail->get_dbh(); // try to resolve user in virtuser table and file - if (!strpos($user, '@')) { + if ($user_email != '' && !strpos($user, '@')) { if ($email_list = self::user2email($user, false)) $user_email = $email_list[0]; } - + $dbh->query( "INSERT INTO ".get_table_name('users')." (created, last_login, username, mail_host, alias, language) @@ -372,7 +378,9 @@ class rcube_user if ($user_email=='') $user_email = strpos($user, '@') ? $user : sprintf('%s@%s', $user, $mail_domain); - $user_name = $user != $user_email ? $user : ''; + if ($user_name == '') { + $user_name = $user != $user_email ? $user : ''; + } if (empty($email_list)) $email_list[] = strip_newlines($user_email); @@ -385,10 +393,10 @@ class rcube_user (user_id, del, standard, name, email) VALUES (?, 0, ?, ?, ?)", $user_id, - $standard, + $standard, strip_newlines($user_name), preg_replace('/^@/', $user . '@', $email)); - $standard = 0; + $standard = 0; } } else @@ -446,9 +454,9 @@ class rcube_user while ($sql_arr = $dbh->fetch_array($sql_result)) if (strpos($sql_arr[0], '@')) { $result[] = $sql_arr[0]; - if ($first) - return $result[0]; - } + if ($first) + return $result[0]; + } } // File lookup $r = self::findinvirtual('[[:space:]]' . quotemeta($user) . '[[:space:]]*$'); @@ -460,7 +468,7 @@ class rcube_user { $result[] = trim(str_replace('\\@', '@', $arr[0])); - if ($first) + if ($first) return $result[0]; } } diff --git a/program/include/rcube_vcard.php b/program/include/rcube_vcard.php index ce5087a0f..444900cde 100644 --- a/program/include/rcube_vcard.php +++ b/program/include/rcube_vcard.php @@ -263,9 +263,9 @@ class rcube_vcard $line[1] .= ';' . (strpos($prop, '=') ? $prop : 'TYPE='.$prop); } - if (!preg_match('/^(BEGIN|END)$/', $line[1]) && preg_match_all('/([^\\;]+);?/', $line[1], $regs2)) { + if (!preg_match('/^(BEGIN|END)$/i', $line[1]) && preg_match_all('/([^\\;]+);?/', $line[1], $regs2)) { $entry = array(''); - $field = $regs2[1][0]; + $field = strtoupper($regs2[1][0]); foreach($regs2[1] as $attrid => $attr) { if ((list($key, $value) = explode('=', $attr)) && $value) { |