From b38775db65a5e87d6609b2c244f0371b411ab931 Mon Sep 17 00:00:00 2001 From: Nathan Gray Date: Thu, 6 Sep 2012 21:50:58 +0000 Subject: [PATCH] - Uploading a new file to a definition fully clears mapping - should fix some of the mapping confusion - Add new / edit definition links in import dialog now open in new window, at the correct step - Reworked import file checking --- .../class.importexport_definitions_ui.inc.php | 38 ++++- .../inc/class.importexport_import_csv.inc.php | 7 - .../inc/class.importexport_import_ui.inc.php | 155 +++++++++++++----- ...portexport_wizard_basic_import_csv.inc.php | 46 ++++-- 4 files changed, 183 insertions(+), 63 deletions(-) diff --git a/importexport/inc/class.importexport_definitions_ui.inc.php b/importexport/inc/class.importexport_definitions_ui.inc.php index 2237f4646a..d430a48a57 100644 --- a/importexport/inc/class.importexport_definitions_ui.inc.php +++ b/importexport/inc/class.importexport_definitions_ui.inc.php @@ -425,11 +425,37 @@ class importexport_definitions_ui return $bodefinitions->get_rows($query, $rows, $readonlys); } + /** + * Edit a definition + * + * To jump to a certain step, pass the previous step in the URL step=wizard_stepXX + * The wizard will validate that step, then display the _next_ step.. + */ function edit() { if(!$_definition = $_GET['definition']) { - //close window + $content = array( + 'edit' => true, + 'application' => $_GET['application'], + 'plugin' => $_GET['plugin'] + ); + + // Jump to a step + if($_GET['step']) + { + $content['edit'] = false; + // Wizard will process previous step, then advance + $content['step'] = $this->get_step($_GET['step'],-1); + $content['button']['next'] = 'pressed'; + $this->wizard($content); + } + else + { + // Initial form + $this->wizard($content); + } + return; } if(is_numeric($_GET['definition'])) { @@ -442,6 +468,14 @@ class importexport_definitions_ui $bodefinitions = new importexport_definitions_bo(); $definition = $bodefinitions->read($definition); $definition['edit'] = true; + // Jump to a step + if($_GET['step']) + { + $definition['edit'] = false; + // Wizard will process previous step, then advance + $definition['step'] = $_GET['step'];; + $definition['button'] = array('next' => 'pressed'); + } $this->wizard($definition); } @@ -572,7 +606,7 @@ class importexport_definitions_ui if ($content['closewindow']) { $this->response->addScript("window.close();"); - $this->response->addScript("opener.location = '" . egw::link('/index.php', array('menuaction' => 'importexport.importexport_definitions_ui.index')) . "';"); + //$this->response->addScript("opener.location = '" . egw::link('/index.php', array('menuaction' => 'importexport.importexport_definitions_ui.index')) . "';"); // If Browser can't close window we display a "close" buuton and // need to disable normal buttons $this->response->addAssign('exec[button][previous]','style.display', 'none'); diff --git a/importexport/inc/class.importexport_import_csv.inc.php b/importexport/inc/class.importexport_import_csv.inc.php index 96e925e937..008874fee6 100755 --- a/importexport/inc/class.importexport_import_csv.inc.php +++ b/importexport/inc/class.importexport_import_csv.inc.php @@ -230,13 +230,6 @@ class importexport_import_csv implements importexport_iface_import_record { //, protected function do_fieldmapping( ) { $record = $this->record; $this->record = array(); - if(count($this->mapping) !== count($record)) - { - throw new egw_exception_wrong_userinput(lang("Column mismatch. Expected %1 columns, your file has %2.", - count($this->mapping), - count($record) - )); - } foreach ($this->mapping as $cvs_idx => $new_idx) { if( $new_idx == '' ) continue; diff --git a/importexport/inc/class.importexport_import_ui.inc.php b/importexport/inc/class.importexport_import_ui.inc.php index 36706d231e..abb0a274f4 100644 --- a/importexport/inc/class.importexport_import_ui.inc.php +++ b/importexport/inc/class.importexport_import_ui.inc.php @@ -78,26 +78,34 @@ $GLOBALS['egw_info']['flags']['currentapp'] = $appname; // Destination if we need to hold the file - $cachefile = new egw_cache_files(array()); - $dst_file = $cachefile->filename(egw_cache::keys(egw_cache::INSTANCE, 'importexport', - 'import_'.md5($content['file']['name'].$GLOBALS['egw_info']['user']['account_id']), true),true); - if($content['dry-run']) + if($file) { - echo $this->preview($file, $definition_obj); + $cachefile = new egw_cache_files(array()); + $dst_file = $cachefile->filename(egw_cache::keys(egw_cache::INSTANCE, 'importexport', + 'import_'.md5($content['file']['name'].$GLOBALS['egw_info']['user']['account_id']), true),true); // Keep file if($dst_file) { - if(copy($content['file']['tmp_name'],$dst_file)) { + if($content['file']['name'] && copy($content['file']['tmp_name'],$dst_file)) { $preserve['file']['tmp_name'] = $dst_file; } } - } elseif ($dst_file && $content['file']['tmp_name'] == $dst_file) { - // Remove file - $cachefile->delete(egw_cache::keys(egw_cache::INSTANCE, 'importexport', - 'import_'.md5($content['file']['name'].$GLOBALS['egw_info']['user']['account_id']))); + + // Check on matching columns + $check_message = array(); + if(!self::check_file($file, $definition_obj, $check_message, $dst_file)) + { + // Set this so plugin doesn't do any data changes + $content['dry-run'] = true; + $definition_obj->plugin_options = (array)$definition_obj->plugin_options + array('dry_run' => true); + } + $this->message .= implode($check_message, "
\n") . "
\n"; + if($content['dry-run']) + { + echo $this->preview($file, $definition_obj); + } + $count = $plugin->import($file, $definition_obj); } - - $count = $plugin->import($file, $definition_obj); $this->message .= lang('%1 records processed', $count); @@ -121,31 +129,15 @@ } if($count != $total_processed) $this->message .= "
\n".lang('Some records may not have been imported'); } - } catch (Exception $e) { - $this->message = $e->getMessage(); - - // Add links for new / edit definition - $config = config::read('importexport'); - if($GLOBALS['egw_info']['user']['apps']['admin'] || $config['users_create_definitions']) - { - // New definition - $add_link = egw::link('/index.php',array( - 'menuaction' => 'importexport.importexport_definitions_ui.edit' - )); - $this->message .= "
\n" . lang('Create a new definition for this file.', $add_link); - - // Edit selected definition, if allowed - if($definition_obj->owner == $GLOBALS['egw_info']['user']['account_id'] || - !$definition_obj->owner && $GLOBALS['egw_info']['user']['apps']['admin']) - { - $edit_link = egw::link('/index.php',array( - 'menuaction' => 'importexport.importexport_definitions_ui.edit', - 'definition' => $definition - )); - $this->message .= "
\n" . lang('Edit definition %2', - $edit_link, $definition_obj->name ); - } + if ($dst_file && $content['file']['tmp_name'] == $dst_file) { + // Remove file + $cachefile->delete(egw_cache::keys(egw_cache::INSTANCE, 'importexport', + 'import_'.md5($content['file']['name'].$GLOBALS['egw_info']['user']['account_id']))); + unset($dst_file); } + + } catch (Exception $e) { + $this->message .= $e->getMessage(); } } elseif($content['cancel']) @@ -161,6 +153,11 @@ ); } + if(!array_key_exists('dry-run',$content)) + { + $data['dry-run'] = true; + } + $data['appname'] = $preserve['appname'] = $appname ? $appname : ($definition_obj ? $definition_obj->application : ''); $data['definition'] = $definition; $data['delimiter'] = $definition_obj->plugin_options['delimiter']; @@ -264,7 +261,91 @@ // Rewind rewind($_stream); - return html::table($rows); + return '

' . lang('Preview') . '

' . html::table($rows); + } + + /** + * Simple check to see if the file at least matches the definition + * + * Checks that column headers match + */ + public static function check_file(&$file, &$definition, &$message = array(), $dst_file = false) + { + $options =& $definition->plugin_options; + $data = fgetcsv($file, 8000, $options['fieldsep']); + rewind($file); + $data = translation::convert($data,$options['charset']); + + $ok = true; + if(max(array_keys($data)) != max(array_keys($options['csv_fields']))) + { + $message[] = lang("Column mismatch. Expected %1 columns, your file has %2.", + max(array_keys($options['csv_fields'])), + max(array_keys($data)) + ); + $ok = false; + } + foreach($data as $index => $header) + { + if($index < count($options['csv_fields']) && !$options['field_mapping'][$index]) + { + // Skipped column in definition + continue; + } + elseif($index < count($options['csv_fields']) && $options['csv_fields'][$index] != $header) + { + // Problem + $message[] = lang("Column mismatch: %1 should be %2, not %3", + $index,$options['csv_fields'][$index], $header); + // But can still continue + // $ok = false; + } + } + if(!$ok) + { + // Add links for new / edit definition + $config = config::read('importexport'); + if($GLOBALS['egw_info']['user']['apps']['admin'] || $config['users_create_definitions']) + { + $actions = ''; + // New definition + $add_link = egw::link('/index.php',array( + 'menuaction' => 'importexport.importexport_definitions_ui.edit', + 'application' => $definition->application, + 'plugin' => $definition->plugin, + // Jump to name step + 'step' => 'wizard_step21' + )); + $add_link = "javascript:egw_openWindowCentered2('$add_link','_blank',500,500,'yes')"; + $actions[] = lang('Create a new definition for this file', $add_link); + + // Edit selected definition, if allowed + if($definition->owner == $GLOBALS['egw_info']['user']['account_id'] || + !$definition->owner && $GLOBALS['egw_info']['user']['apps']['admin']) + { + $edit_link = array( + 'menuaction' => 'importexport.importexport_definitions_ui.edit', + 'definition' => $definition->name, + // Jump to file step + 'step' => 'wizard_step21' + ); + if($dst_file) + { + // Still have uploaded file, jump there + $GLOBALS['egw']->session->appsession('csvfile','',$dst_file); + $edit_link['step'] = 'wizard_step30'; + } + $edit_link = egw::link('/index.php',$edit_link); + $edit_link = "javascript:egw_openWindowCentered2('$edit_link','_blank',500,500,'yes')"; + $actions[] = lang('Edit definition %2 to match your file', + $edit_link, $definition->name ); + } + $actions[] = lang('Edit your file to match the definition: ') + . implode(array_intersect_key($options['csv_fields'],$options['field_mapping']),', '); + $message[] = "\n
  • ".implode($actions,"\n
  • "); + } + } + return $ok; } } ?> 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 92e6a75139..96e9819e57 100644 --- a/importexport/inc/class.importexport_wizard_basic_import_csv.inc.php +++ b/importexport/inc/class.importexport_wizard_basic_import_csv.inc.php @@ -1,12 +1,12 @@ steps appropriately. The key is the function, the value is the title. * Don't go past 80, as that's where the wizard picks it back up again to finish it off. - * + * * For the mapping to work properly, you will have to fill $mapping_fields with the target fields for your application. - * + * * NB: Your wizard class must be in /inc/class.appname_wizard_.inc.php * * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License @@ -15,7 +15,7 @@ * @author Nathan Gray */ -class importexport_wizard_basic_import_csv +class importexport_wizard_basic_import_csv { const TEMPLATE_MARKER = '-eTemplate-'; @@ -81,7 +81,7 @@ class importexport_wizard_basic_import_csv if($content['file']['tmp_name']) { $csvfile = tempnam($GLOBALS['egw_info']['server']['temp_dir'],$content['plugin']."_"); move_uploaded_file($content['file']['tmp_name'], $csvfile); - $GLOBALS['egw']->session->appsession('csvfile','',$csvfile); + $GLOBALS['egw']->session->appsession('csvfile',$content['application'],$csvfile); } unset($content['file']); return $GLOBALS['egw']->importexport_definitions_ui->get_step($content['step'],1); @@ -124,20 +124,22 @@ class importexport_wizard_basic_import_csv { case 'next': // Process sample file for fields - if (($handle = fopen($GLOBALS['egw']->session->appsession('csvfile'), "rb")) !== FALSE) { + if (($handle = fopen($GLOBALS['egw']->session->appsession('csvfile',$content['application']), "rb")) !== FALSE) { $data = fgetcsv($handle, 8000, $content['fieldsep']); fclose($handle); - unlink($GLOBALS['egw']->session->appsession('csvfile')); + + // Remove & forget file + unlink($GLOBALS['egw']->session->appsession('csvfile',$content['application'])); + egw_cache::setSession($content['application'], 'csvfile', ''); $content['csv_fields'] = translation::convert($data,$content['charset']); + // Reset field mapping for new file + $content['field_mapping'] = array(); + // Try to match automatically $english = array(); foreach($content['csv_fields'] as $index => $field) { if($content['field_mapping'][$index]) continue; - if(is_array($content['plugin_options']['field_mapping']) && $content['plugin_options']['field_mapping'][$index]) { - # Copy already set, but allow new file to update - $content['field_mapping'][$index] = $content['plugin_options']['field_mapping'][$index]; - } $best_match = ''; $best_match_value = 0; foreach($this->mapping_fields as $key => $field_name) { @@ -162,7 +164,7 @@ class importexport_wizard_basic_import_csv // Check for similar but slightly different $match = 0; - if(similar_text(strtolower($field), strtolower($field_name), $match) && + if(similar_text(strtolower($field), strtolower($field_name), $match) && $match > 85 && $match > $best_match_value ) { @@ -208,12 +210,22 @@ class importexport_wizard_basic_import_csv if(!$content['num_header_lines'] && $content['plugin_options']['num_header_lines']) { $content['num_header_lines'] = $content['plugin_options']['num_header_lines']; } + else + { + // Default to 1 line + $content['num_header_lines'] = 1; + } if(!$content['update_cats'] && $content['plugin_options']['update_cats']) { $content['update_cats'] = $content['plugin_options']['update_cats']; } if(!array_key_exists('convert', $content) && array_key_exists('convert', $content['plugin_options'])) { $content['convert'] = $content['plugin_options']['convert']; } + else + { + // Default to human + $content['convert'] = 1; + } $sel_options['charset'] = $GLOBALS['egw']->translation->get_installed_charsets()+ array( @@ -247,10 +259,10 @@ class importexport_wizard_basic_import_csv } /** - * Process the sample file, get the fields out of it, then allow them to be mapped onto + * Process the sample file, get the fields out of it, then allow them to be mapped onto * the fields the destination understands. Also, set any translations to be done to the field. - * - * You can use the eTemplate + * + * You can use the eTemplate */ function wizard_step50(&$content, &$sel_options, &$readonlys, &$preserv) { @@ -328,7 +340,7 @@ class importexport_wizard_basic_import_csv $content[++$i]['index'] = $i - 1; if(strstr($field,'no_csv_')) $j++; } - while ($j <= 3) + while ($j <= 3) { $content['csv_fields'][] = 'no_csv_'.$j; $content['field_mapping'][] = $content['field_conversion'][] = ''; @@ -395,7 +407,7 @@ class importexport_wizard_basic_import_csv // Make at least 1 (empty) conditions $j = count($content['conditions']); - while ($j < 1) + while ($j < 1) { $content['conditions'][] = array( 'string' => '',