2005-10-28 20:29:05 +02:00
< ? php
2008-06-22 10:51:38 +02:00
/**
* eGroupWare eTemplate Widget for custom fields
*
* @ link http :// www . egroupware . org
* @ author Ralf Becker < RalfBecker - AT - outdoor - training . de >
* @ author Cornelius Weiss < egw @ von - und - zu - weiss . de >
* @ license http :// opensource . org / licenses / gpl - license . php GPL - GNU General Public License
* @ package etemplate
* @ subpackage extensions
* @ version $Id $
*/
/**
* This widget generates a template for customfields based on definitions in egw_config table
*
* All widgets here have 2 comma - separated options ( $cell [ size ]) :
* - sub - type to display only the cf ' s without subtype or with a matching one
* - use - private to display only ( non - ) private cf ' s ( 0 = regular ones , 1 = private ones , default both )
*
* Private cf ' s the user has no right to see ( neither him nor his memberships are mentioned ) are never displayed .
*/
class customfields_widget
{
var $public_functions = array (
'pre_process' => True ,
);
var $human_name = array (
'customfields' => 'custom fields' ,
'customfields-types' => 'custom field types' ,
'customfields-list' => 'custom field list' ,
'customfields-no-label' => 'custom fields without label' ,
);
2006-04-20 19:12:30 +02:00
/**
2008-06-22 10:51:38 +02:00
* Allowd types of customfields
2006-04-20 19:12:30 +02:00
*
2008-06-22 10:51:38 +02:00
* The additionally allowed app - names from the link - class , will be add by the edit - method only ,
* as the link - class has to be called , which can NOT be instanciated by the constructor , as
* we get a loop in the instanciation .
*
* @ var array
2006-04-20 19:12:30 +02:00
*/
2008-06-22 10:51:38 +02:00
var $cf_types = array (
'text' => 'Text' ,
'label' => 'Label' ,
'select' => 'Selectbox' ,
Add AJAX Select widget as a field type for custom fields.
Select options can be provided using one of three different methods:
1. key=value pairs, one per line, as for a selectbox
2. @filename.php - The file must be in the egw root, and define an array named $options, that has key => value pairs
3. Define the options normally used for the AJAX Select widget (get_rows, get_title, id_field) and it will pull values from the database, as normal.
Other options (icon, filter, template, link) can be used regardless of the method used to get the values.
2010-01-12 23:35:51 +01:00
'ajax_select' => 'Search' ,
2008-06-22 10:51:38 +02:00
'radio' => 'Radiobutton' ,
'checkbox' => 'Checkbox' ,
'date' => 'Date' ,
'date-time' => 'Date+Time' ,
'select-account' => 'Select account' ,
'button' => 'Button' , // button to execute javascript
'url' => 'Url' ,
'url-email' => 'EMail' ,
'url-phone' => 'Phone number' ,
'link-entry' => 'Select entry' , // should be last type, as the individual apps get added behind
);
2005-10-28 20:29:05 +02:00
/**
2008-06-22 10:51:38 +02:00
* @ var $prefix string Prefix for every custiomfield name returned in $content ( # for general (admin) customfields)
*/
var $prefix = '#' ;
/**
* Current application
2005-10-28 20:29:05 +02:00
*
2008-06-22 10:51:38 +02:00
* @ var string
2005-10-28 20:29:05 +02:00
*/
2008-06-22 10:51:38 +02:00
var $appname ;
/**
* Instance of the config class for $appname
*
* @ var config
*/
var $config ;
/**
* Our customfields as name => data array
*
* @ var array
*/
var $customfields ;
var $types ;
var $advanced_search ;
2007-07-11 17:39:41 +02:00
2005-10-28 20:29:05 +02:00
2009-08-20 16:11:10 +02:00
function __construct ( $ui , $appname = null )
2008-06-22 10:51:38 +02:00
{
$this -> appname = $appname ? $appname : $GLOBALS [ 'egw_info' ][ 'flags' ][ 'currentapp' ];
$this -> customfields = config :: get_customfields ( $this -> appname );
$this -> types = config :: get_content_types ( $this -> appname );
$this -> advanced_search = $GLOBALS [ 'egw_info' ][ 'etemplate' ][ 'advanced_search' ];
}
2007-11-27 16:03:54 +01:00
2008-06-22 10:51:38 +02:00
function pre_process ( $name , & $value , & $cell , & $readonlys , & $extension_data , & $tmpl )
{
2009-08-20 16:11:10 +02:00
list ( $app ) = explode ( '.' , $tmpl -> name );
if ( $this -> appname == 'etemplate' || ! $this -> customfields || // if we are in the etemplate editor or the app has no cf's, load the cf's from the app the tpl belongs too
( $app && $app != $this -> appname ) ) // app changed
2005-10-28 20:29:05 +02:00
{
2009-08-20 16:11:10 +02:00
self :: __construct ( null , $app );
2005-10-28 20:29:05 +02:00
}
2008-06-22 10:51:38 +02:00
list ( $type2 , $use_private ) = explode ( ',' , $cell [ 'size' ]);
$fields_with_vals = array ();
$fields = $this -> customfields ;
2005-10-28 20:29:05 +02:00
2008-06-22 10:51:38 +02:00
// remove private or non-private cf's, if only one kind should be displayed
foreach (( array ) $fields as $key => $field )
2005-10-28 20:29:05 +02:00
{
2008-06-22 10:51:38 +02:00
if (( string ) $use_private !== '' && ( boolean ) $field [ 'private' ] != ( boolean ) $use_private )
2007-11-22 21:08:58 +01:00
{
2008-06-22 10:51:38 +02:00
unset ( $fields [ $key ]);
2007-11-22 21:08:58 +01:00
}
2008-06-22 10:51:38 +02:00
}
// check if name refers to a single custom field --> show only that
if (( $pos = strpos ( $cell [ 'name' ], $this -> prefix )) !== false && // allow the prefixed name to be an array index too
preg_match ( " / $this->prefix ([^ \ ]]+)/ " , $cell [ 'name' ], $matches ) && isset ( $fields [ $name = $matches [ 1 ]]))
{
$fields = array ( $name => $fields [ $name ]);
$value = array ( $this -> prefix . $name => $value );
$singlefield = true ;
}
switch ( $type = $cell [ 'type' ])
{
case 'customfields-types' :
$cell [ 'type' ] = 'select' ;
foreach ( $this -> cf_types as $lname => $label )
{
$cell [ 'sel_options' ][ $lname ] = lang ( $label );
$fields_with_vals [] = $lname ;
}
2008-10-21 10:08:56 +02:00
$link_types = egw_link :: app_list ();
2008-06-22 10:51:38 +02:00
ksort ( $link_types );
foreach ( $link_types as $lname => $label ) $cell [ 'sel_options' ][ $lname ] = '- ' . $label ;
$cell [ 'no_lang' ] = true ;
return true ;
2007-07-12 09:30:20 +02:00
2008-06-22 10:51:38 +02:00
case 'customfields-list' :
foreach ( array_reverse ( $fields ) as $lname => $field )
{
if ( ! empty ( $type2 ) && ! empty ( $field [ 'type2' ]) && strpos ( ',' . $field [ 'type2' ] . ',' , ',' . $type2 . ',' ) === false ) continue ; // not for our content type//
if ( isset ( $value [ $this -> prefix . $lname ]) && $value [ $this -> prefix . $lname ] !== '' ) //break;
{
$fields_with_vals [] = $lname ;
}
//$stop_at_field = $name;
}
break ;
default :
foreach ( array_reverse ( $fields ) as $lname => $field )
2008-01-19 06:36:20 +01:00
{
2008-06-22 10:51:38 +02:00
$fields_with_vals [] = $lname ;
2008-01-19 06:36:20 +01:00
}
2008-06-22 10:51:38 +02:00
}
$readonly = $cell [ 'readonly' ] || $readonlys [ $name ] || $type == 'customfields-list' ;
if ( ! is_array ( $fields ))
{
$cell [ 'type' ] = 'label' ;
return True ;
}
// making the cell an empty grid
$cell [ 'type' ] = 'grid' ;
$cell [ 'data' ] = array ( array ());
$cell [ 'rows' ] = $cell [ 'cols' ] = 0 ;
$n = 1 ;
foreach ( $fields as $lname => $field )
{
2009-07-18 17:35:42 +02:00
if ( array_search ( $lname , $fields_with_vals ) !== false )
2007-11-22 21:08:58 +01:00
{
2009-07-18 17:35:42 +02:00
if ( $stop_at_field && $lname == $stop_at_field ) break ; // no further row necessary
// check if the customfield get's displayed for type $value, we can have multiple comma-separated types now
if ( ! empty ( $type2 ) && ! empty ( $field [ 'type2' ]) && strpos ( ',' . $field [ 'type2' ] . ',' , ',' . $type2 . ',' ) === false )
{
continue ; // not for our content type
}
$new_row = null ; etemplate :: add_child ( $cell , $new_row );
if ( $type != 'customfields-list' && $type == 'customfields' )
{
$row_class = 'row' ;
etemplate :: add_child ( $cell , $label =& etemplate :: empty_cell ( 'label' , '' , array (
'label' => $field [ 'label' ],
'no_lang' => substr ( lang ( $field [ 'label' ]), - 1 ) == '*' ? 2 : 0 ,
'span' => $field [ 'type' ] === 'label' ? '2' : '' ,
)));
}
elseif ( $type == 'customfields-list' )
{
if ( isset ( $value [ $this -> prefix . $lname ]) && $value [ $this -> prefix . $lname ] !== '' )
2009-04-29 16:29:57 +02:00
{
2009-07-18 17:35:42 +02:00
switch (( string ) $field [ 'type' ])
{
case 'checkbox' :
if ( $value [ $this -> prefix . $lname ] == 0 ) break ;
default :
etemplate :: add_child ( $cell , $input =& etemplate :: empty_cell ( 'image' , 'info.png' ,
array ( 'label' => $field [ 'label' ], 'width' => " 16px " )));
}
2009-04-29 16:29:57 +02:00
}
2008-06-22 10:51:38 +02:00
}
2009-07-18 17:35:42 +02:00
switch (( string ) $field [ 'type' ])
{
case 'select' :
if ( count ( $field [ 'values' ]) == 1 && isset ( $field [ 'values' ][ '@' ]))
2007-11-27 16:03:54 +01:00
{
2009-07-18 17:35:42 +02:00
$field [ 'values' ] = $this -> _get_options_from_file ( $field [ 'values' ][ '@' ]);
2007-11-27 16:03:54 +01:00
}
2009-07-18 17:35:42 +02:00
if ( $this -> advanced_search && $field [ 'rows' ] <= 1 ) $field [ 'values' ][ '' ] = lang ( 'doesn\'t matter' );
foreach ( $field [ 'values' ] as $key => $val )
{
if ( substr ( $val = lang ( $val ), - 1 ) != '*' )
{
$field [ 'values' ][ $key ] = $val ;
}
}
$input =& etemplate :: empty_cell ( 'select' , $this -> prefix . $lname , array (
2008-06-22 10:51:38 +02:00
'sel_options' => $field [ 'values' ],
'size' => $field [ 'rows' ],
2009-07-18 17:35:42 +02:00
'no_lang' => True ,
));
if ( $this -> advanced_search )
{
$select =& $input ; unset ( $input );
$input =& etemplate :: empty_cell ( 'hbox' );
etemplate :: add_child ( $input , $select ); unset ( $select );
/* the following seem to double the select fields in advanced search .
etemplate :: add_child ( $input , etemplate :: empty_cell ( 'select' , $this -> prefix . $lname , array (
'sel_options' => $field [ 'values' ],
'size' => $field [ 'rows' ],
'no_lang' => True
)));
*/
}
break ;
Add AJAX Select widget as a field type for custom fields.
Select options can be provided using one of three different methods:
1. key=value pairs, one per line, as for a selectbox
2. @filename.php - The file must be in the egw root, and define an array named $options, that has key => value pairs
3. Define the options normally used for the AJAX Select widget (get_rows, get_title, id_field) and it will pull values from the database, as normal.
Other options (icon, filter, template, link) can be used regardless of the method used to get the values.
2010-01-12 23:35:51 +01:00
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 =& etemplate :: empty_cell ( 'ajax_select' , $this -> prefix . $lname , array (
'readonly' => $readonly ,
'no_lang' => True ,
'size' => $options
));
break ;
2009-07-18 17:35:42 +02:00
case 'label' :
$row_class = 'th' ;
break ;
case 'radio' :
$showthis = '#a#l#l#' ;
if ( count ( $field [ 'values' ]) == 1 && isset ( $field [ 'values' ][ '@' ]))
{
$field [ 'values' ] = $this -> _get_options_from_file ( $field [ 'values' ][ '@' ]);
}
if ( $this -> advanced_search && $field [ 'rows' ] <= 1 ) $field [ 'values' ][ '' ] = lang ( 'doesn\'t matter' );
if ( $readonly )
{
$showthis = $value [ $this -> prefix . $lname ];
$input =& etemplate :: empty_cell ( 'hbox' );
}
else
{
$input =& etemplate :: empty_cell ( 'groupbox' );
}
$m = 0 ;
foreach ( $field [ 'values' ] as $key => $val )
{
$radio = etemplate :: empty_cell ( 'radio' , $this -> prefix . $lname );
$radio [ 'label' ] = $val ;
$radio [ 'size' ] = $key ;
if ( $showthis == '#a#l#l#' || $showthis == $key ) etemplate :: add_child ( $input , $radio );
unset ( $radio );
}
break ;
case 'text' :
case 'textarea' :
case '' : // not set
$field [ 'len' ] = $field [ 'len' ] ? $field [ 'len' ] : 20 ;
if ( $type != 'customfields-list' )
{
if ( $field [ 'rows' ] <= 1 )
{ //text
list ( $max , $shown ) = explode ( ',' , $field [ 'len' ]);
$tmparray = array (
'size' => intval ( $shown > 0 ? $shown : $max ) . ',' . intval ( $max ),
'maxlength' => intval ( $max ),
);
if ( is_array ( $field [ 'values' ]))
{
if ( array_key_exists ( 'readonly' , $field [ 'values' ]))
{
if ( ! $this -> advanced_search ) $tmparray [ 'readonly' ] = 'readonly' ;
}
}
$input =& etemplate :: empty_cell ( 'text' , $this -> prefix . $lname , $tmparray );
}
else
{ //textarea
$tmparray = array (
'size' => $field [ 'rows' ] . ( $field [ 'len' ] > 0 ? ',' . ( int ) $field [ 'len' ] : '' )
);
if ( is_array ( $field [ 'values' ]) && array_key_exists ( 'readonly' , $field [ 'values' ]))
2007-11-27 16:03:54 +01:00
{
2009-04-01 10:22:01 +02:00
if ( ! $this -> advanced_search ) $tmparray [ 'readonly' ] = 'readonly' ;
2007-11-27 16:03:54 +01:00
}
2009-07-18 17:35:42 +02:00
$input =& etemplate :: empty_cell ( 'textarea' , $this -> prefix . $lname , $tmparray );
2007-11-27 16:03:54 +01:00
}
2005-10-28 20:29:05 +02:00
}
2008-06-22 10:51:38 +02:00
else
{
2009-07-18 17:35:42 +02:00
$input =& etemplate :: empty_cell ( 'label' , $this -> prefix . $lname );
2008-06-22 10:51:38 +02:00
}
2009-07-18 17:35:42 +02:00
break ;
case 'date' :
case 'date-time' :
$input =& etemplate :: empty_cell ( $field [ 'type' ], $this -> prefix . $lname , array (
'size' => $field [ 'len' ] ? $field [ 'len' ] : ( $field [ 'type' ] == 'date' ? 'Y-m-d' : 'Y-m-d H:i:s' ),
));
break ;
case 'select-account' :
list ( $opts ) = explode ( '=' , $field [ 'values' ][ 0 ]);
$input =& etemplate :: empty_cell ( 'select-account' , $this -> prefix . $lname , array (
'size' => ( $field [ 'rows' ] > 1 ? $field [ 'rows' ] : lang ( 'None' )) . ',' . $opts ,
));
break ;
case 'button' : // button(s) to execute javascript (label=onclick) or textinputs (empty label, readonly with neg. length)
// a button does not seem to be helpful in advanced search ???,
if ( $this -> advanced_search ) break ;
$input =& etemplate :: empty_cell ( 'hbox' );
foreach ( $field [ 'values' ] as $label => $js )
2008-06-22 10:51:38 +02:00
{
2009-07-18 17:35:42 +02:00
if ( ! $label ) // display an readonly input
{
$tmparray = array (
'size' => $field [ 'len' ] ? $field [ 'len' ] : 20 ,
'readonly' => $field [ 'len' ] < 0 ,
'onchange' => $js ,
);
$widget =& etemplate :: empty_cell ( 'text' , $this -> prefix . $lname . $label , $tmparray );
}
else
{
if ( $readonly ) continue ; // dont display buttons if we're readonly
$widget =& etemplate :: empty_cell ( 'buttononly' , $this -> prefix . $lname . $label , array (
'label' => $label ? $label : lang ( 'Submit' ),
'onclick' => $js ,
'no_lang' => True
));
}
etemplate :: add_child ( $input , $widget );
unset ( $widget );
2008-06-22 10:51:38 +02:00
}
2009-07-18 17:35:42 +02:00
break ;
case 'url-email' :
list ( $max , $shown , $validation_type , $default ) = explode ( ',' , $field [ 'len' ], 4 );
if ( empty ( $max )) $max = 128 ;
if ( empty ( $shown )) $shown = 28 ;
if ( empty ( $validation_type )) $validation_type = 1 ;
$field [ 'len' ] = implode ( ',' , array ( $shown , $max , $validation_type , $default ));
$input =& etemplate :: empty_cell ( $field [ 'type' ], $this -> prefix . $lname , array (
'size' => $field [ 'len' ]
));
break ;
case 'url' :
case 'url-phone' :
list ( $max , $shown , $validation_type ) = explode ( ',' , $field [ 'len' ], 3 );
if ( empty ( $max )) $max = 128 ;
if ( empty ( $shown )) $shown = 28 ;
$field [ 'len' ] = implode ( ',' , array ( $shown , $max , $validation_type ));
$input =& etemplate :: empty_cell ( $field [ 'type' ], $this -> prefix . $lname , array (
'size' => $field [ 'len' ]
));
break ;
// other etemplate types, which are used just as is
case 'checkbox' :
$input =& etemplate :: empty_cell ( $field [ 'type' ], $this -> prefix . $lname );
break ;
case 'link-entry' :
default : // link-entry to given app
$input =& etemplate :: empty_cell ( 'link-entry' , $this -> prefix . $lname , array (
'size' => $field [ 'type' ] == 'link-entry' ? '' : $field [ 'type' ],
));
}
$cell [ 'data' ][ 0 ][ 'c' . $n ++ ] = $row_class . ',top' ;
2008-06-22 10:51:38 +02:00
2009-07-18 17:35:42 +02:00
if ( ! is_null ( $input ))
{
if ( $readonly ) $input [ 'readonly' ] = true ;
2005-10-28 20:29:05 +02:00
2009-07-18 17:35:42 +02:00
if ( $cell [ 'needed' ]) $input [ 'needed' ] = $cell [ 'needed' ];
2007-11-27 16:03:54 +01:00
2009-07-18 17:35:42 +02:00
if ( ! empty ( $field [ 'help' ]) && $row_class != 'th' )
{
$input [ 'help' ] = $field [ 'help' ];
$input [ 'no_lang' ] = substr ( lang ( $help ), - 1 ) == '*' ? 2 : 0 ;
}
if ( $singlefield ) // a single field, can & need to be returned instead of the cell (no grid)
{
$cell = $input ;
if ( $type == 'customfields' ) $cell [ 'label' ] = $field [ 'label' ];
return true ;
}
etemplate :: add_child ( $cell , $input );
unset ( $input );
2008-06-22 10:51:38 +02:00
}
2009-07-18 17:35:42 +02:00
unset ( $label );
2007-09-13 14:43:58 +02:00
}
2008-06-22 10:51:38 +02:00
}
if ( $type != 'customfields-list' )
{
$cell [ 'data' ][ 0 ][ 'A' ] = '100' ;
}
list ( $span , $class ) = explode ( ',' , $cell [ 'span' ]); // msie (at least 5.5) shows nothing with div overflow=auto
// we dont want to use up the full space for the table created, so we skip the line below
//$cell['size'] = '100%,100%,0,'.$class.','.(in_array($type,array('customfields-list','customfields-no-label'))?'0,0':',').(html::$user_agent != 'msie' ? ',auto' : '');
return True ; // extra Label is ok
}
2007-11-27 16:03:54 +01:00
2008-06-22 10:51:38 +02:00
/**
* Read the options of a 'select' or 'radio' custom field from a file
*
* For security reasons that file has to be relative to the eGW root
* ( to not use that feature to explore arbitrary files on the server )
* and it has to be a php file setting one variable called options ,
* ( to not display it to anonymously by the webserver ) .
* The $options var has to be an array with value => label pairs , eg :
*
* < ? php
* $options = array (
* 'a' => 'Option A' ,
* 'b' => 'Option B' ,
* 'c' => 'Option C' ,
* );
*
* @ param string $file file name inside the eGW server root , either relative to it or absolute
* @ return array in case of an error we return a single option with the message
*/
function _get_options_from_file ( $file )
{
if ( ! ( $path = realpath ( $file { 0 } == '/' ? $file : EGW_SERVER_ROOT . '/' . $file )) || // file does not exist
substr ( $path , 0 , strlen ( EGW_SERVER_ROOT ) + 1 ) != EGW_SERVER_ROOT . '/' || // we are NOT inside the eGW root
basename ( $path , '.php' ) . '.php' != basename ( $path ) || // extension is NOT .php
basename ( $path ) == 'header.inc.php' ) // dont allow to include our header again
{
return array ( lang ( " '%1' is no php file in the eGW server root (%2)! " . ': ' . $path , $file , EGW_SERVER_ROOT ));
2007-09-13 14:43:58 +02:00
}
2008-06-22 10:51:38 +02:00
include ( $path );
return $options ;
2005-10-28 20:29:05 +02:00
}
2008-09-11 16:57:20 +02:00
/**
* Get the customfield types containing links
*
* @ return array with customefield types as values
*/
public static function get_customfield_link_types ()
{
static $link_types ;
if ( is_null ( $link_types ))
{
$link_types = array_keys ( egw_link :: app_list ());
$link_types [] = 'link-entry' ;
}
return $link_types ;
}
/**
* Check if there are links in the custom fields and update them
*
* This function have to be called manually by an application , if cf ' s linking
* to other entries should be stored as links too ( beside as cf ' s ) .
*
* @ param string $own_app own appname
* @ param array $values new values including the custom fields
* @ param array $old = null old values before the update , if existing
* @ param string $id_name = 'id' name / key of the ( link - ) id in $values
*/
public static function update_customfield_links ( $own_app , array $values , array $old = null , $id_name = 'id' )
{
$link_types = self :: get_customfield_link_types ();
foreach ( config :: get_customfields ( $own_app ) as $name => $data )
{
if ( ! in_array ( $data [ 'type' ], $link_types )) continue ;
// do we have a different old value --> delete that link
if ( $old && $old [ '#' . $name ] && $old [ '#' . $name ] != $values [ '#' . $name ])
{
if ( $data [ 'type' ] == 'link-entry' )
{
list ( $app , $id ) = explode ( ':' , $old [ '#' . $name ]);
}
else
{
$app = $data [ 'type' ];
$id = $old [ '#' . $name ];
}
egw_link :: unlink ( false , $own_app , $values [ $id_name ], '' , $app , $id );
}
if ( $data [ 'type' ] == 'link-entry' )
{
list ( $app , $id ) = explode ( ':' , $values [ '#' . $name ]);
}
else
{
$app = $data [ 'type' ];
$id = $values [ '#' . $name ];
}
if ( $id ) // create new link, does nothing for already existing links
{
egw_link :: link ( $own_app , $values [ $id_name ], $app , $id );
}
}
}
2008-06-22 10:51:38 +02:00
}