diff --git a/importexport/inc/class.importexport_basic_import_csv.inc.php b/importexport/inc/class.importexport_basic_import_csv.inc.php index a3b5a88bd2..418dd1eacf 100644 --- a/importexport/inc/class.importexport_basic_import_csv.inc.php +++ b/importexport/inc/class.importexport_basic_import_csv.inc.php @@ -41,7 +41,8 @@ abstract class importexport_basic_import_csv implements importexport_iface_impor 'action' => insert, 'last' => true, ),*/ - + 'override_values' // Array of values specified that override what's in the file + // 'col_id' => array('value' => ...) ); /** @@ -89,6 +90,11 @@ abstract class importexport_basic_import_csv implements importexport_iface_impor */ protected $user = null; + /** + * Application field types (from egw_record) + */ + protected $types = array(); + /** * Maximum number of errors or warnings before aborting */ @@ -161,11 +167,12 @@ abstract class importexport_basic_import_csv implements importexport_iface_impor // Record class name $app = $_definition->application; $record_class = isset(static::$record_class) ? static::$record_class : "{$app}_egw_record"; + $this->types = $record_class::$types; // Needed for categories to work right $GLOBALS['egw_info']['flags']['currentapp'] = $app; - $this->init($_definition); + $this->init($_definition, $import_csv); while ( $record = $import_csv->get_record() ) { $success = false; @@ -174,11 +181,15 @@ abstract class importexport_basic_import_csv implements importexport_iface_impor if( count( array_unique( $record ) ) < 2 ) continue; - $warning = importexport_import_csv::convert($record, $record_class::$types, $app, $this->lookups, $_definition->plugin_options['convert']); - if($warning) $this->warnings[$import_csv->get_current_position()] = $warning; + $warning = importexport_import_csv::convert($record, $this->types, $app, $this->lookups, $_definition->plugin_options['convert']); + if($warning) $this->warnings[$import_csv->get_current_position()] = $warning; $egw_record = new $record_class(); $egw_record->set_record($record); + if($_definition->plugin_options['override_values']) + { + $this->set_overrides($_definition, $egw_record); + } $success = $this->import_record($egw_record, $import_csv); if($success) @@ -210,7 +221,7 @@ abstract class importexport_basic_import_csv implements importexport_iface_impor /** * Stub to hook into import initialization - set lookups, etc. */ - protected function init(importexport_definition &$definition) + protected function init(importexport_definition &$definition, importexport_import_csv &$import_csv = null) { } @@ -323,6 +334,40 @@ abstract class importexport_basic_import_csv implements importexport_iface_impor */ protected abstract function action ( $_action, importexport_iface_egw_record &$record, $record_num = 0 ); + /** + * Handle setting specific values from the definition on every record + * + * + * @param importexport_definition $_definition + * @param importexport_iface_egw_record $record + */ + protected function set_overrides(importexport_definition &$_definition, importexport_iface_egw_record &$record) + { + $overrides = $_definition->plugin_options['override_values']; + + // Set the record field, with some type checks + $set = function($record, $field, $value) + { + if(is_array($record->$field)) + { + array_unshift($record->$field, $value['value']); + } + else + { + $record->$field = $value['value']; + } + }; + + // Set category + if($overrides['cat_id'] && $record::$types['select-cat']) + { + foreach($record::$types['select-cat'] as $field) + { + $set($record, $field, $overrides['cat_id']); + } + } + } + /** * Handle special fields * diff --git a/importexport/inc/class.importexport_wizard_basic_import_csv.inc.php b/importexport/inc/class.importexport_wizard_basic_import_csv.inc.php index c569a439bd..8942be5e16 100644 --- a/importexport/inc/class.importexport_wizard_basic_import_csv.inc.php +++ b/importexport/inc/class.importexport_wizard_basic_import_csv.inc.php @@ -34,7 +34,8 @@ class importexport_wizard_basic_import_csv 'wizard_step30' => 'importexport.wizard_basic_import_csv.sample_file', 'wizard_step40' => 'importexport.wizard_basic_import_csv.choosesepncharset', 'wizard_step50' => 'importexport.wizard_basic_import_csv.fieldmapping', - 'wizard_step55' => 'importexport.wizard_basic_import_csv.conditions' + 'wizard_step55' => 'importexport.wizard_basic_import_csv.conditions', + 'wizard_step65' => 'importexport.wizard_basic_import_csv.overrides' ); @@ -64,6 +65,8 @@ class importexport_wizard_basic_import_csv 'wizard_step40' => lang('Choose seperator and charset'), 'wizard_step50' => lang('Manage mapping'), 'wizard_step55' => lang('Edit conditions'), + // Many apps have step 60 as choose owner + 'wizard_step65' => lang('Set import values'), ); } @@ -442,6 +445,58 @@ class importexport_wizard_basic_import_csv return $this->step_templates[$content['step']]; } + /** + * Many apps put step60 as choose owner + */ + public function wizard_step65(&$content, &$sel_options, &$readonlys, &$preserv) + { + if($this->debug) error_log(__METHOD__ . '->$content '.print_r($content,true)); + + // return from step65 + if ($content['step'] == 'wizard_step65') + { + switch (array_search('pressed', $content['button'])) + { + case 'next': + return $GLOBALS['egw']->importexport_definitions_ui->get_step($content['step'],1); + case 'previous' : + return $GLOBALS['egw']->importexport_definitions_ui->get_step($content['step'],-1); + case 'finish': + return 'wizard_finish'; + default : + return $this->wizard_step65($content,$sel_options,$readonlys,$preserv); + break; + } + } + // init step65 + $content['text'] = $this->steps['wizard_step65']; + $content['step'] = 'wizard_step65'; + + if(!$content['override_values'] && $content['plugin_options']['override_values']) { + $content['override_values'] = $content['plugin_options']['override_values']; + } + $preserv = $content; + + foreach($content['field_mapping'] as $field) { + $sel_options['string'][$field] = $this->mapping_fields[$field]; + if(!$sel_options['string'][$field]) + { + foreach($this->mapping_fields as $fields) + { + if(is_array($fields) && $fields[$field]) + { + $sel_options['string'][$field] = $fields[$field]; + } + } + } + } + + $preserv['override_values'] = $content['override_values']; + + unset ($preserv['button']); + return $this->step_templates[$content['step']]; + } + /** * Expose import fields for use elsewhere */ diff --git a/importexport/templates/default/wizard_basic_import_csv.overrides.xet b/importexport/templates/default/wizard_basic_import_csv.overrides.xet new file mode 100644 index 0000000000..cdce5e5a0d --- /dev/null +++ b/importexport/templates/default/wizard_basic_import_csv.overrides.xet @@ -0,0 +1,23 @@ + + + + + + diff --git a/infolog/inc/class.infolog_import_infologs_csv.inc.php b/infolog/inc/class.infolog_import_infologs_csv.inc.php index d3515c5c95..2bee3c767b 100644 --- a/infolog/inc/class.infolog_import_infologs_csv.inc.php +++ b/infolog/inc/class.infolog_import_infologs_csv.inc.php @@ -15,6 +15,10 @@ use EGroupware\Api\Link; /** * class import_csv for infolog + * + * This does not follow the others (extending importexport_basic_import_csv + * since we have some fancy things going on with the type / status lookups. + * It could probably be replaced or update to match. */ class infolog_import_infologs_csv implements importexport_iface_import_plugin { @@ -219,6 +223,14 @@ class infolog_import_infologs_csv implements importexport_iface_import_plugin $record['info_status'] = $this->boinfolog->status['defaults'][$record['info_type']]; } + if($_definition->plugin_options['override_values']) + { + $egw_record = new infolog_egw_record(); + $egw_record->set_record($record); + $this->set_overrides($_definition, $egw_record); + $record = $egw_record->get_record_array(); + } + // Set owner, unless it's supposed to come from CSV file if($_definition->plugin_options['owner_from_csv']) { @@ -433,6 +445,40 @@ class infolog_import_infologs_csv implements importexport_iface_import_plugin return $result; } + /** + * Handle setting specific values from the definition on every record + * + * + * @param importexport_definition $_definition + * @param importexport_iface_egw_record $record + */ + protected function set_overrides(importexport_definition &$_definition, importexport_iface_egw_record &$record) + { + $overrides = $_definition->plugin_options['override_values']; + + // Set the record field, with some type checks + $set = function($record, $field, $value) + { + if(is_array($record->$field)) + { + array_unshift($record->$field, $value['value']); + } + else + { + $record->$field = $value['value']; + } + }; + + // Set category + if($overrides['cat_id'] && $record::$types['select-cat']) + { + foreach($record::$types['select-cat'] as $field) + { + $set($record, $field, $overrides['cat_id']); + } + } + } + /** * returns translated name of plugin * diff --git a/timesheet/inc/class.timesheet_import_csv.inc.php b/timesheet/inc/class.timesheet_import_csv.inc.php index 8d84b04027..cad0ce19e4 100644 --- a/timesheet/inc/class.timesheet_import_csv.inc.php +++ b/timesheet/inc/class.timesheet_import_csv.inc.php @@ -17,31 +17,8 @@ use EGroupware\Api\Link; /** * class import_csv for timesheet */ -class timesheet_import_csv implements importexport_iface_import_plugin +class timesheet_import_csv extends importexport_basic_import_csv { - private static $plugin_options = array( - 'fieldsep', // char - 'charset', // string - 'update_cats', // string {override|add} overides record - // with cat(s) from csv OR add the cat from - // csv file to exeisting cat(s) of record - 'num_header_lines', // int number of header lines - 'field_conversion', // array( $csv_col_num => conversion) - 'field_mapping', // array( $csv_col_num => adb_filed) - 'conditions', /* => array containing condition arrays: - 'type' => exists, // exists - 'string' => '#kundennummer', - 'true' => array( - 'action' => update, - 'last' => true, - ), - 'false' => array( - 'action' => insert, - 'last' => true, - ),*/ - - ); - public static $special_fields = array( 'addressbook' => 'Link to Addressbook, use nlast,nfirst[,org] or contact_id from addressbook', 'link_1' => '1. link: appname:appid the entry should be linked to, eg.: addressbook:123', @@ -49,11 +26,6 @@ class timesheet_import_csv implements importexport_iface_import_plugin 'link_3' => '3. link: appname:appid the entry should be linked to, eg.: addressbook:123', ); - /** - * actions wich could be done to data entries - */ - protected static $actions = array( 'none', 'update', 'insert', 'delete', ); - /** * conditions for actions * @@ -61,11 +33,6 @@ class timesheet_import_csv implements importexport_iface_import_plugin */ protected static $conditions = array( 'exists' ); - /** - * @var definition - */ - private $definition; - /** * @var business object */ @@ -77,293 +44,232 @@ class timesheet_import_csv implements importexport_iface_import_plugin protected $tracking; /** - * @var bool - */ - private $dry_run = false; - - /** - * @var int - */ - private $user = null; - - /** - * List of import warnings - */ - protected $warnings = array(); - - /** - * List of import errors - */ - protected $errors = array(); - - /** - * List of actions, and how many times that action was taken - */ - protected $results = array(); - - /** - * imports entries according to given definition object. - * @param resource $_stream - * @param string $_charset + * Initialize for import + * * @param definition $_definition */ - public function import( $_stream, importexport_definition $_definition ) + protected function init( importexport_definition $_definition, importexport_import_csv &$import_csv ) { - $import_csv = new importexport_import_csv( $_stream, array( - 'fieldsep' => $_definition->plugin_options['fieldsep'], - 'charset' => $_definition->plugin_options['charset'], - )); - - $this->definition = $_definition; - - $this->user = $GLOBALS['egw_info']['user']['account_id']; - - // dry run? - $this->dry_run = isset( $_definition->plugin_options['dry_run'] ) ? $_definition->plugin_options['dry_run'] : false; - // fetch the bo $this->bo = new timesheet_bo(); // Get the tracker for changes $this->tracking = new timesheet_tracking($this->bo); - // set FieldMapping. - $import_csv->mapping = $_definition->plugin_options['field_mapping']; - - // set FieldConversion - $import_csv->conversion = $_definition->plugin_options['field_conversion']; - // Add extra conversions $import_csv->conversion_class = $this; - //check if file has a header lines - if ( isset( $_definition->plugin_options['num_header_lines'] ) && $_definition->plugin_options['num_header_lines'] > 0) { - $import_csv->skip_records($_definition->plugin_options['num_header_lines']); - } elseif(isset($_definition->plugin_options['has_header_line']) && $_definition->plugin_options['has_header_line']) { - // First method is preferred - $import_csv->skip_records(1); - } - // set Owner $plugin_options = $_definition->plugin_options; $plugin_options['record_owner'] = isset( $_definition->plugin_options['record_owner'] ) ? $_definition->plugin_options['record_owner'] : $this->user; $_definition->plugin_options = $plugin_options; - // Used to try to automatically match names to account IDs - $addressbook = new Api\Contacts\Storage(); - // For converting human-friendly lookups - $categories = new Api\Categories('timesheet'); - $lookups = array( + $this->lookups = array( 'ts_status' => $this->bo->status_labels ); // Status need leading spaces removed - foreach($lookups['ts_status'] as $id => &$label) + foreach($this->lookups['ts_status'] as $id => &$label) { $label = str_replace(' ', '',trim($label)); - if($label == '') unset($lookups['ts_status'][$id]); + if($label == '') unset($this->lookups['ts_status'][$id]); } + } - // Start counting successes - $count = 0; - $this->results = array(); - - // Failures - $this->errors = array(); - - while ( $record = $import_csv->get_record() ) + /** + *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) + { + // Automatically handle text Api\Categories without explicit Api\Translation + foreach(array('ts_status','cat_id') as $field) { - $success = false; - - // don't import empty records - if( count( array_unique( $record ) ) < 2 ) continue; - - $result = importexport_import_csv::convert($record, timesheet_egw_record::$types, 'timesheet', $lookups, $_definition->plugin_options['convert']); - if($result) $this->warnings[$import_csv->get_current_position()] = $result; - - // Automatically handle text Api\Categories without explicit Api\Translation - foreach(array('ts_status','cat_id') as $field) + if(!is_numeric($record->$field)) { - if(!is_numeric($record[$field])) + $translate_key = 'translate'.(substr($field,0,2) == 'ts' ? substr($field,2) : '_cat_id'); + if(!is_null($this->lookups[$field]) && ($key = array_search($record->$field, $this->lookups[$field]))) { - $translate_key = 'translate'.(substr($field,0,2) == 'ts' ? substr($field,2) : '_cat_id'); - if(!is_null($lookups[$field]) && ($key = array_search($record[$field], $lookups[$field]))) - { - $record[$field] = $key; - } - elseif(array_key_exists($translate_key, $_definition->plugin_options)) - { - $t_field = $_definition->plugin_options[$translate_key]; - switch ($t_field) - { - case '': - case '0': - // Skip that field - unset($record[$field]); - break; - case '~skip~': - continue 2; - default: - if(strpos($t_field, 'add') === 0) - { - // Check for a parent - list($name, $parent_name) = explode('~',$t_field); - if($parent_name) - { - $parent = importexport_helper_functions::cat_name2id($parent_name); - } - - if($field == 'cat_id') - { - $record[$field] = importexport_helper_functions::cat_name2id($record[$field], $parent); - } - elseif ($field == 'ts_status') - { - end($this->bo->status_labels); - $id = key($this->bo->status_labels)+1; - $this->bo->status_labels[$id] = $record[$field]; - $this->bo->status_labels_config[$id] = array( - 'name' => $record[$field], - 'parent' => $parent, - 'admin' => false - ); - Api\Config::save_value('status_labels',$this->bo->status_labels_config,TIMESHEET_APP); - $lookups[$field][$id] = $name; - $record[$field] = $id; - } - } - elseif(!is_null($lookups[$field]) && ($key = array_search($t_field, $lookups[$field]))) - { - $record[$field] = $key; - } - else - { - $record[$field] = $t_field; - } - break; - } - } + $record->$field = $key; } - } - - // Set creator, unless it's supposed to come from CSV file - if($_definition->plugin_options['owner_from_csv'] && $record['ts_owner']) - { - if(!is_numeric($record['ts_owner'])) + elseif(array_key_exists($translate_key, $_definition->plugin_options)) { - // Automatically handle text owner without explicit Api\Translation - $new_owner = importexport_helper_functions::account_name2id($record['ts_owner']); - if($new_owner == '') + $t_field = $_definition->plugin_options[$translate_key]; + switch ($t_field) { - $this->errors[$import_csv->get_current_position()] = lang( - 'Unable to convert "%1" to account ID. Using plugin setting (%2) for %3.', - $record['ts_owner'], - Api\Accounts::username($_definition->plugin_options['record_owner']), - lang($this->bo->field2label['ts_owner']) - ); - $record['ts_owner'] = $_definition->plugin_options['record_owner']; - } - else - { - $record['ts_owner'] = $new_owner; - } - } - } - elseif ($_definition->plugin_options['record_owner']) - { - $record['ts_owner'] = $_definition->plugin_options['record_owner']; - } - - // Check account IDs - foreach(array('ts_modifier') as $field) - { - if($record[$field] && !is_numeric($record[$field])) - { - // Try an automatic conversion - $account_id = importexport_helper_functions::account_name2id($record[$field]); - if($account_id && strtoupper(Api\Accounts::username($account_id)) == strtoupper($record[$field])) - { - $record[$field] = $account_id; - } - else - { - $this->errors[$import_csv->get_current_position()] = lang( - 'Unable to convert "%1" to account ID. Using plugin setting (%2) for %3.', - $record[$field], - Api\Accounts::username($_definition->plugin_options['record_owner']), - $this->bo->field2label[$field] ? lang($this->bo->field2label[$field]) : $field - ); - } - } - } - - // Special values - if ($record['addressbook'] && !is_numeric($record['addressbook'])) - { - list($lastname,$firstname,$org_name) = explode(',',$record['addressbook']); - $record['addressbook'] = self::addr_id($lastname,$firstname,$org_name); - } - if ($record['pm_id'] && !is_numeric($record['pm_id'])) - { - $pms = Link::query('projectmanager',$record['pm_id']); - $record['pm_id'] = key($pms); - } - - if ( $_definition->plugin_options['conditions'] ) - { - foreach ( $_definition->plugin_options['conditions'] as $condition ) - { - $results = array(); - switch ( $condition['type'] ) - { - // exists - case 'exists' : - if($record[$condition['string']]) + case '': + case '0': + // Skip that field + unset($record->$field); + break; + case '~skip~': + continue 2; + default: + if(strpos($t_field, 'add') === 0) { - $results = $this->bo->search(array($condition['string'] => $record[$condition['string']])); + // Check for a parent + list($name, $parent_name) = explode('~',$t_field); + if($parent_name) + { + $parent = importexport_helper_functions::cat_name2id($parent_name); + } + + if($field == 'cat_id') + { + $record->$field = importexport_helper_functions::cat_name2id($record->$field, $parent); + } + elseif ($field == 'ts_status') + { + end($this->bo->status_labels); + $id = key($this->bo->status_labels)+1; + $this->bo->status_labels[$id] = $record->$field; + $this->bo->status_labels_config[$id] = array( + 'name' => $record->$field, + 'parent' => $parent, + 'admin' => false + ); + Api\Config::save_value('status_labels',$this->bo->status_labels_config,TIMESHEET_APP); + $lookups[$field][$id] = $name; + $record->$field = $id; + } } - - if ( is_array( $results ) && count( array_keys( $results )) >= 1 ) + elseif(!is_null($lookups[$field]) && ($key = array_search($t_field, $lookups[$field]))) { - // apply action to all records matching this exists condition - $action = $condition['true']; - foreach ( (array)$results as $result ) - { - $record['ts_id'] = $result['ts_id']; - if ( $_definition->plugin_options['update_cats'] == 'add' ) - { - if ( !is_array( $result['cat_id'] ) ) $result['cat_id'] = explode( ',', $result['cat_id'] ); - if ( !is_array( $record['cat_id'] ) ) $record['cat_id'] = explode( ',', $record['cat_id'] ); - $record['cat_id'] = implode( ',', array_unique( array_merge( $record['cat_id'], $result['cat_id'] ) ) ); - } - $success = $this->action( $action['action'], $record, $import_csv->get_current_position() ); - } + $record->$field = $key; } else { - $action = $condition['false']; - $success = ($this->action( $action['action'], $record, $import_csv->get_current_position() )); + $record->$field = $t_field; } break; - - // not supported action - default : - die('condition / action not supported!!!'); - break; } - if ($action['last']) break; } } - else - { - // unconditional insert - $success = $this->action( 'insert', $record, $import_csv->get_current_position() ); - } - if($success) $count++; } - return $count; + + // Set creator, unless it's supposed to come from CSV file + if($_definition->plugin_options['owner_from_csv'] && $record->ts_owner) + { + if(!is_numeric($record->ts_owner)) + { + // Automatically handle text owner without explicit Api\Translation + $new_owner = importexport_helper_functions::account_name2id($record->ts_owner); + if($new_owner == '') + { + $this->errors[$import_csv->get_current_position()] = lang( + 'Unable to convert "%1" to account ID. Using plugin setting (%2) for %3.', + $record->ts_owner, + Api\Accounts::username($_definition->plugin_options['record_owner']), + lang($this->bo->field2label['ts_owner']) + ); + $record->ts_owner = $_definition->plugin_options['record_owner']; + } + else + { + $record->ts_owner = $new_owner; + } + } + } + elseif ($_definition->plugin_options['record_owner']) + { + $record->ts_owner = $_definition->plugin_options['record_owner']; + } + + // Check account IDs + foreach(array('ts_modifier') as $field) + { + if($record->$field && !is_numeric($record->$field)) + { + // Try an automatic conversion + $account_id = importexport_helper_functions::account_name2id($record->$field); + if($account_id && strtoupper(Api\Accounts::username($account_id)) == strtoupper($record->$field)) + { + $record->$field = $account_id; + } + else + { + $this->errors[$import_csv->get_current_position()] = lang( + 'Unable to convert "%1" to account ID. Using plugin setting (%2) for %3.', + $record->$field, + Api\Accounts::username($_definition->plugin_options['record_owner']), + $this->bo->field2label[$field] ? lang($this->bo->field2label[$field]) : $field + ); + } + } + } + + // Special values + if ($record->addressbook && !is_numeric($record->addressbook)) + { + list($lastname,$firstname,$org_name) = explode(',',$record->addressbook); + $record->addressbook = self::addr_id($lastname,$firstname,$org_name); + } + if ($record->pm_id && !is_numeric($record->pm_id)) + { + $pms = Link::query('projectmanager',$record->pm_id); + $record->pm_id = key($pms); + } + + if ( $_definition->plugin_options['conditions'] ) + { + foreach ( $_definition->plugin_options['conditions'] as $condition ) + { + $results = array(); + switch ( $condition['type'] ) + { + // exists + case 'exists' : + if($record->$condition['string']) + { + $results = $this->bo->search(array($condition['string'] => $record->$condition['string'])); + } + + if ( is_array( $results ) && count( array_keys( $results )) >= 1 ) + { + // apply action to all records matching this exists condition + $action = $condition['true']; + foreach ( (array)$results as $result ) + { + $record->ts_id = $result['ts_id']; + if ( $_definition->plugin_options['update_cats'] == 'add' ) + { + if ( !is_array( $result['cat_id'] ) ) $result['cat_id'] = explode( ',', $result['cat_id'] ); + if ( !is_array( $record->cat_id ) ) $record->cat_id = explode( ',', $record->cat_id ); + $record->cat_id = implode( ',', array_unique( array_merge( $record->cat_id, $result['cat_id'] ) ) ); + } + $success = $this->action( $action['action'], $record, $import_csv->get_current_position() ); + } + } + else + { + $action = $condition['false']; + $success = ($this->action( $action['action'], $record, $import_csv->get_current_position() )); + } + break; + + // not supported action + default : + die('condition / action not supported!!!'); + break; + } + if ($action['last']) break; + } + } + else + { + // unconditional insert + $success = $this->action( 'insert', $record, $import_csv->get_current_position() ); + } + + return $success; } /** @@ -373,8 +279,9 @@ class timesheet_import_csv implements importexport_iface_import_plugin * @param array $_data tracker data for the action * @return bool success or not */ - private function action ( $_action, $_data, $record_num = 0 ) + protected function action ( $_action, importexport_iface_egw_record &$record, $record_num = 0 ) { + $_data = $record->get_record_array(); $result = true; switch ($_action) {