* Addressbook - new view to show duplicate contacts

This commit is contained in:
nathangray 2017-03-13 12:11:37 -06:00
parent f943ed471a
commit 23bf37b98e
6 changed files with 599 additions and 146 deletions

View File

@ -150,6 +150,11 @@ class addressbook_hooks
); );
$contacts = new Api\Contacts(); $contacts = new Api\Contacts();
$fileas_options = $contacts->fileas_options(); $fileas_options = $contacts->fileas_options();
foreach(Api\Contacts\Storage::$duplicate_fields as $key => $label)
{
$duplicate_options[$key] = lang($label);
}
$settings['link_title'] = array( $settings['link_title'] = array(
'type' => 'select', 'type' => 'select',
'label' => 'Link title for contacts show', 'label' => 'Link title for contacts show',
@ -197,6 +202,26 @@ class addressbook_hooks
'admin' => false, 'admin' => false,
'default'=> 'org_name: n_family, n_given', '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( $crm_list_options = array(
'~edit~' => lang('Edit contact'), '~edit~' => lang('Edit contact'),
'infolog' => lang('Open %1 CRM view', lang('infolog')), 'infolog' => lang('Open %1 CRM view', lang('infolog')),

View File

@ -85,10 +85,11 @@ class addressbook_ui extends addressbook_bo
$this->tmpl = new Etemplate(); $this->tmpl = new Etemplate();
$this->org_views = array( $this->grouped_views = array(
'org_name' => lang('Organisations'), 'org_name' => lang('Organisations'),
'org_name,adr_one_locality' => lang('Organisations by location'), 'org_name,adr_one_locality' => lang('Organisations by location'),
'org_name,org_unit' => lang('Organisations by departments'), 'org_name,org_unit' => lang('Organisations by departments'),
'duplicates' => lang('Duplicates')
); );
// make sure the hook for export_limit is registered // 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'); $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 else
{ {
@ -177,11 +179,11 @@ class addressbook_ui extends addressbook_bo
} }
if ($_content['nm']['rows']['view']) // show all contacts of an organisation 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 else
{ {
$org_view = $_content['nm']['org_view']; $grouped_view = $_content['nm']['grouped_view'];
} }
$typeselection = $_content['nm']['col_filter']['tid']; $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 //'actions' => $this->get_actions(), // set on each request, as it depends on some filters
'row_id' => 'id', 'row_id' => 'id',
'row_modified' => 'modified', 'row_modified' => 'modified',
'is_parent' => 'org_count', 'is_parent' => 'group_count',
'parent_id' => 'parent_id', 'parent_id' => 'parent_id',
'favorites' => true, 'favorites' => true,
'placeholder_actions' => array('add') 'placeholder_actions' => array('add')
@ -338,27 +340,25 @@ class addressbook_ui extends addressbook_bo
{ {
$this->tmpl->disableElement('nm[col_filter][tid]'); $this->tmpl->disableElement('nm[col_filter][tid]');
} }
// get the availible org-views plus the label of the contacts view of one org // get the availible grouped-views plus the label of the contacts view of one group
$sel_options['org_view'] = $this->org_views; $sel_options['grouped_view'] = $this->grouped_views;
if (isset($org_view)) 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 // unset the filters regarding grouped views, when there is no group selected
if (empty($sel_options['org_view'][(string) $content['nm']['org_view']]) || stripos($org_view,":") === false ) if (empty($sel_options['grouped_view'][(string) $content['nm']['grouped_view']]) || stripos($grouped_view,":") === false )
{ {
unset($content['nm']['col_filter']['org_name']); $this->unset_grouped_filters($content['nm']);
unset($content['nm']['col_filter']['org_unit']);
unset($content['nm']['col_filter']['adr_one_locality']);
} }
$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'); $this->tmpl->read($do_email ? 'addressbook.email' : 'addressbook.index');
return $this->tmpl->exec($do_email ? 'addressbook.addressbook_ui.emailpopup' : 'addressbook.addressbook_ui.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, 'allowOnMultiple' => false,
'group' => $group=1, 'group' => $group=1,
'onExecute' => 'javaScript:app.addressbook.view', 'onExecute' => 'javaScript:app.addressbook.view',
'disableClass' => 'contact_organisation', 'enableClass' => 'contact_contact',
'hideOnDisabled' => true, 'hideOnDisabled' => true,
// Children added below // Children added below
'children' => array(), 'children' => array(),
@ -391,7 +391,7 @@ class addressbook_ui extends addressbook_bo
'caption' => 'Open', 'caption' => 'Open',
'default' => $GLOBALS['egw_info']['user']['preferences']['addressbook']['crm_list'] == '~edit~', 'default' => $GLOBALS['egw_info']['user']['preferences']['addressbook']['crm_list'] == '~edit~',
'allowOnMultiple' => false, 'allowOnMultiple' => false,
'disableClass' => 'contact_organisation', 'enableClass' => 'contact_contact',
'hideOnDisabled' => true, 'hideOnDisabled' => true,
'url' => 'menuaction=addressbook.addressbook_ui.edit&contact_id=$id', 'url' => 'menuaction=addressbook.addressbook_ui.edit&contact_id=$id',
'popup' => Link::get_registry('addressbook', 'edit_popup'), 'popup' => Link::get_registry('addressbook', 'edit_popup'),
@ -400,7 +400,7 @@ class addressbook_ui extends addressbook_bo
'add' => array( 'add' => array(
'caption' => 'Add', 'caption' => 'Add',
'group' => $group, 'group' => $group,
'disableClass' => 'contact_organisation', 'enableClass' => 'contact_contact',
'hideOnDisabled' => true, 'hideOnDisabled' => true,
'children' => array( 'children' => array(
'new' => array( 'new' => array(
@ -445,20 +445,39 @@ class addressbook_ui extends addressbook_bo
'default' => true, 'default' => true,
'allowOnMultiple' => false, 'allowOnMultiple' => false,
'group' => $group=1, 'group' => $group=1,
'disableClass' => 'contact_contact', 'enableClass' => 'contact_organisation',
'hideOnDisabled' => true 'hideOnDisabled' => true
), ),
'add_org' => array( 'add_org' => array(
'caption' => 'Add', 'caption' => 'Add',
'group' => $group, 'group' => $group,
'allowOnMultiple' => false, 'allowOnMultiple' => false,
'disableClass' => 'contact_contact', 'enableClass' => 'contact_organisation',
'hideOnDisabled' => true, 'hideOnDisabled' => true,
'url' => 'menuaction=addressbook.addressbook_ui.edit&org=$id', 'url' => 'menuaction=addressbook.addressbook_ui.edit&org=$id',
'popup' => Link::get_registry('addressbook', 'add_popup'), '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 ++$group; // other AB related stuff group: lists, AB's, categories
// categories submenu // categories submenu
$actions['cat'] = array( $actions['cat'] = array(
@ -587,6 +606,7 @@ class addressbook_ui extends addressbook_bo
'caption' => lang('View linked InfoLog entries'), 'caption' => lang('View linked InfoLog entries'),
'icon' => 'infolog/navbar', 'icon' => 'infolog/navbar',
'onExecute' => 'javaScript:app.addressbook.view_infolog', 'onExecute' => 'javaScript:app.addressbook.view_infolog',
'enableClass' => 'contact_contact',
'allowOnMultiple' => true, 'allowOnMultiple' => true,
'hideOnDisabled' => true, 'hideOnDisabled' => true,
), ),
@ -607,6 +627,7 @@ class addressbook_ui extends addressbook_bo
'icon' => 'calendar/navbar', 'icon' => 'calendar/navbar',
'caption' => 'Calendar', 'caption' => 'Calendar',
'group' => $group, 'group' => $group,
'enableClass' => 'contact_contact',
'children' => array( 'children' => array(
'calendar_view' => array( 'calendar_view' => array(
'caption' => 'Show', 'caption' => 'Show',
@ -629,7 +650,7 @@ class addressbook_ui extends addressbook_bo
$actions['email'] = array( $actions['email'] = array(
'caption' => 'Email', 'caption' => 'Email',
'icon' => 'mail/navbar', 'icon' => 'mail/navbar',
'disableClass' => 'contact_organisation', 'enableClass' => 'contact_contact',
'hideOnDisabled' => true, 'hideOnDisabled' => true,
'group' => $group, 'group' => $group,
'children' => array( 'children' => array(
@ -681,8 +702,8 @@ class addressbook_ui extends addressbook_bo
'url' => 'menuaction=filemanager.filemanager_ui.index&path=/apps/addressbook/$id&ajax=true', 'url' => 'menuaction=filemanager.filemanager_ui.index&path=/apps/addressbook/$id&ajax=true',
'allowOnMultiple' => false, 'allowOnMultiple' => false,
'group' => $group, 'group' => $group,
// disable for for org-views, as it needs contact-ids // disable for for group-views, as it needs contact-ids
'disableClass' => 'contact_organisation', 'enableClass' => 'contact_contact',
'hideOnMobile' => true 'hideOnMobile' => true
); );
} }
@ -691,6 +712,7 @@ class addressbook_ui extends addressbook_bo
'caption' => 'GeoLocation', 'caption' => 'GeoLocation',
'icon' => 'map', 'icon' => 'map',
'group' => ++$group, 'group' => ++$group,
'enableClass' => 'contact_contact',
'children' => array ( 'children' => array (
'private' => array( 'private' => array(
'caption' => 'Private Address', 'caption' => 'Private Address',
@ -713,6 +735,7 @@ class addressbook_ui extends addressbook_bo
$actions['export'] = array( $actions['export'] = array(
'caption' => 'Export', 'caption' => 'Export',
'icon' => 'filesave', 'icon' => 'filesave',
'enableClass' => 'contact_contact',
'group' => ++$group, 'group' => ++$group,
'children' => array( 'children' => array(
'csv' => array( 'csv' => array(
@ -742,7 +765,7 @@ class addressbook_ui extends addressbook_bo
'icon' => 'filemanager/mail_post_to', 'icon' => 'filemanager/mail_post_to',
'group' => $group, 'group' => $group,
'onExecute' => 'javaScript:app.addressbook.adb_mail_vcard', 'onExecute' => 'javaScript:app.addressbook.adb_mail_vcard',
'disableClass' => 'contact_organisation', 'enableClass' => 'contact_contact',
'hideOnDisabled' => true, 'hideOnDisabled' => true,
'hideOnMobile' => 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 * @param String $view_id Some kind of indicator for a specific group, either
* @return Array ID => name * 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(); $group_name = array();
if (strpos($org,'*AND*')!== false) $org = str_replace('*AND*','&',$org); if (strpos($view_id,'*AND*')!== false) $view_id = str_replace('*AND*','&',$view_id);
foreach(explode('|||',$org) as $part) foreach(explode('|||',$view_id) as $part)
{ {
list(,$name) = explode(':',$part,2); list(,$name) = explode(':',$part,2);
if ($name) $org_name[] = $name; if ($name) $group_name[] = $name;
} }
$name = implode(': ',$org_name); $name = implode(': ',$group_name);
return $name ? array($org => $name) : array(); 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); if(!is_array($org)) $org = array($org);
foreach($org as $org_name) foreach($org as $org_name)
{ {
$query['org_view'] = $org_name; $query['grouped_view'] = $org_name;
$checked = array(); $checked = array();
$readonlys = null; $readonlys = null;
$this->get_rows($query,$checked,$readonlys,true); // true = only return the id's $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 = Api\Cache::getSession('addressbook', 'index');
$query['num_rows'] = -1; // all $query['num_rows'] = -1; // all
$query['org_view'] = $org; $query['grouped_view'] = $org;
$query['searchletter'] = ''; $query['searchletter'] = '';
$checked = $readonlys = null; $checked = $readonlys = null;
$this->get_rows($query,$checked,$readonlys,true); // true = only return the id's $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 // replace org_name:* id's with all id's of that org
$org_contacts = array(); $grouped_contacts = $this->find_grouped_ids($action, $checked, $use_all, $success,$failed,$action_msg,$session_name);
foreach((array)$checked as $n => $id) if ($grouped_contacts) $checked = array_unique($checked ? array_merge($checked,$grouped_contacts) : $grouped_contacts);
{
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);
//_debug_array($checked); exit; //_debug_array($checked); exit;
if (substr($action,0,8) == 'move_to_') if (substr($action,0,8) == 'move_to_')
@ -1338,6 +1420,59 @@ window.egw_LAB.wait(function() {
return !$failed; 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!) * Copy a given contact (not storing it!)
* *
@ -1400,30 +1535,21 @@ window.egw_LAB.wait(function() {
{ {
$old_state = Api\Cache::getSession('addressbook', $what); $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']); // we don't have a grouped view, unset the according col_filters
if (isset($query['col_filter']['adr_one_locality'])) unset($query['col_filter']['adr_one_locality']); $this->unset_grouped_filters($query);
if (isset($query['col_filter']['org_unit'])) unset($query['col_filter']['org_unit']);
} }
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'], // we have a grouped view, reset the advanced search
// 'AdvancedSearch'=>$query['advanced_search']));
//if (is_array($query['search'])) unset($query['search']);
//unset($query['advanced_search']);
if(!$query['search'] && $old_state['advanced_search']) $query['advanced_search'] = $old_state['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 elseif(!$query['search'] && array_key_exists('advanced_search',$old_state)) // eg. paging in an advanced search
{ {
$query['advanced_search'] = $old_state['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 // Make sure old lettersearch filter doesn't stay - current letter filter will be added later
foreach($query['col_filter'] as $key => $col_filter) foreach($query['col_filter'] as $key => $col_filter)
@ -1441,7 +1567,7 @@ window.egw_LAB.wait(function() {
if (!$id_only) if (!$id_only)
{ {
// check if accounts are stored in ldap, which does NOT yet support the org-views // 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 if ($old_state['filter'] === '0') // user changed to org_view
{ {
@ -1449,21 +1575,21 @@ window.egw_LAB.wait(function() {
} }
else // user changed to accounts 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 // save the state of the index in the user prefs
$state = serialize(array( $state = serialize(array(
'filter' => $query['filter'], 'filter' => $query['filter'],
'cat_id' => $query['cat_id'], 'cat_id' => $query['cat_id'],
'order' => $query['order'], 'order' => $query['order'],
'sort' => $query['sort'], 'sort' => $query['sort'],
'col_filter' => array('tid' => $query['col_filter']['tid']), 'col_filter' => array('tid' => $query['col_filter']['tid']),
'org_view' => $query['org_view'], 'grouped_view' => $query['grouped_view'],
)); ));
if ($state != $this->prefs[$what.'_state'] && !$query['csv_export']) 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 // all backends allow now at least to use groups as distribution lists
$query['no_filter2'] = false; $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 $query['grouped_view_label'] = '';
unset($query['col_filter']['parent_id']); $rows = $this->get_grouped_rows($query);
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');
} }
else // contacts view else // contacts view
{ {
@ -1562,19 +1655,21 @@ window.egw_LAB.wait(function() {
} }
if($query['col_filter']['parent_id']) if($query['col_filter']['parent_id'])
{ {
$query['org_view'] = $query['col_filter']['parent_id']; $query['grouped_view'] = $query['col_filter']['parent_id'];
$query['template'] = 'addressbook.index.org_rows'; $query['template'] = strpos($query['grouped_view'], 'duplicate') === 0 ?
'addressbook.index.duplicate_rows' : 'addressbook.index.org_rows';
} }
// Query doesn't like parent_id // Query doesn't like parent_id
unset($query['col_filter']['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']); if (strpos($query['grouped_view'],'*AND*') !== false) $query['grouped_view'] = str_replace('*AND*','&',$query['grouped_view']);
foreach(explode('|||',$query['org_view']) as $part) $fields = explode(',',$GLOBALS['egw_info']['user']['preferences']['addressbook']['duplicate_fields']);
foreach(explode('|||',$query['grouped_view']) as $part)
{ {
list($name,$value) = explode(':',$part,2); list($name,$value) = explode(':',$part,2);
// do NOT set invalid column, as this gives an SQL error ("AND AND" in sql) // 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; $query['col_filter'][$name] = $value;
} }
@ -1582,8 +1677,8 @@ window.egw_LAB.wait(function() {
} }
else if($query['actions'] && !$query['actions']['edit']) else if($query['actions'] && !$query['actions']['edit'])
{ {
// Just switched from org view, update actions // Just switched from grouped view, update actions
$query['actions'] = $this->get_actions($query['col_filter']['tid'], $query['org_view']); $query['actions'] = $this->get_actions($query['col_filter']['tid']);
} }
// translate the select order to the really used over all 3 columns // translate the select order to the really used over all 3 columns
$sort = $query['sort']; $sort = $query['sort'];
@ -1712,17 +1807,17 @@ window.egw_LAB.wait(function() {
list($row['line1'],$row['line2']) = explode(': ',$row['n_fileas']); list($row['line1'],$row['line2']) = explode(': ',$row['n_fileas']);
break; break;
} }
if (isset($this->org_views[(string) $query['org_view']])) if (isset($this->grouped_views[(string) $query['grouped_view']]))
{ {
$row['type'] = 'home'; $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)) if ($query['filter'] && !($this->grants[(int)$query['filter']] & Acl::DELETE))
{ {
$row['class'] .= 'rowNoDelete '; $row['class'] .= 'rowNoDelete ';
} }
$row['class'] .= 'rowNoEdit '; // no edit in OrgView $row['class'] .= 'rowNoEdit '; // no edit in OrgView
$row['class'] .= 'contact_organisation '; $row['class'] .= $query['grouped_view'] == 'duplicates' ? 'contact_duplicate' : 'contact_organisation ';
} }
else else
{ {
@ -1802,7 +1897,7 @@ window.egw_LAB.wait(function() {
// full app-header with all search criteria specially for the print // full app-header with all search criteria specially for the print
$header = array(); $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') : $header[] = ($query['filter'] == '0' ? lang('accounts') :
($GLOBALS['egw']->accounts->get_type($query['filter']) == 'g' ? ($GLOBALS['egw']->accounts->get_type($query['filter']) == 'g' ?
@ -1810,14 +1905,14 @@ window.egw_LAB.wait(function() {
Api\Accounts::username((int)$query['filter']). Api\Accounts::username((int)$query['filter']).
(substr($query['filter'],-1) == 'p' ? ' ('.lang('private').')' : ''))); (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 // 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']); $this->grouped_views += $this->_get_grouped_name($query['grouped_view']);
$rows['sel_options']['org_view'] = $this->org_views; $rows['sel_options']['grouped_view'] = $this->grouped_views;
} }
} }
if($query['advanced_search']) if($query['advanced_search'])
@ -2103,9 +2198,9 @@ window.egw_LAB.wait(function() {
if ($org[0] == '"') $org = substr($org, 1, -1); if ($org[0] == '"') $org = substr($org, 1, -1);
$content = $this->read_org($org); $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 else
{ {
@ -2491,12 +2586,14 @@ window.egw_LAB.wait(function() {
public function ajax_check_values($values, $name, $own_id=0) public function ajax_check_values($values, $name, $own_id=0)
{ {
$matches = null; $matches = null;
$fields = explode(',',$GLOBALS['egw_info']['user']['preferences']['addressbook']['duplicate_fields']);
if (preg_match('/^exec\[([^\]]+)\]$/', $name, $matches)) $name = $matches[1]; // remove exec[ ] if (preg_match('/^exec\[([^\]]+)\]$/', $name, $matches)) $name = $matches[1]; // remove exec[ ]
$ret = array('doublicates' => array(), 'msg' => null); $ret = array('doublicates' => array(), 'msg' => null);
// if email changed, check for doublicates // 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 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 // Full options for et2
$ret['fileas_sel_options'] = $this->fileas_options($values); $ret['fileas_sel_options'] = $this->fileas_options($values);
// if name, firstname or org changed and at least 2 are specified, check for doublicates // if name, firstname or org changed and enough are specified, check for doublicates
if (in_array($name, array('n_given', 'n_family', 'org_name')) && $specified_count = 0;
!empty($values['n_given'])+!empty($values['n_family'])+!empty($values['org_name']) >= 2) foreach($fields as $field)
{
if($values[$field])
{
$specified_count++;
}
}
if (in_array($name,$fields) && $specified_count >= 2)
{ {
$filter = array(); $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]; if (!empty($values[$n])) $filter[$n] = $values[$n];
} }

View 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>

View File

@ -174,7 +174,7 @@
</template> </template>
<template id="addressbook.index.row" template="" lang="" group="0" version="1.3.001"> <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;"/> <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>
<template id="addressbook.index.right" template="" lang="" group="0" version="1.7.001"> <template id="addressbook.index.right" template="" lang="" group="0" version="1.7.001">
<select align="right" id="col_filter[tid]" empty_label="All types"/> <select align="right" id="col_filter[tid]" empty_label="All types"/>

View File

@ -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 $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] : '|||') : ''); $org_key = $row['org_name'].($by ? '|||'.($row[$by] || $row[$by.'_count']==1 ? $row[$by] : '|||') : '');
$row['group_count'] = $row['org_count'];
$orgs[$org_key] = $row; $orgs[$org_key] = $row;
} }
unset($rows); unset($rows);
@ -242,6 +243,141 @@ class Sql extends Api\Storage
return array_values($orgs); 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 * searches db for rows matching searchcriteria
* *

View File

@ -159,6 +159,24 @@ class Storage
*/ */
var $content_types = array(); 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 * Special content type to indicate a deleted addressbook
* *
@ -796,6 +814,80 @@ class Storage
return $rows; 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 * gets all contact fields from database
* *