mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-12-22 06:30:59 +01:00
* Addressbook - new view to show duplicate contacts
This commit is contained in:
parent
f943ed471a
commit
23bf37b98e
@ -150,6 +150,11 @@ class addressbook_hooks
|
||||
);
|
||||
$contacts = new Api\Contacts();
|
||||
$fileas_options = $contacts->fileas_options();
|
||||
foreach(Api\Contacts\Storage::$duplicate_fields as $key => $label)
|
||||
{
|
||||
$duplicate_options[$key] = lang($label);
|
||||
}
|
||||
|
||||
$settings['link_title'] = array(
|
||||
'type' => 'select',
|
||||
'label' => 'Link title for contacts show',
|
||||
@ -197,6 +202,26 @@ class addressbook_hooks
|
||||
'admin' => false,
|
||||
'default'=> 'org_name: n_family, n_given',
|
||||
);
|
||||
$settings['duplicate_fields'] = array(
|
||||
'type' => 'multiselect',
|
||||
'label' => 'Fields to check for duplicates',
|
||||
'name' => 'duplicate_fields',
|
||||
'values' => $duplicate_options,
|
||||
'help' => 'Fields to consider when looking for duplicate contacts.',
|
||||
'admin' => false,
|
||||
'default' => 'n_family, n_given, org_name, contact_email'
|
||||
);
|
||||
$settings['duplicate_threshold'] = array(
|
||||
'type' => 'input',
|
||||
'size' => 5,
|
||||
'label' => 'Duplicate threshold',
|
||||
'name' => 'duplicate_threshold',
|
||||
'help' => 'How many fields must match for the record to be considered a duplicate.',
|
||||
'xmlrpc' => True,
|
||||
'default'=> 3,
|
||||
'admin' => False
|
||||
);
|
||||
|
||||
$crm_list_options = array(
|
||||
'~edit~' => lang('Edit contact'),
|
||||
'infolog' => lang('Open %1 CRM view', lang('infolog')),
|
||||
|
@ -85,10 +85,11 @@ class addressbook_ui extends addressbook_bo
|
||||
|
||||
$this->tmpl = new Etemplate();
|
||||
|
||||
$this->org_views = array(
|
||||
$this->grouped_views = array(
|
||||
'org_name' => lang('Organisations'),
|
||||
'org_name,adr_one_locality' => lang('Organisations by location'),
|
||||
'org_name,org_unit' => lang('Organisations by departments'),
|
||||
'duplicates' => lang('Duplicates')
|
||||
);
|
||||
|
||||
// make sure the hook for export_limit is registered
|
||||
@ -149,9 +150,10 @@ class addressbook_ui extends addressbook_bo
|
||||
{
|
||||
$msg = lang('You need to select some contacts first');
|
||||
}
|
||||
elseif ($_content['nm']['action'] == 'view_org') // org-view via context menu
|
||||
elseif ($_content['nm']['action'] == 'view_org' || $_content['nm']['action'] == 'view_duplicates')
|
||||
{
|
||||
$_content['nm']['org_view'] = array_shift($_content['nm']['selected']);
|
||||
// grouped view via context menu
|
||||
$_content['nm']['grouped_view'] = array_shift($_content['nm']['selected']);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -177,11 +179,11 @@ class addressbook_ui extends addressbook_bo
|
||||
}
|
||||
if ($_content['nm']['rows']['view']) // show all contacts of an organisation
|
||||
{
|
||||
list($org_view) = each($_content['nm']['rows']['view']);
|
||||
list($grouped_view) = each($_content['nm']['rows']['view']);
|
||||
}
|
||||
else
|
||||
{
|
||||
$org_view = $_content['nm']['org_view'];
|
||||
$grouped_view = $_content['nm']['grouped_view'];
|
||||
}
|
||||
$typeselection = $_content['nm']['col_filter']['tid'];
|
||||
}
|
||||
@ -245,7 +247,7 @@ class addressbook_ui extends addressbook_bo
|
||||
//'actions' => $this->get_actions(), // set on each request, as it depends on some filters
|
||||
'row_id' => 'id',
|
||||
'row_modified' => 'modified',
|
||||
'is_parent' => 'org_count',
|
||||
'is_parent' => 'group_count',
|
||||
'parent_id' => 'parent_id',
|
||||
'favorites' => true,
|
||||
'placeholder_actions' => array('add')
|
||||
@ -338,27 +340,25 @@ class addressbook_ui extends addressbook_bo
|
||||
{
|
||||
$this->tmpl->disableElement('nm[col_filter][tid]');
|
||||
}
|
||||
// get the availible org-views plus the label of the contacts view of one org
|
||||
$sel_options['org_view'] = $this->org_views;
|
||||
if (isset($org_view))
|
||||
// get the availible grouped-views plus the label of the contacts view of one group
|
||||
$sel_options['grouped_view'] = $this->grouped_views;
|
||||
if (isset($grouped_view))
|
||||
{
|
||||
$content['nm']['org_view'] = $org_view;
|
||||
$content['nm']['grouped_view'] = $grouped_view;
|
||||
}
|
||||
|
||||
$content['nm']['actions'] = $this->get_actions($content['nm']['col_filter']['tid'], $content['nm']['org_view']);
|
||||
$content['nm']['actions'] = $this->get_actions($content['nm']['col_filter']['tid']);
|
||||
|
||||
if (!isset($sel_options['org_view'][(string) $content['nm']['org_view']]))
|
||||
if (!isset($sel_options['grouped_view'][(string) $content['nm']['grouped_view']]))
|
||||
{
|
||||
$sel_options['org_view'] += $this->_get_org_name((string)$content['nm']['org_view']);
|
||||
$sel_options['grouped_view'] += $this->_get_grouped_name((string)$content['nm']['grouped_view']);
|
||||
}
|
||||
// unset the filters regarding organisations, when there is no organisation selected
|
||||
if (empty($sel_options['org_view'][(string) $content['nm']['org_view']]) || stripos($org_view,":") === false )
|
||||
// unset the filters regarding grouped views, when there is no group selected
|
||||
if (empty($sel_options['grouped_view'][(string) $content['nm']['grouped_view']]) || stripos($grouped_view,":") === false )
|
||||
{
|
||||
unset($content['nm']['col_filter']['org_name']);
|
||||
unset($content['nm']['col_filter']['org_unit']);
|
||||
unset($content['nm']['col_filter']['adr_one_locality']);
|
||||
$this->unset_grouped_filters($content['nm']);
|
||||
}
|
||||
$content['nm']['org_view_label'] = $sel_options['org_view'][(string) $content['nm']['org_view']];
|
||||
$content['nm']['grouped_view_label'] = $sel_options['grouped_view'][(string) $content['nm']['grouped_view']];
|
||||
|
||||
$this->tmpl->read($do_email ? 'addressbook.email' : 'addressbook.index');
|
||||
return $this->tmpl->exec($do_email ? 'addressbook.addressbook_ui.emailpopup' : 'addressbook.addressbook_ui.index',
|
||||
@ -381,7 +381,7 @@ class addressbook_ui extends addressbook_bo
|
||||
'allowOnMultiple' => false,
|
||||
'group' => $group=1,
|
||||
'onExecute' => 'javaScript:app.addressbook.view',
|
||||
'disableClass' => 'contact_organisation',
|
||||
'enableClass' => 'contact_contact',
|
||||
'hideOnDisabled' => true,
|
||||
// Children added below
|
||||
'children' => array(),
|
||||
@ -391,7 +391,7 @@ class addressbook_ui extends addressbook_bo
|
||||
'caption' => 'Open',
|
||||
'default' => $GLOBALS['egw_info']['user']['preferences']['addressbook']['crm_list'] == '~edit~',
|
||||
'allowOnMultiple' => false,
|
||||
'disableClass' => 'contact_organisation',
|
||||
'enableClass' => 'contact_contact',
|
||||
'hideOnDisabled' => true,
|
||||
'url' => 'menuaction=addressbook.addressbook_ui.edit&contact_id=$id',
|
||||
'popup' => Link::get_registry('addressbook', 'edit_popup'),
|
||||
@ -400,7 +400,7 @@ class addressbook_ui extends addressbook_bo
|
||||
'add' => array(
|
||||
'caption' => 'Add',
|
||||
'group' => $group,
|
||||
'disableClass' => 'contact_organisation',
|
||||
'enableClass' => 'contact_contact',
|
||||
'hideOnDisabled' => true,
|
||||
'children' => array(
|
||||
'new' => array(
|
||||
@ -445,20 +445,39 @@ class addressbook_ui extends addressbook_bo
|
||||
'default' => true,
|
||||
'allowOnMultiple' => false,
|
||||
'group' => $group=1,
|
||||
'disableClass' => 'contact_contact',
|
||||
'enableClass' => 'contact_organisation',
|
||||
'hideOnDisabled' => true
|
||||
),
|
||||
'add_org' => array(
|
||||
'caption' => 'Add',
|
||||
'group' => $group,
|
||||
'allowOnMultiple' => false,
|
||||
'disableClass' => 'contact_contact',
|
||||
'enableClass' => 'contact_organisation',
|
||||
'hideOnDisabled' => true,
|
||||
'url' => 'menuaction=addressbook.addressbook_ui.edit&org=$id',
|
||||
'popup' => Link::get_registry('addressbook', 'add_popup'),
|
||||
),
|
||||
);
|
||||
|
||||
// Duplicates view
|
||||
$actions += array(
|
||||
'view_duplicates' => array(
|
||||
'caption' => 'View',
|
||||
'default' => true,
|
||||
'allowOnMultiple' => false,
|
||||
'group' => $group=1,
|
||||
'enableClass' => 'contact_duplicate',
|
||||
'hideOnDisabled' => true
|
||||
),
|
||||
'merge_duplicates' => array(
|
||||
'caption' => 'Merge duplicates',
|
||||
'group' => $group,
|
||||
'allowOnMultiple' => true,
|
||||
'enableClass' => 'contact_duplicate',
|
||||
'hideOnDisabled' => true
|
||||
)
|
||||
);
|
||||
|
||||
++$group; // other AB related stuff group: lists, AB's, categories
|
||||
// categories submenu
|
||||
$actions['cat'] = array(
|
||||
@ -587,6 +606,7 @@ class addressbook_ui extends addressbook_bo
|
||||
'caption' => lang('View linked InfoLog entries'),
|
||||
'icon' => 'infolog/navbar',
|
||||
'onExecute' => 'javaScript:app.addressbook.view_infolog',
|
||||
'enableClass' => 'contact_contact',
|
||||
'allowOnMultiple' => true,
|
||||
'hideOnDisabled' => true,
|
||||
),
|
||||
@ -607,6 +627,7 @@ class addressbook_ui extends addressbook_bo
|
||||
'icon' => 'calendar/navbar',
|
||||
'caption' => 'Calendar',
|
||||
'group' => $group,
|
||||
'enableClass' => 'contact_contact',
|
||||
'children' => array(
|
||||
'calendar_view' => array(
|
||||
'caption' => 'Show',
|
||||
@ -629,7 +650,7 @@ class addressbook_ui extends addressbook_bo
|
||||
$actions['email'] = array(
|
||||
'caption' => 'Email',
|
||||
'icon' => 'mail/navbar',
|
||||
'disableClass' => 'contact_organisation',
|
||||
'enableClass' => 'contact_contact',
|
||||
'hideOnDisabled' => true,
|
||||
'group' => $group,
|
||||
'children' => array(
|
||||
@ -681,8 +702,8 @@ class addressbook_ui extends addressbook_bo
|
||||
'url' => 'menuaction=filemanager.filemanager_ui.index&path=/apps/addressbook/$id&ajax=true',
|
||||
'allowOnMultiple' => false,
|
||||
'group' => $group,
|
||||
// disable for for org-views, as it needs contact-ids
|
||||
'disableClass' => 'contact_organisation',
|
||||
// disable for for group-views, as it needs contact-ids
|
||||
'enableClass' => 'contact_contact',
|
||||
'hideOnMobile' => true
|
||||
);
|
||||
}
|
||||
@ -691,6 +712,7 @@ class addressbook_ui extends addressbook_bo
|
||||
'caption' => 'GeoLocation',
|
||||
'icon' => 'map',
|
||||
'group' => ++$group,
|
||||
'enableClass' => 'contact_contact',
|
||||
'children' => array (
|
||||
'private' => array(
|
||||
'caption' => 'Private Address',
|
||||
@ -713,6 +735,7 @@ class addressbook_ui extends addressbook_bo
|
||||
$actions['export'] = array(
|
||||
'caption' => 'Export',
|
||||
'icon' => 'filesave',
|
||||
'enableClass' => 'contact_contact',
|
||||
'group' => ++$group,
|
||||
'children' => array(
|
||||
'csv' => array(
|
||||
@ -742,7 +765,7 @@ class addressbook_ui extends addressbook_bo
|
||||
'icon' => 'filemanager/mail_post_to',
|
||||
'group' => $group,
|
||||
'onExecute' => 'javaScript:app.addressbook.adb_mail_vcard',
|
||||
'disableClass' => 'contact_organisation',
|
||||
'enableClass' => 'contact_contact',
|
||||
'hideOnDisabled' => true,
|
||||
'hideOnMobile' => true
|
||||
);
|
||||
@ -807,22 +830,99 @@ class addressbook_ui extends addressbook_bo
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of an organization from an ID for the org_view filter
|
||||
* Get a nice name for the given grouped view ID
|
||||
*
|
||||
* @param string $org
|
||||
* @return Array ID => name
|
||||
* @param String $view_id Some kind of indicator for a specific group, either
|
||||
* organisation or duplicate. It looks like key:value pairs seperated by |||.
|
||||
*
|
||||
* @return Array(ID => name), where ID is the $view_id passed in
|
||||
*/
|
||||
private function _get_org_name($org)
|
||||
protected function _get_grouped_name($view_id)
|
||||
{
|
||||
$org_name = array();
|
||||
if (strpos($org,'*AND*')!== false) $org = str_replace('*AND*','&',$org);
|
||||
foreach(explode('|||',$org) as $part)
|
||||
$group_name = array();
|
||||
if (strpos($view_id,'*AND*')!== false) $view_id = str_replace('*AND*','&',$view_id);
|
||||
foreach(explode('|||',$view_id) as $part)
|
||||
{
|
||||
list(,$name) = explode(':',$part,2);
|
||||
if ($name) $org_name[] = $name;
|
||||
if ($name) $group_name[] = $name;
|
||||
}
|
||||
$name = implode(': ',$org_name);
|
||||
return $name ? array($org => $name) : array();
|
||||
$name = implode(': ',$group_name);
|
||||
return $name ? array($view_id => $name) : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset the relevant column filters to clear a grouped view
|
||||
*
|
||||
* @param Array $query
|
||||
*/
|
||||
protected function unset_grouped_filters(&$query)
|
||||
{
|
||||
unset($query['col_filter']['org_name']);
|
||||
unset($query['col_filter']['org_unit']);
|
||||
unset($query['col_filter']['adr_one_locality']);
|
||||
foreach(static::$duplicate_fields as $field => $label)
|
||||
{
|
||||
unset($query['col_filter'][$field]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the query as needed and get the rows for the grouped views (organisation
|
||||
* or duplicate contacts)
|
||||
*
|
||||
* @param Array $query Nextmatch query
|
||||
* @return array rows found
|
||||
*/
|
||||
protected function get_grouped_rows(&$query)
|
||||
{
|
||||
// Query doesn't like empties
|
||||
unset($query['col_filter']['parent_id']);
|
||||
|
||||
if($query['actions'] && $query['actions']['open'])
|
||||
{
|
||||
// Just switched from contact view, update actions
|
||||
$query['actions'] = $this->get_actions($query['col_filter']['tid']);
|
||||
}
|
||||
unset($query['col_filter']['list']); // does not work together
|
||||
$query['no_filter2'] = true; // switch the distribution list selection off
|
||||
|
||||
$query['template'] = $query['grouped_view'] == 'duplicates' ? 'addressbook.index.duplicate_rows' : 'addressbook.index.org_rows';
|
||||
|
||||
if ($query['advanced_search'])
|
||||
{
|
||||
$query['op'] = $query['advanced_search']['operator'];
|
||||
unset($query['advanced_search']['operator']);
|
||||
$query['wildcard'] = $query['advanced_search']['meth_select'];
|
||||
unset($query['advanced_search']['meth_select']);
|
||||
$original_search = $query['search'];
|
||||
$query['search'] = $query['advanced_search'];
|
||||
}
|
||||
|
||||
switch ($query['template'])
|
||||
{
|
||||
case 'addressbook.index.org_rows':
|
||||
if ($query['order'] != 'org_name')
|
||||
{
|
||||
$query['sort'] = 'ASC';
|
||||
$query['order'] = 'org_name';
|
||||
}
|
||||
$query['org_view'] = $query['grouped_view'];
|
||||
$rows = parent::organisations($query);
|
||||
break;
|
||||
case 'addressbook.index.duplicate_rows':
|
||||
$rows = parent::duplicates($query);
|
||||
break;
|
||||
}
|
||||
|
||||
if ($query['advanced_search'])
|
||||
{
|
||||
$query['search'] = $original_search;
|
||||
unset($query['wildcard']);
|
||||
unset($query['op']);
|
||||
}
|
||||
$GLOBALS['egw_info']['flags']['params']['manual'] = array('page' => 'ManualAddressbookIndexOrga');
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -886,7 +986,7 @@ window.egw_LAB.wait(function() {
|
||||
if(!is_array($org)) $org = array($org);
|
||||
foreach($org as $org_name)
|
||||
{
|
||||
$query['org_view'] = $org_name;
|
||||
$query['grouped_view'] = $org_name;
|
||||
$checked = array();
|
||||
$readonlys = null;
|
||||
$this->get_rows($query,$checked,$readonlys,true); // true = only return the id's
|
||||
@ -907,7 +1007,7 @@ window.egw_LAB.wait(function() {
|
||||
{
|
||||
$query = Api\Cache::getSession('addressbook', 'index');
|
||||
$query['num_rows'] = -1; // all
|
||||
$query['org_view'] = $org;
|
||||
$query['grouped_view'] = $org;
|
||||
$query['searchletter'] = '';
|
||||
$checked = $readonlys = null;
|
||||
$this->get_rows($query,$checked,$readonlys,true); // true = only return the id's
|
||||
@ -1047,26 +1147,8 @@ window.egw_LAB.wait(function() {
|
||||
}
|
||||
}
|
||||
// replace org_name:* id's with all id's of that org
|
||||
$org_contacts = array();
|
||||
foreach((array)$checked as $n => $id)
|
||||
{
|
||||
if (substr($id,0,9) == 'org_name:')
|
||||
{
|
||||
if (count($checked) == 1 && !count($org_contacts) && $action == 'infolog')
|
||||
{
|
||||
return $this->infolog_org_view($id); // uses the org-name, instead of 'selected contacts'
|
||||
}
|
||||
unset($checked[$n]);
|
||||
$query = Api\Cache::getSession('addressbook', $session_name);
|
||||
$query['num_rows'] = -1; // all
|
||||
$query['org_view'] = $id;
|
||||
unset($query['filter2']);
|
||||
$extra = $readonlys = null;
|
||||
$this->get_rows($query,$extra,$readonlys,true); // true = only return the id's
|
||||
if ($extra[0]) $org_contacts = array_merge($org_contacts,$extra);
|
||||
}
|
||||
}
|
||||
if ($org_contacts) $checked = array_unique($checked ? array_merge($checked,$org_contacts) : $org_contacts);
|
||||
$grouped_contacts = $this->find_grouped_ids($action, $checked, $use_all, $success,$failed,$action_msg,$session_name);
|
||||
if ($grouped_contacts) $checked = array_unique($checked ? array_merge($checked,$grouped_contacts) : $grouped_contacts);
|
||||
//_debug_array($checked); exit;
|
||||
|
||||
if (substr($action,0,8) == 'move_to_')
|
||||
@ -1338,6 +1420,59 @@ window.egw_LAB.wait(function() {
|
||||
return !$failed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the individual contact IDs for a list of grouped contacts
|
||||
*
|
||||
* Successful lookups are removed from the checked array.
|
||||
*
|
||||
* Used for action on organisation and duplicate views
|
||||
* @param string/int $action 'delete', 'vcard', 'csv' or nummerical account_id to move contacts to that addessbook
|
||||
* @param array $checked contact id's to use if !$use_all
|
||||
* @param boolean $use_all if true use all contacts of the current selection (in the session)
|
||||
* @param int &$success number of succeded actions
|
||||
* @param int &$failed number of failed actions (not enought permissions)
|
||||
* @param string &$action_msg translated verb for the actions, to be used in a message like %1 contacts 'deleted'
|
||||
* @param string/array $session_name 'index' or 'email', or array with session-data depending if we are in the main list or the popup
|
||||
*
|
||||
* @return array List of contact IDs in the provided groups
|
||||
*/
|
||||
protected function find_grouped_ids($action,&$checked,$use_all,&$success,&$failed,&$action_msg,$session_name,&$msg)
|
||||
{
|
||||
$grouped_contacts = array();
|
||||
foreach((array)$checked as $n => $id)
|
||||
{
|
||||
if (substr($id,0,9) == 'org_name:' || substr($id, 0,10) == 'duplicate:')
|
||||
{
|
||||
if (count($checked) == 1 && !count($grouped_contacts) && $action == 'infolog')
|
||||
{
|
||||
return $this->infolog_org_view($id); // uses the org-name, instead of 'selected contacts'
|
||||
}
|
||||
unset($checked[$n]);
|
||||
$query = Api\Cache::getSession('addressbook', $session_name);
|
||||
$query['num_rows'] = -1; // all
|
||||
$query['grouped_view'] = $id;
|
||||
unset($query['filter2']);
|
||||
$extra = $readonlys = null;
|
||||
$this->get_rows($query,$extra,$readonlys,true); // true = only return the id's
|
||||
|
||||
// Merge them here, so we only merge the ones that are duplicates,
|
||||
// not merge all selected together
|
||||
if($action == 'merge_duplicates')
|
||||
{
|
||||
$loop_success = $loop_fail = 0;
|
||||
$this->action('merge', $extra, false, $loop_success, $loop_fail, $action_msg,$session_name,$msg);
|
||||
$success += $loop_success;
|
||||
$failed += $loop_fail;
|
||||
}
|
||||
if ($extra[0])
|
||||
{
|
||||
$grouped_contacts = array_merge($grouped_contacts,$extra);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $grouped_contacts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a given contact (not storing it!)
|
||||
*
|
||||
@ -1400,30 +1535,21 @@ window.egw_LAB.wait(function() {
|
||||
{
|
||||
$old_state = Api\Cache::getSession('addressbook', $what);
|
||||
}
|
||||
if (!isset($this->org_views[(string) $query['org_view']]) || strpos($query['org_view'],':') === false) // we dont have an org view, unset the according col_filters
|
||||
if (!isset($this->grouped_views[(string) $query['grouped_view']]) || strpos($query['grouped_view'],':') === false)
|
||||
{
|
||||
if (isset($query['col_filter']['org_name'])) unset($query['col_filter']['org_name']);
|
||||
if (isset($query['col_filter']['adr_one_locality'])) unset($query['col_filter']['adr_one_locality']);
|
||||
if (isset($query['col_filter']['org_unit'])) unset($query['col_filter']['org_unit']);
|
||||
// we don't have a grouped view, unset the according col_filters
|
||||
$this->unset_grouped_filters($query);
|
||||
}
|
||||
|
||||
if (isset($this->org_views[(string) $query['org_view']])) // we have an org view, reset the advanced search
|
||||
if (isset($this->grouped_views[(string) $query['grouped_view']]))
|
||||
{
|
||||
//_debug_array(array('Search'=>$query['search'],
|
||||
// 'AdvancedSearch'=>$query['advanced_search']));
|
||||
//if (is_array($query['search'])) unset($query['search']);
|
||||
//unset($query['advanced_search']);
|
||||
// we have a grouped view, reset the advanced search
|
||||
if(!$query['search'] && $old_state['advanced_search']) $query['advanced_search'] = $old_state['advanced_search'];
|
||||
}
|
||||
elseif(!$query['search'] && array_key_exists('advanced_search',$old_state)) // eg. paging in an advanced search
|
||||
{
|
||||
$query['advanced_search'] = $old_state['advanced_search'];
|
||||
}
|
||||
/* this cant work anymore, as Framework::set_onload no longer exists
|
||||
if ($do_email && etemplate::$loop)
|
||||
{ // remove previous addEmail() calls, otherwise they will be run again
|
||||
Framework::set_onload(preg_replace('/addEmail\([^)]+\);/','',Framework::set_onload()),true);
|
||||
}*/
|
||||
|
||||
// Make sure old lettersearch filter doesn't stay - current letter filter will be added later
|
||||
foreach($query['col_filter'] as $key => $col_filter)
|
||||
@ -1441,7 +1567,7 @@ window.egw_LAB.wait(function() {
|
||||
if (!$id_only)
|
||||
{
|
||||
// check if accounts are stored in ldap, which does NOT yet support the org-views
|
||||
if ($this->so_accounts && $query['filter'] === '0' && $query['org_view'])
|
||||
if ($this->so_accounts && $query['filter'] === '0' && $query['grouped_view'])
|
||||
{
|
||||
if ($old_state['filter'] === '0') // user changed to org_view
|
||||
{
|
||||
@ -1449,21 +1575,21 @@ window.egw_LAB.wait(function() {
|
||||
}
|
||||
else // user changed to accounts
|
||||
{
|
||||
$query['org_view'] = ''; // --> change to regular contacts view
|
||||
$query['grouped_view'] = ''; // --> change to regular contacts view
|
||||
}
|
||||
}
|
||||
if ($query['org_view'] && isset($this->org_views[$old_state['org_view']]) && !isset($this->org_views[$query['org_view']]))
|
||||
if ($query['grouped_view'] && isset($this->grouped_views[$old_state['grouped_view']]) && !isset($this->grouped_views[$query['grouped_view']]))
|
||||
{
|
||||
$query['searchletter'] = ''; // reset lettersearch if viewing the contacts of one organisation
|
||||
$query['searchletter'] = ''; // reset lettersearch if viewing the contacts of one group (org or duplicates)
|
||||
}
|
||||
// save the state of the index in the user prefs
|
||||
$state = serialize(array(
|
||||
'filter' => $query['filter'],
|
||||
'cat_id' => $query['cat_id'],
|
||||
'order' => $query['order'],
|
||||
'sort' => $query['sort'],
|
||||
'col_filter' => array('tid' => $query['col_filter']['tid']),
|
||||
'org_view' => $query['org_view'],
|
||||
'filter' => $query['filter'],
|
||||
'cat_id' => $query['cat_id'],
|
||||
'order' => $query['order'],
|
||||
'sort' => $query['sort'],
|
||||
'col_filter' => array('tid' => $query['col_filter']['tid']),
|
||||
'grouped_view' => $query['grouped_view'],
|
||||
));
|
||||
if ($state != $this->prefs[$what.'_state'] && !$query['csv_export'])
|
||||
{
|
||||
@ -1511,44 +1637,11 @@ window.egw_LAB.wait(function() {
|
||||
// all backends allow now at least to use groups as distribution lists
|
||||
$query['no_filter2'] = false;
|
||||
|
||||
if (isset($this->org_views[(string) $query['org_view']]) && !$query['col_filter']['parent_id']) // we have an org view
|
||||
// Grouped view
|
||||
if (isset($this->grouped_views[(string) $query['grouped_view']]) && !$query['col_filter']['parent_id'])
|
||||
{
|
||||
// Query doesn't like empties
|
||||
unset($query['col_filter']['parent_id']);
|
||||
|
||||
if($query['actions'] && $query['actions']['open'])
|
||||
{
|
||||
// Just switched from contact view, update actions
|
||||
$query['actions'] = $this->get_actions($query['col_filter']['tid'], $query['org_view']);
|
||||
}
|
||||
unset($query['col_filter']['list']); // does not work together
|
||||
$query['no_filter2'] = true; // switch the distribution list selection off
|
||||
|
||||
$query['template'] = 'addressbook.index.org_rows';
|
||||
|
||||
if ($query['order'] != 'org_name')
|
||||
{
|
||||
$query['sort'] = 'ASC';
|
||||
$query['order'] = 'org_name';
|
||||
}
|
||||
if ($query['advanced_search'])
|
||||
{
|
||||
$query['op'] = $query['advanced_search']['operator'];
|
||||
unset($query['advanced_search']['operator']);
|
||||
$query['wildcard'] = $query['advanced_search']['meth_select'];
|
||||
unset($query['advanced_search']['meth_select']);
|
||||
$original_search = $query['search'];
|
||||
$query['search'] = $query['advanced_search'];
|
||||
}
|
||||
|
||||
$rows = parent::organisations($query);
|
||||
if ($query['advanced_search'])
|
||||
{
|
||||
$query['search'] = $original_search;
|
||||
unset($query['wildcard']);
|
||||
unset($query['op']);
|
||||
}
|
||||
$GLOBALS['egw_info']['flags']['params']['manual'] = array('page' => 'ManualAddressbookIndexOrga');
|
||||
$query['grouped_view_label'] = '';
|
||||
$rows = $this->get_grouped_rows($query);
|
||||
}
|
||||
else // contacts view
|
||||
{
|
||||
@ -1562,19 +1655,21 @@ window.egw_LAB.wait(function() {
|
||||
}
|
||||
if($query['col_filter']['parent_id'])
|
||||
{
|
||||
$query['org_view'] = $query['col_filter']['parent_id'];
|
||||
$query['template'] = 'addressbook.index.org_rows';
|
||||
$query['grouped_view'] = $query['col_filter']['parent_id'];
|
||||
$query['template'] = strpos($query['grouped_view'], 'duplicate') === 0 ?
|
||||
'addressbook.index.duplicate_rows' : 'addressbook.index.org_rows';
|
||||
}
|
||||
// Query doesn't like parent_id
|
||||
unset($query['col_filter']['parent_id']);
|
||||
if ($query['org_view']) // view the contacts of one organisation only
|
||||
if ($query['grouped_view']) // view the contacts of one organisation only
|
||||
{
|
||||
if (strpos($query['org_view'],'*AND*') !== false) $query['org_view'] = str_replace('*AND*','&',$query['org_view']);
|
||||
foreach(explode('|||',$query['org_view']) as $part)
|
||||
if (strpos($query['grouped_view'],'*AND*') !== false) $query['grouped_view'] = str_replace('*AND*','&',$query['grouped_view']);
|
||||
$fields = explode(',',$GLOBALS['egw_info']['user']['preferences']['addressbook']['duplicate_fields']);
|
||||
foreach(explode('|||',$query['grouped_view']) as $part)
|
||||
{
|
||||
list($name,$value) = explode(':',$part,2);
|
||||
// do NOT set invalid column, as this gives an SQL error ("AND AND" in sql)
|
||||
if (in_array($name, array('org_name','org_unit','adr_one_location')))
|
||||
if (static::$duplicate_fields[$name] && in_array($name, $fields) && $value)
|
||||
{
|
||||
$query['col_filter'][$name] = $value;
|
||||
}
|
||||
@ -1582,8 +1677,8 @@ window.egw_LAB.wait(function() {
|
||||
}
|
||||
else if($query['actions'] && !$query['actions']['edit'])
|
||||
{
|
||||
// Just switched from org view, update actions
|
||||
$query['actions'] = $this->get_actions($query['col_filter']['tid'], $query['org_view']);
|
||||
// Just switched from grouped view, update actions
|
||||
$query['actions'] = $this->get_actions($query['col_filter']['tid']);
|
||||
}
|
||||
// translate the select order to the really used over all 3 columns
|
||||
$sort = $query['sort'];
|
||||
@ -1712,17 +1807,17 @@ window.egw_LAB.wait(function() {
|
||||
list($row['line1'],$row['line2']) = explode(': ',$row['n_fileas']);
|
||||
break;
|
||||
}
|
||||
if (isset($this->org_views[(string) $query['org_view']]))
|
||||
if (isset($this->grouped_views[(string) $query['grouped_view']]))
|
||||
{
|
||||
$row['type'] = 'home';
|
||||
$row['type_label'] = lang('Organisation');
|
||||
$row['type_label'] = $query['grouped_view'] == 'duplicate' ? lang('Duplicates') : lang('Organisation');
|
||||
|
||||
if ($query['filter'] && !($this->grants[(int)$query['filter']] & Acl::DELETE))
|
||||
{
|
||||
$row['class'] .= 'rowNoDelete ';
|
||||
}
|
||||
$row['class'] .= 'rowNoEdit '; // no edit in OrgView
|
||||
$row['class'] .= 'contact_organisation ';
|
||||
$row['class'] .= $query['grouped_view'] == 'duplicates' ? 'contact_duplicate' : 'contact_organisation ';
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1802,7 +1897,7 @@ window.egw_LAB.wait(function() {
|
||||
|
||||
// full app-header with all search criteria specially for the print
|
||||
$header = array();
|
||||
if ($query['filter'] !== '' && !isset($this->org_views[$query['org_view']]))
|
||||
if ($query['filter'] !== '' && !isset($this->grouped_views[$query['grouped_view']]))
|
||||
{
|
||||
$header[] = ($query['filter'] == '0' ? lang('accounts') :
|
||||
($GLOBALS['egw']->accounts->get_type($query['filter']) == 'g' ?
|
||||
@ -1810,14 +1905,14 @@ window.egw_LAB.wait(function() {
|
||||
Api\Accounts::username((int)$query['filter']).
|
||||
(substr($query['filter'],-1) == 'p' ? ' ('.lang('private').')' : '')));
|
||||
}
|
||||
if ($query['org_view'])
|
||||
if ($query['grouped_view'])
|
||||
{
|
||||
$header[] = $query['org_view_label'];
|
||||
$header[] = $query['grouped_view_label'];
|
||||
// Make sure option is there
|
||||
if(!array_key_exists($query['org_view'], $this->org_views))
|
||||
if(!array_key_exists($query['grouped_view'], $this->grouped_views))
|
||||
{
|
||||
$this->org_views += $this->_get_org_name($query['org_view']);
|
||||
$rows['sel_options']['org_view'] = $this->org_views;
|
||||
$this->grouped_views += $this->_get_grouped_name($query['grouped_view']);
|
||||
$rows['sel_options']['grouped_view'] = $this->grouped_views;
|
||||
}
|
||||
}
|
||||
if($query['advanced_search'])
|
||||
@ -2103,9 +2198,9 @@ window.egw_LAB.wait(function() {
|
||||
if ($org[0] == '"') $org = substr($org, 1, -1);
|
||||
$content = $this->read_org($org);
|
||||
}
|
||||
elseif ($state['org_view'] && !isset($this->org_views[$state['org_view']]))
|
||||
elseif ($state['grouped_view'] && !isset($this->grouped_views[$state['grouped_view']]))
|
||||
{
|
||||
$content = $this->read_org($state['org_view']);
|
||||
$content = $this->read_org($state['grouped_view']);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -2491,12 +2586,14 @@ window.egw_LAB.wait(function() {
|
||||
public function ajax_check_values($values, $name, $own_id=0)
|
||||
{
|
||||
$matches = null;
|
||||
$fields = explode(',',$GLOBALS['egw_info']['user']['preferences']['addressbook']['duplicate_fields']);
|
||||
|
||||
if (preg_match('/^exec\[([^\]]+)\]$/', $name, $matches)) $name = $matches[1]; // remove exec[ ]
|
||||
|
||||
$ret = array('doublicates' => array(), 'msg' => null);
|
||||
|
||||
// if email changed, check for doublicates
|
||||
if (in_array($name, array('email', 'email_home')))
|
||||
if (in_array($name, array('email', 'email_home')) && in_array($name, $fields))
|
||||
{
|
||||
if (preg_match(Etemplate\Widget\Url::EMAIL_PREG, $values[$name])) // only search for real email addresses, to not return to many contacts
|
||||
{
|
||||
@ -2513,12 +2610,19 @@ window.egw_LAB.wait(function() {
|
||||
// Full options for et2
|
||||
$ret['fileas_sel_options'] = $this->fileas_options($values);
|
||||
|
||||
// if name, firstname or org changed and at least 2 are specified, check for doublicates
|
||||
if (in_array($name, array('n_given', 'n_family', 'org_name')) &&
|
||||
!empty($values['n_given'])+!empty($values['n_family'])+!empty($values['org_name']) >= 2)
|
||||
// if name, firstname or org changed and enough are specified, check for doublicates
|
||||
$specified_count = 0;
|
||||
foreach($fields as $field)
|
||||
{
|
||||
if($values[$field])
|
||||
{
|
||||
$specified_count++;
|
||||
}
|
||||
}
|
||||
if (in_array($name,$fields) && $specified_count >= 2)
|
||||
{
|
||||
$filter = array();
|
||||
foreach(array('email', 'n_given', 'n_family', 'org_name') as $n) // use email too, to exclude obvious false positives
|
||||
foreach($fields as $n) // use email too, to exclude obvious false positives
|
||||
{
|
||||
if (!empty($values[$n])) $filter[$n] = $values[$n];
|
||||
}
|
||||
|
96
addressbook/templates/default/index.duplicate_rows.xet
Normal file
96
addressbook/templates/default/index.duplicate_rows.xet
Normal file
@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE overlay PUBLIC "-//EGroupware GmbH//eTemplate 2//EN" "http://www.egroupware.org/etemplate2.dtd">
|
||||
<!-- $Id$ -->
|
||||
<overlay>
|
||||
<template id="addressbook.index.duplicate_rows" template="" lang="" group="0" version="16.001">
|
||||
<grid width="100%">
|
||||
<columns>
|
||||
<column width="75"/>
|
||||
<column width="25"/>
|
||||
<column width="40%"/>
|
||||
<column width="30%"/>
|
||||
<column width="30%"/>
|
||||
<column width="180"/>
|
||||
<column width="180"/>
|
||||
</columns>
|
||||
<rows>
|
||||
<row class="th">
|
||||
<nextmatch-header label="Type" id="type"/>
|
||||
<nextmatch-header label="#" align="center" id="group_count"/>
|
||||
<grid>
|
||||
<columns>
|
||||
<column/>
|
||||
<column/>
|
||||
</columns>
|
||||
<rows>
|
||||
<row disabled="!@order=n_fileas">
|
||||
<nextmatch-sortheader label="own sorting" id="n_fileas" span="all"/>
|
||||
</row>
|
||||
<row disabled="!@order=n_given">
|
||||
<nextmatch-sortheader label="Firstname" id="n_given"/>
|
||||
<nextmatch-sortheader label="Name" id="n_family"/>
|
||||
</row>
|
||||
<row disabled="!@order=n_family">
|
||||
<nextmatch-sortheader label="Name" id="n_family"/>
|
||||
<nextmatch-sortheader label="Firstname" id="n_given"/>
|
||||
</row>
|
||||
<row>
|
||||
<nextmatch-sortheader label="Organisation" id="org_name" span="all"/>
|
||||
</row>
|
||||
<row disabled="!@order=/^(org_name|n_fileas|adr_one_postalcode|contact_modified|contact_created|#)/">
|
||||
<nextmatch-sortheader label="Name" id="n_family"/>
|
||||
<nextmatch-sortheader label="Firstname" id="n_given" class="leftPad5"/>
|
||||
</row>
|
||||
<row disabled="@order=n_fileas">
|
||||
<nextmatch-sortheader label="own sorting" id="n_fileas" span="all"/>
|
||||
</row>
|
||||
</rows>
|
||||
</grid>
|
||||
<nextmatch-header label="Business address" id="business"/>
|
||||
<!--
|
||||
<vbox>
|
||||
<nextmatch-header label="Business phone" id="tel_work"/>
|
||||
<nextmatch-header label="Mobile phone" id="tel_cell"/>
|
||||
<nextmatch-header label="Home phone" id="tel_home"/>
|
||||
<description value="Fax"/>
|
||||
</vbox>
|
||||
-->
|
||||
<vbox>
|
||||
<nextmatch-header label="Business email" id="email"/>
|
||||
<nextmatch-header label="Home email" id="email_home"/>
|
||||
</vbox>
|
||||
</row>
|
||||
<row class="$row_cont[cat_id] $row_cont[class]" valign="top">
|
||||
<image label="$row_cont[type_label]" src="${row}[type]" align="center" no_lang="1"/>
|
||||
<int id="${row}[group_count]" readonly="true" align="center"/>
|
||||
<vbox id="${row}[id]">
|
||||
<description id="${row}[line1]" no_lang="1"/>
|
||||
<description id="${row}[line2]" no_lang="1"/>
|
||||
<description id="${row}[org_unit]" no_lang="1"/>
|
||||
<description id="${row}[title]" no_lang="1"/>
|
||||
<description id="${row}[first_org]" no_lang="1"/>
|
||||
</vbox>
|
||||
<vbox>
|
||||
<description value=" " id="${row}[adr_one_locality]" no_lang="1" class="leftPad5"/>
|
||||
<menulist>
|
||||
<menupopup type="select-country" id="${row}[adr_one_countrycode]" readonly="true"/>
|
||||
</menulist>
|
||||
</vbox>
|
||||
<!--
|
||||
<vbox>
|
||||
<url-phone id="${row}[tel_work]" readonly="true" class="telNumbers"/>
|
||||
<url-phone id="${row}[tel_cell]" readonly="true" class="telNumbers"/>
|
||||
<url-phone id="${row}[tel_home]" readonly="true" class="telNumbers"/>
|
||||
<url-phone id="${row}[tel_fax]" readonly="true"/>
|
||||
<description id="${row}[tel_prefered]" no_lang="1" href="$row_cont[tel_prefered_link]" extra_link_target="calling" extra_link_popup="$cont[call_popup]"/>
|
||||
</vbox>
|
||||
-->
|
||||
<vbox>
|
||||
<url-email id="${row}[email]" readonly="true" class="fixedHeight"/>
|
||||
<url-email id="${row}[email_home]" readonly="true" class="fixedHeight"/>
|
||||
</vbox>
|
||||
</row>
|
||||
</rows>
|
||||
</grid>
|
||||
</template>
|
||||
</overlay>
|
@ -174,7 +174,7 @@
|
||||
</template>
|
||||
<template id="addressbook.index.row" template="" lang="" group="0" version="1.3.001">
|
||||
<buttononly align="right" statustext="Advanced search" image="advanced-search" background_image="1" id="advanced-search" onclick="egw(window).openPopup(egw::link('/index.php','menuaction=addressbook.addressbook_ui.search'),'870','610','_blank','addressbook',null,true); return false;"/>
|
||||
<select statustext="Select a view" id="org_view" no_lang="1" rows="1" empty_label="All contacts"/>
|
||||
<select statustext="Select a view" id="grouped_view" no_lang="1" rows="1" empty_label="All contacts"/>
|
||||
</template>
|
||||
<template id="addressbook.index.right" template="" lang="" group="0" version="1.7.001">
|
||||
<select align="right" id="col_filter[tid]" empty_label="All types"/>
|
||||
|
@ -207,6 +207,7 @@ class Sql extends Api\Storage
|
||||
$filter['org_name'][$row['org_name']] = $row['org_name']; // use as key too to have every org only once
|
||||
}
|
||||
$org_key = $row['org_name'].($by ? '|||'.($row[$by] || $row[$by.'_count']==1 ? $row[$by] : '|||') : '');
|
||||
$row['group_count'] = $row['org_count'];
|
||||
$orgs[$org_key] = $row;
|
||||
}
|
||||
unset($rows);
|
||||
@ -242,6 +243,141 @@ class Sql extends Api\Storage
|
||||
return array_values($orgs);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Query for duplicate contacts according to given parameters
|
||||
*
|
||||
* We join egw_addressbook to itself, and count how many fields match. If
|
||||
* enough of the fields we care about match, we count those two records as
|
||||
* duplicates.
|
||||
*
|
||||
* @var array $param
|
||||
* @var string $param[grouped_view] 'duplicate', 'duplicate,adr_one_location', 'duplicate,org_name' how to group
|
||||
* @var int $param[owner] addressbook to search
|
||||
* @var string $param[search] search pattern for org_name
|
||||
* @var string $param[searchletter] letter the name need to start with
|
||||
* @var array $param[col_filter] filter
|
||||
* @var string $param[search] or'ed search pattern
|
||||
* @var array $param[advanced_search] indicator that advanced search is active
|
||||
* @var string $param[op] (operator like AND or OR; will be passed when advanced search is active)
|
||||
* @var string $param[wildcard] (wildcard like % or empty or not set (for no wildcard); will be passed when advanced search is active)
|
||||
* @var int $param[start]
|
||||
* @var int $param[num_rows]
|
||||
* @var string $param[sort] ASC or DESC
|
||||
* @return array or arrays with keys org_name,count and evtl. adr_one_location or org_unit
|
||||
*/
|
||||
function duplicates($param)
|
||||
{
|
||||
$join = 'JOIN ' . $this->table_name . ' AS a2 ON ';
|
||||
$filter = array(
|
||||
$this->table_name.'.contact_tid != "D"'
|
||||
);
|
||||
$op = 'OR';
|
||||
if (isset($param['op']) && !empty($param['op'])) $op = $param['op'];
|
||||
$advanced_search = false;
|
||||
if (isset($param['advanced_search']) && !empty($param['advanced_search'])) $advanced_search = true;
|
||||
$wildcard ='%';
|
||||
if ($advanced_search || (isset($param['wildcard']) && !empty($param['wildcard']))) $wildcard = ($param['wildcard']?$param['wildcard']:'');
|
||||
|
||||
// fix cat_id filter to search in comma-separated multiple cats and return subcats
|
||||
if ($param['cat_id'])
|
||||
{
|
||||
$cat_filter = $this->_cat_filter($filter['cat_id']);
|
||||
$filter[] = str_replace('cat_id', $this->table_name . '.cat_id', $cat_filter);
|
||||
$join .= str_replace('cat_id', 'a2.cat_id', $cat_filter) . ' AND ';
|
||||
unset($filter['cat_id']);
|
||||
}
|
||||
// add filter for read ACL in sql, if user is NOT the owner of the addressbook
|
||||
if ($param['owner'] && $param['owner'] == $GLOBALS['egw_info']['user']['account_id'])
|
||||
{
|
||||
$filter['owner'] = $param['owner'];
|
||||
$join .= 'a2.owner = ' . $this->db->quote($filter['owner']) . ' AND ';
|
||||
}
|
||||
else
|
||||
{
|
||||
// we have no private grants in addressbook at the moment, they have then to be added here too
|
||||
if ($param['owner'])
|
||||
{
|
||||
if (!$this->grants[(int) $filter['owner']]) return false; // we have no access to that addressbook
|
||||
|
||||
$filter['owner'] = $param['owner'];
|
||||
$filter['private'] = 0;
|
||||
$join .= 'a2.owner = ' . $this->db->quote($filter['owner']) . ' AND ';
|
||||
$join .= 'a2.private = ' . $this->db->quote($filter['private']) . ' AND ';
|
||||
}
|
||||
else // search all addressbooks, incl. accounts
|
||||
{
|
||||
if ($this->account_repository != 'sql' && $this->contact_repository != 'sql-ldap')
|
||||
{
|
||||
$filter[] = $this->table_name.'.contact_owner != 0'; // in case there have been accounts in sql previously
|
||||
}
|
||||
$filter[] = $access = "(".$this->table_name.".contact_owner=".(int)$GLOBALS['egw_info']['user']['account_id'].
|
||||
" OR {$this->table_name}.contact_private=0 AND ".$this->table_name.".contact_owner IN (".
|
||||
implode(',',array_keys($this->grants))."))";
|
||||
$join .= str_replace($this->table_name, 'a2', $access) . ' AND ';
|
||||
}
|
||||
}
|
||||
if ($param['searchletter'])
|
||||
{
|
||||
$filter[] = $this->table_name.'.n_fn '.$this->db->capabilities[Api\Db::CAPABILITY_CASE_INSENSITIV_LIKE].' '.$this->db->quote($param['searchletter'].'%');
|
||||
}
|
||||
$sort = $param['sort'] == 'DESC' ? 'DESC' : 'ASC';
|
||||
$group = $GLOBALS['egw_info']['user']['preferences']['addressbook']['duplicate_fields'] ?
|
||||
explode(',',$GLOBALS['egw_info']['user']['preferences']['addressbook']['duplicate_fields']):
|
||||
array('n_family', 'n_given', 'org_name', 'contact_email');
|
||||
$match_count = $GLOBALS['egw_info']['user']['preferences']['addressbook']['duplicate_threshold'] ?
|
||||
$GLOBALS['egw_info']['user']['preferences']['addressbook']['duplicate_threshold'] : 3;
|
||||
|
||||
$extra = Array();
|
||||
$order = in_array($param['order'], $group) ? $param['order'] : $group[0];
|
||||
$join .= $this->table_name .'.contact_id != a2.contact_id AND a2.contact_tid != "D" AND (';
|
||||
$join_fields = Array();
|
||||
foreach($group as &$field)
|
||||
{
|
||||
$extra[] = "IF({$this->table_name}.$field = a2.$field, 1, 0)";
|
||||
$join_fields[] = $this->table_name . ".$field = a2.$field";
|
||||
$field = $this->table_name . ".$field AS $field";
|
||||
}
|
||||
$extra = Array(
|
||||
'a2.contact_id AS matched',
|
||||
implode('+', $extra) . ' AS match_count'
|
||||
);
|
||||
$join .= $this->db->column_data_implode(' OR ',$join_fields) . ')';
|
||||
|
||||
$append = " HAVING match_count >= $match_count ORDER BY {$this->table_name}.{$order} $sort, $this->table_name.contact_id";
|
||||
$group[] = $this->table_name.'.contact_id AS contact_id';
|
||||
|
||||
$rows = parent::search($param['search'],$group,
|
||||
$append,$extra,$wildcard,false,$op/*'OR'*/,
|
||||
array($param['start'],$param['num_rows']),$filter, $join);
|
||||
|
||||
// Go through rows and only return one for each pair/triplet/etc. of matches
|
||||
$dupes = array();
|
||||
foreach($rows as $key => &$row)
|
||||
{
|
||||
if(array_key_exists($row['contact_id'], $dupes))
|
||||
{
|
||||
$kept_row =& $rows[$dupes[$row['contact_id']]];
|
||||
$kept_row['group_count']++;
|
||||
|
||||
// Clear not matching fields, or we won't be able to find children
|
||||
foreach($kept_row as $sub_key => $sub_value)
|
||||
{
|
||||
if(in_array($sub_key, array('contact_id','group_count'))) continue;
|
||||
if($row[$sub_key] != $sub_value)
|
||||
{
|
||||
unset($kept_row[$sub_key]);
|
||||
}
|
||||
}
|
||||
unset($rows[$key]);
|
||||
$this->total--;
|
||||
}
|
||||
$dupes[$row['matched']] = $key;
|
||||
$row['group_count'] = 1;
|
||||
}
|
||||
return array_values($rows);
|
||||
}
|
||||
|
||||
/**
|
||||
* searches db for rows matching searchcriteria
|
||||
*
|
||||
|
@ -159,6 +159,24 @@ class Storage
|
||||
*/
|
||||
var $content_types = array();
|
||||
|
||||
/**
|
||||
* These fields are options for checking for duplicate contacts
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $duplicate_fields = array(
|
||||
'n_given' => 'first name',
|
||||
'n_middle' => 'middle name',
|
||||
'n_family' => 'last name',
|
||||
'contact_bday' => 'birthday',
|
||||
'org_name' => 'Organisation',
|
||||
'org_unit' => 'Department',
|
||||
'adr_one_locality' => 'Location',
|
||||
'contact_title' => 'title',
|
||||
'contact_email' => 'business email',
|
||||
'contact_email_home'=> 'email (private)',
|
||||
);
|
||||
|
||||
/**
|
||||
* Special content type to indicate a deleted addressbook
|
||||
*
|
||||
@ -796,6 +814,80 @@ class Storage
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find contacts that appear to be duplicates
|
||||
*
|
||||
* @param Array $param
|
||||
* @param string $param[org_view] 'org_name', 'org_name,adr_one_location', 'org_name,org_unit' how to group
|
||||
* @param int $param[owner] addressbook to search
|
||||
* @param string $param[search] search pattern for org_name
|
||||
* @param string $param[searchletter] letter the org_name need to start with
|
||||
* @param int $param[start]
|
||||
* @param int $param[num_rows]
|
||||
* @param string $param[sort] ASC or DESC
|
||||
*
|
||||
* @return array of arrays
|
||||
*/
|
||||
public function duplicates($param)
|
||||
{
|
||||
if (!method_exists($this->somain,'duplicates'))
|
||||
{
|
||||
$this->total = 0;
|
||||
return false;
|
||||
}
|
||||
if ($param['search'] && !is_array($param['search']))
|
||||
{
|
||||
$search = $param['search'];
|
||||
$param['search'] = array();
|
||||
if($this->somain instanceof Sql)
|
||||
{
|
||||
// Keep the string, let the parent deal with it
|
||||
$param['search'] = $search;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach($this->columns_to_search as $col)
|
||||
{
|
||||
// we don't search the customfields
|
||||
if ($col != 'contact_value') $param['search'][$col] = $search;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is_array($param['search']) && count($param['search']))
|
||||
{
|
||||
$param['search'] = $this->data2db($param['search']);
|
||||
}
|
||||
if(!array_key_exists('tid', $param['col_filter']) || $param['col_filter']['tid'] === '')
|
||||
{
|
||||
$param['col_filter'][] = 'contact_tid != \'' . self::DELETED_TYPE . '\'';
|
||||
}
|
||||
elseif(is_null($param['col_filter']['tid']))
|
||||
{
|
||||
// return all entries including deleted
|
||||
unset($param['col_filter']['tid']);
|
||||
}
|
||||
|
||||
|
||||
$rows = $this->somain->duplicates($param);
|
||||
$this->total = $this->somain->total;
|
||||
|
||||
if (!$rows) return array();
|
||||
|
||||
foreach($rows as $n => $row)
|
||||
{
|
||||
$rows[$n]['id'] = 'duplicate:';
|
||||
foreach(static::$duplicate_fields as $by => $by_label)
|
||||
{
|
||||
if (strpos($row[$by],'&')!==false) $row[$by] = str_replace('&','*AND*',$row[$by]);
|
||||
if($row[$by])
|
||||
{
|
||||
$rows[$n]['id'] .= '|||'.$by.':'.$row[$by];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* gets all contact fields from database
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user