CSV Export for all eTemplate applications using the nextmatch widget to list their entries. The column names, labels and types can be set by the application or get autodetected by eTemplate. Admins can configure an export limit (max. number of entries to export) or disable the functionality completly for non-admins. The charset for the csv export can be configured in the common prefs. Applications can and have to turn the export explicitly off by setting ['nm']['csv_fields']=false. I only made a column-definition for InfoLog, which includes translated column-names.

This commit is contained in:
Ralf Becker 2007-09-22 14:58:10 +00:00
parent 15f201c07e
commit 3beb483c09
24 changed files with 384 additions and 15 deletions

View File

@ -226,6 +226,7 @@ home directory admin de Benutzerverzeichnis
host information admin de Host-Information
hour<br>(0-23) admin de Stunde<br>(0-23)
how many days should entries stay in the access log, before they get deleted (default 90) ? admin de Wie viele Tage sollen Einträge im Zugangsprotokoll bleiben, bevor sie gelöscht werden (Vorgabe 90)?
how many entries should non-admins be able to export (empty = no limit, no = no export) admin de Wie viele Einträge sollen nicht-Administratoren exportieren können (leer = kein Limit, Nein = kein Export)
how many minutes should an account or ip be blocked (default 30) ? admin de Wie viele Minuten soll ein Benutzerkonto oder eine IP gesperrt werden (Vorgabe 30)?
how should email addresses for new users be constructed? admin de In welchem Format sollen E-Mail-Adressen von neuen Benutzern erzeugt werden?
icon admin de Icon

View File

@ -224,6 +224,7 @@ home directory admin en Home directory
host information admin en Host information
hour<br>(0-23) admin en Hour<br>(0-23)
how many days should entries stay in the access log, before they get deleted (default 90) ? admin en How many days should entries stay in the access log, before they get deleted (default 90) ?
how many entries should non-admins be able to export (empty = no limit, no = no export) admin en How many entries should non-admins be able to export (empty = no limit, no = no export)
how many minutes should an account or ip be blocked (default 30) ? admin en How many minutes should an account or IP be blocked (default 30) ?
how should email addresses for new users be constructed? admin en How should EMail addresses for new users be constructed?
icon admin en Icon

View File

@ -240,6 +240,10 @@
</select>
</td>
</tr>
<tr class="row_on">
<td>{lang_How_many_entries_should_non-admins_be_able_to_export_(empty_=_no_limit,_no_=_no_export)}:</td>
<td><input name="newsettings[export_limit]" value="{value_export_limit}" size="5"></td>
</tr>
<!-- END body -->

View File

