From 0a91bc5aa95cbdf72675057b6a9dbfd989bc7ff6 Mon Sep 17 00:00:00 2001 From: Nathan Gray Date: Mon, 31 Dec 2012 20:36:28 +0000 Subject: [PATCH] Add support for filtering records to be exported. Filterable fields are attempted to be autodetected by using the exportable fields. Records can be filtered by fields with type select,select-cat,select-account,date,date-time (according to egw_record class) only at this time. Filters are saved in the definition and used with scheduled exports. They are also available to the user for modification in the export dialog. --- importexport/inc/class.filter_widget.inc.php | 257 ++++++++++++++++++ .../inc/class.importexport_definition.inc.php | 28 ++ .../class.importexport_definitions_ui.inc.php | 2 + .../inc/class.importexport_export_ui.inc.php | 51 +++- ...lass.importexport_helper_functions.inc.php | 196 +++++++++++++ .../class.importexport_schedule_ui.inc.php | 26 +- ...portexport_wizard_basic_export_csv.inc.php | 77 +++++- importexport/js/importexport.js | 3 + importexport/setup/etemplates.inc.php | 15 +- importexport/setup/setup.inc.php | 3 +- importexport/setup/tables_current.inc.php | 3 +- importexport/setup/tables_update.inc.php | 10 + .../templates/default/export_dialog.xet | 31 ++- .../wizard_basic_export_csv.choose_fields.xet | 37 +++ .../wizard_basic_export_csv.filter.xet | 19 ++ 15 files changed, 743 insertions(+), 15 deletions(-) create mode 100644 importexport/inc/class.filter_widget.inc.php create mode 100644 importexport/templates/default/wizard_basic_export_csv.choose_fields.xet create mode 100644 importexport/templates/default/wizard_basic_export_csv.filter.xet diff --git a/importexport/inc/class.filter_widget.inc.php b/importexport/inc/class.filter_widget.inc.php new file mode 100644 index 0000000000..a9bc5cda2a --- /dev/null +++ b/importexport/inc/class.filter_widget.inc.php @@ -0,0 +1,257 @@ + value combination + * we send via col_filter, but the plugin gets a chance to interpret the filter + * settings. + * + * Selectboxes are easy, we just turn them into multi-selects. + * + * For dates we either use a relative date selection, or a literal date selection. + * Relative date options are in importexport_helper_functions::$relative_dates + * + * Most text fields are ignored. + */ +class filter_widget extends customfields_widget +{ + + public $prefix = ''; + public $human_name = array( + 'filter' => 'Import|Export filter' + ); + + public function __construct($ui, $appname = null) + { + $this->advanced_search = true; + parent::__construct($ui, $appname); + } + + /** + * pre-processing of the extension + * + * This function is called before the extension gets rendered + * + * @param string $form_name form-name of the control + * @param mixed &$value value / existing content, can be modified + * @param array &$cell array with the widget, can be modified for ui-independent widgets + * @param array &$readonlys names of widgets as key, to be made readonly + * @param mixed &$extension_data data the extension can store persisten between pre- and post-process + * @param etemplate &$tmpl reference to the template we belong too + * @return boolean true if extra label is allowed, false otherwise + */ + public function pre_process($form_name,&$value,&$cell,&$readonlys,&$extension_data,$tmpl) + { + $fields = $value['fields']; + unset($value['fields']); + + list($relative_dates) = explode(',',$cell['size']); + if($cell['relative_dates']) $relative_dates = true; + + // Fallback, so there's something there... + if(!is_array($fields)) + { + $cell['type'] = 'label'; + $cell['label'] = 'No fields'; + return True; + } + + + // making the cell an empty grid + $cell['type'] = 'grid'; + $cell['data'] = array(array()); + $cell['rows'] = $cell['cols'] = 0; + $cell['size'] = ''; + + $n = 1; + foreach($fields as $lname => $field) + { + $new_row = null; boetemplate::add_child($cell,$new_row); + $row_class = 'row'; + boetemplate::add_child($cell,$label =& boetemplate::empty_cell('label','',array( + 'label' => $field['label'], + 'no_lang' => substr(lang($field['label']),-1) == '*' ? 2 : 0, + 'span' => $field['type'] === 'label' ? '2' : '', + ))); + + switch($field['type']) + { + case 'date': + case 'date-time': + // Need a range here + $options = ''; + if($relative_dates) + { + $input = self::do_relative_date($lname, $value, $options, $readonly); + } + else + { + $input = self::do_absolute_date($lname, $value, $options, $readonly); + } + break; + case 'ajax_select' : + // Set some reasonable defaults for the widget + $options = array( + 'get_title' => 'etemplate.ajax_select_widget.array_title', + 'get_rows' => 'etemplate.ajax_select_widget.array_rows', + 'id_field' => ajax_select_widget::ARRAY_KEY, + ); + if($field['rows']) { + $options['num_rows'] = $field['rows']; + } + + // If you specify an option known to the AJAX Select widget, it will be pulled from the list of values + // and used as such. All unknown values will be used for selection, not passed through to the query + if (isset($field['values']['@'])) + { + $options['values'] = $this->_get_options_from_file($field['values']['@']); + unset($field['values']['@']); + } else { + $options['values'] = array_diff_key($field['values'], array_flip(ajax_select_widget::$known_options)); + } + $options = array_merge($options, array_intersect_key($field['values'], array_flip(ajax_select_widget::$known_options))); + + $input = boetemplate::empty_cell('ajax_select', $lname, array( + 'readonly' => $readonly, + 'no_lang' => True, + 'size' => $options + )); + break; + + case 'link-entry': + $input =& boetemplate::empty_cell('link-entry',$this->prefix.$lname,array( + 'size' => $field['type'] == 'link-entry' ? '' : $field['type'], + )); + // register post-processing of link widget to get eg. needed/required validation + etemplate_old::$request->set_to_process(etemplate_old::form_name($form_name,$this->prefix.$lname), 'ext-link'); + break; + case 'select': + default: + if(strpos($field['type'],'select') === 0) + { + if (count($field['values']) == 1 && isset($field['values']['@'])) + { + $field['values'] = $this->_get_options_from_file($field['values']['@']); + } + foreach((array)$field['values'] as $key => $val) + { + if (substr($val = lang($val),-1) != '*') + { + $field['values'][$key] = $val; + } + } + $input =& boetemplate::empty_cell($field['type'],$lname,array( + 'sel_options' => $field['values'], + 'size' => $field['rows'], + 'enhance' => true, + 'no_lang' => True, + )); + } + elseif (in_array($field['type'], array_keys(egw_link::app_list()))) + { + // Link entry to a specific app + $input =& boetemplate::empty_cell('link-entry',$lname,array( + 'size' => $field['type'] == 'link-entry' ? '' : $field['type'], + )); + // register post-processing of link widget to get eg. needed/required validation + etemplate_old::$request->set_to_process(etemplate_old::form_name($form_name,$lname), 'ext-link'); + + } else { +error_log('Trying to filter with unsupported field type: ' . $field['type']); + $input =& boetemplate::empty_cell($field['type'],$lname,array( + 'sel_options' => $field['values'], + 'size' => $field['rows'], + 'no_lang' => True, + )); + } + + } + $cell['data'][0]['c'.$n++] = $row_class.',top'; + + if (!is_null($input)) + { + if ($readonly) $input['readonly'] = true; + + $input['needed'] = $cell['needed'] || $field['needed']; + + if (!empty($field['help']) && $row_class != 'th') + { + $input['help'] = $field['help']; + $input['no_lang'] = substr(lang($help),-1) == '*' ? 2 : 0; + } + boetemplate::add_child($cell,$input); + unset($input); + } + unset($label); + } + return false; + } + + /** + * Create widgets to select a relative date range + * + * @param $lname Field name + * @param $options + * @param $readonly + * + * @return Array of widget info + */ + protected static function do_relative_date($lname, Array &$value, $options, $readonly) + { + // Maybe this could be moved to date widget + $input = boetemplate::empty_cell('select',$lname, array( + 'readonly' => $readonly, + 'no_lang' => true, + 'options' => $options, + 'sel_options' => array('' => lang('all')) + )); + foreach(importexport_helper_functions::$relative_dates as $label => $values) + { + $input['sel_options'][$label] = lang($label); + } + + return $input; + } + + /** + * Create widgets to select an absolute date range + * + * @param $lname Field name + * @param $options + * @param $readonly + * + * @return Array of widget info + */ + protected static function do_absolute_date($lname, Array &$value, $options, $readonly) + { + $input = boetemplate::empty_cell('hbox',$lname); + + $type = 'date'; + $from = boetemplate::empty_cell($type, $lname.'[from]',array( + 'readonly' => $readonly, + 'no_lang' => True, + 'size' => $options + )); + + $to = boetemplate::empty_cell($type, $lname.'[to]', array( + 'readonly' => $readonly, + 'no_lang' => True, + 'size' => $options + )); + boetemplate::add_child($input, $from); + boetemplate::add_child($input,boetemplate::empty_cell('label','',array( + 'label' => lang('to'), + 'no_lang' => true + ))); + boetemplate::add_child($input, $to); + return $input; + } +} diff --git a/importexport/inc/class.importexport_definition.inc.php b/importexport/inc/class.importexport_definition.inc.php index 9f0ef58e65..dee6a9f84d 100644 --- a/importexport/inc/class.importexport_definition.inc.php +++ b/importexport/inc/class.importexport_definition.inc.php @@ -31,6 +31,7 @@ class importexport_definition implements importexport_iface_egw_record { 'type' => 'string', 'allowed_users' => 'array', 'plugin_options' => 'array', + 'filter' => 'array', 'owner' => 'int', 'description' => 'string', 'modified' => 'timestamp' @@ -79,6 +80,8 @@ class importexport_definition implements importexport_iface_egw_record { } $options_data = importexport_arrayxml::xml2array( $this->definition['plugin_options'] ); $this->definition['plugin_options'] = $options_data['root']; + if($this->definition['filter']) $filter = importexport_arrayxml::xml2array( $this->definition['filter'] ); + $this->definition['filter'] = $filter['root']; } } @@ -110,6 +113,8 @@ class importexport_definition implements importexport_iface_egw_record { return $this->get_allowed_users(); case 'plugin_options' : return $this->get_options(); + case 'filter': + return $this->get_filter(); default : return $this->definition[$_attribute_name]; } @@ -124,6 +129,8 @@ class importexport_definition implements importexport_iface_egw_record { return $this->set_allowed_users($_data); case 'plugin_options' : return $this->set_options($_data); + case 'filter': + return $this->set_filter($_data); default : $this->definition[$_attribute_name] = $_data; return; @@ -166,6 +173,24 @@ class importexport_definition implements importexport_iface_egw_record { $this->definition['plugin_options'] = $_plugin_options; } + /** + * Get stored data filter + * + * @return array + */ + private function get_filter() { + return $this->definition['filter']; + } + + /** + * Set stored data filter + * + * @param filter array of field => settings + */ + private function set_filter(Array $filter) { + $this->definition['filter'] = $filter; + } + /** * converts this object to array. * @abstract We need such a function cause PHP5 @@ -178,6 +203,7 @@ class importexport_definition implements importexport_iface_egw_record { $definition = $this->definition; $definition['allowed_users'] = $this->get_allowed_users(); $definition['plugin_options'] = $this->get_options(); + $definition['filter'] = $this->get_filter(); return $definition; } @@ -211,6 +237,7 @@ class importexport_definition implements importexport_iface_egw_record { // convert plugin_options into internal representation $this->set_allowed_users( $this->definition['allowed_users'] ); $this->set_options( $this->definition['plugin_options'] ? $this->definition['plugin_options'] : array()); + $this->set_filter( $this->definition['filter'] ? $this->definition['filter'] : array()); } /** @@ -235,6 +262,7 @@ class importexport_definition implements importexport_iface_egw_record { $this->so_sql->data = $this->definition; $this->so_sql->data['plugin_options'] = importexport_arrayxml::array2xml( $this->definition['plugin_options'] ); + $this->so_sql->data['filter'] = importexport_arrayxml::array2xml( $this->definition['filter'] ); $this->so_sql->data['modified'] = time(); if ($this->so_sql->save( array( 'definition_id' => $_dst_identifier ))) { throw new Exception('Error: so_sql was not able to save definition: '.$this->get_identifier()); diff --git a/importexport/inc/class.importexport_definitions_ui.inc.php b/importexport/inc/class.importexport_definitions_ui.inc.php index bde76d81d4..30e9e55f8a 100644 --- a/importexport/inc/class.importexport_definitions_ui.inc.php +++ b/importexport/inc/class.importexport_definitions_ui.inc.php @@ -612,6 +612,7 @@ class importexport_definitions_ui { $this->response = new xajaxResponse(); + egw_framework::include_css_js_response(); if ($content['closewindow']) { $this->response->addScript("opener.location.reload();"); @@ -639,6 +640,7 @@ class importexport_definitions_ui { $GLOBALS['egw']->js->set_onload("document.getElementById('picturebox').style.display = 'none';"); egw_framework::validate_file('.', 'etemplate', 'etemplate'); + egw_framework::validate_file('.', 'etemplate', 'etemplate'); common::egw_header(); echo '
'."\n"; echo '

{Im|Ex}port Wizard

'; diff --git a/importexport/inc/class.importexport_export_ui.inc.php b/importexport/inc/class.importexport_export_ui.inc.php index 927eb1c723..fa01e07523 100644 --- a/importexport/inc/class.importexport_export_ui.inc.php +++ b/importexport/inc/class.importexport_export_ui.inc.php @@ -173,13 +173,31 @@ class importexport_export_ui { $content['plugin_options_template'] = $options; } } + $content['filter'] = $definition->filter; + $content['filter']['fields'] = importexport_helper_functions::get_filter_fields($_appname, $selected_plugin); + if(!$content['filter']['fields']) + { + $this->js->set_onload("\$j('input[value=\"filter\"]').parent().hide();"); + $content['no_filter'] = true; + } + else + { + // Process relative dates into the current absolute date + foreach($content['filter']['fields'] as $field => $settings) + { + if($content['filter'][$field] && strpos($settings['type'],'date') === 0) + { + $content['filter'][$field] = importexport_helper_functions::date_rel2abs($content['filter'][$field]); + } + } + } } // fill selection tab if($definition && $definition->plugin_options['selection'] && !$content['selection_passed']) { $_selection = $definition->plugin_options['selection']; } - + if ($_selection && ($content['old_definition'] == $content['definition'] || $content['selection_passed'])) { $readonlys[$tabs]['selection_tab'] = true; $content['selection'] = $_selection; @@ -209,9 +227,18 @@ class importexport_export_ui { disable_button('exec[export]'); "); } + + // Disable / hide definition filter if not selected + if($content['selection'] !== 'filter') + { + $this->js->set_onload(" + \$j('div.filters').hide(); + "); + } + $preserv['old_definition'] = $content['definition']; if (($prefs = $GLOBALS['egw_info']['user']['preferences']['importexport'][$definition->definition_id]) && - ($prefs = unserialize($prefs)) && !$content['selection']['plugin_override']) + ($prefs = unserialize($prefs)) && is_array($content['selection']) && !$content['selection']['plugin_override']) { $selection = $content['selection']; $content = array_merge_recursive($content,$prefs); @@ -260,6 +287,26 @@ class importexport_export_ui { 'mapping' => array() ); } + + // Set filter + // Note that because not all dates are DB dates, the plugin has to handle them + $filter = $definition->filter; + foreach($_content['filter'] as $key => $value) + { + // Handle multiple values + if(!is_array($value) && strpos($value,',') !== false) $value = explode(',',$value); + + $filter[$key] = $value; + + // Skip empty values or empty ranges + if(!$value || is_array($value) && array_key_exists('from',$value) && !$value['from'] && !$value['to'] ) + { + unset($filter[$key]); + } + } + unset($_content['filter']); + $definition->filter = $filter; + $definition->plugin_options = array_merge( $definition->plugin_options, $_content diff --git a/importexport/inc/class.importexport_helper_functions.inc.php b/importexport/inc/class.importexport_helper_functions.inc.php index 5c0a9eb4d0..7bdb3ba7d9 100755 --- a/importexport/inc/class.importexport_helper_functions.inc.php +++ b/importexport/inc/class.importexport_helper_functions.inc.php @@ -26,6 +26,25 @@ class importexport_helper_functions { */ public static $dry_run = false; + /** + * Relative date ranges for filtering + */ + public static $relative_dates = array( // Start: year,month,day,week, End: year,month,day,week + 'Today' => array(0,0,0,0, 0,0,1,0), + 'Yesterday' => array(0,0,-1,0, 0,0,0,0), + 'This week' => array(0,0,0,0, 0,0,0,1), + 'Last week' => array(0,0,0,-1, 0,0,0,0), + 'This month' => array(0,0,0,0, 0,1,0,0), + 'Last month' => array(0,-1,0,0, 0,0,0,0), + 'Last 3 months' => array(0,-3,0,0, 0,1,0,0), + 'This quarter'=> array(0,0,0,0, 0,0,0,0), // Just a marker, needs special handling + 'Last quarter'=> array(0,-4,0,0, 0,-4,0,0), // Just a marker + 'This year' => array(0,0,0,0, 1,0,0,0), + 'Last year' => array(-1,0,0,0, 0,0,0,0), + '2 years ago' => array(-2,0,0,0, -1,0,0,0), + '3 years ago' => array(-3,0,0,0, -2,0,0,0), + ); + /** * Files known to cause problems, and will be skipped in a plugin scan * If you put appname => true, the whole app will be skipped. @@ -515,4 +534,181 @@ class importexport_helper_functions { } return $list; } + + /** + * Get a list of filterable fields, and options for those fields + * + * It tries to automatically pull filterable fields from the list of fields in the wizard, + * and sets widget properties. The plugin can edit / set the fields by implementing + * get_filter_fields(Array &$fields). + * + * Currently only supports select,select-cat,select-account,date,date-time + * + * @param $app_name String name of app + * @param $plugin_name Name of the plugin + * + * @return Array ([fieldname] => array(widget settings), ...) + */ + public static function get_filter_fields($app_name, $plugin_name, $wizard_plugin = null, $record_classname = null) + { + $fields = array(); + try { + if($record_classname == null) $record_classname = $app_name . '_egw_record'; + if(!class_exists($record_classname)) throw new Exception('Bad class name ' . $record_classname); + + $plugin = is_object($plugin_name) ? $plugin_name : new $plugin_name(); + $plugin_name = get_class($plugin); + + if(!$wizard_plugin) + { + $wizard_name = $app_name . '_wizard_' . str_replace($app_name . '_', '', $plugin_name); + if(!class_exists($wizard_name)) throw new Exception('Bad wizard name ' . $wizard_name); + $wizard_plugin = new $wizard_name; + } + } + catch (Exception $e) + { + error_log($e->getMessage()); + return array(); + } + + // Get field -> label map and initialize fields using wizard field order + $fields = $export_fields = $wizard_plugin->get_export_fields(); + + foreach($record_classname::$types as $type => $type_fields) + { + // Only these for now, until filter methods for others are figured out + if(!in_array($type, array('select','select-cat','select-account','date','date-time'))) continue; + foreach($type_fields as $field_name) + { + $fields[$field_name] = array( + 'name' => $field_name, + 'label' => $export_fields[$field_name] ? $export_fields[$field_name] : $field_name, + 'type' => $type + ); + } + } + // Add custom fields + $custom = config::get_customfields($app_name); + foreach($custom as $field_name => $settings) + { + $settings['name'] = '#'.$field_name; + $fields['#'.$field_name] = $settings; + } + + foreach($fields as $field_name => &$settings) { + // Can't really filter on these (or at least no generic, sane way figured out yet) + if(!is_array($settings) || in_array($settings['type'], array('text','button', 'label','url','url-email','url-phone','htmlarea'))) + { + unset($fields[$field_name]); + continue; + } + if($settings['type'] == 'radio') $settings['type'] = 'select'; + switch($settings['type']) + { + case 'checkbox': + // This isn't quite right - there's only 2 options and you can select both + $settings['type'] = 'select-bool'; + $settings['rows'] = 1; + $settings['enhance'] = true; + break; + case 'select-cat': + $settings['rows'] = "5,,,$app_name"; + $settings['enhance'] = true; + break; + case 'select-account': + $settings['rows'] = '5,both'; + $settings['enhance'] = true; + break; + case 'select': + $settings['rows'] = 5; + $settings['enhance'] = true; + break; + } + } + + if(method_exists($plugin, 'get_filter_fields')) + { + $plugin->get_filter_fields($fields); + } + return $fields; + } + + /** + * Parse a relative date (Yesterday) into absolute (2012-12-31) date + * + * @param $value String description of date matching $relative_dates + * + * @return Array([from] => timestamp, [to]=> timestamp), inclusive + */ + public static function date_rel2abs($value) + { + if(is_array($value)) + { + $abs = array(); + foreach($value as $key => $val) + { + $abs[$key] = self::date_rel2abs($val); + } + return $abs; + } + if($date = self::$relative_dates[$value]) + { + $year = (int) date('Y'); + $month = (int) date('m'); + $day = (int) date('d'); + $today = mktime(0,0,0,date('m'),date('d'),date('Y')); + + list($syear,$smonth,$sday,$sweek,$eyear,$emonth,$eday,$eweek) = $date; + + if(stripos($value, 'quarter') !== false) + { + // Handle quarters + $start = mktime(0,0,0,((int)floor(($smonth+$month) / 3.1)) * 3 + 1, 1, $year); + $end = mktime(0,0,0,((int)floor(($emonth+$month) / 3.1)+1) * 3 + 1, 1, $year); + } + elseif ($syear || $eyear) + { + $start = mktime(0,0,0,1,1,$syear+$year); + $end = mktime(0,0,0,1,1,$eyear+$year); + } + elseif ($smonth || $emonth) + { + $start = mktime(0,0,0,$smonth+$month,1,$year); + $end = mktime(0,0,0,$emonth+$month,1,$year); + } + elseif ($sday || $eday) + { + $start = mktime(0,0,0,$month,$sday+$day,$year); + $end = mktime(0,0,0,$month,$eday+$day,$year); + } + elseif ($sweek || $eweek) + { + $wday = (int) date('w'); // 0=sun, ..., 6=sat + switch($GLOBALS['egw_info']['user']['preferences']['calendar']['weekdaystarts']) + { + case 'Sunday': + $weekstart = $today - $wday * 24*60*60; + break; + case 'Saturday': + $weekstart = $today - (6-$wday) * 24*60*60; + break; + case 'Moday': + default: + $weekstart = $today - ($wday ? $wday-1 : 6) * 24*60*60; + break; + } + $start = $weekstart + $sweek*7*24*60*60; + $end = $weekstart + $eweek*7*24*60*60; + } + $end_param = $end - 24*60*60; + + // Take 1 second off end to provide an inclusive range.for filtering + $end -= 1; + + //echo __METHOD__."($value,$start,$end) today=".date('l, Y-m-d H:i',$today)." ==>
".date('l, Y-m-d H:i:s',$start)." <= date <= ".date('l, Y-m-d H:i:s',$end)."

\n"; + return array('from' => $start, 'to' => $end); + } + return null; + } } // end of importexport_helper_functions diff --git a/importexport/inc/class.importexport_schedule_ui.inc.php b/importexport/inc/class.importexport_schedule_ui.inc.php index a1caf2766b..ee06b56804 100644 --- a/importexport/inc/class.importexport_schedule_ui.inc.php +++ b/importexport/inc/class.importexport_schedule_ui.inc.php @@ -460,8 +460,30 @@ if($type == 'export') { - // Set to export all - $definition->plugin_options = array_merge($definition->plugin_options, array('selection' => 'all')); + // Set to export all or filter, if set + $selection = array('selection' => 'all'); + if($definition->filter) + { + $fields = importexport_helper_functions::get_filter_fields($definition->application, $po); + $selection = array('selection' => 'filter'); + $filters = array(); + foreach($definition->filter as $field => $value) + { + // Handle multiple values + if(!is_array($value) && strpos($value,',') !== false) $value = explode(',',$value); + + $filters[$field] = $value; + + // Process relative dates into the current absolute date + if($filters[$field] && strpos($fields[$field]['type'],'date') === 0) + { + $filters[$field] = importexport_helper_functions::date_rel2abs($value); + } + } + // Update filter to use current absolute dates + $definition->filter = $filters; + } + $definition->plugin_options = array_merge($definition->plugin_options, $selection); } foreach($targets as $target) diff --git a/importexport/inc/class.importexport_wizard_basic_export_csv.inc.php b/importexport/inc/class.importexport_wizard_basic_export_csv.inc.php index e554ba1ed5..e81e2b27ab 100644 --- a/importexport/inc/class.importexport_wizard_basic_export_csv.inc.php +++ b/importexport/inc/class.importexport_wizard_basic_export_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 field list to work properly, you'll have to populate $export_fields with the fields available - * + * * 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_export_csv +class importexport_wizard_basic_export_csv { const TEMPLATE_MARKER = '-eTemplate-'; @@ -31,6 +31,7 @@ class importexport_wizard_basic_export_csv protected $step_templates = array( 'wizard_step30' => 'importexport.wizard_basic_export_csv.choose_fields', 'wizard_step40' => 'importexport.wizard_basic_export_csv.choosesepncharset', + 'wizard_step50' => 'importexport.wizard_basic_export_csv.filter', ); @@ -58,6 +59,7 @@ class importexport_wizard_basic_export_csv $this->steps = array( 'wizard_step30' => lang('Choose fields to export'), 'wizard_step40' => lang('Choose seperator and charset'), + 'wizard_step50' => lang('Filters'), ); list($appname, $part2) = explode('_', get_class($this)); if(!$GLOBALS['egw_info']['apps'][$appname]) $appname .= '_'.$part2; // Handle apps with _ in the name @@ -222,6 +224,73 @@ class importexport_wizard_basic_export_csv } + /** + * Set export filters + * + * @param array $content + * @param array $sel_options + * @param array $readonlys + * @param array $preserv + * @return string template name + */ + function wizard_step50(&$content, &$sel_options, &$readonlys, &$preserv) + { + if($this->debug) error_log(get_class($this) . '::' . __METHOD__ .'->$content '.print_r($content,true)); + // return from submit + if ($content['step'] == 'wizard_step50') { + // Process submitted + unset($content['filter']); + unset($content['set_filter']['fields']); + foreach($content['set_filter'] as $key => $value) + { + if($value) { + $content['filter'][$key] = $value; + } + } + unset($content['set_filter']); + + // Next step + 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_step50($content,$sel_options,$readonlys,$preserv); + } + } else { + + // Step 50 - filters + $content['msg'] = $this->steps['wizard_step50']; + $content['step'] = 'wizard_step50'; + + // Find filterable fields + if(!$content['set_filter'] && $content['filter']) { + $load = true; + } + $content['set_filter']['fields'] = importexport_helper_functions::get_filter_fields( + $content['application'],$content['plugin'],$this + ); + // Load existing filter from either content or definition + if($load) + { + foreach($content['set_filter']['fields'] as $field => $settings) + { + $content['set_filter'][$field] = $content['filter'][$field]; + } + } + + $sel_options = array(); + + $preserv = $content; + unset ($preserv['button']); + return $this->step_templates[$content['step']]; + } + } + /** * Expose export fields for use elsewhere */ diff --git a/importexport/js/importexport.js b/importexport/js/importexport.js index e10216b1c3..de423b5de4 100644 --- a/importexport/js/importexport.js +++ b/importexport/js/importexport.js @@ -10,5 +10,8 @@ function clear_options(id) { for(var count = list.options.length - 1; count >= 0; count--) { list.options[count] = null; } + if($j().chosen && list) { + $j(list).trigger("liszt:updated"); + } } diff --git a/importexport/setup/etemplates.inc.php b/importexport/setup/etemplates.inc.php index 2a0112f1be..8141ebf5d9 100644 --- a/importexport/setup/etemplates.inc.php +++ b/importexport/setup/etemplates.inc.php @@ -2,7 +2,7 @@ /** * EGroupware - eTemplates for Application importexport * http://www.egroupware.org - * generated by soetemplate::dump4setup() 2012-12-17 16:23 + * generated by soetemplate::dump4setup() 2012-12-31 13:19 * * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @package importexport @@ -89,7 +89,14 @@ $templ_data[] = array('name' => 'importexport.export_dialog.options_tab','templa display: none; }','modified' => '1286482130',); -$templ_data[] = array('name' => 'importexport.export_dialog.selection_tab','template' => '','lang' => '','group' => '0','version' => '','data' => 'a:1:{i:0;a:6:{s:4:"type";s:4:"grid";s:4:"data";a:3:{i:0;a:2:{s:2:"c1";s:4:",top";s:2:"h1";s:27:",@plugin_selectors_template";}i:1;a:1:{s:1:"A";a:3:{s:4:"type";s:4:"html";s:4:"name";s:21:"plugin_selectors_html";s:7:"no_lang";s:1:"1";}}i:2;a:1:{s:1:"A";a:2:{s:4:"type";s:8:"template";s:4:"name";s:26:"@plugin_selectors_template";}}}s:4:"rows";i:2;s:4:"cols";i:1;s:4:"size";s:6:",200px";s:7:"options";a:1:{i:1;s:5:"200px";}}}','size' => ',200px','style' => '','modified' => '1158223796',); +$templ_data[] = array('name' => 'importexport.export_dialog.selection_tab','template' => '','lang' => '','group' => '0','version' => '','data' => 'a:1:{i:0;a:6:{s:4:"type";s:4:"grid";s:4:"data";a:5:{i:0;a:10:{s:2:"h3";s:30:"20,!@plugin_selectors_template";s:2:"h2";s:27:",@plugin_selectors_template";s:2:"c2";s:4:",top";s:2:"h4";s:28:",!@plugin_selectors_template";s:2:"c3";s:2:"th";s:2:"h1";s:29:"20,@plugin_selectors_template";s:2:"c1";s:2:"th";s:2:"c4";s:4:",top";s:1:"B";s:14:"300,@no_filter";s:1:"A";s:3:"150";}i:1;a:2:{s:1:"A";a:1:{s:4:"type";s:5:"label";}s:1:"B";a:2:{s:4:"type";s:5:"label";s:5:"label";s:17:"Definition filter";}}i:2;a:2:{s:1:"A";a:3:{s:4:"type";s:4:"html";s:4:"name";s:21:"plugin_selectors_html";s:7:"no_lang";s:1:"1";}s:1:"B";a:4:{s:4:"type";s:3:"box";s:4:"span";s:8:",filters";s:4:"size";s:1:"1";i:1;a:2:{s:4:"type";s:6:"filter";s:4:"name";s:6:"filter";}}}i:3;a:2:{s:1:"A";a:1:{s:4:"type";s:5:"label";}s:1:"B";a:2:{s:4:"type";s:5:"label";s:5:"label";s:17:"Definition filter";}}i:4;a:2:{s:1:"A";a:2:{s:4:"type";s:8:"template";s:4:"name";s:26:"@plugin_selectors_template";}s:1:"B";a:5:{s:4:"type";s:3:"box";s:4:"span";s:8:",filters";s:4:"size";s:1:"1";i:1;a:2:{s:4:"type";s:6:"filter";s:4:"name";s:6:"filter";}s:7:"onclick";s:105:"\\$j(\'input[value=\\\'filter\\\']\').not(\':checked\').attr(\'checked\',true).parent().effect(\'highlight\',{},2000);";}}}s:4:"rows";i:4;s:4:"cols";i:2;s:4:"size";s:10:"100%,200px";s:7:"options";a:2:{i:0;s:4:"100%";i:1;s:5:"200px";}}}','size' => '100%,200px','style' => 'select[multiple] { + width: 400px; +} +.filters { + max-height: 300px; + min-height: 150px; + overflow-y: auto; +}','modified' => '1158223796',); $templ_data[] = array('name' => 'importexport.import_definition','template' => '','lang' => '','group' => '0','version' => '1.9.001','data' => 'a:1:{i:0;a:4:{s:4:"type";s:4:"grid";s:4:"data";a:4:{i:0;a:0:{}i:1;a:1:{s:1:"A";a:2:{s:4:"type";s:5:"label";s:5:"label";s:92:"Import definitions (Attension: Existing definitions with equal names will be overwritten!!!)";}}i:2;a:1:{s:1:"A";a:2:{s:4:"type";s:4:"file";s:4:"name";s:11:"import_file";}}i:3;a:1:{s:1:"A";a:4:{s:4:"type";s:4:"hbox";s:4:"size";s:1:"2";i:1;a:3:{s:4:"type";s:6:"button";s:5:"label";s:6:"Import";s:4:"name";s:6:"import";}i:2;a:3:{s:4:"type";s:6:"button";s:4:"name";s:6:"update";s:5:"label";s:26:"Update default-definitions";}}}}s:4:"rows";i:3;s:4:"cols";i:1;}}','size' => '','style' => '','modified' => '1306253183',); @@ -122,6 +129,10 @@ $templ_data[] = array('name' => 'importexport.wizard_basic_export_csv.choosesepn $templ_data[] = array('name' => 'importexport.wizard_basic_export_csv.choose_fields','template' => '','lang' => '','group' => '0','version' => '1.9.001','data' => 'a:1:{i:0;a:5:{s:4:"type";s:4:"grid";s:4:"data";a:3:{i:0;a:0:{}i:1;a:1:{s:1:"A";a:3:{s:4:"type";s:5:"label";s:4:"span";s:3:"all";s:4:"name";s:3:"msg";}}i:2;a:1:{s:1:"A";a:7:{s:4:"type";s:4:"grid";s:4:"data";a:3:{i:0;a:1:{s:2:"c1";s:2:"th";}i:1;a:2:{s:1:"A";a:2:{s:4:"type";s:5:"label";s:5:"label";s:5:"Field";}s:1:"B";a:4:{s:4:"type";s:4:"hbox";s:4:"size";s:1:"2";i:1;a:2:{s:4:"type";s:5:"label";s:5:"label";s:6:"Export";}i:2;a:7:{s:4:"type";s:10:"buttononly";s:4:"size";s:5:"check";s:5:"label";s:9:"Check all";s:4:"name";s:9:"check_all";s:4:"help";s:9:"Check all";s:7:"onclick";s:98:"jQuery(\'input:checkbox\').attr(\'checked\', !jQuery(\'input:checkbox\').attr(\'checked\')); return false;";s:6:"needed";s:1:"1";}}}i:2;a:2:{s:1:"A";a:3:{s:4:"type";s:5:"label";s:4:"name";s:12:"${row}[name]";s:7:"no_lang";s:1:"1";}s:1:"B";a:4:{s:4:"type";s:8:"checkbox";s:7:"no_lang";s:1:"1";s:4:"name";s:14:"export[${row}]";s:4:"size";s:18:"{$row_cont[field]}";}}}s:4:"rows";i:2;s:4:"cols";i:2;s:4:"size";s:10:",,,,,,auto";s:4:"name";s:6:"fields";s:7:"options";a:1:{i:6;s:4:"auto";}}}}s:4:"rows";i:2;s:4:"cols";i:1;s:7:"options";a:0:{}}}','size' => '','style' => '','modified' => '1286466690',); +$templ_data[] = array('name' => 'importexport.wizard_basic_export_csv.filter','template' => '','lang' => '','group' => '0','version' => '1.9.001','data' => 'a:1:{i:0;a:6:{s:4:"type";s:4:"grid";s:4:"data";a:2:{i:0;a:0:{}i:1;a:1:{s:1:"A";a:3:{s:4:"type";s:6:"filter";s:4:"name";s:10:"set_filter";s:4:"size";s:1:"1";}}}s:4:"rows";i:1;s:4:"cols";i:1;s:4:"size";s:4:"100%";s:7:"options";a:1:{i:0;s:4:"100%";}}}','size' => '100%','style' => 'select { + width: 200px; +}','modified' => '1356969972',); + $templ_data[] = array('name' => 'importexport.wizard_basic_import_csv.choosesepncharset','template' => '','lang' => '','group' => '0','version' => '1.9.001','data' => 'a:1:{i:0;a:4:{s:4:"type";s:4:"grid";s:4:"data";a:7:{i:0;a:2:{s:1:"B";s:5:"180px";s:2:"h5";s:9:",@no_cats";}i:1;a:2:{s:1:"A";a:4:{s:4:"type";s:5:"label";s:7:"no_lang";s:1:"1";s:4:"name";s:3:"msg";s:4:"span";s:3:"all";}s:1:"B";a:1:{s:4:"type";s:5:"label";}}i:2;a:2:{s:1:"A";a:2:{s:4:"type";s:5:"label";s:5:"label";s:14:"Fieldseperator";}s:1:"B";a:4:{s:4:"type";s:4:"text";s:7:"no_lang";s:1:"1";s:4:"name";s:8:"fieldsep";s:4:"size";s:1:"1";}}i:3;a:2:{s:1:"A";a:2:{s:4:"type";s:5:"label";s:5:"label";s:15:"Charset of file";}s:1:"B";a:4:{s:4:"type";s:6:"select";s:7:"no_lang";s:1:"1";s:4:"name";s:7:"charset";s:4:"span";s:9:",width180";}}i:4;a:2:{s:1:"A";a:2:{s:4:"type";s:5:"label";s:5:"label";s:20:"Header lines to skip";}s:1:"B";a:3:{s:4:"type";s:3:"int";s:4:"name";s:16:"num_header_lines";s:4:"size";s:1:"0";}}i:5;a:2:{s:1:"A";a:2:{s:4:"type";s:5:"label";s:5:"label";s:17:"Update categories";}s:1:"B";a:2:{s:4:"type";s:6:"select";s:4:"name";s:11:"update_cats";}}i:6;a:2:{s:1:"A";a:2:{s:4:"type";s:5:"label";s:5:"label";s:14:"Import data is";}s:1:"B";a:2:{s:4:"type";s:6:"select";s:4:"name";s:7:"convert";}}}s:4:"rows";i:6;s:4:"cols";i:2;}}','size' => '','style' => '.width180 select { width:150px;}','modified' => '1287425796',); $templ_data[] = array('name' => 'importexport.wizard_basic_import_csv.conditions','template' => '','lang' => '','group' => '0','version' => '1.9.002','data' => 'a:1:{i:0;a:8:{s:4:"type";s:4:"vbox";s:4:"data";a:2:{i:0;a:0:{}i:1;a:4:{s:1:"A";a:2:{s:4:"type";s:6:"select";s:4:"name";s:14:"${row}[string]";}s:1:"B";a:4:{s:4:"type";s:4:"hbox";s:4:"size";s:6:"2,,0,0";i:1;a:2:{s:4:"type";s:6:"select";s:4:"name";s:12:"${row}[type]";}i:2;a:2:{s:4:"type";s:4:"text";s:4:"name";s:12:"${row}[op_2]";}}s:1:"C";a:2:{s:4:"type";s:6:"select";s:4:"name";s:12:"${row}[true]";}s:1:"D";a:2:{s:4:"type";s:6:"select";s:4:"name";s:13:"${row}[false]";}}}s:4:"rows";i:1;s:4:"cols";i:4;s:4:"size";s:1:"3";i:1;a:2:{s:4:"type";s:5:"label";s:5:"label";s:142:"How to merge with existing data? For each record, all conditions are evaluated from top to bottom, or stop. For blind insert, leave blank. ";}i:2;a:6:{s:4:"type";s:4:"grid";s:4:"data";a:3:{i:0;a:1:{s:2:"c1";s:2:"th";}i:1;a:6:{s:1:"A";a:2:{s:4:"type";s:5:"label";s:5:"label";s:5:"Field";}s:1:"B";a:2:{s:4:"type";s:5:"label";s:5:"label";s:9:"Condition";}s:1:"C";a:5:{s:4:"type";s:4:"vbox";s:4:"size";s:1:"2";s:4:"span";s:1:"2";i:1;a:4:{s:4:"type";s:5:"label";s:4:"span";s:1:"2";s:5:"label";s:4:"True";s:5:"align";s:6:"center";}i:2;a:4:{s:4:"type";s:4:"hbox";s:4:"size";s:6:"2,,2,0";i:1;a:2:{s:4:"type";s:5:"label";s:5:"label";s:6:"Action";}i:2;a:3:{s:4:"type";s:5:"label";s:5:"label";s:4:"Stop";s:5:"align";s:5:"right";}}}s:1:"D";a:1:{s:4:"type";s:5:"label";}s:1:"E";a:5:{s:4:"type";s:4:"vbox";s:4:"size";s:1:"2";s:4:"span";s:1:"2";i:1;a:4:{s:4:"type";s:5:"label";s:4:"span";s:1:"2";s:5:"label";s:5:"False";s:5:"align";s:6:"center";}i:2;a:4:{s:4:"type";s:4:"hbox";s:4:"size";s:6:"2,,2,0";i:1;a:2:{s:4:"type";s:5:"label";s:5:"label";s:6:"Action";}i:2;a:3:{s:4:"type";s:5:"label";s:5:"label";s:4:"Stop";s:5:"align";s:5:"right";}}}s:1:"F";a:1:{s:4:"type";s:5:"label";}}i:2;a:6:{s:1:"A";a:4:{s:4:"type";s:6:"select";s:4:"name";s:14:"${row}[string]";s:4:"size";s:6:"Select";s:7:"no_lang";s:1:"1";}s:1:"B";a:4:{s:4:"type";s:4:"hbox";s:4:"size";s:6:"2,,0,0";i:1;a:3:{s:4:"type";s:6:"select";s:4:"name";s:12:"${row}[type]";s:7:"no_lang";s:1:"1";}i:2;a:3:{s:4:"type";s:4:"text";s:4:"name";s:12:"${row}[op_2]";s:7:"no_lang";s:1:"1";}}s:1:"C";a:4:{s:4:"type";s:6:"select";s:4:"name";s:20:"${row}[true][action]";s:4:"size";s:6:"Select";s:7:"no_lang";s:1:"1";}s:1:"D";a:3:{s:4:"type";s:8:"checkbox";s:5:"align";s:6:"center";s:4:"name";s:18:"${row}[true][stop]";}s:1:"E";a:4:{s:4:"type";s:6:"select";s:4:"name";s:21:"${row}[false][action]";s:4:"size";s:6:"Select";s:7:"no_lang";s:1:"1";}s:1:"F";a:4:{s:4:"type";s:8:"checkbox";s:5:"align";s:6:"center";s:4:"name";s:19:"${row}[false][stop]";s:4:"size";s:7:"1,false";}}}s:4:"rows";i:2;s:4:"cols";i:6;s:4:"name";s:10:"conditions";s:7:"options";a:0:{}}i:3;a:5:{s:4:"type";s:6:"button";s:4:"name";s:11:"button[add]";s:5:"label";s:3:"add";s:4:"help";s:27:"Add an additional condition";s:7:"onclick";s:37:"xajax_eT_wrapper(this); return false;";}}}','size' => '','style' => '','modified' => '1320350666',); diff --git a/importexport/setup/setup.inc.php b/importexport/setup/setup.inc.php index 539251976e..a461586bbc 100644 --- a/importexport/setup/setup.inc.php +++ b/importexport/setup/setup.inc.php @@ -10,7 +10,7 @@ */ $setup_info['importexport']['name'] = 'importexport'; -$setup_info['importexport']['version'] = '1.9.003'; +$setup_info['importexport']['version'] = '1.9.004'; $setup_info['importexport']['app_order'] = 2; $setup_info['importexport']['enable'] = 2; $setup_info['importexport']['tables'] = array('egw_importexport_definitions'); @@ -57,3 +57,4 @@ $setup_info['importexport']['check_install'] = array( ), ); + diff --git a/importexport/setup/tables_current.inc.php b/importexport/setup/tables_current.inc.php index a8b983ee37..feed482a46 100644 --- a/importexport/setup/tables_current.inc.php +++ b/importexport/setup/tables_current.inc.php @@ -25,7 +25,8 @@ $phpgw_baseline = array( 'plugin_options' => array('type' => 'longtext'), 'owner' => array('type' => 'int','precision' => '20'), 'description' => array('type' => 'varchar','precision' => '255'), - 'modified' => array('type' => 'timestamp') + 'modified' => array('type' => 'timestamp'), + 'filter' => array('type' => 'longtext') ), 'pk' => array('definition_id'), 'fk' => array(), diff --git a/importexport/setup/tables_update.inc.php b/importexport/setup/tables_update.inc.php index 50744ade67..a9b935a13b 100755 --- a/importexport/setup/tables_update.inc.php +++ b/importexport/setup/tables_update.inc.php @@ -100,3 +100,13 @@ function importexport_upgrade1_9_002() return $GLOBALS['setup_info']['importexport']['currentver'] = '1.9.003'; } + +function importexport_upgrade1_9_003() +{ + $GLOBALS['egw_setup']->oProc->AddColumn('egw_importexport_definitions','filter',array( + 'type' => 'longtext' + )); + + return $GLOBALS['setup_info']['importexport']['currentver'] = '1.9.004'; +} + diff --git a/importexport/templates/default/export_dialog.xet b/importexport/templates/default/export_dialog.xet index 828a71aaa1..16585e8746 100644 --- a/importexport/templates/default/export_dialog.xet +++ b/importexport/templates/default/export_dialog.xet @@ -52,19 +52,44 @@