2011-07-13 14:30:19 +02:00
< ? php
2017-01-21 13:58:53 +01:00
2021-11-30 23:29:51 +01:00
use Engelsystem\Database\Db ;
2022-07-20 13:48:04 +02:00
use Engelsystem\Helpers\Carbon ;
2022-11-09 00:02:30 +01:00
use Engelsystem\Models\AngelType ;
2020-09-06 23:50:36 +02:00
use Engelsystem\Models\Room ;
2023-04-30 13:41:59 +02:00
use Engelsystem\Models\Shifts\NeededAngelType ;
use Engelsystem\Models\Shifts\Shift ;
2022-12-03 00:57:04 +01:00
use Engelsystem\Models\UserAngelType ;
2016-10-02 23:00:01 +02:00
use Engelsystem\ShiftsFilter ;
2022-12-03 00:57:04 +01:00
use Illuminate\Database\Eloquent\Builder ;
use Illuminate\Database\Eloquent\Collection as EloquentCollection ;
2020-09-06 23:50:36 +02:00
use Illuminate\Support\Collection ;
2014-09-28 19:44:53 +02:00
2017-01-03 03:22:48 +01:00
/**
* @ return string
*/
2017-01-02 03:57:23 +01:00
function shifts_title ()
{
2018-08-29 21:55:32 +02:00
return __ ( 'Shifts' );
2013-11-25 21:04:58 +01:00
}
2016-10-02 21:19:03 +02:00
/**
* Start different controllers for deleting shifts and shift_entries , edit shifts and add shift entries .
2016-11-17 14:35:04 +01:00
* FIXME :
* Transform into shift controller and shift entry controller .
* Split actions into shift edit , shift delete , shift entry edit , shift entry delete
* Introduce simpler and beautiful actions for shift entry join / leave for users
2017-01-03 03:22:48 +01:00
*
* @ return string
2016-10-02 21:19:03 +02:00
*/
2017-01-02 03:57:23 +01:00
function user_shifts ()
{
2017-07-18 21:38:53 +02:00
$request = request ();
2017-01-02 15:43:36 +01:00
2023-01-18 13:02:11 +01:00
if ( auth () -> user () -> isFreeloader ()) {
2019-09-08 02:25:49 +02:00
throw_redirect ( page_link_to ( 'user_myshifts' ));
2017-01-02 03:57:23 +01:00
}
2017-01-02 15:43:36 +01:00
2017-12-20 00:42:58 +01:00
if ( $request -> has ( 'edit_shift' )) {
2017-01-02 15:43:36 +01:00
return shift_edit_controller ();
2017-07-18 21:38:53 +02:00
} elseif ( $request -> has ( 'delete_shift' )) {
2017-01-02 15:43:36 +01:00
return shift_delete_controller ();
}
2017-01-02 03:57:23 +01:00
return view_user_shifts ();
2011-12-28 14:45:49 +01:00
}
2011-12-27 22:13:17 +01:00
2016-10-04 18:36:57 +02:00
/**
* Helper function that updates the start and end time from request data .
* Use update_ShiftsFilter () .
*
2017-01-03 03:22:48 +01:00
* @ param ShiftsFilter $shiftsFilter The shiftfilter to update .
* @ param string [] $days
2016-10-04 18:36:57 +02:00
*/
2017-01-02 03:57:23 +01:00
function update_ShiftsFilter_timerange ( ShiftsFilter $shiftsFilter , $days )
{
$start_time = $shiftsFilter -> getStartTime ();
2018-01-14 17:47:26 +01:00
if ( is_null ( $start_time )) {
2022-07-20 13:48:04 +02:00
$now = ( new DateTime ()) -> format ( 'Y-m-d' );
2021-10-08 15:38:58 +02:00
$first_day = DateTime :: createFromFormat (
'Y-m-d' ,
2022-07-20 13:48:04 +02:00
in_array ( $now , $days ) ? $now : ( $days [ 0 ] ? ? ( new DateTime ()) -> format ( 'Y-m-d' ))
2021-10-08 15:38:58 +02:00
) -> getTimestamp ();
2022-04-13 01:02:37 +02:00
if ( time () < $first_day ) {
2020-04-03 10:28:55 +02:00
$start_time = $first_day ;
} else {
$start_time = time ();
}
2017-01-02 03:57:23 +01:00
}
2017-01-02 15:43:36 +01:00
2017-01-02 03:57:23 +01:00
$end_time = $shiftsFilter -> getEndTime ();
2022-07-20 13:48:04 +02:00
if ( is_null ( $end_time )) {
2017-01-02 03:57:23 +01:00
$end_time = $start_time + 24 * 60 * 60 ;
2022-07-20 13:48:04 +02:00
$end = Carbon :: createFromTimestamp ( $end_time );
if ( ! in_array ( $end -> format ( 'Y-m-d' ), $days )) {
$end -> startOfDay () -> subSecond (); // the day before
$end_time = $end -> timestamp ;
}
2017-01-02 03:57:23 +01:00
}
2017-01-02 15:43:36 +01:00
2017-12-25 23:12:52 +01:00
$shiftsFilter -> setStartTime ( check_request_datetime (
'start_day' ,
'start_time' ,
$days ,
$start_time
));
$shiftsFilter -> setEndTime ( check_request_datetime (
'end_day' ,
'end_time' ,
$days ,
$end_time
));
2017-01-02 15:43:36 +01:00
2017-01-02 03:57:23 +01:00
if ( $shiftsFilter -> getStartTime () > $shiftsFilter -> getEndTime ()) {
$shiftsFilter -> setEndTime ( $shiftsFilter -> getStartTime () + 24 * 60 * 60 );
}
2016-10-04 18:36:57 +02:00
}
2016-10-02 23:00:01 +02:00
/**
* Update given ShiftsFilter with filter params from user input
*
2022-12-03 00:57:04 +01:00
* @ param ShiftsFilter $shiftsFilter The shifts filter to update from request data
2017-01-21 13:58:53 +01:00
* @ param boolean $user_shifts_admin Has the user user_shift_admin privilege ?
2022-12-03 00:57:04 +01:00
* @ param string [] $days An array of available filter days
2016-10-02 23:00:01 +02:00
*/
2017-01-02 03:57:23 +01:00
function update_ShiftsFilter ( ShiftsFilter $shiftsFilter , $user_shifts_admin , $days )
{
$shiftsFilter -> setUserShiftsAdmin ( $user_shifts_admin );
$shiftsFilter -> setFilled ( check_request_int_array ( 'filled' , $shiftsFilter -> getFilled ()));
$shiftsFilter -> setRooms ( check_request_int_array ( 'rooms' , $shiftsFilter -> getRooms ()));
$shiftsFilter -> setTypes ( check_request_int_array ( 'types' , $shiftsFilter -> getTypes ()));
update_ShiftsFilter_timerange ( $shiftsFilter , $days );
2016-10-02 23:00:01 +02:00
}
2017-01-03 03:22:48 +01:00
/**
2020-09-06 23:50:36 +02:00
* @ return Room [] | Collection
2017-01-03 03:22:48 +01:00
*/
2023-04-30 13:41:59 +02:00
function load_rooms ( bool $onlyWithActiveShifts = false )
2017-01-02 03:57:23 +01:00
{
2023-04-30 13:41:59 +02:00
$rooms = Room :: orderBy ( 'name' );
if ( $onlyWithActiveShifts ) {
$roomIdsFromAngelType = NeededAngelType :: query ()
-> whereNotNull ( 'room_id' )
-> select ( 'room_id' );
$roomIdsFromShift = Shift :: query ()
-> leftJoin ( 'needed_angel_types' , 'shifts.id' , 'needed_angel_types.shift_id' )
-> whereNotNull ( 'needed_angel_types.shift_id' )
-> select ( 'shifts.room_id' );
$rooms -> whereIn ( 'id' , $roomIdsFromAngelType )
-> orWhereIn ( 'id' , $roomIdsFromShift );
}
$rooms = $rooms -> get ();
2020-09-06 23:50:36 +02:00
if ( $rooms -> isEmpty ()) {
2018-08-29 21:55:32 +02:00
error ( __ ( 'The administration has not configured any rooms yet.' ));
2019-09-08 02:25:49 +02:00
throw_redirect ( page_link_to ( '/' ));
2017-01-02 03:57:23 +01:00
}
2020-09-06 23:50:36 +02:00
2017-01-02 03:57:23 +01:00
return $rooms ;
2016-10-04 18:36:57 +02:00
}
2017-01-03 03:22:48 +01:00
/**
* @ return array
*/
2017-01-02 03:57:23 +01:00
function load_days ()
{
2022-06-16 22:50:52 +02:00
$days = ( new Collection ( Db :: select (
2021-01-03 01:47:39 +01:00
'
2023-01-03 22:19:03 +01:00
SELECT DISTINCT DATE ( `start` ) AS `id` , DATE ( `start` ) AS `name`
FROM `shifts`
2021-01-03 01:47:39 +01:00
ORDER BY `id` , `name`
'
)))
-> pluck ( 'id' )
-> toArray ();
2017-01-21 13:58:53 +01:00
if ( empty ( $days )) {
2018-08-29 21:55:32 +02:00
error ( __ ( 'The administration has not configured any shifts yet.' ));
2019-03-07 13:01:52 +01:00
// Do not try to redirect to the current page
if ( config ( 'home_site' ) != 'user_shifts' ) {
2019-09-08 02:25:49 +02:00
throw_redirect ( page_link_to ( '/' ));
2019-03-07 13:01:52 +01:00
}
2017-01-02 03:57:23 +01:00
}
return $days ;
2016-10-04 18:36:57 +02:00
}
2017-01-03 03:22:48 +01:00
/**
2017-12-25 23:12:52 +01:00
* @ return array [] | false
2017-01-03 03:22:48 +01:00
*/
2017-01-02 03:57:23 +01:00
function load_types ()
{
2018-10-31 12:48:22 +01:00
$user = auth () -> user ();
2017-01-02 15:43:36 +01:00
2022-11-09 00:02:30 +01:00
if ( ! AngelType :: count ()) {
2018-08-29 21:55:32 +02:00
error ( __ ( 'The administration has not configured any angeltypes yet - or you are not subscribed to any angeltype.' ));
2019-09-08 02:25:49 +02:00
throw_redirect ( page_link_to ( '/' ));
2017-01-02 03:57:23 +01:00
}
2022-12-03 00:57:04 +01:00
2022-10-18 19:15:22 +02:00
$types = Db :: select (
'
2017-01-21 13:58:53 +01:00
SELECT
2022-11-09 00:02:30 +01:00
`angel_types` . `id` ,
`angel_types` . `name` ,
2017-01-21 13:58:53 +01:00
(
2022-11-09 00:02:30 +01:00
`angel_types` . `restricted` = 0
2017-01-21 13:58:53 +01:00
OR (
2022-12-03 00:57:04 +01:00
NOT `user_angel_type` . `confirm_user_id` IS NULL
OR `user_angel_type` . `id` IS NULL
2017-01-21 13:58:53 +01:00
)
) AS `enabled`
2022-11-09 00:02:30 +01:00
FROM `angel_types`
2022-12-03 00:57:04 +01:00
LEFT JOIN `user_angel_type`
2017-01-21 13:58:53 +01:00
ON (
2022-12-03 00:57:04 +01:00
`user_angel_type` . `angel_type_id` = `angel_types` . `id`
AND `user_angel_type` . `user_id` = ?
2017-01-02 15:43:36 +01:00
)
2022-11-09 00:02:30 +01:00
ORDER BY `angel_types` . `name`
2017-01-21 13:58:53 +01:00
' ,
[
2018-10-08 21:15:56 +02:00
$user -> id ,
2017-01-21 13:58:53 +01:00
]
);
2022-12-03 00:57:04 +01:00
2017-01-02 03:57:23 +01:00
if ( empty ( $types )) {
2020-11-24 01:18:05 +01:00
return unrestricted_angeltypes ();
2017-01-02 03:57:23 +01:00
}
2022-12-03 00:57:04 +01:00
2017-01-02 03:57:23 +01:00
return $types ;
2016-10-04 18:36:57 +02:00
}
2020-11-24 01:18:05 +01:00
/**
* @ return array []
*/
function unrestricted_angeltypes ()
{
2022-11-09 00:02:30 +01:00
return AngelType :: whereRestricted ( 0 ) -> get ([ 'id' , 'name' ]) -> toArray ();
2020-11-24 01:18:05 +01:00
}
2017-01-03 03:22:48 +01:00
/**
* @ return string
*/
2017-01-02 03:57:23 +01:00
function view_user_shifts ()
{
2018-10-10 03:10:28 +02:00
$user = auth () -> user ();
2017-01-02 15:43:36 +01:00
2017-08-30 19:57:01 +02:00
$session = session ();
2017-01-02 03:57:23 +01:00
$days = load_days ();
2023-04-30 13:41:59 +02:00
$rooms = load_rooms ( true );
2017-01-02 03:57:23 +01:00
$types = load_types ();
2023-04-01 15:14:32 +02:00
$ownAngelTypes = [];
2021-07-14 00:40:31 +02:00
2022-12-03 00:57:04 +01:00
/** @var EloquentCollection|UserAngelType[] $userAngelTypes */
$userAngelTypes = UserAngelType :: whereUserId ( $user -> id )
-> leftJoin ( 'angel_types' , 'user_angel_type.angel_type_id' , 'angel_types.id' )
-> where ( function ( Builder $query ) {
$query -> whereNotNull ( 'user_angel_type.confirm_user_id' )
-> orWhere ( 'angel_types.restricted' , false );
})
-> get ();
foreach ( $userAngelTypes as $type ) {
2023-04-01 15:14:32 +02:00
$ownAngelTypes [] = $type -> angel_type_id ;
2020-05-11 22:41:18 +02:00
}
2017-01-02 15:43:36 +01:00
2018-08-07 02:38:41 +02:00
if ( ! $session -> has ( 'shifts-filter' )) {
2020-09-06 23:50:36 +02:00
$room_ids = $rooms -> pluck ( 'id' ) -> toArray ();
2023-04-01 15:14:32 +02:00
$shiftsFilter = new ShiftsFilter ( auth () -> can ( 'user_shifts_admin' ), $room_ids , $ownAngelTypes );
2018-08-07 02:38:41 +02:00
$session -> set ( 'shifts-filter' , $shiftsFilter -> sessionExport ());
2017-01-02 03:57:23 +01:00
}
2017-08-30 19:57:01 +02:00
2018-08-07 02:38:41 +02:00
$shiftsFilter = new ShiftsFilter ();
$shiftsFilter -> sessionImport ( $session -> get ( 'shifts-filter' ));
2018-11-12 14:41:23 +01:00
update_ShiftsFilter ( $shiftsFilter , auth () -> can ( 'user_shifts_admin' ), $days );
2018-08-07 02:38:41 +02:00
$session -> set ( 'shifts-filter' , $shiftsFilter -> sessionExport ());
2017-01-02 15:43:36 +01:00
2017-01-02 03:57:23 +01:00
$shiftCalendarRenderer = shiftCalendarRendererByShiftFilter ( $shiftsFilter );
2017-01-02 15:43:36 +01:00
2018-10-10 03:10:28 +02:00
if ( empty ( $user -> api_key )) {
2017-01-02 03:57:23 +01:00
User_reset_api_key ( $user , false );
}
2017-01-02 15:43:36 +01:00
2017-01-02 03:57:23 +01:00
$filled = [
2017-01-02 15:43:36 +01:00
[
'id' => '1' ,
2023-02-05 18:03:00 +01:00
'name' => __ ( 'occupied' ),
2017-01-02 15:43:36 +01:00
],
[
'id' => '0' ,
2023-02-05 18:03:00 +01:00
'name' => __ ( 'free' ),
],
2017-01-02 15:43:36 +01:00
];
2023-01-03 22:19:03 +01:00
$start_day = $shiftsFilter -> getStart () -> format ( 'Y-m-d' );
$start_time = $shiftsFilter -> getStart () -> format ( 'H:i' );
$end_day = $shiftsFilter -> getEnd () -> format ( 'Y-m-d' );
$end_time = $shiftsFilter -> getEnd () -> format ( 'H:i' );
2017-01-02 15:43:36 +01:00
2018-10-10 03:10:28 +02:00
if ( config ( 'signup_requires_arrival' ) && ! $user -> state -> arrived ) {
2017-12-20 23:12:17 +01:00
info ( render_user_arrived_hint ());
2017-08-31 12:25:06 +02:00
}
2023-02-04 02:43:47 +01:00
$formattedDays = collect ( $days ) -> map ( function ( $value ) {
return Carbon :: make ( $value ) -> format ( __ ( 'Y-m-d' ));
}) -> toArray ();
2017-01-02 03:57:23 +01:00
return page ([
2017-01-02 15:43:36 +01:00
div ( 'col-md-12' , [
msg (),
2018-09-10 17:22:05 +02:00
view ( __DIR__ . '/../../resources/views/pages/user-shifts.html' , [
2017-01-02 15:43:36 +01:00
'title' => shifts_title (),
2022-12-02 23:03:23 +01:00
'room_select' => make_select (
$rooms ,
$shiftsFilter -> getRooms (),
'rooms' ,
icon ( 'pin-map-fill' ) . __ ( 'Rooms' )
),
2017-12-25 23:12:52 +01:00
'start_select' => html_select_key (
'start_day' ,
'start_day' ,
2023-02-04 02:43:47 +01:00
array_combine ( $days , $formattedDays ),
2017-12-25 23:12:52 +01:00
$start_day
),
2017-01-02 15:43:36 +01:00
'start_time' => $start_time ,
2017-12-25 23:12:52 +01:00
'end_select' => html_select_key (
'end_day' ,
'end_day' ,
2023-02-04 02:43:47 +01:00
array_combine ( $days , $formattedDays ),
2017-12-25 23:12:52 +01:00
$end_day
),
2017-01-02 15:43:36 +01:00
'end_time' => $end_time ,
'type_select' => make_select (
$types ,
$shiftsFilter -> getTypes (),
2017-01-03 14:12:17 +01:00
'types' ,
2022-12-02 23:03:23 +01:00
icon ( 'person-lines-fill' ) . __ ( 'Angeltypes' ) . '<sup>1</sup>' ,
2023-04-01 15:14:32 +02:00
$ownAngelTypes
2017-01-02 15:43:36 +01:00
),
2022-12-02 23:03:23 +01:00
'filled_select' => make_select (
$filled ,
$shiftsFilter -> getFilled (),
'filled' ,
icon ( 'person-fill-slash' ) . __ ( 'Occupancy' )
),
2017-01-02 15:43:36 +01:00
'task_notice' =>
'<sup>1</sup>'
2018-08-29 21:55:32 +02:00
. __ ( 'The tasks shown here are influenced by the angeltypes you joined already!' )
2023-02-13 19:28:10 +01:00
. ' <a href="' . url ( '/angeltypes/about' ) . '">'
2018-08-29 21:55:32 +02:00
. __ ( 'Description of the jobs.' )
2017-01-03 03:22:48 +01:00
. '</a>' ,
2017-01-02 15:43:36 +01:00
'shifts_table' => msg () . $shiftCalendarRenderer -> render (),
2021-07-24 20:42:19 +02:00
'ical_text' => div ( 'mt-3' , ical_hint ()),
2018-08-29 21:55:32 +02:00
'filter' => __ ( 'Filter' ),
2021-07-24 17:04:45 +02:00
'filter_toggle' => __ ( 'shifts.filter.toggle' ),
2018-08-29 21:55:32 +02:00
'set_yesterday' => __ ( 'Yesterday' ),
'set_today' => __ ( 'Today' ),
'set_tomorrow' => __ ( 'Tomorrow' ),
'set_last_8h' => __ ( 'last 8h' ),
'set_last_4h' => __ ( 'last 4h' ),
'set_next_4h' => __ ( 'next 4h' ),
'set_next_8h' => __ ( 'next 8h' ),
2017-12-25 23:12:52 +01:00
'buttons' => button (
public_dashboard_link (),
2021-07-22 21:22:21 +02:00
icon ( 'speedometer2' ) . __ ( 'Public Dashboard' )
2023-02-05 18:03:00 +01:00
),
]),
]),
2017-01-02 15:43:36 +01:00
]);
2011-12-28 14:45:49 +01:00
}
2017-12-26 20:41:35 +01:00
/**
* Returns a hint for the user how the ical feature works .
2020-04-20 00:01:37 +02:00
*
* @ return string
2017-12-26 20:41:35 +01:00
*/
2017-12-27 13:36:38 +01:00
function ical_hint ()
{
2018-10-31 12:48:22 +01:00
$user = auth () -> user ();
2022-10-18 19:15:22 +02:00
if ( ! auth () -> can ( 'ical' )) {
2020-04-20 00:01:37 +02:00
return '' ;
}
2017-12-26 20:41:35 +01:00
2019-12-09 22:59:20 +01:00
return heading ( __ ( 'iCal export and API' ) . ' ' . button_help ( 'user/ical' ), 2 )
2017-12-27 15:06:39 +01:00
. '<p>' . sprintf (
2018-08-29 21:55:32 +02:00
__ ( 'Export your own shifts. <a href="%s">iCal format</a> or <a href="%s">JSON format</a> available (please keep secret, otherwise <a href="%s">reset the api key</a>).' ),
2018-10-08 21:15:56 +02:00
page_link_to ( 'ical' , [ 'key' => $user -> api_key ]),
page_link_to ( 'shifts_json_export' , [ 'key' => $user -> api_key ]),
2017-12-26 20:41:35 +01:00
page_link_to ( 'user_myshifts' , [ 'reset' => 1 ])
2019-12-09 22:59:20 +01:00
)
2021-07-24 20:31:40 +02:00
. ' < button class = " btn btn-sm btn-danger " type = " button "
data - bs - toggle = " collapse " data - bs - target = " #collapseApiKey "
aria - expanded = " false " aria - controls = " collapseApiKey " >
2020-05-13 18:26:32 +02:00
' . __(' Show API Key ') . '
</ button > '
2019-12-09 22:59:20 +01:00
. '</p>'
. '<p id="collapseApiKey" class="collapse"><code>' . $user -> api_key . '</code></p>' ;
2017-12-26 20:41:35 +01:00
}
2017-01-03 03:22:48 +01:00
/**
* @ param array $array
* @ return array
*/
2017-01-02 03:57:23 +01:00
function get_ids_from_array ( $array )
{
2017-01-03 14:12:17 +01:00
return $array [ 'id' ];
2011-07-13 14:30:19 +02:00
}
2017-12-27 13:36:38 +01:00
/**
* @ param array $items
* @ param array $selected
* @ param string $name
* @ param string $title
2023-04-01 15:14:32 +02:00
* @ param int [] $ownSelect
2017-12-27 13:36:38 +01:00
* @ return string
*/
2023-04-01 15:14:32 +02:00
function make_select ( $items , $selected , $name , $title = null , $ownSelect = [])
2017-01-02 03:57:23 +01:00
{
2017-12-29 16:20:30 +01:00
$html = '' ;
2017-01-02 03:57:23 +01:00
if ( isset ( $title )) {
2017-12-29 16:20:30 +01:00
$html .= '<h4>' . $title . '</h4>' . " \n " ;
2017-01-02 03:57:23 +01:00
}
2017-01-02 15:43:36 +01:00
2023-04-01 15:14:32 +02:00
$buttons = [
button_checkbox_selection ( $name , __ ( 'All' ), 'true' ),
button_checkbox_selection ( $name , __ ( 'None' ), 'false' ),
];
if ( count ( $ownSelect ) > 0 ) {
$buttons [] = button_checkbox_selection ( $name , __ ( 'Own' ), json_encode ( $ownSelect ));
}
2017-12-29 16:20:30 +01:00
$html .= buttons ( $buttons );
2023-01-02 18:54:32 +01:00
$html .= '<div id="selection_' . $name . '" class="mb-3 selection ' . $name . '">' . " \n " ;
2017-12-29 16:20:30 +01:00
2023-01-02 18:54:32 +01:00
$htmlItems = [];
2017-01-02 03:57:23 +01:00
foreach ( $items as $i ) {
2022-11-30 00:14:08 +01:00
$id = $name . '_' . $i [ 'id' ];
$htmlItems [] = '<div class="form-check">'
. '<input class="form-check-input" type="checkbox" id="' . $id . '" name="' . $name . '[]" value="' . $i [ 'id' ] . '" '
2017-01-02 15:43:36 +01:00
. ( in_array ( $i [ 'id' ], $selected ) ? ' checked="checked"' : '' )
2022-12-03 23:49:55 +01:00
. '><label class="form-check-label" for="' . $id . '">' . $i [ 'name' ] . '</label>'
2022-12-02 23:03:23 +01:00
. ( ! isset ( $i [ 'enabled' ]) || $i [ 'enabled' ] ? '' : icon ( 'mortarboard-fill' ))
2018-01-22 20:45:31 +01:00
. '</div>' ;
2017-01-02 03:57:23 +01:00
}
2017-12-27 13:36:38 +01:00
$html .= implode ( " \n " , $htmlItems );
2023-01-02 18:54:32 +01:00
$html .= '</div>' . " \n " ;
2017-12-27 13:36:38 +01:00
$html .= buttons ( $buttons );
2017-01-02 03:57:23 +01:00
return $html ;
2011-07-13 14:30:19 +02:00
}