@ -839,6 +839,9 @@ $content[$id] = array( <span>// I = value set by the app, 0 = value on return /
'columnselection-pref' => <span>// I name of the preference (plus 'nextmatch-' prefix), default = template-name</span>
'default_cols' => <span>// I columns to use if there's no user or default pref (! as first char uses all but the named columns), default all columns</span>
'options-selectcols' => <span>// I array with name/label pairs for the column-selection, this gets autodetected by default. A name => false suppresses a column completly.</span>
'return' => <span>// IO allows to return something from the get_rows function if $query is a var-param!</span>
'csv_fields' => <span>// I false=disable csv export, true or unset=enable it with auto-detected fieldnames,
or array with name=>label or name=>array('label'=>label,'type'=>type) pairs (type is a eT widget-type)</span>
);
<span>/*

View File

@ -48,7 +48,13 @@
* 'rows' => // O content set by callback
* 'total' => // O the total number of entries
* 'sel_options' => // O additional or changed sel_options set by the callback and merged into $tmpl->sel_options
* 'no_columnselection' => // I turns off the columnselection completly, turned on by default
* 'columnselection-pref' => // I name of the preference (plus 'nextmatch-' prefix), default = template-name
* 'default_cols' => // I columns to use if there's no user or default pref (! as first char uses all but the named columns), default all columns
* 'options-selectcols' => // I array with name/label pairs for the column-selection, this gets autodetected by default. A name => false suppresses a column completly.
* 'return' => // IO allows to return something from the get_rows function if $query is a var-param!
* 'csv_fields' => // I false=disable csv export, true or unset=enable it with auto-detected fieldnames,
* or array with name=>label or name=>array('label'=>label,'type'=>type) pairs (type is a eT widget-type)
* );
* @package etemplate
* @subpackage extensions
@ -198,6 +204,9 @@
// save values in persistent extension_data to be able use it in post_process
$extension_data += $value;
$value['no_csv_export'] = $value['csv_fields'] === false ||
$GLOBALS['egw_info']['server']['export_limit'] && !is_numeric($GLOBALS['egw_info']['server']['export_limit']);
if (!$value['filter_onchange']) $value['filter_onchange'] = 'this.form.submit();';
if (!$value['filter2_onchange']) $value['filter2_onchange'] = 'this.form.submit();';
@ -583,7 +592,7 @@
$value[$name] = $value['bottom'][$name];
}
}
$buttons = array('start_search','first','left','right','last');
$buttons = array('start_search','first','left','right','last','export');
foreach($buttons as $name)
{
if (isset($value['bottom'][$name]) && $value['bottom'][$name])
@ -697,6 +706,253 @@
$GLOBALS['egw']->preferences->save_repository(false,$pref);
$loop = True;
}
if ($value['export'])
{
$this->_csv_export($extension_data);
}
return True;
}
var $separator = ';';
var $charset_out;
var $charset;
/**
* Export the list as csv file download
*
* @param array $value content of the nextmatch widget
* @param boolean false=error, eg. get_rows callback does not exits, true=nothing to export, otherwise we do NOT return!
*/
function _csv_export(&$value)
{
if (!isset($GLOBALS['egw_info']['user']['apps']['admin']))
{
$export_limit = $GLOBALS['egw_info']['server']['export_limit'];
}
list($app,$class,$method) = explode('.',$value['get_rows']);
if ($app && $class)
{
if (is_object($GLOBALS[$class])) // use existing instance (put there by a previous CreateObject)
{
$obj =& $GLOBALS[$class];
}
else
{
$obj =& CreateObject($app.'.'.$class);
}
}
if (!is_object($obj) || !method_exists($obj,$method))
{
$GLOBALS['egw_info']['etemplate']['validation_errors'][$name] = "nextmatch_widget::pre_process($cell[name]): '$value[get_rows]' is no valid method !!!";
return false;
}
$this->charset = $this->charset_out = $GLOBALS['egw']->translation->charset();
if (isset($value['csv_charset']))
{
$this->charset_out = $value['csv_charset'];
}
elseif ($GLOBALS['egw_info']['user']['preferences']['common']['csv_charset'])
{
$this->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
{
if (!($total = $obj->$method($value,$rows,$readonlys=array())))
{
break; // nothing to export
}
if ($export_limit && (!is_numeric($export_limit) || $export_limit < $total))
{
$GLOBALS['egw_info']['etemplate']['validation_errors'][$name] = lang('You are not allowed to export more then %1 entries!',$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 neccessary headers
{
$fp = $this->_csv_open($rows[0],$value['csv_fields'],$app);
}
//_debug_array($rows);
foreach($rows as $key => $row)
{
if (!is_numeric($key)) continue; // not a real rows
fwrite($fp,$this->_csv_encode($row,$value['csv_fields'])."\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
{
$obj->$method($value,$rows,$readonlys=array());
}
if ($fp)
{
fclose($fp);
$GLOBALS['egw']->common->egw_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
* @return FILE
*/
function _csv_open($row0,&$fields,$app)
{
if (!is_array($fields) || !count($fields))
{
$fields = $this->_autodetect_fields($row0,$app);
}
$browser =& CreateObject('phpgwapi.browser');
$browser->content_header('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,$this->_csv_encode($labels,$fields,false)."\n");
}
return $fp;
}
/**
* CSV encode a single row, including some basic type conversation
*
* @param array $data
* @param array $fields
* @param boolean $use_type=true
* @return string
*/
function _csv_encode($data,$fields,$use_type=true)
{
static $date_format,$time_format;
if (is_null($date_format))
{
$time_format = $date_format = $GLOBALS['egw_info']['user']['preferences']['common']['dateformat'];
$time_format .= ' '.($GLOBALS['egw_info']['user']['preferences']['common']['timeformat'] == 12 ? 'h:ia' : 'H:i');
}
$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')))
{
foreach($value as $key => $val)
{
switch($label['type'])
{
case 'select-account':
if ($val) $value[$key] = $GLOBALS['egw']->common->grab_owner_name($val);
break;
case 'select-cat':
if ($val)
{
if (!is_object($GLOBALS['egw']->categories))
{
$GLOBALS['egw']->categories =& CreateObject('phpgwapi.categories');
}
$value[$key] = $GLOBALS['egw']->categories->id2name($val);
}
break;
case 'date-time':
if ($val) $value[$key] = date($time_format,$val);
break;
case 'date':
if ($val) $value[$key] = date($date_format,$val);
break;
}
}
}
$value = implode(', ',$value);
if (strpos($value,$this->separator) !== false || strpos($value,"\n") !== false || strpos($value,"\r") !== false)
{
$value = '"'.str_replace(array('\\','"'),array('\\\\','\\"'),$value).'"';
}
$out[] = $value;
}
$out = implode($this->separator,$out);
if ($this->charset_out && $this->charset != $this->charset_out)
{
$out = $GLOBALS['egw']->translation->convert($out,$this->charset,$this->charset_out);
}
return $out;
}
/**
* Try to autodetect the fields from the first data-row and the app-name
*
* @param array $row0 first data-row
* @param string $app
*/
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)
{
include_once(EGW_API_INC.'/class.config.inc.php');
$config =& new config($app);
$config->read_repository();
$customfields = isset($config->config_data['customfields']) ? $config->config_data['customfields'] : $config->config_data['custom_fields'];
if (is_array($customfields))
{
foreach($customfields as $name => $data)
{
$fields['#'.$name] = array(
'label' => $data['label'],
'type' => $data['type'],
);
}
}
}
//_debug_array($fields);
return $fields;
}
}

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,7 @@
<?xml version="1.0"?>
<!-- $Id$ -->
<overlay>
<template id="etemplate.nextmatch_widget.nm_row" template="" lang="" group="0" version="1.3.002">
<template id="etemplate.nextmatch_widget.nm_row" template="" lang="" group="0" version="1.5.001">
<grid width="100%" spacing="0" padding="3">
<columns>
<column width="1%"/>
@ -14,10 +14,11 @@
<column/>
<column width="1%"/>
<column width="1%"/>
<column/>
<column disabled="@no_columnselection"/>
<column disabled="@no_csv_export"/>
</columns>
<rows>
<row class="th" disabled="@no_columnselection">
<row class="th">
<button image="first.gif" ro_image="first-grey.gif" label="First" id="first" statustext="go to the first entry"/>
<button image="left.gif" ro_image="left-grey.gif" label="Left" id="left" statustext="go to the previous page of entries"/>
<menulist>
@ -48,6 +49,7 @@
</hbox>
</groupbox>
</box>
<button id="export" image="filesave" label="CSV Export"/>
</row>
</rows>
</grid>

View File

@ -320,8 +320,14 @@ class uiinfolog
//echo "<p>uiinfolog.get_rows(start=$query[start],search='$query[search]',filter='$query[filter]',cat_id=$query[cat_id],action='$query[action]/$query[action_id]',col_filter=".print_r($query['col_filter'],True).")</p>\n";
if (!isset($query['start'])) $query['start'] = 0;
if (!$query['csv_export'])
{
$this->save_sessiondata($query);
}
else
{
$query['csv_fields'] = $this->csv_export_fields($query['col_filter']['info_type']);
}
// check if we have a custom, type-specific template
unset($query['template']);
unset($query['custom_fields']);
@ -357,13 +363,17 @@ class uiinfolog
$readonlys = $rows = array();
foreach($ids as $id => $info)
{
if (!$query['csv_export'])
{
$info = $this->get_info($info,$readonlys,$query['action'],$query['action_id'],$query['filter2'],$details);
if (!$query['filter2'] && $this->prefs['show_links'] == 'no_describtion' ||
$query['filter2'] == 'no_describtion')
{
unset($info['info_des']);
}
}
$rows[] = $info;
}
if ($query['cat_id']) $rows['no_cat_id'] = true;
@ -579,6 +589,7 @@ class uiinfolog
$values['action_title'] = $persist['action_title'] = $values['nm']['action_title'] = $action_title;
$persist['called_as'] = $called_as;
$persist['own_referer'] = $own_referer;
$values['nm']['csv_fields'] = true; // get set in get_rows to not include all custom fields
$all_stati = array();
foreach($this->bo->status as $typ => $stati)
@ -1466,4 +1477,52 @@ class uiinfolog
),True);
unset($GLOBALS['egw_info']['etemplate']['hooked']);
}
/**
* Defines the fields for the csv export
*
* @param string $type=null infolog type to include only the matching custom fields if set
* @return array
*/
function csv_export_fields($type=null)
{
$fields = array(
'info_type' => lang('Type'),
'info_from' => lang('Contact'),
'info_addr' => lang('Phone/Email'),
// 'info_link_id' => lang('primary link'),
'info_cat' => array('label' => lang('Category'),'type' => 'select-cat'),
'info_priority' => lang('Priority'),
'info_owner' => array('label' => lang('Owner'),'type' => 'select-account'),
'info_access' => lang('Access'),
'info_status' => lang('Status'),
'info_percent' => lang('Completed'),
'info_datecompleted' => lang('Date completed'),
'info_location' => lang('Location'),
'info_startdate' => lang('Startdate'),
'info_enddate' => lang('Enddate'),
'info_responsible' => array('label' => lang('Responsible'),'type' => 'select-account'),
'info_subject' => lang('Subject'),
'info_des' => lang('Description'),
// PM fields
'info_planned_time' => lang('planned time'),
'info_used_time' => lang('used time'),
'pl_id' => lang('pricelist'),
'info_price' => lang('price'),
);
foreach($this->bo->timestamps as $name)
{
$fields[$name] = array('label' => $fields[$name],'type' => 'date-time');
}
foreach($this->bo->customfields as $name => $data)
{
if ($data['type2'] && $type && $data['type2'] != $type) continue;
$fields['#'.$name] = array(
'label' => $data['label'],
'type' => $data['type'],
);
}
return $fields;
}
}

