summaryrefslogtreecommitdiff
path: root/program
diff options
context:
space:
mode:
authoralecpl <alec@alec.pl>2011-09-06 16:35:14 +0000
committeralecpl <alec@alec.pl>2011-09-06 16:35:14 +0000
commitf8e48df71540b268ceac058d32b8ee848fc2ab6b (patch)
treed2e8ea19086014e8a89f761a9444e72d84145164 /program
parent66df084203a217ab74a416064c459cc3420a648c (diff)
- Merge devel-saved_search branch (Addressbook Saved Searches)
Diffstat (limited to 'program')
-rw-r--r--program/include/rcube_user.php127
-rw-r--r--program/js/app.js184
-rw-r--r--program/localization/en_US/labels.inc6
-rw-r--r--program/localization/en_US/messages.inc6
-rw-r--r--program/steps/addressbook/func.inc29
-rw-r--r--program/steps/addressbook/search.inc71
6 files changed, 379 insertions, 44 deletions
diff --git a/program/include/rcube_user.php b/program/include/rcube_user.php
index dc5767d14..90edad6e9 100644
--- a/program/include/rcube_user.php
+++ b/program/include/rcube_user.php
@@ -47,6 +47,8 @@ class rcube_user
*/
private $rc;
+ const SEARCH_ADDRESSBOOK = 1;
+ const SEARCH_MAIL = 2;
/**
* Object constructor
@@ -551,4 +553,129 @@ class rcube_user
return empty($plugin['email']) ? NULL : $plugin['email'];
}
+
+ /**
+ * Return a list of saved searches linked with this user
+ *
+ * @param int $type Search type
+ *
+ * @return array List of saved searches indexed by search ID
+ */
+ function list_searches($type)
+ {
+ $plugin = $this->rc->plugins->exec_hook('saved_search_list', array('type' => $type));
+
+ if ($plugin['abort']) {
+ return (array) $plugin['result'];
+ }
+
+ $result = array();
+
+ $sql_result = $this->db->query(
+ "SELECT search_id AS id, ".$this->db->quoteIdentifier('name')
+ ." FROM ".get_table_name('searches')
+ ." WHERE user_id = ?"
+ ." AND ".$this->db->quoteIdentifier('type')." = ?"
+ ." ORDER BY ".$this->db->quoteIdentifier('name'),
+ (int) $this->ID, (int) $type);
+
+ while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
+ $sql_arr['data'] = unserialize($sql_arr['data']);
+ $result[$sql_arr['id']] = $sql_arr;
+ }
+
+ return $result;
+ }
+
+
+ /**
+ * Return saved search data.
+ *
+ * @param int $id Row identifier
+ *
+ * @return array Data
+ */
+ function get_search($id)
+ {
+ $plugin = $this->rc->plugins->exec_hook('saved_search_get', array('id' => $id));
+
+ if ($plugin['abort']) {
+ return $plugin['result'];
+ }
+
+ $sql_result = $this->db->query(
+ "SELECT ".$this->db->quoteIdentifier('name')
+ .", ".$this->db->quoteIdentifier('data')
+ .", ".$this->db->quoteIdentifier('type')
+ ." FROM ".get_table_name('searches')
+ ." WHERE user_id = ?"
+ ." AND search_id = ?",
+ (int) $this->ID, (int) $id);
+
+ while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
+ return array(
+ 'id' => $id,
+ 'name' => $sql_arr['name'],
+ 'type' => $sql_arr['type'],
+ 'data' => unserialize($sql_arr['data']),
+ );
+ }
+
+ return null;
+ }
+
+
+ /**
+ * Deletes given saved search record
+ *
+ * @param int $sid Search ID
+ *
+ * @return boolean True if deleted successfully, false if nothing changed
+ */
+ function delete_search($sid)
+ {
+ if (!$this->ID)
+ return false;
+
+ $this->db->query(
+ "DELETE FROM ".get_table_name('searches')
+ ." WHERE user_id = ?"
+ ." AND search_id = ?",
+ (int) $this->ID, $sid);
+
+ return $this->db->affected_rows();
+ }
+
+
+ /**
+ * Create a new saved search record linked with this user
+ *
+ * @param array $data Hash array with col->value pairs to save
+ *
+ * @return int The inserted search ID or false on error
+ */
+ function insert_search($data)
+ {
+ if (!$this->ID)
+ return false;
+
+ $insert_cols[] = 'user_id';
+ $insert_values[] = (int) $this->ID;
+ $insert_cols[] = $this->db->quoteIdentifier('type');
+ $insert_values[] = (int) $data['type'];
+ $insert_cols[] = $this->db->quoteIdentifier('name');
+ $insert_values[] = $data['name'];
+ $insert_cols[] = $this->db->quoteIdentifier('data');
+ $insert_values[] = serialize($data['data']);
+
+ $sql = "INSERT INTO ".get_table_name('searches')
+ ." (".join(', ', $insert_cols).")"
+ ." VALUES (".join(', ', array_pad(array(), sizeof($insert_values), '?')).")";
+
+ call_user_func_array(array($this->db, 'query'),
+ array_merge(array($sql), $insert_values));
+
+ return $this->db->insert_id('searches');
+ }
+
}
diff --git a/program/js/app.js b/program/js/app.js
index e671ce434..002f345ee 100644
--- a/program/js/app.js
+++ b/program/js/app.js
@@ -328,7 +328,7 @@ function rcube_webmail()
this.enable_command('export', true);
this.enable_command('add', 'import', this.env.writable_source);
- this.enable_command('list', 'listgroup', 'advanced-search', true);
+ this.enable_command('list', 'listgroup', 'listsearch', 'advanced-search', true);
// load contacts of selected source
if (!this.env.action)
@@ -524,21 +524,15 @@ function rcube_webmail()
break;
case 'list':
- if (this.task=='mail') {
- if (!this.env.search_request || (props && props != this.env.mailbox))
- this.reset_qsearch();
-
+ this.reset_qsearch();
+ if (this.task == 'mail') {
this.list_mailbox(props);
if (this.env.trash_mailbox && !this.env.flag_for_deletion)
this.set_alttext('delete', this.env.mailbox != this.env.trash_mailbox ? 'movemessagetotrash' : 'deletemessage');
}
else if (this.task == 'addressbook') {
- if (!this.env.search_request || (props != this.env.source))
- this.reset_qsearch();
-
this.list_contacts(props);
- this.enable_command('add', 'import', this.env.writable_source);
}
break;
@@ -1008,6 +1002,7 @@ function rcube_webmail()
break;
case 'listgroup':
+ this.reset_qsearch();
this.list_contacts(props.source, props.id);
break;
@@ -1994,7 +1989,7 @@ function rcube_webmail()
if (mbox != this.env.mailbox || (mbox == this.env.mailbox && !page && !sort))
url += '&_refresh=1';
- this.select_folder(mbox, this.env.mailbox);
+ this.select_folder(mbox);
this.env.mailbox = mbox;
// load message list remotely
@@ -3455,6 +3450,7 @@ function rcube_webmail()
this.env.qsearch = null;
this.env.search_request = null;
+ this.env.search_id = null;
};
this.sent_successfully = function(type, msg)
@@ -3829,7 +3825,7 @@ function rcube_webmail()
this.list_contacts = function(src, group, page)
{
- var add_url = '',
+ var folder, add_url = '',
target = window;
if (!src)
@@ -3845,7 +3841,12 @@ function rcube_webmail()
else if (group != this.env.group)
page = this.env.current_page = 1;
- this.select_folder((group ? 'G'+src+group : src), (this.env.group ? 'G'+this.env.source+this.env.group : this.env.source));
+ if (this.env.search_id)
+ folder = 'S'+this.env.search_id;
+ else
+ folder = group ? 'G'+src+group : src;
+
+ this.select_folder(folder);
this.env.source = src;
this.env.group = group;
@@ -3890,8 +3891,8 @@ function rcube_webmail()
if (group)
url += '&_gid='+group;
- // also send search request to get the right messages
- if (this.env.search_request)
+ // also send search request to get the right messages
+ if (this.env.search_request)
url += '&_search='+this.env.search_request;
this.http_request('list', url, lock);
@@ -4092,19 +4093,7 @@ function rcube_webmail()
this.group_create = function()
{
- if (!this.gui_objects.folderlist)
- return;
-
- if (!this.name_input) {
- this.name_input = $('<input>').attr('type', 'text');
- this.name_input.bind('keydown', function(e){ return rcmail.add_input_keydown(e); });
- this.name_input_li = $('<li>').addClass('contactgroup').append(this.name_input);
-
- var li = this.get_folder_li(this.env.source)
- this.name_input_li.insertAfter(li);
- }
-
- this.name_input.select().focus();
+ this.add_input_row('contactgroup');
};
this.group_rename = function()
@@ -4150,18 +4139,40 @@ function rcube_webmail()
this.list_contacts(prop.source, 0);
};
+ // @TODO: maybe it would be better to use popup instead of inserting input to the list?
+ this.add_input_row = function(type)
+ {
+ if (!this.gui_objects.folderlist)
+ return;
+
+ if (!this.name_input) {
+ this.name_input = $('<input>').attr('type', 'text').data('tt', type);
+ this.name_input.bind('keydown', function(e){ return rcmail.add_input_keydown(e); });
+ this.name_input_li = $('<li>').addClass(type).append(this.name_input);
+
+ var li = type == 'contactsearch' ? $('li:last', this.gui_objects.folderlist) : this.get_folder_li(this.env.source);
+ this.name_input_li.insertAfter(li);
+ }
+
+ this.name_input.select().focus();
+ };
+
// handler for keyboard events on the input field
this.add_input_keydown = function(e)
{
- var key = rcube_event.get_keycode(e);
+ var key = rcube_event.get_keycode(e),
+ input = $(e.target), itype = input.data('tt');
// enter
if (key == 13) {
- var newname = this.name_input.val();
+ var newname = input.val();
if (newname) {
var lock = this.set_busy(true, 'loading');
- if (this.env.group_renaming)
+
+ if (itype == 'contactsearch')
+ this.http_post('search-create', '_search='+urlencode(this.env.search_request)+'&_name='+urlencode(newname), lock);
+ else if (this.env.group_renaming)
this.http_post('group-rename', '_source='+urlencode(this.env.source)+'&_gid='+urlencode(this.env.group)+'&_name='+urlencode(newname), lock);
else
this.http_post('group-create', '_source='+urlencode(this.env.source)+'&_name='+urlencode(newname), lock);
@@ -4477,11 +4488,106 @@ function rcube_webmail()
// unselect directory/group
this.unselect_directory = function()
{
- if (this.env.address_sources.length > 1 || this.env.group != '') {
- this.select_folder('', (this.env.group ? 'G'+this.env.source+this.env.group : this.env.source));
- this.env.group = '';
- this.env.source = '';
+ this.select_folder('');
+ this.enable_command('search-delete', false);
+ };
+
+ // callback for creating a new saved search record
+ this.insert_saved_search = function(name, id)
+ {
+ this.reset_add_input();
+
+ var key = 'S'+id,
+ link = $('<a>').attr('href', '#')
+ .attr('rel', id)
+ .click(function() { return rcmail.command('listsearch', id, this); })
+ .html(name),
+ li = $('<li>').attr({id: 'rcmli'+key.replace(this.identifier_expr, '_'), 'class': 'contactsearch'})
+ .append(link),
+ prop = {name:name, id:id, li:li[0]};
+
+ this.add_saved_search_row(prop, li);
+ this.select_folder('S'+id);
+ this.enable_command('search-delete', true);
+ this.env.search_id = id;
+
+ this.triggerEvent('abook_search_insert', prop);
+ };
+
+ // add saved search row to the list, with sorting
+ this.add_saved_search_row = function(prop, li, reloc)
+ {
+ var row, sibling, name = prop.name.toUpperCase();
+
+ // When renaming groups, we need to remove it from DOM and insert it in the proper place
+ if (reloc) {
+ row = li.clone(true);
+ li.remove();
+ }
+ else
+ row = li;
+
+ $('li[class~="contactsearch"]', this.gui_objects.folderlist).each(function(i, elem) {
+ if (!sibling)
+ sibling = this.previousSibling;
+
+ if (name >= $(this).text().toUpperCase())
+ sibling = elem;
+ else
+ return false;
+ });
+
+ if (sibling)
+ row.insertAfter(sibling);
+ else
+ row.appendTo(this.gui_objects.folderlist);
+ };
+
+ // creates an input for saved search name
+ this.search_create = function()
+ {
+ this.add_input_row('contactsearch');
+ };
+
+ this.search_delete = function()
+ {
+ if (this.env.search_request) {
+ var lock = this.set_busy(true, 'savedsearchdeleting');
+ this.http_post('search-delete', '_sid='+urlencode(this.env.search_id), lock);
+ }
+ };
+
+ // callback from server upon search-delete command
+ this.remove_search_item = function(id)
+ {
+ var li, key = 'S'+id;
+ if ((li = this.get_folder_li(key))) {
+ this.triggerEvent('search_delete', { id:id, li:li });
+
+ li.parentNode.removeChild(li);
+ }
+
+ this.env.search_id = null;
+ this.env.search_request = null;
+ this.list_contacts_clear();
+ this.reset_qsearch();
+ this.enable_command('search-delete', 'search-create', false);
+ };
+
+ this.listsearch = function(id)
+ {
+ var folder, lock = this.set_busy(true, 'searching');
+
+ if (this.contact_list) {
+ this.list_contacts_clear();
}
+
+ this.reset_qsearch();
+ this.select_folder('S'+id);
+
+ // reset vars
+ this.env.current_page = 1;
+ this.http_request('search', '_sid='+urlencode(id), lock);
};
@@ -5231,20 +5337,20 @@ function rcube_webmail()
};
// mark a mailbox as selected and set environment variable
- this.select_folder = function(name, old, prefix)
+ this.select_folder = function(name, prefix)
{
if (this.gui_objects.folderlist) {
var current_li, target_li;
- if ((current_li = this.get_folder_li(old, prefix))) {
- $(current_li).removeClass('selected').addClass('unfocused');
+ if ((current_li = $('li.selected', this.gui_objects.folderlist))) {
+ current_li.removeClass('selected').addClass('unfocused');
}
if ((target_li = this.get_folder_li(name, prefix))) {
$(target_li).removeClass('unfocused').addClass('selected');
}
// trigger event hook
- this.triggerEvent('selectfolder', { folder:name, old:old, prefix:prefix });
+ this.triggerEvent('selectfolder', { folder:name, prefix:prefix });
}
};
@@ -5791,6 +5897,8 @@ function rcube_webmail()
this.enable_command('export', (this.contact_list && this.contact_list.rowcount > 0));
if (response.action == 'list' || response.action == 'search') {
+ this.enable_command('search-create', this.env.source == '');
+ this.enable_command('search-delete', this.env.search_id);
this.update_group_commands();
this.triggerEvent('listupdate', { folder:this.env.source, rowcount:this.contact_list.rowcount });
}
diff --git a/program/localization/en_US/labels.inc b/program/localization/en_US/labels.inc
index 7facc12df..17544bb6d 100644
--- a/program/localization/en_US/labels.inc
+++ b/program/localization/en_US/labels.inc
@@ -140,7 +140,7 @@ $labels['markread'] = 'As read';
$labels['markunread'] = 'As unread';
$labels['markflagged'] = 'As flagged';
$labels['markunflagged'] = 'As unflagged';
-$labels['messageactions'] = 'More actions...';
+$labels['moreactions'] = 'More actions...';
$labels['select'] = 'Select';
$labels['all'] = 'All';
@@ -317,7 +317,6 @@ $labels['print'] = 'Print';
$labels['export'] = 'Export';
$labels['exportvcards'] = 'Export contacts in vCard format';
$labels['newcontactgroup'] = 'Create new contact group';
-$labels['groupactions'] = 'Actions for contact groups...';
$labels['grouprename'] = 'Rename group';
$labels['groupdelete'] = 'Delete group';
@@ -330,6 +329,9 @@ $labels['group'] = 'Group';
$labels['groups'] = 'Groups';
$labels['personaladrbook'] = 'Personal Addresses';
+$labels['searchsave'] = 'Save search';
+$labels['searchdelete'] = 'Delete search';
+
$labels['import'] = 'Import';
$labels['importcontacts'] = 'Import contacts';
$labels['importfromfile'] = 'Import from file:';
diff --git a/program/localization/en_US/messages.inc b/program/localization/en_US/messages.inc
index c871ca53e..65bc9ca6a 100644
--- a/program/localization/en_US/messages.inc
+++ b/program/localization/en_US/messages.inc
@@ -76,10 +76,10 @@ $messages['nosubjectwarning'] = 'The "Subject" field is empty. Would you like t
$messages['nobodywarning'] = 'Send this message without text?';
$messages['notsentwarning'] = 'Message has not been sent. Do you want to discard your message?';
$messages['noldapserver'] = 'Please select an ldap server to search.';
-$messages['nocontactsreturned'] = 'No contacts were found.';
$messages['nosearchname'] = 'Please enter a contact name or email address.';
$messages['notuploadedwarning'] = 'Not all attachments have been uploaded yet. Please wait or cancel the upload.';
$messages['searchsuccessful'] = '$nr messages found.';
+$messages['contactsearchsuccessful'] = '$nr contacts found.';
$messages['searchnomatch'] = 'Search returned no matches.';
$messages['searching'] = 'Searching...';
$messages['checking'] = 'Checking...';
@@ -139,6 +139,10 @@ $messages['contactrestored'] = 'Contact(s) restored successfully.';
$messages['groupdeleted'] = 'Group deleted successfully.';
$messages['grouprenamed'] = 'Group renamed successfully.';
$messages['groupcreated'] = 'Group created successfully.';
+$messages['savedsearchdeleted'] = 'Saved search deleted successfully.';
+$messages['savedsearchdeleteerror'] = 'Could not delete saved search.';
+$messages['savedsearchcreated'] = 'Saved search created successfully.';
+$messages['savedsearchcreateerror'] = 'Could not create saved search.';
$messages['messagedeleted'] = 'Message(s) deleted successfully.';
$messages['messagemoved'] = 'Message(s) moved successfully.';
$messages['messagecopied'] = 'Message(s) copied successfully.';
diff --git a/program/steps/addressbook/func.inc b/program/steps/addressbook/func.inc
index 55d4255aa..b290bbb2d 100644
--- a/program/steps/addressbook/func.inc
+++ b/program/steps/addressbook/func.inc
@@ -227,7 +227,32 @@ function rcmail_directory_list($attrib)
$out = $groupdata['out'];
}
- $OUTPUT->set_env('contactgroups', $jsdata);
+ $line_templ = html::tag('li', array(
+ 'id' => 'rcmliS%s', 'class' => '%s'),
+ html::a(array('href' => '#', 'rel' => 'S%s',
+ 'onclick' => "return ".JS_OBJECT_NAME.".command('listsearch', '%s', this)"), '%s'));
+
+ // Saved searches
+ $sources = $RCMAIL->user->list_searches(rcube_user::SEARCH_ADDRESSBOOK);
+ foreach ($sources as $j => $source) {
+ $id = $source['id'];
+ $js_id = JQ($id);
+
+ // set class name(s)
+ $class_name = 'contactsearch';
+ if ($current === $id)
+ $class_name .= ' selected';
+ if ($source['class_name'])
+ $class_name .= ' ' . $source['class_name'];
+
+ $out .= sprintf($line_templ,
+ html_identifier($id),
+ $class_name,
+ $id,
+ $js_id, (!empty($source['name']) ? Q($source['name']) : Q($id)));
+ }
+
+ $OUTPUT->set_env('contactgroups', $jsdata);
$OUTPUT->add_gui_object('folderlist', $attrib['id']);
// add some labels to client
$OUTPUT->add_label('deletegroupconfirm', 'groupdeleting', 'addingmember', 'removingmember');
@@ -745,4 +770,6 @@ $RCMAIL->register_action_map(array(
'group-delete' => 'groups.inc',
'group-addmembers' => 'groups.inc',
'group-delmembers' => 'groups.inc',
+ 'search-create' => 'search.inc',
+ 'search-delete' => 'search.inc',
));
diff --git a/program/steps/addressbook/search.inc b/program/steps/addressbook/search.inc
index 352556de0..ad1df9792 100644
--- a/program/steps/addressbook/search.inc
+++ b/program/steps/addressbook/search.inc
@@ -21,6 +21,60 @@
*/
+if ($RCMAIL->action == 'search-create') {
+ $id = get_input_value('_search', RCUBE_INPUT_POST);
+ $name = get_input_value('_name', RCUBE_INPUT_POST, true);
+
+ if (($params = $_SESSION['search_params']) && $params['id'] == $id) {
+
+ $data = array(
+ 'type' => rcube_user::SEARCH_ADDRESSBOOK,
+ 'name' => $name,
+ 'data' => array(
+ 'fields' => $params['data'][0],
+ 'search' => $params['data'][1],
+ ),
+ );
+
+ $plugin = $RCMAIL->plugins->exec_hook('saved_search_create', array('data' => $data));
+
+ if (!$plugin['abort'])
+ $result = $RCMAIL->user->insert_search($plugin['data']);
+ else
+ $result = $plugin['result'];
+ }
+
+ if ($result) {
+ $OUTPUT->show_message('savedsearchcreated', 'confirmation');
+ $OUTPUT->command('insert_saved_search', Q($name), Q($result));
+ }
+ else
+ $OUTPUT->show_message($plugin['message'] ? $plugin['message'] : 'savedsearchcreateerror', 'error');
+
+ $OUTPUT->send();
+}
+
+if ($RCMAIL->action == 'search-delete') {
+ $id = get_input_value('_sid', RCUBE_INPUT_POST);
+
+ $plugin = $RCMAIL->plugins->exec_hook('saved_search_delete', array('id' => $id));
+
+ if (!$plugin['abort'])
+ $result = $RCMAIL->user->delete_search($id);
+ else
+ $result = $plugin['result'];
+
+ if ($result) {
+ $OUTPUT->show_message('savedsearchdeleted', 'confirmation');
+ $OUTPUT->command('remove_search_item', Q($id));
+ }
+ else
+ $OUTPUT->show_message($plugin['message'] ? $plugin['message'] : 'savedsearchdeleteerror', 'error');
+
+ $OUTPUT->send();
+}
+
+
if (!isset($_GET['_form'])) {
rcmail_contact_search();
}
@@ -34,9 +88,15 @@ function rcmail_contact_search()
global $RCMAIL, $OUTPUT, $CONFIG, $SEARCH_MODS_DEFAULT;
$adv = isset($_POST['_adv']);
+ $sid = get_input_value('_sid', RCUBE_INPUT_GET);
+ // get search criteria from saved search
+ if ($sid && ($search = $RCMAIL->user->get_search($sid))) {
+ $fields = $search['data']['fields'];
+ $search = $search['data']['search'];
+ }
// get fields/values from advanced search form
- if ($adv) {
+ else if ($adv) {
foreach (array_keys($_POST) as $key) {
$s = trim(get_input_value($key, RCUBE_INPUT_POST, true));
if (strlen($s) && preg_match('/^_search_([a-zA-Z0-9_-]+)$/', $key, $m)) {
@@ -145,6 +205,7 @@ function rcmail_contact_search()
// save search settings in session
$_SESSION['search'][$search_request] = $search_set;
+ $_SESSION['search_params'] = array('id' => $search_request, 'data' => array($fields, $search));
$_SESSION['page'] = 1;
if ($adv)
@@ -153,6 +214,7 @@ function rcmail_contact_search()
if ($result->count > 0) {
// create javascript list
rcmail_js_contacts_list($result);
+ $OUTPUT->show_message('contactsearchsuccessful', 'confirmation', array('nr' => $result->count));
}
else {
$OUTPUT->show_message('nocontactsfound', 'notice');
@@ -162,9 +224,14 @@ function rcmail_contact_search()
$OUTPUT->command('set_env', 'search_request', $search_request);
$OUTPUT->command('set_env', 'pagecount', ceil($result->count / $CONFIG['pagesize']));
$OUTPUT->command('set_rowcount', rcmail_get_rowcount_text($result));
+ // Re-set current source
+ $OUTPUT->command('set_env', 'search_id', $sid);
+ $OUTPUT->command('set_env', 'source', '');
+ $OUTPUT->command('set_env', 'group', '');
// unselect currently selected directory/group
- $OUTPUT->command('unselect_directory');
+ if (!$sid)
+ $OUTPUT->command('unselect_directory');
$OUTPUT->command('update_group_commands');
// send response