2011-09-09 13:29:07 +02:00
< ? php
/**
* EGroupware - eTemplate serverside implementation of the nextmatch widget
*
* @ license http :// opensource . org / licenses / gpl - license . php GPL - GNU General Public License
2016-03-19 14:06:07 +01:00
* @ package api
* @ subpackage etemplate
2011-09-09 13:29:07 +02:00
* @ link http :// www . egroupware . org
* @ author Ralf Becker < RalfBecker @ outdoor - training . de >
2019-03-12 17:13:18 +01:00
* @ copyright 2002 - 19 by RalfBecker @ outdoor - training . de
2011-09-09 13:29:07 +02:00
*/
2016-03-19 14:06:07 +01:00
namespace EGroupware\Api\Etemplate\Widget ;
use EGroupware\Api\Etemplate ;
use EGroupware\Api ;
2011-09-09 13:29:07 +02:00
/**
2011-09-09 16:00:30 +02:00
* eTemplate serverside implementation of the nextmatch widget
*
* $content [ $id ] = array ( // I = value set by the app, 0 = value on return / output
* 'get_rows' => // I method/callback to request the data for the rows eg. 'notes.bo.get_rows'
2016-02-17 14:23:27 +01:00
* 'cat_id_label' => // I label for category (optional)
2011-09-09 16:00:30 +02:00
* 'filter_label' => // I label for filter (optional)
2016-02-17 14:23:27 +01:00
* 'filter2_label' => // I label for filter2 (optional)
2011-09-09 16:00:30 +02:00
* 'filter_help' => // I help-msg for filter (optional)
* 'no_filter' => True // I disable the 1. filter
* 'no_filter2' => True // I disable the 2. filter (params are the same as for filter)
* 'no_cat' => True // I disable the cat-selectbox
2013-05-29 21:12:14 +02:00
* 'cat_app' => // I application the cat's should be from, default app in get_rows
* 'cat_is_select' => // I true||'no_lang' use selectbox instead of category selection, default null
2011-09-09 16:00:30 +02:00
* 'template' => // I template to use for the rows, if not set via options
* 'header_left' => // I template to show left of the range-value, left-aligned (optional)
* 'header_right' => // I template to show right of the range-value, right-aligned (optional)
* 'bottom_too' => True // I show the nextmatch-line (arrows, filters, search, ...) again after the rows
* 'never_hide' => True // I never hide the nextmatch-line if less then maxmatch entries
2013-05-29 21:12:14 +02:00
* 'lettersearch' => True // I show a lettersearch
* 'searchletter' => // IO active letter of the lettersearch or false for [all]
2011-09-09 16:00:30 +02:00
* 'start' => // IO position in list
* 'num_rows' => // IO number of rows to show, defaults to maxmatches from the general prefs
* 'cat_id' => // IO category, if not 'no_cat' => True
* 'search' => // IO search pattern
* 'order' => // IO name of the column to sort after (optional for the sortheaders)
* 'sort' => // IO direction of the sort: 'ASC' or 'DESC'
* 'col_filter' => // IO array of column-name value pairs (optional for the filterheaders)
2013-02-07 13:43:57 +01:00
* // grid requires implementation of folowing filters in get_rows, even if not used as regular filters!
* // O col_filter[$row_id] to query certain rows only
2013-10-10 13:29:31 +02:00
* // O col_filter[$parent_id] row_id of parent to query children for hierachical display
2011-09-09 16:00:30 +02:00
* 'filter' => // IO filter, if not 'no_filter' => True
* 'filter_no_lang' => True // I set no_lang for filter (=dont translate the options)
* 'filter_onchange' => 'this.form.submit();' // I onChange action for filter, default: this.form.submit();
* 'filter2' => // IO filter2, if not 'no_filter2' => True
* 'filter2_no_lang' => True // I set no_lang for filter2 (=dont translate the options)
* 'filter2_onchange' => 'this.form.submit();' // I onChange action for filter2, default: this.form.submit();
* '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
2014-06-04 20:37:58 +02:00
* 'columnselection_pref' => // I name of the preference (plus 'nextmatch-' prefix), default = template-name
2011-09-09 16:00:30 +02:00
* '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.
2013-05-29 21:12:14 +02:00
* '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 preferred importexport definition,
2011-09-09 16:00:30 +02:00
* array with name => label or name => array ( 'label' => label , 'type' => type ) pairs ( type is a eT widget - type )
* or name of import / export definition
2013-02-07 13:43:57 +01:00
* 'row_id' => // I key into row content to set it's value as row-id, eg. 'id'
* 'row_modified' => // I key into row content for modification date or state of a row, to not query it again
2013-10-10 13:29:31 +02:00
* 'parent_id' => // I key into row content of children linking them to their parent, also used as col_filter to query children
2013-02-07 13:43:57 +01:00
* 'is_parent' => // I key into row content to mark a row to have children
2013-04-20 14:19:27 +02:00
* 'is_parent_value' => // I if set value of is_parent, otherwise is_parent is evaluated as boolean
2013-02-06 11:49:46 +01:00
* 'dataStorePrefix' => // I Optional prefix for client side cache to prevent collisions in applications that have more than one data set, such as ProjectManager / Project elements. Defaults to appname if not set.
2011-09-09 16:00:30 +02:00
* 'actions' => // I array with actions, see nextmatch_widget::egw_actions
* 'action_links' => // I array with enabled actions or ones which should be checked if they are enabled
* optional , default id of all first level actions plus the ones with enabled = 'javaScript:...'
* 'action_var' => 'action' // I name of var to return choosen action, default 'action'
* 'action' => // O string selected action
* 'selected' => // O array with selected id's
* 'checkboxes' => // O array with checkbox id as key and boolean checked value
* 'select_all' => // O boolean value of select_all checkbox, reference to above value for key 'select_all'
2013-02-27 19:13:54 +01:00
* 'favorites' => // I boolean|array True to enable favorites, or an array of additional, app specific settings to include
* in the saved filters ( eg : pm_id )
2013-03-13 19:42:03 +01:00
* 'placeholder' => // I String Optional text to display in the empty row placeholder. If not provided, it's "No matches found."
2013-10-04 15:25:05 +02:00
* 'placeholder_actions' => // I Array Optional list of actions allowed on the placeholder. If not provided, it's ["add"].
2011-09-09 13:29:07 +02:00
*/
2016-03-19 14:06:07 +01:00
class Nextmatch extends Etemplate\Widget
2011-09-09 13:29:07 +02:00
{
2016-05-09 12:05:57 +02:00
/**
* Path where the icons are stored ( relative to webserver_url )
*/
const ICON_PATH = '/api/images' ;
2011-09-09 16:00:30 +02:00
public function __construct ( $xml = '' )
{
2011-09-09 16:32:55 +02:00
if ( $xml ) {
parent :: __construct ( $xml );
}
2011-09-09 13:29:07 +02:00
}
2011-10-05 18:12:40 +02:00
/**
* Legacy options
*/
protected $legacy_options = 'template' ;
2012-03-20 15:25:12 +01:00
2011-09-09 16:00:30 +02:00
/**
* Number of rows to send initially
*/
2014-01-09 12:25:08 +01:00
const INITIAL_ROWS = 50 ;
2011-09-09 16:00:30 +02:00
/**
* Set up what we know on the server side .
*
* Sending a first chunk of rows
*
* @ param string $cname
2012-05-03 20:06:27 +02:00
* @ param array $expand values for keys 'c' , 'row' , 'c_' , 'row_' , 'cont'
2011-09-09 16:00:30 +02:00
*/
2013-11-04 16:41:58 +01:00
public function beforeSendToClient ( $cname , array $expand = null )
2011-09-09 16:00:30 +02:00
{
2012-05-03 20:06:27 +02:00
$form_name = self :: form_name ( $cname , $this -> id , $expand );
2012-07-17 01:00:44 +02:00
$value = self :: get_array ( self :: $request -> content , $form_name , true );
2011-09-09 16:00:30 +02:00
$value [ 'start' ] = 0 ;
2023-04-14 18:28:16 +02:00
if ( ! array_key_exists ( 'num_rows' , $value ))
2014-07-28 23:00:39 +02:00
{
$value [ 'num_rows' ] = self :: INITIAL_ROWS ;
}
2011-09-14 11:41:08 +02:00
$value [ 'rows' ] = array ();
2012-07-17 01:00:44 +02:00
$send_value = $value ;
2013-03-12 23:57:42 +01:00
2023-04-14 18:28:16 +02:00
list ( $app ) = explode ( '.' , $value [ 'get_rows' ]);
if ( empty ( $GLOBALS [ 'egw_info' ][ 'apps' ][ $app ]))
2015-06-29 22:33:10 +02:00
{
2023-04-14 18:28:16 +02:00
list ( $app ) = explode ( '.' , $this -> attrs [ 'template' ]);
}
// Check for sort preference. We only apply this on first load so it can be changed
if ( $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ $app ][ $this -> attrs [ 'template' ] . " _sort " ])
{
$send_value [ 'sort' ] = $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ $app ][ $this -> attrs [ 'template' ] . " _sort " ];
2015-06-29 22:33:10 +02:00
}
2013-04-23 00:32:40 +02:00
2013-03-12 23:57:42 +01:00
// Check for a favorite in URL
2023-04-14 18:28:16 +02:00
if ( ! empty ( $_GET [ 'favorite' ]) && ! empty ( $value [ 'favorites' ]))
2013-03-12 23:57:42 +01:00
{
2023-04-14 18:28:16 +02:00
$safe_name = preg_replace ( '/[^A-Za-z0-9-_]/' , '_' , strip_tags ( $_GET [ 'favorite' ]));
$pref_name = " favorite_ " . $safe_name ;
2013-03-12 23:57:42 +01:00
// Do some easy applying of filters server side
$favorite = $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ $app ][ $pref_name ];
if ( ! $favorite && $_GET [ 'favorite' ] == 'blank' )
{
// Have to go through each of these
2023-04-14 18:28:16 +02:00
foreach ( array ( 'search' , 'cat_id' , 'filter' , 'filter2' ) as $filter )
2013-03-12 23:57:42 +01:00
{
$send_value [ $filter ] = '' ;
}
unset ( $send_value [ 'col_filter' ]);
}
2015-02-02 18:19:20 +01:00
// Old type
2013-03-12 23:57:42 +01:00
if ( $favorite && $favorite [ 'filter' ])
{
2015-02-02 18:19:20 +01:00
$favorite [ 'state' ] = $favorite [ 'filter' ];
}
if ( $favorite && $favorite [ 'state' ])
{
$send_value = array_merge ( $value , $favorite [ 'state' ]);
2013-03-12 23:57:42 +01:00
// Ajax call can handle the saved sort here, but this can't
2015-02-02 18:19:20 +01:00
if ( $favorite [ 'state' ][ 'sort' ])
2013-03-12 23:57:42 +01:00
{
unset ( $send_value [ 'sort' ]);
2015-02-02 18:19:20 +01:00
$send_value [ 'order' ] = $favorite [ 'state' ][ 'sort' ][ 'id' ];
$send_value [ 'sort' ] = $favorite [ 'state' ][ 'sort' ][ 'asc' ] ? 'ASC' : 'DESC' ;
2013-03-12 23:57:42 +01:00
}
}
}
// Make sure it's not set
unset ( $send_value [ 'favorite' ]);
2013-06-18 18:24:01 +02:00
// Parse sort into something that get_rows functions are expecting: db_field in order, ASC/DESC in sort
2022-04-26 20:24:37 +02:00
if ( ! empty ( $send_value [ 'sort' ]) && is_array ( $send_value [ 'sort' ]))
2013-06-18 18:24:01 +02:00
{
$send_value [ 'order' ] = $send_value [ 'sort' ][ 'id' ];
$send_value [ 'sort' ] = $send_value [ 'sort' ][ 'asc' ] ? 'ASC' : 'DESC' ;
}
2014-07-28 23:00:39 +02:00
if ( $value [ 'num_rows' ] != 0 )
{
2014-10-08 14:16:13 +02:00
$total = self :: call_get_rows ( $send_value , $send_value [ 'rows' ], self :: $request -> readonlys , null , null , $this );
2014-07-28 23:00:39 +02:00
}
2014-10-01 21:10:59 +02:00
if ( true ) $value =& self :: get_array ( self :: $request -> content , $form_name , true );
2013-03-12 23:57:42 +01:00
// Add favorite here so app doesn't save it in the session
2021-10-05 10:44:48 +02:00
if ( ! empty ( $_GET [ 'favorite' ]))
2013-03-12 23:57:42 +01:00
{
$send_value [ 'favorite' ] = $safe_name ;
}
2014-10-01 21:10:59 +02:00
if ( true ) $value = $send_value ;
2021-10-04 18:50:51 +02:00
$value [ 'total' ] = $total ? ? null ;
2011-10-03 16:56:20 +02:00
// Send categories
2021-10-04 18:50:51 +02:00
if ( empty ( $value [ 'no_cat' ]) && empty ( $value [ 'cat_is_select' ]))
2011-10-03 16:56:20 +02:00
{
2021-10-04 18:50:51 +02:00
$value [ 'options-cat_id' ] = self :: $request -> sel_options [ 'cat_id' ] ? ? [];
2014-07-14 12:02:47 +02:00
2014-03-03 18:53:27 +01:00
// Add 'All', if not already there
2021-10-04 18:50:51 +02:00
if ( empty ( $value [ 'options-cat_id' ][ '' ]) && empty ( $value [ 'options-cat_id' ][ 0 ]))
2014-03-03 18:53:27 +01:00
{
2016-02-17 14:23:27 +01:00
$value [ 'options-cat_id' ][ '' ] = lang ( 'All categories' );
2014-03-03 18:53:27 +01:00
}
2011-10-03 16:56:20 +02:00
}
2013-02-27 19:13:54 +01:00
// Favorite group for admins
2021-10-07 10:14:08 +02:00
if ( ! empty ( $GLOBALS [ 'egw_info' ][ 'apps' ][ 'admin' ]) && ! empty ( $value [ 'favorites' ]))
2013-02-27 19:13:54 +01:00
{
self :: $request -> sel_options [ $form_name ][ 'favorite' ][ 'group' ] = array ( 'all' => lang ( 'All users' )) +
2016-03-19 14:06:07 +01:00
Select :: typeOptions ( 'select-account' , ',groups' );
2013-02-27 19:13:54 +01:00
}
2013-07-08 23:16:45 +02:00
foreach ( $value as $name => & $_value )
2012-05-24 23:30:19 +02:00
{
2013-06-04 01:17:01 +02:00
if ( strpos ( $name , 'options-' ) !== false && $_value )
2012-05-24 23:30:19 +02:00
{
$select = substr ( $name , 8 );
2021-10-04 18:50:51 +02:00
if ( empty ( self :: $request -> sel_options [ $select ]))
2013-04-08 15:18:27 +02:00
{
self :: $request -> sel_options [ $select ] = array ();
}
2016-03-19 14:06:07 +01:00
Select :: fix_encoded_options ( $_value , TRUE );
2013-04-08 15:18:27 +02:00
self :: $request -> sel_options [ $select ] += $_value ;
2012-05-30 18:34:20 +02:00
// The client doesn't need them in content, but we can't unset them because
// some apps don't send them on re-load, pulling them from the session
//unset($value[$name]);
2012-05-24 23:30:19 +02:00
}
}
2021-10-04 18:50:51 +02:00
if ( ! empty ( $value [ 'rows' ][ 'sel_options' ]))
2012-07-17 01:00:44 +02:00
{
self :: $request -> sel_options = array_merge ( self :: $request -> sel_options , $value [ 'rows' ][ 'sel_options' ]);
unset ( $value [ 'rows' ][ 'sel_options' ]);
}
2013-02-04 16:42:08 +01:00
2013-04-23 00:32:40 +02:00
// If column selection preference is forced, set a flag to turn off UI
2021-10-04 18:50:51 +02:00
$pref_name = 'nextmatch-' . ( $value [ 'columnselection_pref' ] ? ? $this -> attrs [ 'template' ] ? ? '' );
$value [ 'no_columnselection' ] = ! empty ( $value [ 'no_columnselection' ]) || (
! empty ( $GLOBALS [ 'egw' ] -> preferences -> forced [ $app ][ $pref_name ]) &&
2013-04-23 00:32:40 +02:00
// Need to check admin too, or it will be impossible to turn off
2021-10-04 18:50:51 +02:00
empty ( $GLOBALS [ 'egw_info' ][ 'user' ][ 'apps' ][ 'admin' ])
2013-04-23 00:32:40 +02:00
);
2013-06-12 00:50:05 +02:00
// Use this flag to indicate to the admin that columns are forced (and that's why they can't change)
2021-10-04 18:50:51 +02:00
$value [ 'columns_forced' ] = ! empty ( $GLOBALS [ 'egw' ] -> preferences -> forced [ $app ][ $pref_name ]);
2013-04-23 00:32:40 +02:00
2011-09-09 16:00:30 +02:00
// todo: no need to store rows in request, it's enought to send them to client
2012-03-20 15:25:12 +01:00
//error_log(__METHOD__."() $this: total=$value[total]");
2011-09-14 11:41:08 +02:00
//foreach($value['rows'] as $n => $row) error_log("$n: ".array2string($row));
// set up actions, but only if they are defined AND not already set up (run throught self::egw_actions())
if ( isset ( $value [ 'actions' ]) && ! isset ( $value [ 'actions' ][ 0 ]))
{
2012-03-27 16:02:34 +02:00
$value [ 'action_links' ] = array ();
2021-10-04 18:50:51 +02:00
$template_name = isset ( $value [ 'template' ]) ? $value [ 'template' ] : ( $this -> attrs [ 'template' ] ? ? $this -> attrs [ 'options' ] ? ? null );
2011-09-14 11:41:08 +02:00
if ( ! is_array ( $value [ 'action_links' ])) $value [ 'action_links' ] = array ();
$value [ 'actions' ] = self :: egw_actions ( $value [ 'actions' ], $template_name , '' , $value [ 'action_links' ]);
}
2011-09-09 16:00:30 +02:00
}
/**
* Callback to fetch more rows
*
2012-03-20 15:25:12 +01:00
* Callback uses existing get_rows callback , but requires now 'row_id' to be set .
* If no 'row_modified' is set , rows cant checked for modification and therefore
* are always returned to client if in range or deleted if outside range .
*
2011-09-09 16:00:30 +02:00
* @ param string $exec_id identifys the etemplate request
2012-03-30 14:28:07 +02:00
* @ param array $queriedRange array with values for keys " start " , " num_rows " and optional " refresh " , " parent_id "
2011-09-27 19:58:10 +02:00
* @ param array $filters Search and filter parameters , passed to data source
2014-10-01 21:10:59 +02:00
* @ param string $form_name = 'nm' full id of widget incl . all namespaces
* @ param array $knownUids = null uid ' s know to client
* @ param int $lastModified = null date $knowUids last checked
2012-03-20 15:25:12 +01:00
* @ todo for $queriedRange [ refresh ] first check if there ' s any modification since $lastModified , return $result [ order ] === null
* @ return array with values for keys 'total' , 'rows' , 'readonlys' , 'order' , 'data' and 'lastModification'
2011-09-09 16:00:30 +02:00
*/
2012-03-20 15:25:12 +01:00
static public function ajax_get_rows ( $exec_id , array $queriedRange , array $filters = array (), $form_name = 'nm' ,
array $knownUids = null , $lastModified = null )
2011-09-09 13:29:07 +02:00
{
2016-03-19 14:06:07 +01:00
self :: $request = Etemplate\Request :: read ( $exec_id );
2014-08-11 17:12:34 +02:00
// fix for somehow empty etemplate request content
if ( ! is_array ( self :: $request -> content ))
{
self :: $request -> content = array ( $form_name => array ());
}
2016-03-20 14:02:55 +01:00
self :: $response = Api\Json\Response :: get ();
2014-02-27 18:43:51 +01:00
2011-09-09 16:00:30 +02:00
$value = self :: get_array ( self :: $request -> content , $form_name , true );
2012-06-18 19:43:39 +02:00
if ( ! is_array ( $value ))
{
$value = ( $value ) ? array ( $value ) : array ();
}
2014-08-25 19:28:00 +02:00
// Validate filters
2016-03-19 14:06:07 +01:00
if (( $template = Template :: instance ( self :: $request -> template [ 'name' ], self :: $request -> template [ 'template_set' ],
2014-08-25 19:28:00 +02:00
self :: $request -> template [ 'version' ], self :: $request -> template [ 'load_via' ])))
{
2014-09-02 18:26:37 +02:00
$template = $template -> getElementById ( $form_name , strpos ( $form_name , 'history' ) === 0 ? 'historylog' : 'nextmatch' );
2014-08-25 19:28:00 +02:00
$expand = array (
'cont' => array ( $form_name => $filters ),
);
$valid_filters = array ();
2014-08-27 19:13:38 +02:00
if ( $template )
{
$template -> run ( 'validate' , array ( '' , $expand , $expand [ 'cont' ], & $valid_filters ), false ); // $respect_disabled=false: as client may disable things, here we validate everything and leave it to the get_rows to interpret
$filters = $valid_filters [ $form_name ];
}
2016-05-09 21:17:04 +02:00
// Avoid empty arrays, they cause problems with db filtering
2018-12-18 17:49:16 +01:00
foreach (( array ) $filters [ 'col_filter' ] as $col => $val )
2016-05-09 21:17:04 +02:00
{
if ( is_array ( $val ) && count ( $val ) == 0 )
{
2016-05-11 21:40:49 +02:00
unset ( $filters [ 'col_filter' ][ $col ]);
2016-05-09 21:17:04 +02:00
}
}
2014-08-25 19:28:00 +02:00
//error_log($this . " Valid filters: " . array2string($filters));
}
2019-01-07 18:13:08 +01:00
else
{
$template = null ; // get_rows method requires null, not false
}
2014-08-25 19:28:00 +02:00
2014-10-01 21:10:59 +02:00
if ( true ) $value = $value_in = array_merge ( $value , $filters );
2013-10-10 13:29:31 +02:00
2012-05-14 18:46:52 +02:00
//error_log(__METHOD__."('".substr($exec_id,0,10)."...', range=".array2string($queriedRange).', filters='.array2string($filters).", '$form_name', knownUids=".array2string($knownUids).", lastModified=$lastModified) parent_id=$value[parent_id], is_parent=$value[is_parent]");
2012-03-30 14:28:07 +02:00
2012-03-20 15:25:12 +01:00
$result = array ();
2011-09-09 13:29:07 +02:00
2011-10-19 00:20:27 +02:00
// Parse sort into something that get_rows functions are expecting: db_field in order, ASC/DESC in sort
2011-10-03 17:34:10 +02:00
if ( is_array ( $value [ 'sort' ]))
2011-10-19 00:20:27 +02:00
{
$value [ 'order' ] = $value [ 'sort' ][ 'id' ];
2012-03-20 15:25:12 +01:00
$value [ 'sort' ] = $value [ 'sort' ][ 'asc' ] ? 'ASC' : 'DESC' ;
2011-10-19 00:20:27 +02:00
}
2011-10-03 17:34:10 +02:00
2012-03-20 15:25:12 +01:00
$value [ 'start' ] = ( int ) $queriedRange [ 'start' ];
$value [ 'num_rows' ] = ( int ) $queriedRange [ 'num_rows' ];
2014-01-21 16:21:42 +01:00
if ( $value [ 'num_rows' ] == 0 ) $value [ 'num_rows' ] = self :: INITIAL_ROWS ;
2012-03-30 14:28:07 +02:00
// if app supports parent_id / hierarchy ($value['parent_id'] not empty), set parent_id as filter
if (( $parent_id = $value [ 'parent_id' ]))
{
2012-07-11 18:01:14 +02:00
// Infolog at least wants 'parent_id' instead of $parent_id
2013-10-10 13:29:31 +02:00
$value [ 'col_filter' ][ $parent_id ] = $queriedRange [ 'parent_id' ];
if ( $queriedRange [ 'parent_id' ]) $value [ 'csv_export' ] = 'children' ;
2012-03-30 14:28:07 +02:00
}
2012-07-05 00:41:51 +02:00
2013-09-05 13:53:25 +02:00
// Set current app for get_rows
list ( $app ) = explode ( '.' , self :: $request -> method );
if ( ! $app ) list ( $app ) = explode ( '::' , self :: $request -> method );
if ( $app )
{
$GLOBALS [ 'egw_info' ][ 'flags' ][ 'currentapp' ] = $app ;
2016-03-19 14:06:07 +01:00
Api\Translation :: add_app ( $app );
2013-09-05 13:53:25 +02:00
}
2012-07-05 00:41:51 +02:00
// If specific data requested, just do that
2012-07-11 18:01:14 +02:00
if (( $row_id = $value [ 'row_id' ]) && $queriedRange [ 'refresh' ])
2012-07-05 00:41:51 +02:00
{
$value [ 'col_filter' ][ $row_id ] = $queriedRange [ 'refresh' ];
2013-02-14 12:25:24 +01:00
$value [ 'csv_export' ] = 'refresh' ;
2012-07-05 00:41:51 +02:00
}
2012-03-20 15:25:12 +01:00
$rows = $result [ 'data' ] = $result [ 'order' ] = array ();
2014-10-08 14:16:13 +02:00
$result [ 'total' ] = self :: call_get_rows ( $value , $rows , $result [ 'readonlys' ], null , null , $template );
2016-03-19 14:06:07 +01:00
$result [ 'lastModification' ] = Api\DateTime :: to ( 'now' , 'ts' ) - 1 ;
2012-03-20 15:25:12 +01:00
2013-09-05 13:53:25 +02:00
if ( isset ( $GLOBALS [ 'egw_info' ][ 'flags' ][ 'app_header' ]) && self :: $request -> app_header != $GLOBALS [ 'egw_info' ][ 'flags' ][ 'app_header' ])
{
self :: $request -> app_header = $GLOBALS [ 'egw_info' ][ 'flags' ][ 'app_header' ];
2016-03-20 14:02:55 +01:00
Api\Json\Response :: get () -> apply ( 'egw_app_header' , array ( $GLOBALS [ 'egw_info' ][ 'flags' ][ 'app_header' ]));
2013-09-05 13:53:25 +02:00
}
2013-10-10 13:29:31 +02:00
2018-12-20 18:03:12 +01:00
$GLOBALS [ 'egw' ] -> session -> commit_session ();
2021-10-04 18:50:51 +02:00
$row_id = $value [ 'row_id' ] ? ? 'id' ;
$row_modified = $value [ 'row_modified' ] ? ? null ;
2012-03-20 15:25:12 +01:00
foreach ( $rows as $n => $row )
2011-09-09 16:00:30 +02:00
{
2013-02-14 12:25:24 +01:00
$kUkey = false ;
2013-02-06 11:49:46 +01:00
if ( is_int ( $n ) && $row )
2012-03-20 15:25:12 +01:00
{
if ( ! isset ( $row [ $row_id ])) unset ( $row_id ); // unset default row_id of 'id', if not used
2021-10-04 18:50:51 +02:00
if ( empty ( $row [ $row_modified ])) unset ( $row_modified );
2011-09-09 16:00:30 +02:00
2012-03-20 15:25:12 +01:00
$id = $row_id ? $row [ $row_id ] : $n ;
2012-03-23 15:42:06 +01:00
$result [ 'order' ][] = $id ;
2011-09-09 16:32:55 +02:00
2021-10-04 18:50:51 +02:00
$modified = $row [ $row_modified ] ? ? null ;
2014-10-08 14:16:13 +02:00
if ( isset ( $modified ) && ! ( is_int ( $modified ) || is_string ( $modified ) && is_numeric ( $modified )))
{
2016-03-19 14:06:07 +01:00
$modified = Api\DateTime :: to ( str_replace ( 'Z' , '' , $modified ), 'ts' );
2014-10-08 14:16:13 +02:00
}
2012-03-20 15:25:12 +01:00
// check if we need to send the data
2016-03-19 14:06:07 +01:00
//error_log("$id Known: " . (array_search($id, $knownUids) !== false ? 'Yes' : 'No') . ' Modified: ' . Api\DateTime::to($row[$row_modified]) . ' > ' . Api\DateTime::to($lastModified).'? ' . ($row[$row_modified] > $lastModified ? 'Yes' : 'No'));
2012-03-20 15:25:12 +01:00
if ( ! $row_id || ! $knownUids || ( $kUkey = array_search ( $id , $knownUids )) === false ||
2016-05-26 01:28:59 +02:00
! $lastModified || ! isset ( $modified ) || $modified > $lastModified ||
$queriedRange [ 'refresh' ] && $id == $queriedRange [ 'refresh' ]
)
2012-03-20 15:25:12 +01:00
{
2014-10-08 14:16:13 +02:00
$result [ 'data' ][ $id ] = $row ;
2012-03-20 15:25:12 +01:00
}
2014-10-08 14:16:13 +02:00
2012-03-23 15:42:06 +01:00
if ( $kUkey !== false ) unset ( $knownUids [ $kUkey ]);
2012-03-20 15:25:12 +01:00
}
else // non-row data set by get_rows method
{
2014-04-08 19:42:19 +02:00
// Encode all select options and re-index to avoid Firefox's problem with
// '' => 'All'
if ( $n == 'sel_options' )
{
2014-10-01 21:10:59 +02:00
foreach ( $row as & $options )
2014-04-08 19:42:19 +02:00
{
2016-03-19 14:06:07 +01:00
Select :: fix_encoded_options ( $options , true );
2014-04-08 19:42:19 +02:00
}
}
2012-03-20 15:25:12 +01:00
$result [ 'rows' ][ $n ] = $row ;
}
}
2014-03-19 20:19:34 +01:00
// check knowUids outside of range for modification - includes deleted
/*
2012-03-20 15:25:12 +01:00
if ( $knownUids )
{
2014-03-19 20:19:34 +01:00
// row_id not set for nextmatch --> just skip them, we can't identify the rows
2014-07-14 12:02:47 +02:00
if ( ! $row_id )
2012-03-20 15:25:12 +01:00
{
2012-03-23 17:05:06 +01:00
foreach ( $knownUids as $uid )
2012-03-20 15:25:12 +01:00
{
2013-02-14 12:25:24 +01:00
// Just don't send it back for now
unset ( $result [ 'data' ][ $uid ]);
//$result['data'][$uid] = null;
2012-03-20 15:25:12 +01:00
}
}
2014-03-19 20:19:34 +01:00
else
2012-03-20 15:25:12 +01:00
{
2014-03-19 20:19:34 +01:00
error_log ( __METHOD__ . " () knowUids left to check " . array2string ( $knownUids ));
2012-03-23 17:05:06 +01:00
// check if they are up to date: we create a query similar to csv-export without any filters
2014-03-19 20:19:34 +01:00
$uid_query = $value ;
$uid_query [ 'csv_export' ] = 'knownUids' ; // do not store $value in session
$uid_query [ 'filter' ] = $uid_query [ 'filter2' ] = $uid_query [ 'cat_id' ] = $uid_query [ 'search' ] = '' ;
$uid_query [ 'col_filter' ] = array ( $row_id => $knownUids );
2012-03-23 17:05:06 +01:00
// if we know name of modification column and have a last-modified date
if ( $row_modified && $lastModified ) // --> set filter to return only modified entries
2012-03-23 15:42:06 +01:00
{
2014-03-19 20:19:34 +01:00
$uid_query [ 'col_filter' ][] = $row_modified . ' > ' . ( int ) $lastModified ;
2012-03-23 17:05:06 +01:00
}
2014-03-19 20:19:34 +01:00
$uid_query [ 'start' ] = 0 ;
$uid_query [ 'num_rows' ] = count ( $knownUids );
2012-03-23 17:05:06 +01:00
$rows = array ();
2014-03-19 20:19:34 +01:00
try
2012-03-23 17:05:06 +01:00
{
2014-03-19 20:19:34 +01:00
if ( self :: call_get_rows ( $uid_query , $rows ))
2012-03-23 17:05:06 +01:00
{
2014-03-19 20:19:34 +01:00
foreach ( $rows as $n => $row )
2012-03-23 17:05:06 +01:00
{
2014-03-19 20:19:34 +01:00
if ( ! is_int ( $n )) continue ; // ignore non-row data set by get_rows method
if ( ! $row_modified || ! isset ( $row [ $row_modified ]) ||
! isset ( $lastModified ) || $row [ $row_modified ] > $lastModified )
{
$result [ 'data' ][ $row [ $row_id ]] = $row ;
$kUkey = array_search ( $id , $knownUids );
if ( $kUkey !== false ) unset ( $knownUids [ $kUkey ]);
}
2012-03-23 17:05:06 +01:00
}
}
2012-03-23 15:42:06 +01:00
}
2014-03-19 20:19:34 +01:00
catch ( Exception $e )
{
unset ( $value [ 'row_modified' ]);
error_log ( " Error trying to find changed rows with { $value [ 'get_rows' ] } , falling back to all rows. " );
error_log ( $e );
}
// Remove any remaining knownUIDs from the grid
foreach ( $knownUids as $uid )
{
$result [ 'data' ][ $uid ] = null ;
}
}
}
*/
// Check for anything changed in the query
// Tell the client about the changes
$request_value =& self :: get_array ( self :: $request -> content , $form_name , true );
$changes = $no_rows = false ;
2018-11-28 22:57:08 +01:00
foreach ( array_keys ( $value_in ) + array_keys ( $value ) as $key )
2014-03-19 20:19:34 +01:00
{
// These keys are ignored
if ( in_array ( $key , array ( 'col_filter' , 'start' , 'num_rows' , 'total' , 'order' , 'sort' )))
{
continue ;
}
2021-10-04 18:50:51 +02:00
if (( $value_in [ $key ] ? ? null ) == ( $value [ $key ] ? ? null )) continue ;
2014-03-19 20:19:34 +01:00
// These keys we don't send row data back, as they cause a partial reload
2021-10-04 18:50:51 +02:00
if ( in_array ( $key , array ( 'template' ))) $no_rows = true ;
2014-03-19 20:19:34 +01:00
// Actions still need extra handling
if ( $key == 'actions' && ! isset ( $value [ 'actions' ][ 0 ]))
{
$value [ 'action_links' ] = array ();
2021-04-19 18:42:48 +02:00
$template_name = $value [ 'template' ] ? : $template -> attrs [ 'template' ] ? : '' ;
2014-03-19 20:19:34 +01:00
if ( ! is_array ( $value [ 'action_links' ])) $value [ 'action_links' ] = array ();
$value [ 'actions' ] = self :: egw_actions ( $value [ 'actions' ], $template_name , '' , $value [ 'action_links' ]);
}
$changes = true ;
$request_value [ $key ] = $value [ $key ];
2016-03-20 14:02:55 +01:00
Api\Json\Response :: get () -> generic ( 'assign' , array (
2014-03-19 20:19:34 +01:00
'etemplate_exec_id' => $exec_id ,
'id' => $form_name ,
'key' => $key ,
'value' => $value [ $key ],
));
}
// Request doesn't handle changing by reference, so force it
if ( $changes )
{
$content = self :: $request -> content ;
self :: $request -> content = array ();
self :: $request -> content = $content ;
2012-03-20 15:25:12 +01:00
}
2013-09-05 13:53:25 +02:00
2014-03-19 20:19:34 +01:00
// Send back data
2013-03-21 00:09:04 +01:00
//foreach($result as $name => $value) if ($name != 'readonlys') error_log(__METHOD__."() result['$name']=".array2string($name == 'data' ? array_keys($value) : $value));
2016-03-20 14:02:55 +01:00
Api\Json\Response :: get () -> data ( $result );
2013-10-10 13:29:31 +02:00
2013-10-09 16:11:44 +02:00
// If etemplate_exec_id has changed, update the client side
2013-10-11 13:20:21 +02:00
if (( $new_id = self :: $request -> id ()) != $exec_id )
2013-10-09 16:11:44 +02:00
{
2016-03-20 14:02:55 +01:00
Api\Json\Response :: get () -> generic ( 'assign' , array (
2013-10-11 13:20:21 +02:00
'etemplate_exec_id' => $exec_id ,
'id' => '' ,
'key' => 'etemplate_exec_id' ,
'value' => $new_id ,
));
2013-10-09 16:11:44 +02:00
}
2011-09-09 13:29:07 +02:00
}
2011-09-09 16:00:30 +02:00
/**
* Calling our callback
*
* Signature of get_rows callback is either :
* a ) int get_rows ( $query , & $rows , & $readonlys )
* b ) int get_rows ( & $query , & $rows , & $readonlys )
*
* If get_rows is called static ( and php >= 5.2 . 3 ), it is always b ) independent on how it ' s defined !
*
* @ param array & $value
2011-09-14 11:41:08 +02:00
* @ param array & $rows on return : rows are indexed by their row - number : $value [ start ], ... , $value [ start ] + $value [ num_rows ] - 1
2014-10-01 21:10:59 +02:00
* @ param array & $readonlys = null
* @ param object $obj = null ( internal )
* @ param string | array $method = null ( internal )
2016-03-19 14:06:07 +01:00
* @ param Etemplate\Widget $widget = null instanciated nextmatch widget to let it ' s widgets transform each row
2011-09-09 16:00:30 +02:00
* @ return int | boolean total items found of false on error ( $value [ 'get_rows' ] not callable )
*/
2021-05-23 08:58:33 +02:00
protected static function call_get_rows ( array & $value , array & $rows , array & $readonlys = null , $obj = null , $method = null , Etemplate\Widget $widget = null )
2011-09-09 16:00:30 +02:00
{
if ( is_null ( $method )) $method = $value [ 'get_rows' ];
if ( is_null ( $obj ))
{
// allow static callbacks
if ( strpos ( $method , '::' ) !== false )
{
list ( $class , $method ) = explode ( '::' , $method );
// workaround for php < 5.2.3: do NOT call it static, but allow application code to specify static callbacks
if ( version_compare ( PHP_VERSION , '5.2.3' , '>=' ))
{
$method = array ( $class , $method );
unset ( $class );
}
}
else
{
list ( $app , $class , $method ) = explode ( '.' , $value [ 'get_rows' ]);
}
2022-04-26 20:24:37 +02:00
if ( ! empty ( $class ))
2011-09-09 16:00:30 +02:00
{
if ( ! $app && ! is_object ( $GLOBALS [ $class ]))
{
$GLOBALS [ $class ] = new $class ();
}
if ( is_object ( $GLOBALS [ $class ])) // use existing instance (put there by a previous CreateObject)
{
$obj = $GLOBALS [ $class ];
}
else
{
$obj = CreateObject ( $app . '.' . $class );
}
}
}
2014-10-01 21:10:59 +02:00
$raw_rows = array ();
2011-09-14 11:41:08 +02:00
if ( ! is_array ( $readonlys )) $readonlys = array ();
2021-12-16 22:43:33 +01:00
// allow other apps to mess with query
Api\Hooks :: process (
array (
'hook_location' => 'etemplate2_before_get_rows' ,
'get_rows' => $method ,
'value' => & $value ,
'rows' => & $rows ,
'readonlys' => & $readonlys ,
'total' => & $total ,
), array (), true
);
2011-09-09 16:00:30 +02:00
if ( is_callable ( $method )) // php5.2.3+ static call (value is always a var param!)
{
2011-09-14 11:41:08 +02:00
$total = call_user_func_array ( $method , array ( & $value , & $raw_rows , & $readonlys ));
2011-09-09 16:00:30 +02:00
}
elseif ( is_object ( $obj ) && method_exists ( $obj , $method ))
{
2011-09-14 11:41:08 +02:00
$total = $obj -> $method ( $value , $raw_rows , $readonlys );
2011-09-09 16:00:30 +02:00
}
else
{
$total = false ; // method not callable
}
2018-06-12 19:04:02 +02:00
// allow to hook into get_rows of other apps
Api\Hooks :: process ( array (
'hook_location' => 'etemplate2_after_get_rows' ,
'get_rows' => $method ,
'value' => & $value ,
'rows' => & $rows ,
'readonlys' => & $readonlys ,
'total' => & $total ,
), array (), true ); // true = no permission check
2014-10-08 14:16:13 +02:00
// if we have a nextmatch widget, find the repeating row
2021-10-04 18:50:51 +02:00
if ( $widget && ! empty ( $widget -> attrs [ 'template' ]))
2011-09-09 16:00:30 +02:00
{
2014-10-08 14:16:13 +02:00
$row_template = $widget -> getElementById ( $widget -> attrs [ 'template' ]);
if ( ! $row_template )
{
2016-03-19 14:06:07 +01:00
$row_template = Template :: instance ( $widget -> attrs [ 'template' ]);
2014-10-08 14:16:13 +02:00
}
// Try to find just the repeating part
$repeating_row = null ;
// First child should be a grid, we want last row
foreach ( $row_template -> children [ 0 ] -> children [ 1 ] -> children as $child )
{
if ( $child -> type == 'row' ) $repeating_row = $child ;
}
2011-09-09 16:00:30 +02:00
}
2021-10-04 18:50:51 +02:00
// otherwise, we might get stopped by max_excutiontime
2011-09-09 16:00:30 +02:00
if ( $total > 200 ) @ set_time_limit ( 0 );
2011-09-09 21:46:44 +02:00
2021-10-04 18:50:51 +02:00
$is_parent = $value [ 'is_parent' ] ? ? null ;
$is_parent_value = $value [ 'is_parent_value' ] ? ? null ;
$parent_id = $value [ 'parent_id' ] ? ? null ;
2014-02-06 00:13:47 +01:00
2011-09-09 21:46:44 +02:00
// remove empty rows required by old etemplate to compensate for header rows
2012-05-14 18:46:52 +02:00
$first = $total ? null : 0 ;
2011-09-14 11:41:08 +02:00
foreach ( $raw_rows as $n => $row )
2011-09-09 21:46:44 +02:00
{
2011-09-14 11:41:08 +02:00
// skip empty rows inserted for each header-line in old etemplate
if ( is_int ( $n ) && is_array ( $rows ))
{
if ( is_null ( $first )) $first = $n ;
2014-02-18 17:35:54 +01:00
2021-10-04 18:50:51 +02:00
if ( ! empty ( $row [ $is_parent ])) // if app supports parent_id / hierarchy, set parent_id and is_parent
2014-02-06 00:13:47 +01:00
{
$row [ 'is_parent' ] = isset ( $is_parent_value ) ?
$row [ $is_parent ] == $is_parent_value : ( boolean ) $row [ $is_parent ];
2021-10-04 18:50:51 +02:00
$row [ 'parent_id' ] = $row [ $parent_id ] ? ? null ; // seems NOT used on client!
2014-02-06 00:13:47 +01:00
}
2014-10-01 21:10:59 +02:00
// run beforeSendToClient methods of widgets in row on row-data
2021-10-04 18:50:51 +02:00
if ( ! empty ( $repeating_row ))
2014-10-08 14:16:13 +02:00
{
// Change anything by widget for each row ($row set to 1)
$_row = array ( 1 => & $row );
$repeating_row -> run ( 'set_row_value' , array ( '' , array ( 'row' => 1 ), & $_row ), true );
}
2016-03-19 14:06:07 +01:00
else if ( ! $widget || get_class ( $widget ) != __NAMESPACE__ . '\\HistoryLog' )
2014-10-08 14:16:13 +02:00
{
// Fallback based on widget names
2015-09-03 00:43:24 +02:00
//error_log(self::$request->template['name'] . ' had to fallback to run_beforeSendToClient() because it could not find the row');
2014-10-08 14:16:13 +02:00
$row = self :: run_beforeSendToClient ( $row );
}
$rows [ $n - $first + $value [ 'start' ]] = $row ;
2011-09-14 11:41:08 +02:00
}
2012-07-17 01:00:44 +02:00
elseif ( ! is_numeric ( $n )) // rows with string-keys, after numeric rows
2011-09-14 11:41:08 +02:00
{
2013-06-26 19:28:51 +02:00
if ( $n == 'sel_options' )
{
2016-04-05 14:56:10 +02:00
foreach ( $row as $name => & $options )
2013-06-26 19:28:51 +02:00
{
2016-04-05 14:56:10 +02:00
// remember newly set options for validation of nextmatch filters
self :: $request -> sel_options [ $name ] = $options ;
2016-03-19 14:06:07 +01:00
Select :: fix_encoded_options ( $options , true );
2013-06-26 19:28:51 +02:00
}
}
2011-09-14 11:41:08 +02:00
$rows [ $n ] = $row ;
}
2011-09-09 21:46:44 +02:00
}
2011-09-09 16:00:30 +02:00
//error_log($value['get_rows'].'() returning '.array2string($total).', method = '.array2string($method).', value = '.array2string($value));
return $total ;
}
2014-10-01 21:10:59 +02:00
/**
* Run beforeSendToClient methods of widgets in row over row - data
*
* This is currently only a hack to convert everything looking like a timestamp to a 'Y-m-d\TH:i:s\Z' string , fix timezone problems !
*
* @ todo instanciate row of template and run it ' s beforeSendToClient
* @ param array $row
* @ return array
*/
private static function run_beforeSendToClient ( array $row )
{
2014-10-06 13:20:58 +02:00
$timestamps = self :: get_timestamps ();
2014-10-01 21:10:59 +02:00
foreach ( $row as $name => & $value )
{
2014-10-06 13:20:58 +02:00
if ( $name [ 0 ] != '#' && in_array ( $name , $timestamps ) && $value &&
2014-10-01 21:10:59 +02:00
( is_int ( $value ) || is_string ( $value ) && is_numeric ( $value )) &&
( $value > 21000000 || $value < 19000000 ))
{
2016-03-19 14:06:07 +01:00
$value = Api\DateTime :: to ( $value , 'Y-m-d\TH:i:s\Z' );
2014-10-01 21:10:59 +02:00
}
}
return $row ;
}
2014-10-06 13:20:58 +02:00
/**
* Get all timestamp columns incl . names with removed prefixes like cal_ or contact_
*
* @ return array
*/
private static function get_timestamps ()
{
2016-03-19 14:06:07 +01:00
return Api\Cache :: getTree ( __CLASS__ , 'timestamps' , function ()
2014-10-06 13:20:58 +02:00
{
$timestamps = array ();
foreach ( scandir ( EGW_SERVER_ROOT ) as $app )
{
$dir = EGW_SERVER_ROOT . '/' . $app ;
if ( is_dir ( $dir ) && file_exists ( $dir . '/setup/tables_current.inc.php' ) &&
( $tables_defs = $GLOBALS [ 'egw' ] -> db -> get_table_definitions ( $app )))
{
foreach ( $tables_defs as $defintion )
{
foreach ( $defintion [ 'fd' ] as $col => $data )
{
if ( $data [ 'type' ] == 'timestamp' || $data [ 'meta' ] == 'timestamp' )
{
$timestamps [] = $col ;
// some apps remove a prefix --> add prefix-less version too
$matches = null ;
if ( preg_match ( '/^(tz|acl|async|cal|contact|lock|history|link|cf|cat|et)_(.+)$/' , $col , $matches ))
{
$timestamps [] = $matches [ 2 ];
}
}
}
}
}
}
2014-10-06 13:22:02 +02:00
//error_log(__METHOD__."() returning ".array2string($timestamps));
2014-10-06 13:20:58 +02:00
return $timestamps ;
}, array (), 86400 ); // cache for 1 day
}
2019-03-12 17:13:18 +01:00
/**
* Merges actions together with positions based on group parameter
*
* @ param array $actions
* @ param array $actions2
* @ return array
*/
static function merge_actions_by_group ( array $actions , array $actions2 )
{
//error_log(__METHOD__.'('.array2string($actions).', '.array2string($actions2).')');
//return array_merge_recursive($actions, $actions2);
foreach ( $actions2 as $name => $action )
{
// overwrite existing action of given name or append action without group
if ( isset ( $actions [ $name ]) || ! isset ( $action [ 'group' ]))
{
$actions [ $name ] = $action ;
}
// find position to insert action
else
{
$n = 0 ;
foreach ( $actions as $a )
{
if ( $a [ 'group' ] > $action [ 'group' ]) break ;
++ $n ;
}
$actions = array_merge ( array_slice ( $actions , 0 , $n ),
array ( $name => $action ),
array_slice ( $actions , $n , count ( $actions ) - $n ));
}
}
//error_log(__METHOD__.'() returning '.array2string($actions));
return $actions ;
}
2011-09-14 11:41:08 +02:00
/**
2014-10-01 21:10:59 +02:00
* Default maximum length for context submenus , longer menus are put as a " More " submenu
2011-09-14 11:41:08 +02:00
*/
const DEFAULT_MAX_MENU_LENGTH = 14 ;
/**
* Return egw_actions
*
* The following attributes are understood for actions on eTemplate / PHP side :
* - string 'id' id of the action ( set as key not attribute ! )
* - string 'caption' name / label or action , get ' s automatic translated
* - boolean 'no_lang' do NOT translate caption , default false
* - string 'icon' icon , eg . 'edit' or 'infolog/task' , if no app given app of template or API is used
* - string 'iconUrl' full url of icon , better use 'icon'
* - boolean | string 'allowOnMultiple' should action be shown if multiple lines are marked , or string 'only' , default true !
* - boolean | string 'enabled' is action available , or string with javascript function to call , default true !
2013-10-07 18:12:04 +02:00
* - string 'disableClass' class name to check if action should be disabled ( if presend , enabled if not )
2011-09-14 11:41:08 +02:00
* ( add that css class in get_rows (), if row lacks rights for an action )
2013-10-07 18:12:04 +02:00
* - string 'enableClass' class name to check if action should be enabled ( if present , disabled if not )
* - string 'enableId' regular expression row - id has to match to enable action
* - boolean 'hideOnDisabled' hide disabled actions , default false
2011-09-14 11:41:08 +02:00
* - string 'type' type of action , default 'popup' for contenxt menus , 'drag' or 'drop'
* - boolean 'default' is that action the default action , default false
* - array 'children' array with actions of submenu
* - int 'group' to group items , default all actions are in one group
2013-10-07 18:12:04 +02:00
* - string 'onExecute' javascript to run , default 'javaScript:nm_action' or eg . 'javaScript:app.myapp.someMethod'
2011-09-14 11:41:08 +02:00
* which runs action specified in nm_action attribute :
* - string 'nm_action'
* + 'alert' debug action , shows alert with action caption , id and id ' s of selected rows
* + 'submit' default action , sets nm [ action ], nm [ selected ] and nm [ select_all ]
* + 'location' redirects / set location . href to 'url' attribute
* + 'popup' opens popup with url given in 'url' attribute
* - string 'url' url for location or popup
* - string 'target' target for location or popup
* - string 'width' for popup
* - string 'height' for popup
* - string 'confirm' confirmation message
* - string 'confirm_multiple' confirmation message for multiple selected , defaults to 'confirm'
2013-10-07 18:12:04 +02:00
* - boolean 'postSubmit' eg . downloads need a submit via POST request not our regular Ajax submit , only works with nm_action = submit !
2014-12-04 10:19:03 +01:00
* - string 'hint' tooltip on menu item
2011-09-14 11:41:08 +02:00
*
* @ param array $actions id indexed array of actions / array with valus for keys : 'iconUrl' , 'caption' , 'onExecute' , ...
2014-10-01 21:10:59 +02:00
* @ param string $template_name = '' name of the template , used as default for app name of images
* @ param string $prefix = '' prefix for ids
* @ param array & $action_links = array () on return all first - level actions plus the ones with enabled = 'javaScript:...'
* @ param int $max_length = self :: DEFAULT_MAX_MENU_LENGTH automatic pagination , not for first menu level !
* @ param array $default_attrs = null default attributes
2011-09-14 11:41:08 +02:00
* @ return array
*/
public static function egw_actions ( array $actions = null , $template_name = '' , $prefix = '' , array & $action_links = array (),
$max_length = self :: DEFAULT_MAX_MENU_LENGTH , array $default_attrs = null )
{
//echo "<p>".__METHOD__."(\$actions, '$template_name', '$prefix', \$action_links, $max_length) \$actions="; _debug_array($actions);
$first_level = ! $action_links ; // add all first level actions
2019-03-12 17:13:18 +01:00
if ( $first_level )
{
// allow other apps to add actions
foreach ( Api\Hooks :: process ( array (
'location' => 'add_row_actions' ,
'template_name' => $template_name ,
), array ( 'policy' ), true ) as $app => $data )
{
// todo: place new items based on group
if ( $data ) $actions = self :: merge_actions_by_group (( array ) $actions , $data );
}
}
2011-09-14 11:41:08 +02:00
//echo "actions="; _debug_array($actions);
$egw_actions = array ();
$n = 1 ;
2013-10-24 19:29:11 +02:00
$group = false ;
2013-11-04 16:41:58 +01:00
2011-09-14 11:41:08 +02:00
foreach (( array ) $actions as $id => $action )
{
2017-04-04 16:52:25 +02:00
if ( ! empty ( $action [ 'hideOnMobile' ]) && Api\Header\UserAgent :: mobile ())
{
continue ; // no need to send this action to client, specially document actions can be huge
}
2018-11-27 11:50:23 +01:00
if ( ! empty ( $action [ 'disableIfNoEPL' ]) && $action [ 'disableIfNoEPL' ] && ! $GLOBALS [ 'egw_info' ][ 'apps' ][ 'stylite' ])
{
2018-11-27 15:22:50 +01:00
$action [ 'enabled' ] =
$action [ 'hideOnDisabled' ] = false ;
2018-11-27 11:50:23 +01:00
$action [ 'hint' ] = Lang ( " This feature is only available in EPL version. " );
}
else if ( ! empty ( $action [ 'disableIfNoEPL' ]))
{
unset ( $action [ 'disableIfNoEPL' ]);
}
2011-09-14 11:41:08 +02:00
// in case it's only selectbox id => label pairs
if ( ! is_array ( $action )) $action = array ( 'caption' => $action );
if ( $default_attrs ) $action += $default_attrs ;
2013-10-24 19:29:11 +02:00
// Add 'Select All' after first group
2021-10-07 10:14:08 +02:00
if ( $first_level && $group !== false && ( $action [ 'group' ] ? ? null ) != $group && empty ( $egw_actions [ $prefix . 'select_all' ]))
2013-10-24 19:29:11 +02:00
{
2013-11-04 16:41:58 +01:00
2013-10-24 19:29:11 +02:00
$egw_actions [ $prefix . 'select_all' ] = array (
'caption' => 'Select all' ,
//'checkbox' => true,
'hint' => 'Select all entries' ,
'enabled' => true ,
'shortcut' => array (
'keyCode' => 65 , // A
'ctrl' => true ,
2016-09-09 12:54:52 +02:00
'caption' => lang ( 'Ctrl' ) . '+A'
2013-10-24 19:29:11 +02:00
),
'group' => $action [ 'group' ],
);
2013-12-12 01:03:07 +01:00
$action_links [] = $prefix . 'select_all' ;
2013-10-24 19:29:11 +02:00
}
2021-10-04 18:50:51 +02:00
$group = $action [ 'group' ] ? ? 0 ;
2013-10-24 19:29:11 +02:00
2011-09-14 11:41:08 +02:00
if ( ! $first_level && $n == $max_length && count ( $actions ) > $max_length )
{
$id = 'more_' . count ( $actions ); // we need a new unique id
$action = array (
'caption' => 'More' ,
'prefix' => $prefix ,
// display rest of actions incl. current one as children
2017-10-17 12:48:25 +02:00
'children' => array_slice ( $actions , $max_length - 1 , count ( $actions ) - $max_length + 1 , true ),
2011-09-14 11:41:08 +02:00
);
//echo "*** Inserting id=$prefix$id"; _debug_array($action);
// we break at end of foreach loop, as rest of actions is already dealt with
// by putting them as children
2018-06-12 19:04:02 +02:00
// sets the default attributes to every children dataset
if ( is_array ( $action [ 'children' ]))
{
foreach ( $action [ 'children' ] as $key => $children )
{
2017-11-09 14:43:49 +01:00
// checks if children is a valid array and if the "$default_attrs" variable exists
2018-06-12 19:04:02 +02:00
if ( is_array ( $children ) && $default_attrs )
{
2017-11-09 14:43:49 +01:00
$action [ 'children' ][ $key ] += $default_attrs ;
}
}
2018-06-12 19:04:02 +02:00
}
}
2011-09-14 11:41:08 +02:00
// add all first level popup actions plus ones with enabled = 'javaScript:...' to action_links
2021-10-04 18:50:51 +02:00
if (( ! isset ( $action [ 'type' ]) || in_array ( $action [ 'type' ], array ( 'popup' , 'drag' , 'drop' ))) && // popup is the default
( $first_level || isset ( $action [ 'enabled' ]) && substr ( $action [ 'enabled' ], 0 , 11 ) === 'javaScript:' ))
2011-09-14 11:41:08 +02:00
{
2013-07-20 17:58:08 +02:00
$action_links [] = $prefix . $id ;
2011-09-14 11:41:08 +02:00
}
2021-10-04 18:50:51 +02:00
// add sub-menus
if ( ! empty ( $action [ 'children' ]))
2011-09-14 11:41:08 +02:00
{
static $inherit_attrs = array ( 'url' , 'popup' , 'nm_action' , 'onExecute' , 'type' , 'egw_open' , 'allowOnMultiple' , 'confirm' , 'confirm_multiple' );
2014-03-25 17:47:27 +01:00
$inherit_keys = array_flip ( $inherit_attrs );
2021-10-04 18:50:51 +02:00
$action [ 'children' ] = self :: egw_actions ( $action [ 'children' ], $template_name , $action [ 'prefix' ] ? ? '' , $action_links , $max_length ,
2014-03-25 17:47:27 +01:00
array_intersect_key ( $action , $inherit_keys ));
2011-09-14 11:41:08 +02:00
unset ( $action [ 'prefix' ]);
2014-03-25 17:47:27 +01:00
// Allow default actions to keep their onExecute
2021-10-04 18:50:51 +02:00
if ( ! empty ( $action [ 'default' ])) unset ( $inherit_keys [ 'onExecute' ]);
2014-03-25 17:47:27 +01:00
$action = array_diff_key ( $action , $inherit_keys );
2011-09-14 11:41:08 +02:00
}
// link or popup action
2021-10-04 18:50:51 +02:00
if ( ! empty ( $action [ 'url' ]))
2011-09-14 11:41:08 +02:00
{
2017-10-17 12:48:25 +02:00
$action [ 'url' ] = Api\Framework :: link ( '/index.php' , str_replace ( '$action' , $id , $action [ 'url' ]));
2022-04-26 20:24:37 +02:00
if ( ! empty ( $action [ 'popup' ]))
2011-09-14 11:41:08 +02:00
{
list ( $action [ 'data' ][ 'width' ], $action [ 'data' ][ 'height' ]) = explode ( 'x' , $action [ 'popup' ]);
unset ( $action [ 'popup' ]);
$action [ 'data' ][ 'nm_action' ] = 'popup' ;
}
else
{
$action [ 'data' ][ 'nm_action' ] = 'location' ;
2022-04-26 20:24:37 +02:00
if ( empty ( $action [ 'target' ]) && strpos ( $action [ 'url' ], 'menuaction' ) > 0 )
2013-11-26 16:55:56 +01:00
{
// It would be better if app set target, but we'll auto-detect if not
list (, $menuaction ) = explode ( '=' , $action [ 'url' ]);
list ( $app ) = explode ( '.' , $menuaction );
$action [ 'data' ][ 'target' ] = $app ;
}
2011-09-14 11:41:08 +02:00
}
}
2021-10-04 18:50:51 +02:00
if ( ! empty ( $action [ 'egw_open' ]))
2011-09-14 11:41:08 +02:00
{
$action [ 'data' ][ 'nm_action' ] = 'egw_open' ;
}
2013-07-20 17:58:08 +02:00
$egw_actions [ $prefix . $id ] = $action ;
2013-11-04 16:41:58 +01:00
2011-09-14 11:41:08 +02:00
if ( ! $first_level && $n ++ == $max_length ) break ;
}
2013-11-15 20:59:25 +01:00
// Make sure select all is in a group by itself
foreach ( $egw_actions as $id => & $_action )
{
2021-10-04 18:50:51 +02:00
if ( $id == $prefix . 'select_all' ) continue ;
if (( $_action [ 'group' ] ? ? 0 ) >= (( $egw_actions [ $prefix . 'select_all' ] ? ? [])[ 'group' ] ? ? 0 ))
2013-11-15 20:59:25 +01:00
{
2021-10-04 18:50:51 +02:00
$egw_actions [ $id ][ 'group' ] = ( $egw_actions [ $id ][ 'group' ] ? ? 0 ) + 1 ;
2013-11-15 20:59:25 +01:00
}
}
2011-09-14 11:41:08 +02:00
//echo "egw_actions="; _debug_array($egw_actions);
return $egw_actions ;
}
/**
* Action with submenu for categories
*
* Automatic switch to hierarchical display , if more then $max_cats_flat = 14 cats found .
*
* @ param string $app
2014-10-01 21:10:59 +02:00
* @ param int $group = 0 see self :: egw_actions
* @ param string $caption = 'Change category'
* @ param string $prefix = 'cat_' prefix category id to get action id
* @ param boolean $globals = true application global categories too
* @ param int $parent_id = 0 only returns cats of a certain parent
* @ param int $max_cats_flat = self :: DEFAULT_MAX_MENU_LENGTH use hierarchical display if more cats
2011-09-14 11:41:08 +02:00
* @ return array like self :: egw_actions
*/
public static function category_action ( $app , $group = 0 , $caption = 'Change category' ,
$prefix = 'cat_' , $globals = true , $parent_id = 0 , $max_cats_flat = self :: DEFAULT_MAX_MENU_LENGTH )
{
2016-03-20 14:41:33 +01:00
$cat = new Api\Categories ( null , $app );
2014-10-01 21:10:59 +02:00
$cats = $cat -> return_sorted_array ( $start = 0 , false , '' , 'ASC' , 'cat_name' , $globals , $parent_id , true );
2011-09-14 11:41:08 +02:00
// if more then max_length cats, switch automatically to hierarchical display
if ( count ( $cats ) > $max_cats_flat )
{
$cat_actions = self :: category_hierarchy ( $cats , $prefix , $parent_id );
}
else // flat, indented categories
{
$cat_actions = array ();
foreach (( array ) $cats as $cat )
{
$name = str_repeat ( ' ' , 2 * $cat [ 'level' ]) . stripslashes ( $cat [ 'name' ]);
$cat_actions [ $cat [ 'id' ]] = array (
'caption' => $name ,
'no_lang' => true ,
);
// add category icon
2021-10-04 18:50:51 +02:00
if ( is_array ( $cat [ 'data' ]) && ! empty ( $cat [ 'data' ][ 'icon' ]) && file_exists ( EGW_SERVER_ROOT . self :: ICON_PATH . '/' . basename ( $cat [ 'data' ][ 'icon' ])))
2011-09-14 11:41:08 +02:00
{
2016-05-09 12:05:57 +02:00
$cat_actions [ $cat [ 'id' ]][ 'iconUrl' ] = $GLOBALS [ 'egw_info' ][ 'server' ][ 'webserver_url' ] . self :: ICON_PATH . '/' . $cat [ 'data' ][ 'icon' ];
2011-09-14 11:41:08 +02:00
}
}
}
return array (
'caption' => $caption ,
'children' => $cat_actions ,
'enabled' => ( boolean ) $cat_actions ,
'group' => $group ,
'prefix' => $prefix ,
);
}
/**
* Return one level of the category hierarchy
*
2014-10-01 21:10:59 +02:00
* @ param array $cats = null all cats if already read
* @ param string $prefix = 'cat_' prefix category id to get action id
* @ param int $parent_id = 0 only returns cats of a certain parent
2011-09-14 11:41:08 +02:00
* @ return array
*/
private static function category_hierarchy ( array $cats , $prefix , $parent_id = 0 )
{
$cat_actions = array ();
foreach ( $cats as $key => $cat )
{
// current hierarchy level
if ( $cat [ 'parent' ] == $parent_id )
{
$name = stripslashes ( $cat [ 'name' ]);
$cat_actions [ $cat [ 'id' ]] = array (
'caption' => $name ,
'no_lang' => true ,
'prefix' => $prefix ,
);
// add category icon
2021-10-04 18:50:51 +02:00
if ( ! empty ( $cat [ 'data' ][ 'icon' ]) && file_exists ( EGW_SERVER_ROOT . self :: ICON_PATH . '/' . basename ( $cat [ 'data' ][ 'icon' ])))
2011-09-14 11:41:08 +02:00
{
2016-05-09 12:05:57 +02:00
$cat_actions [ $cat [ 'id' ]][ 'iconUrl' ] = $GLOBALS [ 'egw_info' ][ 'server' ][ 'webserver_url' ] . self :: ICON_PATH . '/' . $cat [ 'data' ][ 'icon' ];
2011-09-14 11:41:08 +02:00
}
unset ( $cats [ $key ]);
}
// direct children
elseif ( isset ( $cat_actions [ $cat [ 'parent' ]]))
{
$cat_actions [ 'sub_' . $cat [ 'parent' ]] = $cat_actions [ $cat [ 'parent' ]];
// have to add category itself to children, to be able to select it!
$cat_actions [ $cat [ 'parent' ]][ 'group' ] = - 1 ; // own group on top
$cat_actions [ 'sub_' . $cat [ 'parent' ]][ 'children' ] = array (
$cat [ 'parent' ] => $cat_actions [ $cat [ 'parent' ]],
) + self :: category_hierarchy ( $cats , $prefix , $cat [ 'parent' ]);
unset ( $cat_actions [ $cat [ 'parent' ]]);
}
}
return $cat_actions ;
}
2011-10-04 23:45:54 +02:00
/**
* Validate input
*
* Following attributes get checked :
* - needed : value must NOT be empty
* - min , max : int and float widget only
* - maxlength : maximum length of string ( longer strings get truncated to allowed size )
* - preg : perl regular expression incl . delimiters ( set by default for int , float and colorpicker )
* - int and float get casted to their type
*
* @ param string $cname current namespace
2012-05-03 16:17:47 +02:00
* @ param array $expand values for keys 'c' , 'row' , 'c_' , 'row_' , 'cont'
2011-10-04 23:45:54 +02:00
* @ param array $content
2014-10-01 21:10:59 +02:00
* @ param array & $validated = array () validated content
2011-10-04 23:45:54 +02:00
*/
2012-05-03 16:17:47 +02:00
public function validate ( $cname , array $expand , array $content , & $validated = array ())
2011-10-04 23:45:54 +02:00
{
2012-05-03 16:17:47 +02:00
$form_name = self :: form_name ( $cname , $this -> id , $expand );
2011-10-04 23:45:54 +02:00
$value = self :: get_array ( $content , $form_name );
2014-03-03 22:00:31 +01:00
2016-04-05 14:56:10 +02:00
// Some (most) nextmatch settings are set in its value, not attributes, which aren't in
2014-03-03 22:00:31 +01:00
// $content. Fetch them from the request, so we actually have them.
$content_value = self :: get_array ( self :: $request -> content , $form_name );
2013-02-27 19:13:54 +01:00
list ( $app ) = explode ( '.' , $this -> attrs [ 'template' ]);
2011-10-04 23:45:54 +02:00
2013-03-12 23:57:42 +01:00
unset ( $value [ 'favorite' ]);
2011-11-09 22:34:52 +01:00
// On client, rows does not get its own namespace, but all apps are expecting it
2012-04-10 22:27:37 +02:00
$value [ 'rows' ] = $value ;
2011-11-09 22:34:52 +01:00
2012-04-18 00:56:04 +02:00
// Legacy support - action popups were not properly namespaced
$preserve = self :: get_array ( self :: $request -> preserv , $form_name );
if ( $value [ $preserve [ 'action_var' ]] && $content [ $value [ $preserve [ 'action_var' ]] . '_popup' ])
{
$validated += $content [ $value [ $preserve [ 'action_var' ]] . '_popup' ];
}
2013-04-23 00:32:40 +02:00
// Save current column settings as default, clear, or force (admins only)
2015-07-27 17:24:27 +02:00
if ( $GLOBALS [ 'egw_info' ][ 'user' ][ 'apps' ][ 'admin' ] && $app && $value [ 'selectcols' ])
2011-10-04 23:45:54 +02:00
{
2014-03-03 22:00:31 +01:00
$pref_name = 'nextmatch-' . ( isset ( $content_value [ 'columnselection_pref' ]) ? $content_value [ 'columnselection_pref' ] : $this -> attrs [ 'template' ]);
2013-04-23 00:32:40 +02:00
$refresh_pref_name = $pref_name . '-autorefresh' ;
2015-09-21 23:41:54 +02:00
switch ( $value [ 'nm_col_preference' ]) {
case 'force' :
$pref_level = 'forced' ;
break ;
case 'reset' :
case 'default' :
$pref_level = 'default' ;
break ;
default :
$pref_level = 'user' ;
}
2013-04-23 00:32:40 +02:00
// Clear forced pref before setting default
if ( $pref_level != 'forced' )
{
$GLOBALS [ 'egw' ] -> preferences -> delete ( $app , $pref_name , 'forced' );
$GLOBALS [ 'egw' ] -> preferences -> delete ( $app , $refresh_pref_name , 'forced' );
2015-03-16 23:28:18 +01:00
$GLOBALS [ 'egw' ] -> preferences -> delete ( $app , $pref_name . '-size' , 'forced' );
$GLOBALS [ 'egw' ] -> preferences -> delete ( $app , $pref_name . '-lettersearch' , 'forced' );
2013-06-12 00:50:05 +02:00
$GLOBALS [ 'egw' ] -> preferences -> save_repository ( true , 'forced' );
2013-04-23 00:32:40 +02:00
}
// Set columns + refresh as default for all users
2013-11-06 23:51:57 +01:00
// Columns included in submit, preference might not be updated yet
$cols = $value [ 'selectcols' ];
2015-03-16 23:28:18 +01:00
$GLOBALS [ 'egw' ] -> preferences -> read_repository ( true );
2013-04-23 00:32:40 +02:00
$GLOBALS [ 'egw' ] -> preferences -> add ( $app , $pref_name , is_array ( $cols ) ? implode ( ',' , $cols ) : $cols , $pref_level );
// Autorefresh
2013-06-12 00:50:05 +02:00
$refresh = $value [ 'nm_autorefresh' ];
2013-04-23 00:32:40 +02:00
$GLOBALS [ 'egw' ] -> preferences -> add ( $app , $refresh_pref_name ,( int ) $refresh , $pref_level );
2015-03-16 23:28:18 +01:00
// Lettersearch
$lettersearch = is_array ( $cols ) && in_array ( 'lettersearch' , $cols );
$GLOBALS [ 'egw' ] -> preferences -> add ( $app , $pref_name . '-lettersearch' ,( int ) $lettersearch , $pref_level );
2013-06-12 00:50:05 +02:00
$GLOBALS [ 'egw' ] -> preferences -> save_repository ( true , $pref_level );
2014-10-01 21:10:59 +02:00
$GLOBALS [ 'egw' ] -> preferences -> read ( true );
2013-04-23 00:32:40 +02:00
if ( $value [ 'nm_col_preference' ] == 'reset' )
2011-10-04 23:45:54 +02:00
{
2013-04-23 00:32:40 +02:00
// Clear column + refresh preference so users go back to default
$GLOBALS [ 'egw' ] -> preferences -> delete_preference ( $app , $pref_name );
2013-06-12 00:50:05 +02:00
$GLOBALS [ 'egw' ] -> preferences -> delete_preference ( $app , $pref_name . '-size' );
2015-03-16 23:28:18 +01:00
$GLOBALS [ 'egw' ] -> preferences -> delete_preference ( $app , $pref_name . '-lettersearch' );
2013-06-12 00:50:05 +02:00
$GLOBALS [ 'egw' ] -> preferences -> delete_preference ( $app , $refresh_pref_name );
2011-10-04 23:45:54 +02:00
}
}
2013-04-23 00:32:40 +02:00
unset ( $value [ 'nm_col_preference' ]);
2012-04-10 22:27:37 +02:00
$validated [ $form_name ] = $value ;
2011-10-04 23:45:54 +02:00
}
2011-10-14 19:55:24 +02:00
/**
* Run a given method on all children
*
* Reimplemented to add namespace , and make sure row template gets included
*
2019-03-19 15:34:44 +01:00
* @ param string | callable $method_name or function ( $cname , $expand , $widget )
2014-10-01 21:10:59 +02:00
* @ param array $params = array ( '' ) parameter ( s ) first parameter has to be cname , second $expand !
* @ param boolean $respect_disabled = false false ( default ) : ignore disabled , true : method is NOT run for disabled widgets AND their children
2011-10-14 19:55:24 +02:00
*/
public function run ( $method_name , $params = array ( '' ), $respect_disabled = false )
{
2012-03-30 18:05:29 +02:00
$old_param0 = $params [ 0 ];
2011-10-14 19:55:24 +02:00
$cname =& $params [ 0 ];
// Need this check or the headers will get involved too
2012-05-03 16:17:47 +02:00
if ( $this -> type == 'nextmatch' )
{
2011-10-14 19:55:24 +02:00
parent :: run ( $method_name , $params , $respect_disabled );
2012-05-03 20:06:27 +02:00
if ( $this -> id ) $cname = self :: form_name ( $cname , $this -> id , $params [ 1 ]);
2012-05-03 16:17:47 +02:00
2014-08-25 19:28:00 +02:00
// Run on all the sub-templates
foreach ( array ( 'template' , 'header_left' , 'header_right' , 'header_row' ) as $sub_template )
2011-10-14 19:55:24 +02:00
{
2021-10-04 18:50:51 +02:00
if ( ! empty ( $this -> attrs [ $sub_template ]))
2014-08-25 19:28:00 +02:00
{
2016-03-19 14:06:07 +01:00
$row_template = Template :: instance ( $this -> attrs [ $sub_template ]);
2014-08-25 19:28:00 +02:00
$row_template -> run ( $method_name , $params , $respect_disabled );
}
2011-10-14 19:55:24 +02:00
}
}
2012-03-30 18:05:29 +02:00
$params [ 0 ] = $old_param0 ;
2011-10-14 19:55:24 +02:00
}
2013-02-18 10:47:39 +01:00
/**
* Refresh given rows for specified change
*
* Change type parameters allows for quicker refresh then complete server side reload :
* - edit : send just modified data from given rows
* - delete : just send null for given rows to clientside ( no backend call neccessary )
* - add : requires full reload
*
* @ param array | string $row_ids rows to refresh
2014-10-01 21:10:59 +02:00
* @ param string $type = 'edit' " edit " ( default ), " delete " or " add "
2013-02-18 10:47:39 +01:00
*/
public function refresh ( $row_ids , $type = 'edit' )
{
2014-10-01 21:10:59 +02:00
unset ( $row_ids , $type ); // not used, but required by function signature
2016-03-19 14:06:07 +01:00
throw new Api\Exception ( 'Not yet implemented' );
2013-02-18 10:47:39 +01:00
}
2011-09-09 13:29:07 +02:00
}
2011-10-14 19:55:24 +02:00
// Registration needs to go here, otherwise customfields won't be loaded until some other cf shows up
2022-04-26 20:24:37 +02:00
Etemplate\Widget :: registerWidget ( __NAMESPACE__ . '\\Customfields' , array ( 'nextmatch-customfields' ));