View File

@ -239,5 +239,14 @@
'drag and drop, it will be disabled automatically. This feature is experimental at the moment.',
'xmlrpc' => False,
'admin' => False
)
),
'csv_charset' => array(
'type' => 'select',
'label' => 'Charset for the CSV export',
'name' => 'csv_charset',
'values' => $GLOBALS['egw']->translation->get_installed_charsets()+array('utf-8' => 'utf-8 (Unicode)'),
'help' => 'Which charset should be used for the CSV export. The system default is the charset of this eGroupWare installation.',
'xmlrpc' => True,
'admin' => false,
),
);

View File

@ -83,3 +83,5 @@ you do not have permission to set acl's in this mode! preferences cs Nem
you must enter a password preferences cs Musíte zadat heslo
your current theme is: %1 preferences cs Va¹e aktuální téma je: %1
your preferences preferences cs Va¹e pøedvolby
charset for the csv export addressbook cs Znaková sada pro export do CSV
which charset should be used for the csv export. the system default is the charset of this egroupware installation. addressbook cs Ketrá znaková sada má být pou¾ita pro CSV export? Výchozí znakovou sadou je znaková sada pou¾itá pro instalaci eGroupWare.

View File

@ -79,3 +79,5 @@ you do not have permission to set acl's in this mode! preferences de Sie haben k
you must enter a password preferences de Sie müssen ein Passwort angeben
your current theme is: %1 preferences de Ihr aktuelles Farbschema ist: %1
your preferences preferences de Persönliche Einstellungen
charset for the csv export addressbook de Zeichensatz für den CSV Export
which charset should be used for the csv export. the system default is the charset of this egroupware installation. addressbook de Welcher Zeichensatz soll für den CSV Export verwendet werden. Die systemweite Vorgabe ist der Zeichensatz der eGroupWare Installation.

