2010-10-04 14:12:11 +02:00
< ? php
/**
2012-08-09 15:07:07 +02:00
* EGroupware eTemplate Widget for custom fields
2010-10-04 14:12:11 +02:00
*
* @ 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' ,
);
/**
* Allowd types of customfields
*
* 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
*/
var $cf_types = array (
'text' => 'Text' ,
'label' => 'Label' ,
'select' => 'Selectbox' ,
'ajax_select' => 'Search' ,
'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
);
/**
* @ var $prefix string Prefix for every custiomfield name returned in $content ( # for general (admin) customfields)
*/
var $prefix = '#' ;
/**
* Current application
*
* @ var string
*/
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 ;
function __construct ( $ui , $appname = null )
{
$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' ];
}
function pre_process ( $name , & $value , & $cell , & $readonlys , & $extension_data , & $tmpl )
{
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
{
self :: __construct ( null , $app );
}
list ( $type2 , $use_private ) = explode ( ',' , $cell [ 'size' ]);
$fields_with_vals = array ();
$fields = $this -> customfields ;
// remove private or non-private cf's, if only one kind should be displayed
foreach (( array ) $fields as $key => $field )
{
if (( string ) $use_private !== '' && ( boolean ) $field [ 'private' ] != ( boolean ) $use_private )
{
unset ( $fields [ $key ]);
}
}
// 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 ;
}
$link_types = egw_link :: app_list ();
ksort ( $link_types );
foreach ( $link_types as $lname => $label ) $cell [ 'sel_options' ][ $lname ] = '- ' . $label ;
$cell [ 'no_lang' ] = true ;
return true ;
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 )
{
$fields_with_vals [] = $lname ;
}
}
$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 ;
2011-08-18 11:26:59 +02:00
$cell [ 'size' ] = '' ;
2010-10-04 14:12:11 +02:00
$n = 1 ;
foreach ( $fields as $lname => $field )
{
if ( array_search ( $lname , $fields_with_vals ) !== false )
{
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 ] !== '' )
{
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 " )));
}
}
}
switch (( string ) $field [ 'type' ])
{
case 'select' :
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' );
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 (
'sel_options' => $field [ 'values' ],
'size' => $field [ 'rows' ],
'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 ;
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 ;
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 ),
'no_lang' => True ,
);
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' ] : '' ),
'no_lang' => True ,
);
if ( is_array ( $field [ 'values' ]) && array_key_exists ( 'readonly' , $field [ 'values' ]))
{
if ( ! $this -> advanced_search ) $tmparray [ 'readonly' ] = 'readonly' ;
}
$input =& etemplate :: empty_cell ( 'textarea' , $this -> prefix . $lname , $tmparray );
}
}
else
{
$input =& etemplate :: empty_cell ( 'label' , $this -> prefix . $lname , array ( 'no_lang' => True ));
}
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 )
{
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 );
}
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' ;
if ( ! is_null ( $input ))
{
if ( $readonly ) $input [ 'readonly' ] = true ;
if ( $cell [ 'needed' ]) $input [ 'needed' ] = $cell [ 'needed' ];
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' ];
$value = $value [ $this -> prefix . $lname ];
return true ;
}
etemplate :: add_child ( $cell , $input );
unset ( $input );
}
unset ( $label );
}
}
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
}
/**
* 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
*/
2012-08-09 15:07:07 +02:00
public static function _get_options_from_file ( $file )
2010-10-04 14:12:11 +02:00
{
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 ));
}
include ( $path );
return $options ;
}
/**
* 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 );
}
}
}
2012-08-09 15:07:07 +02:00
/**
* Non printable custom fields eg . UI elements
*
* @ var array
*/
public static $non_printable_fields = array ( 'button' );
/**
* Format a single custom field value as string
*
* @ param array $field field defintion incl . type
* @ param string $value field value
* @ return string formatted value
*/
public static function format_customfield ( array $field , $value )
{
switch ( $field [ 'type' ])
{
case 'select-account' :
if ( $value )
{
$values = array ();
foreach ( $field [ 'rows' ] > 1 ? explode ( ',' , $value ) : ( array ) $value as $value )
{
$values [] = common :: grab_owner_name ( $value );
}
$value = implode ( ', ' , $values );
}
break ;
case 'checkbox' :
$value = $value ? 'X' : '' ;
break ;
case 'select' :
case 'radio' :
if ( count ( $field [ 'values' ]) == 1 && isset ( $field [ 'values' ][ '@' ]))
{
$field [ 'values' ] = customfields_widget :: _get_options_from_file ( $field [ 'values' ][ '@' ]);
}
$values = array ();
foreach ( $field [ 'rows' ] > 1 ? explode ( ',' , $value ) : ( array ) $value as $value )
{
$values [] = isset ( $field [ 'values' ][ $value ]) ? $field [ 'values' ][ $value ] : '#' . $value ;
}
$value = implode ( ', ' , $values );
break ;
case 'date' :
case 'date-time' :
if ( $value )
{
$format = $field [ 'len' ] ? $field [ 'len' ] : ( $field [ 'type' ] == 'date' ? 'Y-m-d' : 'Y-m-d H:i:s' );
$date = array_combine ( preg_split ( '/[\\/. :-]/' , $format ), preg_split ( '/[\\/. :-]/' , $value ));
$value = common :: dateformatorder ( $date [ 'Y' ], $date [ 'm' ], $date [ 'd' ], true );
if ( isset ( $date [ 'H' ])) $value .= ' ' . common :: formattime ( $date [ 'H' ], $date [ 'i' ]);
}
break ;
case 'htmlarea' : // ToDo: EMail probably has a nicer html2text method
if ( $value ) $value = strip_tags ( preg_replace ( '/<(br|p)[^>]*>/i' , " \r \n " , str_replace ( array ( " \r " , " \n " ), '' , $value )));
break ;
case 'ajax_select' : // ToDo: returns unchanged value for now
break ;
default :
// handling for several link types
if ( $value && in_array ( $field [ 'type' ], self :: get_customfield_link_types ()))
{
if ( $field [ 'type' ] == 'link-entry' || strpos ( $value , ':' ) !== false )
{
list ( $app , $value ) = explode ( ':' , $value );
}
else
{
$app = $field [ 'type' ];
}
if ( $value ) $value = egw_link :: title ( $app , $value );
}
break ;
}
return $value ;
}
2010-10-04 14:12:11 +02:00
}