mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-12-22 23:00:56 +01:00
port csv-export from old eTemplate nextmatch to separate Api\Etemplate\Export class to not have to rely on old eTemplate
This commit is contained in:
parent
5fbdd1376a
commit
39ad3a7977
307
api/src/Etemplate/Export.php
Normal file
307
api/src/Etemplate/Export.php
Normal file
@ -0,0 +1,307 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* EGroupware - CSV export for eT2 nextmatch widget
|
||||||
|
*
|
||||||
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||||
|
* @package api
|
||||||
|
* @subpackage etemplate
|
||||||
|
* @link http://www.egroupware.org
|
||||||
|
* @author Ralf Becker <RalfBecker@outdoor-training.de>
|
||||||
|
* @copyright 2002-21 by RalfBecker@outdoor-training.de
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace EGroupware\Api\Etemplate;
|
||||||
|
|
||||||
|
use EGroupware\Api;
|
||||||
|
use EGroupware\Api\Storage\Merge;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CSV export for eT2 nextmatch widget
|
||||||
|
*
|
||||||
|
* Ported from old eTemplate nextmatch widget
|
||||||
|
*/
|
||||||
|
class Export extends Widget\Nextmatch
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Export the list as csv file download
|
||||||
|
*
|
||||||
|
* @param array $value array('get_rows' => $method), further values see nextmatch widget $query parameter
|
||||||
|
* @param string $separator=';'
|
||||||
|
* @return boolean false=error, eg. get_rows callback does not exits, true=nothing to export, otherwise we do NOT return!
|
||||||
|
*/
|
||||||
|
static public function csv(&$value,$separator=';')
|
||||||
|
{
|
||||||
|
$exportLimitExempted = Merge::is_export_limit_excepted();
|
||||||
|
if (!$exportLimitExempted)
|
||||||
|
{
|
||||||
|
$name = is_object($value['template']) ? $value['template']->name : $value['template'];
|
||||||
|
list($app) = explode('.',$name);
|
||||||
|
$export_limit = Merge::getExportLimit($app);
|
||||||
|
//if (isset($value['export_limit'])) $export_limit = $value['export_limit'];
|
||||||
|
}
|
||||||
|
$charset = $charset_out = Api\Translation::charset();
|
||||||
|
if (isset($value['csv_charset']))
|
||||||
|
{
|
||||||
|
$charset_out = $value['csv_charset'];
|
||||||
|
}
|
||||||
|
elseif ($GLOBALS['egw_info']['user']['preferences']['common']['csv_charset'])
|
||||||
|
{
|
||||||
|
$charset_out = $GLOBALS['egw_info']['user']['preferences']['common']['csv_charset'];
|
||||||
|
}
|
||||||
|
$backup_start = $value['start'];
|
||||||
|
$backup_num_rows = $value['num_rows'];
|
||||||
|
|
||||||
|
$value['start'] = 0;
|
||||||
|
$value['num_rows'] = 500;
|
||||||
|
$value['csv_export'] = true; // so get_rows method _can_ produce different content or not store state in the session
|
||||||
|
do
|
||||||
|
{
|
||||||
|
$rows = [];
|
||||||
|
if (!($total = self::call_get_rows($value,$rows)))
|
||||||
|
{
|
||||||
|
break; // nothing to export
|
||||||
|
}
|
||||||
|
if (!$exportLimitExempted && (!Merge::hasExportLimit($export_limit,'ISALLOWED') || (Merge::hasExportLimit($export_limit) && (int)$export_limit < $total)))
|
||||||
|
{
|
||||||
|
etemplate::set_validation_error($name,lang('You are not allowed to export more than %1 entries!',(int)$export_limit));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!isset($value['no_csv_support'])) $value['no_csv_support'] = !is_array($value['csv_fields']);
|
||||||
|
|
||||||
|
//echo "<p>start=$value[start], num_rows=$value[num_rows]: total=$total, count(\$rows)=".count($rows)."</p>\n";
|
||||||
|
if (!$value['start']) // send the necessary headers
|
||||||
|
{
|
||||||
|
// skip empty data row(s) used to adjust to number of header-lines
|
||||||
|
foreach($rows as $row0)
|
||||||
|
{
|
||||||
|
if (is_array($row0) && count($row0) > 1) break;
|
||||||
|
}
|
||||||
|
$fp = self::csvOpen($row0,$value['csv_fields'],$app,$charset_out,$charset,$separator);
|
||||||
|
}
|
||||||
|
foreach($rows as $key => $row)
|
||||||
|
{
|
||||||
|
if (!is_numeric($key) || !$row) continue; // not a real rows
|
||||||
|
fwrite($fp,self::csvEncode($row,$value['csv_fields'],true,$rows['sel_options'],$charset_out,$charset,$separator)."\n");
|
||||||
|
}
|
||||||
|
$value['start'] += $value['num_rows'];
|
||||||
|
|
||||||
|
@set_time_limit(10); // 10 more seconds
|
||||||
|
}
|
||||||
|
while($total > $value['start']);
|
||||||
|
|
||||||
|
unset($value['csv_export']);
|
||||||
|
$value['start'] = $backup_start;
|
||||||
|
$value['num_rows'] = $backup_num_rows;
|
||||||
|
if ($value['no_csv_support']) // we need to call the get_rows method in case start&num_rows are stored in the session
|
||||||
|
{
|
||||||
|
self::call_get_rows($value);
|
||||||
|
}
|
||||||
|
if ($fp)
|
||||||
|
{
|
||||||
|
fclose($fp);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the csv output (download) and writes the header line
|
||||||
|
*
|
||||||
|
* @param array $row0 first row to guess the available fields
|
||||||
|
* @param array $fields name=>label or name=>array('lable'=>label,'type'=>type) pairs
|
||||||
|
* @param string $app app-name
|
||||||
|
* @param string $charset_out=null output charset
|
||||||
|
* @param string $charset data charset
|
||||||
|
* @param string $separator=';'
|
||||||
|
* @return FILE
|
||||||
|
*/
|
||||||
|
private static function csvOpen($row0, &$fields, $app, $charset_out=null, $charset=null, $separator=';')
|
||||||
|
{
|
||||||
|
if (!is_array($fields) || !count($fields))
|
||||||
|
{
|
||||||
|
$fields = self::autodetect_fields($row0,$app);
|
||||||
|
}
|
||||||
|
Api\Header\Content::type('export.csv','text/comma-separated-values');
|
||||||
|
//echo "<pre>";
|
||||||
|
|
||||||
|
if (($fp = fopen('php://output','w')))
|
||||||
|
{
|
||||||
|
$labels = array();
|
||||||
|
foreach($fields as $field => $label)
|
||||||
|
{
|
||||||
|
if (is_array($label)) $label = $label['label'];
|
||||||
|
$labels[$field] = $label ? $label : $field;
|
||||||
|
}
|
||||||
|
fwrite($fp,self::csvEncode($labels,$fields,false,null,$charset_out,$charset,$separator)."\n");
|
||||||
|
}
|
||||||
|
return $fp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CSV encode a single row, including some basic type conversation
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
* @param array $fields
|
||||||
|
* @param boolean $use_type=true
|
||||||
|
* @param array $extra_sel_options=null
|
||||||
|
* @param string $charset_out=null output charset
|
||||||
|
* @param string $charset data charset
|
||||||
|
* @param string $separator=';'
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private static function csvEncode($data, $fields, $use_type=true, array $extra_sel_options=null, $charset_out=null, $charset=null, $separator=';')
|
||||||
|
{
|
||||||
|
$sel_options = Api\Etemplate::$request->sel_options;
|
||||||
|
|
||||||
|
$out = array();
|
||||||
|
foreach($fields as $field => $label)
|
||||||
|
{
|
||||||
|
$value = (array)$data[$field];
|
||||||
|
if ($use_type && is_array($label) && in_array($label['type'],array('select-account','select-cat','date-time','date','select','int','float')))
|
||||||
|
{
|
||||||
|
foreach($value as $key => $val)
|
||||||
|
{
|
||||||
|
switch($label['type'])
|
||||||
|
{
|
||||||
|
case 'select-account':
|
||||||
|
if ($val) $value[$key] = Api\Accounts::username($val);
|
||||||
|
break;
|
||||||
|
case 'select-cat':
|
||||||
|
if ($val)
|
||||||
|
{
|
||||||
|
$cats = array();
|
||||||
|
foreach(is_array($val) ? $val : explode(',',$val) as $cat_id)
|
||||||
|
{
|
||||||
|
$cats[] = $GLOBALS['egw']->categories->id2name($cat_id);
|
||||||
|
}
|
||||||
|
$value[$key] = implode('; ',$cats);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'date-time':
|
||||||
|
case 'date':
|
||||||
|
if ($val)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$value[$key] = Api\DateTime::to($val,$label['type'] == 'date' ? true : '');
|
||||||
|
}
|
||||||
|
catch (\Exception $e) {
|
||||||
|
// ignore conversation errors, leave value unchanged (might be a wrongly as date(time) detected field
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'select':
|
||||||
|
if (isset($sel_options[$field]))
|
||||||
|
{
|
||||||
|
if ($val) $value[$key] = self::getLabel($val, $sel_options[$field]);
|
||||||
|
}
|
||||||
|
elseif(is_array($extra_sel_options) && isset($extra_sel_options[$field]))
|
||||||
|
{
|
||||||
|
if ($val) $value[$key] = self::getLabel($val, $extra_sel_options[$field]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'int': // size: [min],[max],[len],[precission/sprint format]
|
||||||
|
case 'float':
|
||||||
|
list(,,,$pre) = explode(',',$label['size']);
|
||||||
|
if (($label['type'] == 'float' || !is_numeric($pre)) && $val && $pre)
|
||||||
|
{
|
||||||
|
$val = str_replace(array(' ',','),array('','.'),$val);
|
||||||
|
$value[$key] = is_numeric($pre) ? round($value,$pre) : sprintf($pre,$value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$value = implode(', ',$value);
|
||||||
|
|
||||||
|
if (strpos($value,$separator) !== false || strpos($value,"\n") !== false || strpos($value,"\r") !== false)
|
||||||
|
{
|
||||||
|
$value = '"'.str_replace(array('\\', '"',),array('\\\\','""'),$value).'"';
|
||||||
|
$value = str_replace("\r\n", "\n", $value); // to avoid early linebreak by Excel
|
||||||
|
}
|
||||||
|
$out[] = $value;
|
||||||
|
}
|
||||||
|
$out = implode($separator,$out);
|
||||||
|
|
||||||
|
if ($charset_out && $charset != $charset_out)
|
||||||
|
{
|
||||||
|
$out = Api\Translation::convert($out,$charset,$charset_out);
|
||||||
|
}
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get label for given value
|
||||||
|
*
|
||||||
|
* @param $value
|
||||||
|
* @param array $options either value => label pairs or [['value'=>$value,'label'=>$label], ...]
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected static function getLabel($value, array &$options)
|
||||||
|
{
|
||||||
|
if (!is_array($options)) return;
|
||||||
|
|
||||||
|
if (!isset($options[$value]) && isset($options[0]))
|
||||||
|
{
|
||||||
|
$options = array_combine(
|
||||||
|
array_map(static function($data)
|
||||||
|
{
|
||||||
|
return $data['value'];
|
||||||
|
}, $options),
|
||||||
|
array_map(static function($data)
|
||||||
|
{
|
||||||
|
return $data['label'];
|
||||||
|
}, $options)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return lang($options[$value]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to autodetect the fields from the first data-row and the app-name
|
||||||
|
*
|
||||||
|
* @param array $row0 first data-row
|
||||||
|
* @param string $app
|
||||||
|
*/
|
||||||
|
private static function autodetect_fields($row0,$app)
|
||||||
|
{
|
||||||
|
$fields = array_combine(array_keys($row0),array_keys($row0));
|
||||||
|
|
||||||
|
foreach($fields as $name => $label)
|
||||||
|
{
|
||||||
|
// try to guess field-type from the fieldname
|
||||||
|
if (preg_match('/(modified|created|start|end)/',$name) && strpos($name,'by')===false &&
|
||||||
|
(!$row0[$name] || is_numeric($row0[$name]))) // only use for real timestamps
|
||||||
|
{
|
||||||
|
$fields[$name] = array('label' => $label,'type' => 'date-time');
|
||||||
|
}
|
||||||
|
elseif (preg_match('/(cat_id|category|cat)/',$name))
|
||||||
|
{
|
||||||
|
$fields[$name] = array('label' => $label,'type' => 'select-cat');
|
||||||
|
}
|
||||||
|
elseif (preg_match('/(owner|creator|modifier|assigned|by|coordinator|responsible)/',$name))
|
||||||
|
{
|
||||||
|
$fields[$name] = array('label' => $label,'type' => 'select-account');
|
||||||
|
}
|
||||||
|
elseif(preg_match('/(jpeg|photo)/',$name))
|
||||||
|
{
|
||||||
|
unset($fields[$name]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($app)
|
||||||
|
{
|
||||||
|
$customfields = Api\Storage\Customfields::get($app);
|
||||||
|
|
||||||
|
if (is_array($customfields))
|
||||||
|
{
|
||||||
|
foreach($customfields as $name => $data)
|
||||||
|
{
|
||||||
|
$fields['#'.$name] = array(
|
||||||
|
'label' => $data['label'],
|
||||||
|
'type' => $data['type'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//_debug_array($fields);
|
||||||
|
return $fields;
|
||||||
|
}
|
||||||
|
}
|
@ -562,7 +562,7 @@ class Nextmatch extends Etemplate\Widget
|
|||||||
* @param Etemplate\Widget $widget =null instanciated nextmatch widget to let it's widgets transform each row
|
* @param Etemplate\Widget $widget =null instanciated nextmatch widget to let it's widgets transform each row
|
||||||
* @return int|boolean total items found of false on error ($value['get_rows'] not callable)
|
* @return int|boolean total items found of false on error ($value['get_rows'] not callable)
|
||||||
*/
|
*/
|
||||||
private static function call_get_rows(array &$value,array &$rows,array &$readonlys=null,$obj=null,$method=null, Etemplate\Widget $widget=null)
|
protected static function call_get_rows(array &$value,array &$rows,array &$readonlys=null,$obj=null,$method=null, Etemplate\Widget $widget=null)
|
||||||
{
|
{
|
||||||
if (is_null($method)) $method = $value['get_rows'];
|
if (is_null($method)) $method = $value['get_rows'];
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user