View File

@ -83,3 +83,5 @@ you do not have permission to set acl's in this mode! preferences en You do not
you must enter a password preferences en You must enter a password
your current theme is: %1 preferences en your current theme is: %1
your preferences preferences en Your Preferences
charset for the csv export addressbook en Charset for the CSV export
which charset should be used for the csv export. the system default is the charset of this egroupware installation. addressbook en Which charset should be used for the CSV export. The system default is the charset of this eGroupWare installation.

View File

@ -83,3 +83,5 @@ you do not have permission to set acl's in this mode! preferences es-es Usted no
you must enter a password preferences es-es Debe introducir una contraseña
your current theme is: %1 preferences es-es Su actual tema es: %1
your preferences preferences es-es Sus preferencias
charset for the csv export addressbook es-es Juego de caracteres para exportar a CSV
which charset should be used for the csv export. the system default is the charset of this egroupware installation. addressbook es-es Juego de caracteres a usar para exportar a CSV. El predeterminado del sistema es el juego de caracteres de esta instalación de eGroupWare.

View File

@ -78,3 +78,4 @@ you do not have permission to set acl's in this mode! preferences eu Ez daukazu
you must enter a password preferences eu Pasahitz bat sartu behar duzu
your current theme is: %1 preferences eu Zure momentuko gaia da: %1
your preferences preferences eu Zure hobespenak
charset for the csv export addressbook eu Fitxategiaren karaktere jokoa

