2007-06-08 00:31:08 +02:00
< ? php
/**
2016-03-06 14:45:15 +01:00
* EGroupware Addressbook
2007-06-08 00:31:08 +02:00
*
* @ license http :// opensource . org / licenses / gpl - license . php GPL - GNU General Public License
* @ package importexport
* @ link http :// www . egroupware . org
* @ author Cornelius Weiss < nelius @ cwtech . de >
* @ copyright Cornelius Weiss < nelius @ cwtech . de >
2016-03-06 14:45:15 +01:00
* @ version $Id $
2007-06-08 00:31:08 +02:00
*/
2016-03-06 14:45:15 +01:00
use EGroupware\Api ;
2007-06-08 00:31:08 +02:00
/**
* class import_csv for addressbook
*/
2012-10-12 21:50:26 +02:00
class addressbook_import_contacts_csv extends importexport_basic_import_csv {
2008-05-10 14:02:49 +02:00
2007-06-08 00:31:08 +02:00
/**
* conditions for actions
*
* @ var array
*/
2014-06-16 18:22:18 +02:00
protected static $conditions = array ( 'exists' , 'equal' );
2008-05-10 14:02:49 +02:00
2007-06-08 00:31:08 +02:00
/**
2018-05-09 10:05:34 +02:00
* @ var addressbook_bo
2007-06-08 00:31:08 +02:00
*/
private $bocontacts ;
2008-05-10 14:02:49 +02:00
2010-02-26 00:18:45 +01:00
/**
2016-03-06 14:45:15 +01:00
* For figuring out if a contact has changed
*
* @ var Api\Contacts\Tracking
*/
2010-02-26 00:18:45 +01:00
protected $tracking ;
2015-01-27 22:20:02 +01:00
/**
* @ var boolean If import file has no type , it can generate a lot of warnings .
* Users don ' t like this , so we only warn once .
*/
private $type_warned = false ;
2010-03-03 17:29:25 +01:00
2018-11-06 23:15:36 +01:00
/**
* To empty addressbook before importing , we actually keep track of
* what ' s imported and delete the others to keep history .
*
* @ var type
*/
private $ids = array ();
/**
* imports entries according to given definition object .
* @ param resource $_stream
* @ param string $_charset
* @ param definition $_definition
*/
public function import ( $_stream , importexport_definition $_definition ) {
parent :: import ( $_stream , $_definition );
if ( $_definition -> plugin_options [ 'empty_addressbook' ])
{
$this -> empty_addressbook ( $this -> user , $this -> ids );
}
}
2007-06-08 00:31:08 +02:00
/**
* imports entries according to given definition object .
* @ param resource $_stream
* @ param string $_charset
* @ param definition $_definition
*/
2012-10-12 21:50:26 +02:00
public function init ( importexport_definition & $_definition ) {
2011-12-09 18:49:05 +01:00
2007-06-08 00:31:08 +02:00
// fetch the addressbook bo
2018-05-09 10:05:34 +02:00
$this -> bocontacts = new addressbook_bo ();
2008-05-10 14:02:49 +02:00
2010-02-26 00:18:45 +01:00
// Get the tracker for changes
2016-03-06 14:45:15 +01:00
$this -> tracking = new Api\Contacts\Tracking ( $this -> bocontacts );
2010-02-26 00:18:45 +01:00
2012-10-12 21:50:26 +02:00
$this -> lookups = array (
'tid' => array ( 'n' => 'contact' )
);
foreach ( $this -> bocontacts -> content_types as $tid => $data )
{
$this -> lookups [ 'tid' ][ $tid ] = $data [ 'name' ];
2007-06-08 00:31:08 +02:00
}
2008-05-10 14:02:49 +02:00
2017-10-05 21:23:09 +02:00
// Try and set a default type, for use if file does not specify
if ( ! $this -> lookups [ 'tid' ][ Api\Contacts\Storage :: DELETED_TYPE ] && count ( $this -> lookups [ 'tid' ]) == 1 ||
$this -> lookups [ 'tid' ][ Api\Contacts\Storage :: DELETED_TYPE ] && count ( $this -> lookups [ 'tid' ]) == 2 )
{
reset ( $this -> lookups [ 'tid' ]);
$this -> default_type = key ( $this -> lookups [ 'tid' ]);
}
2011-07-29 01:26:08 +02:00
// set contact owner
$contact_owner = isset ( $_definition -> plugin_options [ 'contact_owner' ] ) ?
2007-06-23 14:06:08 +02:00
$_definition -> plugin_options [ 'contact_owner' ] : $this -> user ;
2012-10-12 21:50:26 +02:00
2017-09-27 19:34:18 +02:00
// Check to make sure target addressbook is valid
if ( ! in_array ( $contact_owner , array_keys ( $this -> bocontacts -> get_addressbooks ( Api\Acl :: ADD ))))
{
$this -> warnings [ 0 ] = lang ( " Unable to import into %1, using %2 " ,
$contact_owner . ' (' . Api\Accounts :: username ( $record -> owner ) . ')' ,
Api\Accounts :: username ( $this -> user )
);
$contact_owner = 'personal' ;
}
2011-07-29 01:26:08 +02:00
// Import into importer's personal addressbook
2012-05-29 16:37:19 +02:00
if ( $contact_owner == 'personal' )
2011-07-29 01:26:08 +02:00
{
$contact_owner = $this -> user ;
}
2012-10-12 21:50:26 +02:00
$this -> user = $contact_owner ;
}
2008-05-10 14:02:49 +02:00
2012-10-12 21:50:26 +02:00
/**
2014-06-16 18:22:18 +02:00
* Import a single record
*
* You don 't need to worry about mappings or translations, they' ve been done already .
* You do need to handle the conditions and the actions taken .
*
* Updates the count of actions taken
*
* @ return boolean success
*/
protected function import_record ( importexport_iface_egw_record & $record , & $import_csv )
2012-10-12 21:50:26 +02:00
{
// Set owner, unless it's supposed to come from CSV file
if ( $this -> definition -> plugin_options [ 'owner_from_csv' ] && $record -> owner ) {
if ( ! is_numeric ( $record -> owner )) {
// Automatically handle text owner without explicit translation
$new_owner = importexport_helper_functions :: account_name2id ( $record -> owner );
if ( $new_owner == '' ) {
$this -> errors [ $import_csv -> get_current_position ()] = lang (
'Unable to convert "%1" to account ID. Using plugin setting (%2) for owner.' ,
$record -> owner ,
2016-04-29 12:41:53 +02:00
Api\Accounts :: username ( $this -> user )
2012-10-12 21:50:26 +02:00
);
$record -> owner = $this -> user ;
} else {
$record -> owner = $new_owner ;
2010-09-23 21:49:07 +02:00
}
}
2012-10-12 21:50:26 +02:00
} else {
$record -> owner = $this -> user ;
}
2010-03-02 00:08:50 +01:00
2012-10-12 21:50:26 +02:00
// Check that owner (addressbook) is allowed
if ( ! array_key_exists ( $record -> owner , $this -> bocontacts -> get_addressbooks ()))
{
$this -> errors [ $import_csv -> get_current_position ()] = lang ( " Unable to import into %1, using %2 " ,
2016-04-29 12:41:53 +02:00
Api\Accounts :: username ( $record -> owner ),
Api\Accounts :: username ( $this -> user )
2012-10-12 21:50:26 +02:00
);
$record -> owner = $this -> user ;
}
2012-09-17 17:47:47 +02:00
2012-10-12 21:50:26 +02:00
// Do not allow owner == 0 (accounts) without an account_id
// It causes the contact to be filed as an account, and can't delete
if ( ! $record -> owner && ! $record -> account_id )
{
$record -> owner = $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ];
}
2012-09-17 17:47:47 +02:00
2014-09-30 17:08:46 +02:00
// Do not import into non-existing type, warn and change
if ( ! $record -> tid || ! $this -> lookups [ 'tid' ][ $record -> tid ])
{
2015-01-27 22:20:02 +01:00
// Avoid lots of warnings about type (2 types are contact and deleted)
2017-10-05 21:23:09 +02:00
if ( $record -> tid && ! $this -> type_warned [ $record -> tid ] && ! $this -> lookups [ 'tid' ][ $record -> tid ] )
2015-01-27 22:20:02 +01:00
{
$this -> warnings [ $import_csv -> get_current_position ()] = lang ( 'Unknown type %1, imported as %2' , $record -> tid , lang ( $this -> lookups [ 'tid' ][ 'n' ]));
2017-10-05 21:23:09 +02:00
$this -> type_warned [ $record -> tid ] = true ;
2015-01-27 22:20:02 +01:00
}
2017-10-05 21:23:09 +02:00
$record -> tid = $this -> default_type ;
2014-09-30 17:08:46 +02:00
}
2012-10-12 21:50:26 +02:00
// Also handle categories in their own field
$record_array = $record -> get_record_array ();
$more_categories = array ();
2016-03-06 14:45:15 +01:00
foreach ( $this -> definition -> plugin_options [ 'field_mapping' ] as $field_name ) {
2012-10-12 21:50:26 +02:00
if ( ! array_key_exists ( $field_name , $record_array ) ||
substr ( $field_name , 0 , 3 ) != 'cat' || ! $record -> $field_name || $field_name == 'cat_id' ) continue ;
2016-03-06 14:45:15 +01:00
list (, $cat_id ) = explode ( '-' , $field_name );
2012-10-12 21:50:26 +02:00
if ( is_numeric ( $record -> $field_name ) && $record -> $field_name != 1 ) {
// Column has a single category ID
$more_categories [] = $record -> $field_name ;
} elseif ( $record -> $field_name == '1' ||
( ! is_numeric ( $record -> $field_name ) && strtolower ( $record -> $field_name ) == strtolower ( lang ( 'Yes' )))) {
// Each category got its own column. '1' is the database value, lang('yes') is the human value
$more_categories [] = $cat_id ;
} else {
// Text categories
$more_categories = array_merge ( $more_categories , importexport_helper_functions :: cat_name2id ( is_array ( $record -> $field_name ) ? $record -> $field_name : explode ( ',' , $record -> $field_name ), $cat_id ));
2011-03-17 00:18:48 +01:00
}
2012-10-12 21:50:26 +02:00
}
if ( count ( $more_categories ) > 0 ) $record -> cat_id = array_merge ( is_array ( $record -> cat_id ) ? $record -> cat_id : explode ( ',' , $record -> cat_id ), $more_categories );
2011-03-04 17:57:25 +01:00
2012-10-12 21:50:26 +02:00
// Private set but missing causes hidden entries
if ( array_key_exists ( 'private' , $record_array ) && ( ! isset ( $record_array [ 'private' ]) || $record_array [ 'private' ] == '' )) unset ( $record -> private );
2011-08-09 21:14:03 +02:00
2012-10-12 21:50:26 +02:00
// Format birthday as backend requires - converter should give timestamp
2012-10-23 17:15:54 +02:00
if ( $record -> bday && is_numeric ( $record -> bday ))
2012-10-12 21:50:26 +02:00
{
2016-04-29 12:41:53 +02:00
$time = new Api\DateTime ( $record -> bday );
2012-10-12 21:50:26 +02:00
$record -> bday = $time -> format ( 'Y-m-d' );
}
2011-10-25 20:49:14 +02:00
2012-10-12 21:50:26 +02:00
if ( $this -> definition -> plugin_options [ 'conditions' ] ) {
foreach ( $this -> definition -> plugin_options [ 'conditions' ] as $condition ) {
$contacts = array ();
switch ( $condition [ 'type' ] ) {
// exists
case 'exists' :
if ( $record_array [ $condition [ 'string' ]]) {
$searchcondition = array ( $condition [ 'string' ] => $record_array [ $condition [ 'string' ]]);
// if we use account_id for the condition, we need to set the owner for filtering, as this
2016-03-06 14:45:15 +01:00
// enables Api\Contacts\Storage to decide what backend is to be used
2012-10-12 21:50:26 +02:00
if ( $condition [ 'string' ] == 'account_id' ) $searchcondition [ 'owner' ] = 0 ;
$contacts = $this -> bocontacts -> search (
//array( $condition['string'] => $record[$condition['string']],),
'' ,
$this -> definition -> plugin_options [ 'update_cats' ] == 'add' ? false : true ,
'' , '' , '' , false , 'AND' , false ,
$searchcondition
);
}
if ( is_array ( $contacts ) && count ( array_keys ( $contacts ) ) >= 1 ) {
// apply action to all contacts matching this exists condition
$action = $condition [ 'true' ];
foreach ( ( array ) $contacts as $contact ) {
$record -> id = $contact [ 'id' ];
if ( $this -> definition -> plugin_options [ 'update_cats' ] == 'add' ) {
if ( ! is_array ( $contact [ 'cat_id' ] ) ) $contact [ 'cat_id' ] = explode ( ',' , $contact [ 'cat_id' ] );
if ( ! is_array ( $record_array [ 'cat_id' ] ) ) $record -> cat_id = explode ( ',' , $record -> cat_id );
$record -> cat_id = implode ( ',' , array_unique ( array_merge ( $record -> cat_id , $contact [ 'cat_id' ] ) ) );
2007-06-08 00:31:08 +02:00
}
2014-06-16 22:16:58 +02:00
$success = $this -> action ( $action [ 'action' ], $record , $import_csv -> get_current_position () );
2007-06-08 00:31:08 +02:00
}
2012-10-12 21:50:26 +02:00
} else {
$action = $condition [ 'false' ];
2014-06-16 22:16:58 +02:00
$success = ( $this -> action ( $action [ 'action' ], $record , $import_csv -> get_current_position () ));
2012-10-12 21:50:26 +02:00
}
break ;
2014-06-16 18:22:18 +02:00
case 'equal' :
// Match on field
2016-03-06 14:45:15 +01:00
$result = $this -> equal ( $record , $condition );
2014-06-16 18:22:18 +02:00
if ( $result )
{
// Apply true action to any matching records found
$action = $condition [ 'true' ];
2014-06-16 22:16:58 +02:00
$success = ( $this -> action ( $action [ 'action' ], $record , $import_csv -> get_current_position () ));
2014-06-16 18:22:18 +02:00
}
else
{
// Apply false action if no matching records found
$action = $condition [ 'false' ];
2014-06-16 22:16:58 +02:00
$success = ( $this -> action ( $action [ 'action' ], $record , $import_csv -> get_current_position () ));
2014-06-16 18:22:18 +02:00
}
break ;
2012-10-12 21:50:26 +02:00
// not supported action
default :
die ( 'condition / action not supported!!!' );
2007-06-08 00:31:08 +02:00
}
2012-10-12 21:50:26 +02:00
if ( $action [ 'stop' ]) break ;
2007-06-08 00:31:08 +02:00
}
2012-10-12 21:50:26 +02:00
} else {
// unconditional insert
2014-06-16 22:16:58 +02:00
$success = $this -> action ( 'insert' , $record , $import_csv -> get_current_position () );
2007-06-08 00:31:08 +02:00
}
2012-10-12 21:50:26 +02:00
return $success ;
2007-06-08 00:31:08 +02:00
}
2008-05-10 14:02:49 +02:00
2007-06-08 00:31:08 +02:00
/**
* perform the required action
*
* @ param int $_action one of $this -> actions
2014-06-16 22:16:58 +02:00
* @ param importexport_iface_egw_record $record contact data for the action
2007-06-08 00:31:08 +02:00
* @ return bool success or not
*/
2014-06-16 22:16:58 +02:00
protected function action ( $_action , importexport_iface_egw_record & $record , $record_num = 0 ) {
$_data = $record -> get_record_array ();
2007-06-08 00:31:08 +02:00
switch ( $_action ) {
case 'none' :
return true ;
2013-04-29 17:32:09 +02:00
case 'delete' :
if ( $_data [ 'id' ])
{
if ( $this -> dry_run ) {
//print_r($_data);
$this -> results [ $_action ] ++ ;
return true ;
}
$result = $this -> bocontacts -> delete ( $_data );
if ( $result && $result === true )
{
$this -> results [ $_action ] ++ ;
}
else
{
// Failure of some kind - unknown cause
$this -> errors [ $record_num ] = lang ( 'unable to delete' );
}
}
break ;
2007-06-08 00:31:08 +02:00
case 'update' :
2010-02-26 00:18:45 +01:00
// Only update if there are changes
$old = $this -> bocontacts -> read ( $_data [ 'id' ]);
2010-10-29 09:29:40 +02:00
// if we get countrycodes as countryname, try to translate them -> the rest should be handled by bo classes.
foreach ( array ( 'adr_one_' , 'adr_two_' ) as $c_prefix ) {
2016-03-06 14:45:15 +01:00
if ( strlen ( trim ( $_data [ $c_prefix . 'countryname' ])) == 2 )
$_data [ $c_prefix . 'countryname' ] = $GLOBALS [ 'egw' ] -> country -> get_full_name ( trim ( $_data [ $c_prefix . 'countryname' ]), true );
2010-10-29 09:29:40 +02:00
}
2010-03-24 16:43:52 +01:00
// Don't change a user account into a contact
if ( $old [ 'owner' ] == 0 ) {
unset ( $_data [ 'owner' ]);
2010-09-23 21:49:07 +02:00
} elseif ( ! $this -> definition -> plugin_options [ 'change_owner' ]) {
// Don't change addressbook of an existing contact
unset ( $_data [ 'owner' ]);
2010-03-24 16:43:52 +01:00
}
2018-11-06 23:15:36 +01:00
$this -> ids [] = $_data [ 'id' ];
2010-02-26 00:18:45 +01:00
// Merge to deal with fields not in import record
$_data = array_merge ( $old , $_data );
$changed = $this -> tracking -> changed_fields ( $_data , $old );
if ( count ( $changed ) == 0 ) {
return true ;
2010-10-29 09:29:40 +02:00
} else {
//error_log(__METHOD__.__LINE__.array2string($changed).' Old:'.$old['adr_one_countryname'].' ('.$old['adr_one_countrycode'].') New:'.$_data['adr_one_countryname'].' ('.$_data['adr_one_countryname'].')');
2010-09-23 17:38:28 +02:00
}
2016-03-06 14:45:15 +01:00
2010-09-23 17:38:28 +02:00
// Make sure n_fn gets updated
unset ( $_data [ 'n_fn' ]);
2010-02-26 00:18:45 +01:00
// Fall through
2007-06-08 00:31:08 +02:00
case 'insert' :
2010-03-02 00:08:50 +01:00
if ( $_action == 'insert' ) {
// Addressbook backend doesn't like inserting with ID specified, it screws up the owner & etag
unset ( $_data [ 'id' ]);
}
2010-09-23 17:38:28 +02:00
if ( ! isset ( $_data [ 'org_name' ])) {
// org_name is a trigger to update n_fileas
$_data [ 'org_name' ] = '' ;
}
2012-09-17 17:47:47 +02:00
2007-06-21 20:08:17 +02:00
if ( $this -> dry_run ) {
2010-08-16 16:56:14 +02:00
//print_r($_data);
2010-03-03 17:29:25 +01:00
$this -> results [ $_action ] ++ ;
2010-02-26 00:18:45 +01:00
return true ;
2007-06-21 20:08:17 +02:00
} else {
2010-02-26 00:18:45 +01:00
$result = $this -> bocontacts -> save ( $_data , $this -> is_admin );
if ( ! $result ) {
$this -> errors [ $record_num ] = $this -> bocontacts -> error ;
2010-03-03 17:29:25 +01:00
} else {
2018-11-06 23:15:36 +01:00
$this -> ids [] = $result ;
2010-03-03 17:29:25 +01:00
$this -> results [ $_action ] ++ ;
2014-06-16 22:16:58 +02:00
// This does nothing (yet?) but update the identifier
$record -> save ( $result );
2010-02-26 00:18:45 +01:00
}
return $result ;
2007-06-21 20:08:17 +02:00
}
2010-03-22 16:11:12 +01:00
default :
2016-04-29 12:41:53 +02:00
throw new Api\Exception ( 'Unsupported action: ' . $_action );
2016-03-06 14:45:15 +01:00
2007-06-08 00:31:08 +02:00
}
}
2008-05-10 14:02:49 +02:00
2018-11-06 23:15:36 +01:00
/**
* Delete all contacts from the addressbook , except the given list
*
* @ param int $addressbook Addressbook to clear
* @ param array $ids Contacts to keep
*/
protected function empty_addressbook ( $addressbook , $ids )
{
// Get all IDs in addressbook
$contacts = $this -> bocontacts -> search ( array ( 'owner' => $addressbook ), true );
$contacts = array_column ( $contacts , 'id' );
$delete = array_diff ( $contacts , $ids );
foreach ( $delete as $id )
{
if ( $this -> dry_run || $this -> bocontacts -> delete ( $id ))
{
$this -> results [ 'deleted' ] ++ ;
}
else
{
$this -> warnings [] = lang ( 'Unable to delete' ) . ': ' . Api\Link :: title ( 'addressbook' , $id );
}
}
}
2007-06-08 00:31:08 +02:00
/**
* returns translated name of plugin
*
* @ return string name
*/
public static function get_name () {
2010-02-26 00:18:45 +01:00
return lang ( 'Addressbook CSV import' );
2007-06-08 00:31:08 +02:00
}
2008-05-10 14:02:49 +02:00
2007-06-08 00:31:08 +02:00
/**
* returns translated ( user ) description of plugin
*
* @ return string descriprion
*/
public static function get_description () {
2018-03-26 16:02:42 +02:00
return lang ( " Imports contacts into your Addressbook from a CSV File. CSV means 'Comma Separated Values'. However in the options Tab you can also choose other seperators. " );
2007-06-08 00:31:08 +02:00
}
2008-05-10 14:02:49 +02:00
2007-06-08 00:31:08 +02:00
/**
* retruns file suffix ( s ) plugin can handle ( e . g . csv )
*
* @ return string suffix ( comma seperated )
*/
public static function get_filesuffix () {
return 'csv' ;
}
2008-05-10 14:02:49 +02:00
2007-06-08 00:31:08 +02:00
/**
* return etemplate components for options .
* @ abstract We can ' t deal with etemplate objects here , as an uietemplate
* objects itself are scipt orientated and not " dialog objects "
2008-05-10 14:02:49 +02:00
*
2007-06-08 00:31:08 +02:00
* @ return array (
* name => string ,
* content => array ,
* sel_options => array ,
* preserv => array ,
* )
*/
public function get_options_etpl () {
// lets do it!
}
2008-05-10 14:02:49 +02:00
2007-06-08 00:31:08 +02:00
/**
* returns etemplate name for slectors of this plugin
*
* @ return string etemplate name
*/
public function get_selectors_etpl () {
// lets do it!
}
2011-12-15 17:24:47 +01:00
/**
* Returns warnings that were encountered during importing
* Maximum of one warning message per record , but you can append if you need to
*
* @ return Array (
* record_ # => warning message
* )
*/
public function get_warnings () {
return $this -> warnings ;
}
2010-02-26 00:18:45 +01:00
/**
* Returns errors that were encountered during importing
* Maximum of one error message per record , but you can append if you need to
*
* @ return Array (
* record_ # => error message
* )
*/
public function get_errors () {
return $this -> errors ;
}
2010-03-03 17:29:25 +01:00
/**
* Returns a list of actions taken , and the number of records for that action .
* Actions are things like 'insert' , 'update' , 'delete' , and may be different for each plugin .
*
* @ return Array (
* action => record count
* )
*/
public function get_results () {
return $this -> results ;
}
2016-04-29 12:41:53 +02:00
}