View File

@ -82,3 +82,5 @@ you do not have permission to set acl's in this mode! preferences fi Et voi m
you must enter a password preferences fi Anna salasana
your current theme is: %1 preferences fi nykyinen teema on: %1
your preferences preferences fi Henkilökohtaiset asetukset
charset for the csv export addressbook fi CSV viennissä käytettävä merkistö
which charset should be used for the csv export. the system default is the charset of this egroupware installation. addressbook fi Mitä merkistökoodausta käytetään CSV viennissä. Järjestelmän oletus on merkistökoodaus joka määriteltiin asennuksen yhteydessä.

View File

@ -83,3 +83,5 @@ you do not have permission to set acl's in this mode! preferences fr Vous n'avez
you must enter a password preferences fr Vous devez entrer un mot de passe
your current theme is: %1 preferences fr Votre thème actuel est: %1
your preferences preferences fr Vos préférences
charset for the csv export addressbook fr Jeu de caractères pour l'exportation CSV
which charset should be used for the csv export. the system default is the charset of this egroupware installation. addressbook fr Quel jeu de caractères faut-il utiliser pour l'exportation CSV. Le paramétrage par défaut est celui utilisé lors de l'installation d'eGroupWare.

View File

@ -80,3 +80,4 @@ you do not have permission to set acl's in this mode! preferences it Non hai il
you must enter a password preferences it Devi inserire una password
your current theme is: %1 preferences it il tuo tema attuale è: %1
your preferences preferences it Preferenze personali
charset for the csv export addressbook it Set di Caratteri per esportazione CSV

View File

@ -83,3 +83,5 @@ you do not have permission to set acl's in this mode! preferences nl U heeft gee
you must enter a password preferences nl U moet een wachtwoord invoeren
your current theme is: %1 preferences nl uw huidige thema is %1
your preferences preferences nl Uw voorkeuren
charset for the csv export addressbook nl Karakterset voor de CSV export
which charset should be used for the csv export. the system default is the charset of this egroupware installation. addressbook nl Welke karakterset moet gebruikt worden voor de CSV export. De systeem standaard is de karakterset van deze eGroupWare installatie.

View File

@ -79,3 +79,5 @@ you do not have permission to set acl's in this mode! preferences pl Nie posiada
you must enter a password preferences pl Musisz wprowadziæ has³o
your current theme is: %1 preferences pl Twój bie¿±cy temat to: %1
your preferences preferences pl Twoje ustawienia
charset for the csv export addressbook pl strona kodowa dla eksportu CSV
which charset should be used for the csv export. the system default is the charset of this egroupware installation. addressbook pl Jakiego zestawu znaków u¿yæ przy eksporcie do CSV? Zestaw 'systemowy domy¶lny' jest zestawem wybrany w tej instalacji eGroupWare.

View File

@ -83,3 +83,5 @@ you do not have permission to set acl's in this mode! preferences pt-br Voc
you must enter a password preferences pt-br Você deve digitar uma senha
your current theme is: %1 preferences pt-br Seu tema atual é: %1
your preferences preferences pt-br Suas preferências
charset for the csv export addressbook pt-br Códificação de caracteres para a exportação CSV
which charset should be used for the csv export. the system default is the charset of this egroupware installation. addressbook pt-br Que codificação de caracter deverá ser usado para exportação CSV. O padrão é a codificação desta instalação do eGroupWare.

View File

@ -83,3 +83,5 @@ you do not have permission to set acl's in this mode! preferences sk Nem
you must enter a password preferences sk Musíte zada» heslo
your current theme is: %1 preferences sk Va¹a súèasná farebná téma je: <b>%1</b>
your preferences preferences sk Va¹e predvoµby
charset for the csv export addressbook sk Znaková sada pre export CSV
which charset should be used for the csv export. the system default is the charset of this egroupware installation. addressbook sk Ktorú znakovú sadu pou¾íva» pri exporte do CSV. Predvolená je tá, ktorá je pou¾ívaná ako znaková sada v tejto in¹talácii eGroupWare.

View File

@ -83,3 +83,5 @@ you do not have permission to set acl's in this mode! preferences sl Nimate dovo
you must enter a password preferences sl Vpisati morate geslo
your current theme is: %1 preferences sl Vaša trenutna tema je: %1
your preferences preferences sl Vaše nastavitve
charset for the csv export addressbook sl Nabor znakov za izvoz CSV
which charset should be used for the csv export. the system default is the charset of this egroupware installation. addressbook sl Kateri nabor znakov naj se uporabi pri izvozu CSV. Privzeto se uporabi nabor znakov, ki je nastavljen v eGroupWare.

View File

@ -83,3 +83,5 @@ you do not have permission to set acl's in this mode! preferences sv Du saknar r
you must enter a password preferences sv Ange ett lösenord
your current theme is: %1 preferences sv Ditt nuvarande tema är: %1
your preferences preferences sv Dina inställningar
charset for the csv export addressbook sv Teckenuppsättning för CSV export
which charset should be used for the csv export. the system default is the charset of this egroupware installation. addressbook sv Vilken teckeuppsättning ska användas vid CSV export? Systemets standard är det som används av eGroupWare.

View File

@ -83,3 +83,5 @@ you do not have permission to set acl's in this mode! preferences zh-tw 您沒
you must enter a password preferences zh-tw 您必須輸入密碼
your current theme is: %1 preferences zh-tw 您目前的佈景是: %1
your preferences preferences zh-tw 您的偏好設定
charset for the csv export addressbook zh-tw CSV 檔案匯出編碼
which charset should be used for the csv export. the system default is the charset of this egroupware installation. addressbook zh-tw CSV 格式檔案希望使用的字元編碼,預設會與安裝一致。