egroupware_official/timesheet/inc/class.timesheet_ui.inc.php
Ralf Becker 288f46aafd fixed not changed quantity, if duration was changed, caused by slightly different semantic for html5 placeholders to old eTemplate
old eTemplate returned empty value, if value was equal to placeholder, html5 shows placeholder only if value is empty, but will return a value equal to placeholder
--> I think we should go with html5 placeholder semantic and therefore change this in timesheet to give an empty value for quantity, if equal to duration and duration as placeholder/blur
2014-06-12 08:30:21 +00:00

1220 lines
41 KiB
PHP

<?php
/**
* TimeSheet - user interface
*
* @link http://www.egroupware.org
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @package timesheet
* @copyright (c) 2005-13 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @version $Id$
*/
/**
* User interface object of the TimeSheet
*/
class timesheet_ui extends timesheet_bo
{
var $public_functions = array(
'view' => true,
'edit' => true,
'index' => true,
'editstatus' => true,
);
/**
* ProjectManager integration: 'none', 'full' or default null
*
* @var string
*/
var $pm_integration;
/**
* TimeSheet view type: 'short' or 'normal'
*
* @var string
*/
var $ts_viewtype;
/**
* Constructor
*
*/
function __construct()
{
parent::__construct();
$this->pm_integration = $this->config_data['pm_integration'];
$this->ts_viewtype = $this->config_data['ts_viewtype'];
}
function view()
{
$this->edit(null,true);
}
function edit($content = null,$view = false)
{
$etpl = new etemplate_new('timesheet.edit');
if (!is_array($content))
{
if ($_GET['msg']) $msg = strip_tags($_GET['msg']);
if ($view || (int)$_GET['ts_id'])
{
if (!$this->read((int)$_GET['ts_id']))
{
egw_framework::window_close(lang('Permission denied!!!'));
}
if (!$view && !$this->check_acl(EGW_ACL_EDIT))
{
$view = true;
}
}
else // new entry
{
$this->data = array(
'ts_start' => $this->today,
'start_time' => '', // force empty start-time
'end_time' => egw_time::to($this->now,'H:i'),
'ts_owner' => $GLOBALS['egw_info']['user']['account_id'],
'cat_id' => (int) $_REQUEST['cat_id'],
'ts_status'=> $GLOBALS['egw_info']['user']['preferences']['timesheet']['predefined_status'],
);
}
$matches = null;
$referer = preg_match('/menuaction=([^&]+)/',$_SERVER['HTTP_REFERER'],$matches) ? $matches[1] :
(strpos($_SERVER['HTTP_REFERER'],'/infolog/index.php') !== false ? 'infolog.infolog_ui.index' : TIMESHEET_APP.'.timesheet_ui.index');
if (!$this->check_statusForEditRights($this->data))
{
$view = true; //only admin can edit with this status
$only_admin_edit = true;
$msg = lang('only Admin can edit this status');
}
}
else
{
//echo "<p>ts_start=$content[ts_start], start_time=$content[start_time], end_time=$content[end_time], ts_duration=$content[ts_duration], ts_quantity=$content[ts_quantity]</p>\n";
if (!isset($GLOBALS['egw_info']['user']['apps']['admin']) && $content['ts_status'])
{
if ($this->status_labels_config[$content['ts_status']]['admin'])
{
$view = true; //only admin can edit with this status
$only_admin_edit = true;
$msg = lang('only Admin can edit this status');
}
}
if ($this->ts_viewtype == 'short')
{
$content['start_time']=$content['end_time']=null;
}
// we only need 2 out of 3 values from start-, end-time or duration (the date in ts_start is always required!)
if (isset($content['start_time'])) // start-time specified
{
//$content['ts_start'] += $content['start_time'];
$start = new egw_time($content['ts_start']);
$start_time = explode(':',$content['start_time']);
$start->setTime($start_time[0],$start_time[1]);
$content['ts_start'] = $start->format('ts');
}
if (isset($content['end_time'])) // end-time specified
{
$end = new egw_time($content['ts_start']);
$end_time = explode(':',$content['end_time']);
$end->setTime($end_time[0],$end_time[1]);
}
if ($end && $start) // start- & end-time --> calculate the duration
{
$content['ts_duration'] = ($end->format('ts') - $start->format('ts')) / 60;
// check if negative duration is caused by wrap over midnight
if ($content['ts_duration'] < 0 && $content['ts_duration'] > -24*60)
{
$yesterday = new egw_time();
$yesterday->modify('-1day');
if ($start->format('Y-m-d') == $yesterday->format('Y-m-d'))
{
$content['ts_duration'] += 24*60;
}
}
//echo "<p>end_time=$content[end_time], start_time=$content[start_time] --> duration=$content[ts_duration]</p>\n";
}
elseif ($content['ts_duration'] && $end) // no start, calculate from end and duration
{
$content['ts_start'] = $end->format('ts') - 60*$content['ts_duration'];
//echo "<p>end_time=$content[end_time], duration=$content[ts_duration] --> ts_start=$content[ts_start]=".egw_time::to($content['ts_start'])."</p>\n";
}
if ($content['ts_duration'] > 0) unset($content['end_time']);
// now we only deal with start (date+time) and duration
list($button) = @each($content['button']);
$view = $content['view'];
$referer = $content['referer'];
$this->data = $content;
foreach(array('button','view','referer','tabs','start_time') as $key)
{
unset($this->data[$key]);
}
// user switched project to none --> remove project and blur
if ($this->data['ts_project_blur'] && !$this->data['pm_id'] && $this->data['old_pm_id'])
{
unset($this->data['ts_project_blur']);
unset($content['ts_project_blur']);
unset($this->data['ts_project']);
unset($content['ts_project']);
}
switch($button)
{
case 'edit':
if ($this->check_acl(EGW_ACL_EDIT) && !$only_admin_edit) $view = false;
break;
case 'undelete':
if($content['ts_status'] == self::DELETED_STATUS)
{
unset($content['ts_status']);
$this->data['ts_status'] = '';
}
$button = 'apply';
// fall through
case 'save':
case 'save_new':
case 'apply':
if ((!$this->data['ts_quantity'] || $this->ts_viewtype == 'short') && $this->data['ts_duration']) // set the quantity (in h) from the duration (in min)
{
$this->data['ts_quantity'] = $this->data['ts_duration'] / 60.0;
}
if (!$this->data['ts_quantity'])
{
$etpl->set_validation_error('ts_quantity',lang('Field must not be empty !!!'));
}
if ($this->data['ts_duration'] < 0) // for layout purpose we show the error behind the quantity field
{
$etpl->set_validation_error('ts_quantity',lang('Starttime has to be before endtime !!!'));
}
// only store project-blur, if a project is selected
if (!$this->data['ts_project'] && $this->data['pm_id']) $this->data['ts_project'] = $this->data['ts_project_blur'];
// set ts_title to ts_project if short viewtype (title is not editable)
if($this->ts_viewtype == 'short')
{
$this->data['ts_title'] = $this->data['ts_project'];
}
if (!$this->data['ts_title'])
{
$this->data['ts_title'] = $this->data['ts_title_blur'] ?
$this->data['ts_title_blur'] : $this->data['ts_project_blur'];
if (!$this->data['ts_title'])
{
$etpl->set_validation_error('ts_title',lang('Field must not be empty !!!'));
}
elseif($button != 'save_new')
{
// remove title-blur for same behavior after apply, as for opening the saved entry again
unset($this->data['ts_title_blur']);
unset($content['ts_title_blur']);
unset($this->data['ts_project_blur']);
unset($content['ts_project_blur']);
}
}
if ($etpl->validation_errors()) break; // the user need to fix the error, before we can save the entry
// check if we are linked to a project, but that is NOT set as project
if (!$this->data['pm_id'] && is_array($content['link_to']['to_id']))
{
foreach($content['link_to']['to_id'] as $data)
{
if ($data['app'] == 'projectmanager')
{
$this->data['pm_id'] = $data['id'];
break;
}
}
}
if ($this->save() != 0)
{
$msg = lang('Error saving the entry!!!');
$button = '';
}
else
{
$msg = lang('Entry saved');
if ((int) $this->data['pm_id'] != (int) $this->data['old_pm_id'])
{
// update links accordingly
if ($this->data['pm_id'])
{
egw_link::link(TIMESHEET_APP,$content['link_to']['to_id'],'projectmanager',$this->data['pm_id']);
}
if ($this->data['old_pm_id'])
{
egw_link::unlink2(0,TIMESHEET_APP,$content['link_to']['to_id'],0,'projectmanager',$this->data['old_pm_id']);
unset($this->data['old_pm_id']);
}
}
if (is_array($content['link_to']['to_id']) && count($content['link_to']['to_id']))
{
egw_link::link(TIMESHEET_APP,$this->data['ts_id'],$content['link_to']['to_id']);
}
}
egw_framework::refresh_opener($msg, 'timesheet', $this->data['ts_id'], $content['ts_id'] ? 'update' : 'add');
if ($button == 'apply') break;
if ($button == 'save_new')
{
$msg .= ', '.lang('creating new entry'); // giving some feedback to the user
if (!is_array($content['link_to']['to_id'])) // set links again, so new entry gets the same links as the existing one
{
$content['link_to']['to_id'] = 0;
foreach(egw_link::get_links(TIMESHEET_APP,$this->data['ts_id'],'!'.egw_link::VFS_APPNAME) as $link)
{
egw_link::link(TIMESHEET_APP,$content['link_to']['to_id'],$link['app'],$link['id'],$link['remark']);
}
}
// create a new entry
$this->data['ts_start'] += 60 * $this->data['ts_duration'];
foreach(array('ts_id','ts_title','ts_description','ts_duration','ts_quantity','ts_modified','ts_modifier') as $name)
{
unset($this->data[$name]);
}
// save the selected project, to delete the project-link, if the user changes the project
$this->data['old_pm_id'] = $this->data['pm_id'];
break;
}
// fall-through for save
case 'delete':
if ($button == 'delete')
{
if ($this->delete())
{
$msg = lang('Entry deleted');
egw_framework::refresh_opener($msg, 'timesheet', $this->data['ts_id'], 'delete');
}
else
{
$msg = lang('Error deleting the entry!!!');
break; // dont close window
}
}
// fall-through for save
case 'cancel':
egw_framework::window_close();
}
}
$preserv = $this->data + array(
'view' => $view,
'referer' => $referer,
'ts_title_blur' => $content['ts_title_blur'],
);
$content = array_merge($this->data,array(
'msg' => $msg,
'view' => $view,
'tabs' => $content['tabs'],
'link_to' => array(
'to_id' => $this->data['ts_id'] ? $this->data['ts_id'] : $content['link_to']['to_id'],
'to_app' => TIMESHEET_APP,
),
'ts_quantity_blur' => $this->data['ts_duration'] ? round($this->data['ts_duration'] / 60.0,3) : '',
'ts_quantity' => $this->data['ts_duration']/60.0 == $this->data['ts_quantity'] ? null : $this->data['ts_quantity'],
'start_time' => isset($this->data['start_time']) ? $this->data['start_time'] : $this->data['ts_start'],
'pm_integration' => $this->pm_integration,
'no_ts_status' => !$this->status_labels && ($this->data['ts_status'] != self::DELETED_STATUS),
));
$links = array();
// create links specified in the REQUEST (URL)
if (!$this->data['ts_id'] && isset($_REQUEST['link_app']) && isset($_REQUEST['link_id']) && !is_array($content['link_to']['to_id']))
{
$link_ids = is_array($_REQUEST['link_id']) ? $_REQUEST['link_id'] : array($_REQUEST['link_id']);
foreach(is_array($_REQUEST['link_app']) ? $_REQUEST['link_app'] : array($_REQUEST['link_app']) as $n => $link_app)
{
$link_id = $link_ids[$n];
if (preg_match('/^[a-z_0-9-]+:[:a-z_0-9-]+$/i',$link_app.':'.$link_id)) // gard against XSS
{
switch ($link_app)
{
case 'projectmanager':
$links[] = $link_id;
// fall-through;
default:
if(!$n)
{
// get title from first linked app
$preserv['ts_title_blur'] = egw_link::title($link_app,$link_id);
// ask first linked app via "timesheet_set" hook, for further data to set, incl. links
if (($set = $GLOBALS['egw']->hooks->single(array('location'=>'timesheet_set','id'=>$link_id),$link_app)))
{
foreach((array)$set['link_app'] as $i => $l_app)
{
if (($l_id=$set['link_id'][$i])) egw_link::link(TIMESHEET_APP,$content['link_to']['to_id'],$l_app,$l_id);
if ($l_app == 'projectmanager') $links[] = $l_id;
}
unset($set['link_app']);
unset($set['link_id']);
$content = array_merge($content,$set);
}
}
break;
}
egw_link::link(TIMESHEET_APP,$content['link_to']['to_id'],$link_app,$link_id);
}
}
}
elseif ($this->data['ts_id'])
{
$links = egw_link::get_links(TIMESHEET_APP,$this->data['ts_id'],'projectmanager');
}
// make all linked projects availible for the pm-pricelist widget, to be able to choose prices from all
$content['all_pm_ids'] = array_values($links);
// set old id, pm selector (for later removal)
if (count($links) > 0)
{
$preserv['old_pm_id'] = array_shift($links);
}
if (!isset($this->data['pm_id']) && $preserv['old_pm_id'])
{
$content['pm_id'] = $preserv['old_pm_id'];
}
if ($content['pm_id'])
{
$preserv['ts_project_blur'] = $content['ts_project_blur'] = egw_link::title('projectmanager',$content['pm_id']);
}
if ($this->pm_integration == 'full')
{
$preserv['ts_project'] = $preserv['ts_project_blur'];
}
$content['history'] = array(
'id' => $this->data['ts_id'],
'app' => 'timesheet',
'status-widgets' => array(
'ts_status' => $this->status_labels + array(self::DELETED_STATUS => 'Deleted'),
'ts_modifier' => 'select-account',
'cat_id' => 'select-cat',
),
);
$sel_options['status'] = $this->field2label;
// the actual title-blur is either the preserved title blur (if we are called from infolog entry),
// or the preserved project-blur comming from the current selected project
$content['ts_title_blur'] = $preserv['ts_title_blur'] ? $preserv['ts_title_blur'] : $preserv['ts_project_blur'];
$readonlys = array(
'button[delete]' => !$this->data['ts_id'] || !$this->check_acl(EGW_ACL_DELETE) || $this->data['ts_status'] == self::DELETED_STATUS,
'button[undelete]' => $this->data['ts_status'] != self::DELETED_STATUS,
'button[edit]' => !$view || !$this->check_acl(EGW_ACL_EDIT),
'button[save]' => $view,
'button[save_new]' => $view,
'button[apply]' => $view,
);
if ($view)
{
foreach(array_merge(array_keys($this->data),array('pm_id','pl_id','link_to')) as $key)
{
$readonlys[$key] = true;
}
$readonlys['start_time'] = $readonlys['end_time'] = true;
}
$edit_grants = $this->grant_list(EGW_ACL_EDIT);
if (count($edit_grants) == 1)
{
$readonlys['ts_owner'] = true;
}
// in view mode, we need to add the owner, if it does not exist, otherwise it's displayed empty
if ($view && $content['ts_owner'] && !isset($edit_grants[$content['ts_owner']]))
{
$edit_grants[$content['ts_owner']] = common::grab_owner_name($content['ts_owner']);
}
$sel_options['ts_owner'] = $edit_grants;
$sel_options['ts_status'] = $this->status_labels;
if($this->config_data['history'] && $content['ts_status'] == self::DELETED_STATUS)
{
$sel_options['ts_status'][self::DELETED_STATUS] = 'Deleted';
}
$GLOBALS['egw_info']['flags']['app_header'] = lang('timesheet').' - '.
($view ? lang('View') : ($this->data['ts_id'] ? lang('Edit') : lang('Add')));
// supress unknow widget 'projectmanager-*', if projectmanager is not installed or old
if (!@file_exists(EGW_INCLUDE_ROOT.'/projectmanager/inc/class.projectmanager_widget.inc.php'))
{
$etpl->set_cell_attribute('pm_id','disabled',true);
$etpl->set_cell_attribute('pl_id','disabled',true);
}
if($this->ts_viewtype == 'short')
{
$content['ts_viewtype'] = $readonlys['tabs']['notes'] = true;
}
if (!$this->customfields) $readonlys['tabs']['customfields'] = true; // suppress tab if there are not customfields
if (!$this->data['ts_id']) $readonlys['tabs']['history'] = true; //suppress history for the first loading without ID
return $etpl->exec(TIMESHEET_APP.'.timesheet_ui.edit',$content,$sel_options,$readonlys,$preserv,2);
}
/**
* Calculate the time from a timestamp containing date & time
*
* @param int $datetime
* @return int
*/
function datetime2time($datetime)
{
if (!$datetime) return 0;
return $datetime - mktime(0,0,0,date('m',$datetime),date('d',$datetime),date('Y',$datetime));
}
/**
* query projects for nextmatch in the projects-list
*
* reimplemented from so_sql to disable action-buttons based on the acl and make some modification on the data
*
* @param array &$query
* @param array &$rows returned rows/cups
* @param array &$readonlys eg. to disable buttons based on acl
* @param boolean $id_only=false if true only return (via $rows) an array of contact-ids, dont save state to session
* @return int total number of contacts matching the selection
*/
function get_rows(&$query_in,&$rows,&$readonlys,$id_only=false)
{
$this->show_sums = false;
if ($query_in['filter'])
{
$date_filter = $this->date_filter($query_in['filter'],$query_in['startdate'],$query_in['enddate']);
$start = explode('-',date('Y-m-d',$query_in['startdate']+12*60*60));
$end = explode('-',date('Y-m-d',$query_in['enddate'] ? $query_in['enddate'] : $query_in['startdate']+7.5*24*60*60));
// show year-sums, if we are year-aligned (show full years)?
if ((int)$start[2] == 1 && (int)$start[1] == 1 && (int)$end[2] == 31 && (int)$end[1] == 12)
{
$this->show_sums[] = 'year';
}
// show month-sums, if we are month-aligned (show full monthes)?
if ((int)$start[2] == 1 && (int)$end[2] == (int)date('d',mktime(12,0,0,$end[1]+1,0,$end[0])))
{
$this->show_sums[] = 'month';
}
// show week-sums, if we are week-aligned (show full weeks)?
$week_start_day = $GLOBALS['egw_info']['user']['preferences']['calendar']['weekdaystarts'];
if (!$week_start_day) $week_start_day = 'Sunday';
switch($week_start_day)
{
case 'Sunday': $week_end_day = 'Saturday'; break;
case 'Monday': $week_end_day = 'Sunday'; break;
case 'Saturday': $week_end_day = 'Friday'; break;
}
$filter_start_day = date('l',$query_in['startdate']+12*60*60);
$filter_end_day = $query_in['enddate'] ? date('l',$query_in['enddate']+12*60*60) : false;
//echo "<p align=right>prefs: $week_start_day - $week_end_day, filter: $filter_start_day - $filter_end_day</p>\n";
if ($filter_start_day == $week_start_day && (!$filter_end_day || $filter_end_day == $week_end_day))
{
$this->show_sums[] = 'week';
}
// show day-sums, if range <= 5 weeks
if (!$query_in['enddate'] || $query_in['enddate'] - $query_in['startdate'] < 36*24*60*60)
{
$this->show_sums[] = 'day';
}
}
//echo "<p align=right>show_sums=".print_r($this->show_sums,true)."</p>\n";
if (!$id_only && !$query_in['csv_export']) $GLOBALS['egw']->session->appsession('index',TIMESHEET_APP,$query_in);
// Refresh actions (undelete needs this)
$query_in['actions'] = $this->get_actions($query_in);
$query = $query_in; // keep the original query
if($this->ts_viewtype == 'short') $query_in['options-selectcols'] = array('ts_quantity'=>false,'ts_unitprice'=>false,'ts_total'=>false);
if ($query['no_status']) $query_in['options-selectcols']['ts_status'] = false;
//_debug_array($query['col_filter']);
//echo "PM Integration:".$this->pm_integration.'<br>';
// PM project filter for the PM integration
if ($this->pm_integration == 'full')
{
unset($query['col_filter']['ts_project']);
}
if ((string)$query['col_filter']['pm_id'] != '' && (string)$query['col_filter']['pm_id'] != '0')
{
//$query['col_filter']['ts_id'] = egw_link::get_links('projectmanager',$query['col_filter']['pm_id'],'timesheet');
$query['col_filter']['ts_id'] = $this->get_ts_links($query['col_filter']['pm_id']);
if (empty($query['col_filter']['ts_id'])) $query['col_filter']['ts_id'] = -1;
if (!$query['col_filter']['ts_id']) $query['col_filter']['ts_id'] = 0;
}
if ((string)$query['col_filter']['pm_id'] != '' && (string)$query['col_filter']['pm_id'] == '0')
{
$query['col_filter']['ts_project'] = 0;
unset($query['col_filter']['ts_id']);
}
unset($query['col_filter']['pm_id']);
// handle linked filter (show only entries linked to a certain other entry)
if ($query['col_filter']['linked'])
{
list($app,$id) = explode(':',$query['col_filter']['linked']);
if (!($links = egw_link::get_links($app,$id,'timesheet')))
{
$rows = array(); // no infologs linked to project --> no rows to return
return 0;
}
if (!$query['col_filter']['ts_id'])
{
$query['col_filter']['ts_id'] = array_values(array_unique($links));
}
// allow to combine with other filters using ts_id --> intersect ids
elseif (!($query['col_filter']['ts_id'] = array_intersect((array)$query['col_filter']['ts_id'],array_values(array_unique($links)))))
{
$rows = array(); // no infologs linked to project --> no rows to return
return 0;
}
}
unset($query['col_filter']['linked']);
// filter for no project
if ((string)$query['col_filter']['ts_project'] == '0')
{
$query['col_filter']['ts_project'] = null;
}
// filter for no status
if ((string)$query['col_filter']['ts_status'] == '0')
{
$query['col_filter']['ts_status'] = null;
}
#_debug_array($query['col_filter']);
if (isset($this->status_labels_substatus[$query['col_filter']['ts_status']]))
{
$query['col_filter']['ts_status'] = $this->status_labels_substatus[$query['col_filter']['ts_status']];
foreach ($query['col_filter']['ts_status'] as $status_id)
{
if (isset($this->status_labels_substatus['2level'][$status_id]))
{
$query['col_filter']['ts_status'] = array_merge($query['col_filter']['ts_status'],$this->status_labels_substatus[$status_id]);
}
}
}
if ((int)$query['filter2'] != (int)$GLOBALS['egw_info']['user']['preferences'][TIMESHEET_APP]['show_details'])
{
$GLOBALS['egw']->preferences->add(TIMESHEET_APP,'show_details',(int)$query['filter2']);
$GLOBALS['egw']->preferences->save_repository(true);
}
// category filter: cat_id or ''=All cats or 0=No cat
if ($query['cat_id'])
{
$cats = $GLOBALS['egw']->categories->return_all_children((int)$query['cat_id']);
$query['col_filter']['cat_id'] = count($cats) > 1 ? $cats : $query['cat_id'];
}
elseif ((string)$query['cat_id'] == '0') // no category
{
$query['col_filter']['cat_id'] = null;
}
else // all cats --> no filter
{
unset($query['col_filter']['cat_id']);
}
$GLOBALS['egw_info']['flags']['app_header'] = lang('timesheet');
if ($query['col_filter']['ts_owner'])
{
$GLOBALS['egw_info']['flags']['app_header'] .= ': '.common::grab_owner_name($query['col_filter']['ts_owner']);
#if ($GLOBALS['egw']->accounts->get_type($query['col_filter']['ts_owner']) == 'g') $GLOBALS['egw_info']['flags']['app_header'] .= ' '. lang("and its members");
#_debug_array($GLOBALS['egw']->accounts->members($query['col_filter']['ts_owner'],true));
if ($query['col_filter']['ts_owner']<0) $query['col_filter']['ts_owner'] = array_merge(array($query['col_filter']['ts_owner']),$GLOBALS['egw']->accounts->members($query['col_filter']['ts_owner'],true));
}
else
{
unset($query['col_filter']['ts_owner']);
}
if ($query['filter'])
{
$query['col_filter'][0] = $date_filter;
// generate a meaningful app-header / report title
if ($this->show_sums['month'])
{
if ((int)$start[1] == 1 && (int) $end[1] == 12) // whole year(s)
{
$GLOBALS['egw_info']['flags']['app_header'] .= ': ' . $start[0] . ($start[0] != $end[0] ? ' - '.$end[0] : '');
}
else
{
$GLOBALS['egw_info']['flags']['app_header'] .= ': ' . lang(date('F',$query['startdate']+12*60*60)) . ' ' . $start[0];
if ($start[0] != $end[0] || $start[1] != $end[1])
{
$GLOBALS['egw_info']['flags']['app_header'] .= ' - ' . lang(date('F',$query['enddate']+12*60*60)) . ' ' . $end[0];
}
}
}
elseif ($this->show_sums['week'])
{
$GLOBALS['egw_info']['flags']['app_header'] .= ': ' . lang('week') . ' ' . date('W',$query['startdate']+36*60*60) . '/' . $start[0];
if ($query['enddate'] && $query['enddate'] - $query['startdate'] > 10*24*60*60)
{
$GLOBALS['egw_info']['flags']['app_header'] .= ' - ' . date('W',$query['enddate']-36*60*60) . '/' . $end[0];
}
}
else
{
$df = $GLOBALS['egw_info']['user']['preferences']['common']['dateformat'];
$GLOBALS['egw_info']['flags']['app_header'] .= ': ' . common::show_date($query['startdate']+12*60*60,$df,false);
if ($start != $end)
{
$GLOBALS['egw_info']['flags']['app_header'] .= ' - '.common::show_date($query['enddate']+12*60*60,$df,false);
}
}
}
$total = parent::get_rows($query,$rows,$readonlys);
$ids = array();
foreach($rows as &$row)
{
if ($row['ts_id'] > 0) $ids[] = $row['ts_id'];
}
if ($id_only)
{
$rows = $ids;
return $this->total; // no need to set other fields or $readonlys
}
$links = array();
$links3 = egw_link::get_links_multiple(TIMESHEET_APP,$ids,true,'projectmanager'); // only check for pm links!
//as the full array is expected, we must supply the missing but needed (since expected further down) information
if (is_array($links3))
{
foreach ($links3 as $likey => $liarray)
{
if (is_array($liarray))
{
foreach ($liarray as $li2key => $lival)
{
$links[$likey][$li2key]['id'] = $lival;
$links[$likey][$li2key]['app'] = 'projectmanager';
}
}
}
}
unset($query['col_filter'][0]);
$readonlys = array();
$have_cats = false;
foreach($rows as &$row)
{
if ($row['cat_id']) $have_cats = true;
$row['class'] = 'row';
if ($row['ts_id'] <= 0) // sums
{
if ($query['sort'] == 'ASC') $row['ts_start'] -= 7200; // fix for DSL change
// Remove fake modified date, it breaks nextmatch checks
unset($row['ts_modified']);
switch($row['ts_id'])
{
case 0: // day-sum
$row['ts_title'] = lang('Sum %1:',lang(date('l',$row['ts_start'])).' '.common::show_date($row['ts_start'],
$GLOBALS['egw_info']['user']['preferences']['common']['dateformat'],false));
$row['ts_id'] = 'sum-day-'.$row['ts_start'];
break;
case -1: // week-sum
$row['ts_title'] = lang('Sum %1:',lang('week').' '.substr($row['ts_week'],4).'/'.substr($row['ts_week'],0,4));
$row['ts_id'] = 'sum-week-'.$row['ts_week'];
break;
case -2: // month-sum
$row['ts_title'] = lang('Sum %1:',lang(date('F',$row['ts_start'])).' '.substr($row['ts_month'],0,4));
$row['ts_id'] = 'sum-month-'.$row['ts_month'];
break;
case -3: // year-sum
$row['ts_title'] = lang('Sum %1:',$row['ts_year']);
$row['ts_id'] = 'sum-year-'.$row['ts_year'];
break;
}
$row['ts_start'] = $row['ts_unitprice'] = '';
if (!$this->quantity_sum) $row['ts_quantity'] = '';
$row['class'] = 'th rowNoEdit rowNoDelete rowNoUndelete';
$row['titleClass'] = 'timesheet_titleSum';
continue;
}
if (!$this->check_acl(EGW_ACL_EDIT,$row))
{
$row['class'] .= ' rowNoEdit ';
}
if (!$this->check_statusForEditRights($row))
{
$row['class'] .= ' rowNoEdit ';
}
if (!$this->check_acl(EGW_ACL_DELETE,$row))
{
$row['class'] .= ' rowNoDelete ';
}
if($row['ts_status'] != self::DELETED_STATUS)
{
$row['class'] .= ' rowNoUndelete ';
}
if ($query['col_filter']['ts_project'])
{
unset($row['ts_project']); // dont need or want to show it
}
elseif ($links[$row['ts_id']])
{
foreach($links[$row['ts_id']] as $link)
{
if ($link['app'] == 'projectmanager')
{
$row['ts_link'] = $link;
$row['ts_link']['title'] = $row['ts_project'];
break;
}
}
}
if(!$row['titleClass']) $row['titleClass'] = 'timesheet_titleDetails';
}
if (!$have_cats || $query['cat_id']) $rows['no_cat_id'] = true;
if ($query['col_filter']['ts_owner']) $rows['ownerClass'] = 'noPrint';
$rows['no_owner_col'] = $query['no_owner_col'];
if (!$rows['no_owner_col'] && $query['selectcols'] && !strpos($query['selectcols'],'ts_owner')) $rows['no_owner_col'] = 1;
if ($query['filter'])
{
$rows += $this->summary;
}
$rows['pm_integration'] = $this->pm_integration;
$rows['ts_viewtype'] = $rows['no_ts_quantity'] = $rows['no_ts_unitprice'] = $rows['no_ts_total'] = $this->ts_viewtype == 'short';
if (!$rows['ts_viewtype'])
{
#_debug_array($query['selectcols']);
#ts_quantity,ts_unitprice,ts_total
if ($query['selectcols'] && strpos($query['selectcols'],'ts_quantity')===false) $rows['no_ts_quantity'] = 1;
if ($query['selectcols'] && strpos($query['selectcols'],'ts_unitprice')===false) $rows['no_ts_unitprice'] = 1;
if ($query['selectcols'] && strpos($query['selectcols'],'ts_total')===false) $rows['no_ts_total'] = 1;
}
$rows['no_ts_status'] = strpos($query['selectcols'], 'ts_status') === false && !$this->config_data['history'] ||
$query['no_status'];
return $total;
}
/**
* List timesheet entries
*
* @param array $content=null
* @param string $msg=''
*/
function index($content = null,$msg='')
{
$etpl = new etemplate_new('timesheet.index');
if ($_GET['msg']) $msg = $_GET['msg'];
if ($content['nm']['rows']['delete'])
{
list($ts_id) = each($content['nm']['rows']['delete']);
if ($this->delete($ts_id))
{
$msg = lang('Entry deleted');
}
else
{
$msg = lang('Error deleting the entry!!!');
}
}
if (is_array($content) && isset($content['nm']['rows']['document'])) // handle insert in default document button like an action
{
list($id) = @each($content['nm']['rows']['document']);
$content['nm']['action'] = 'document';
$content['nm']['selected'] = array($id);
}
if ($content['nm']['action'])
{
// remove sum-* rows from checked rows
$content['nm']['selected'] = array_filter($content['nm']['selected'], function($id)
{
return $id > 0;
});
if (!count($content['nm']['selected']) && !$content['nm']['select_all'])
{
$msg = lang('You need to select some entries first!');
}
else
{
$success = $failed = $action_msg = null;
if ($this->action($content['nm']['action'],$content['nm']['selected'],$content['nm']['select_all'],
$success,$failed,$action_msg,'index',$msg))
{
$msg .= lang('%1 timesheets(s) %2',$success,$action_msg);
}
elseif(empty($msg))
{
$msg .= lang('%1 timesheets(s) %2, %3 failed because of insufficent rights !!!',$success,$action_msg,$failed);
}
}
}
$content = array(
'nm' => $GLOBALS['egw']->session->appsession('index',TIMESHEET_APP),
'msg' => $msg,
);
if (!is_array($content['nm']))
{
$date_filters = array('All');
foreach(array_keys($this->date_filters) as $name)
{
$date_filters[$name] = $name;
}
$date_filters['custom'] = 'custom';
$content['nm'] = array(
'get_rows' => TIMESHEET_APP.'.timesheet_ui.get_rows',
'options-filter' => $date_filters,
'options-filter2' => array('No details','Details'),
'order' => 'ts_start',// IO name of the column to sort after (optional for the sortheaders)
'sort' => 'DESC',// IO direction of the sort: 'ASC' or 'DESC'
'header_left' => 'timesheet.index.dates',
'header_row' => 'timesheet.index.add',
'filter_onchange' => "app.timesheet.filter_change();",
'filter2_onchange' => "app.timesheet.filter2_change();",
'filter2' => (int)$GLOBALS['egw_info']['user']['preferences'][TIMESHEET_APP]['show_details'],
'row_id' => 'ts_id',
'row_modified' => 'ts_modified',
//'actions' => $this->get_actions(),
'default_cols' => '!legacy_actions', // switch legacy actions column and row off by default
);
}
$content['nm']['actions'] = $this->get_actions($content['nm']);
if($_GET['search'])
{
$content['nm']['search'] = $_GET['search'];
}
$read_grants = $this->grant_list(EGW_ACL_READ);
$content['nm']['no_owner_col'] = count($read_grants) == 1;
if ($GLOBALS['egw_info']['user']['preferences']['timesheet']['nextmatch-timesheet.index.rows']) $content['nm']['selectcols'] = $GLOBALS['egw_info']['user']['preferences']['timesheet']['nextmatch-timesheet.index.rows'];
$sel_options = array(
'ts_owner' => $read_grants,
'pm_id' => array(lang('No project')),
'cat_id' => array(lang('None')),
'ts_status' => $this->status_labels+array(lang('No status')),
);
if($this->config_data['history'])
{
$sel_options['ts_status'][self::DELETED_STATUS] = 'Deleted';
}
$content['nm']['no_status'] = count($sel_options['ts_status']) <= 1; // 1 because of 'No status'
$content['nm']['favorites'] = true; //Enable favorite
if ($this->pm_integration != 'full')
{
$projects =& $this->query_list('ts_project');
if (!is_array($projects)) $projects = array();
$sel_options['ts_project'] = $projects + array(lang('No project'));
}
// dont show [Export] button if app is not availible to the user or we are on php4
$readonlys['export'] = !$GLOBALS['egw_info']['user']['apps']['importexport'] || (int) phpversion() < 5;
return $etpl->exec(TIMESHEET_APP.'.timesheet_ui.index',$content,$sel_options,$readonlys);
}
/**
* Get actions / context menu for index
*
* Changes here, require to log out, as $content['nm'] get stored in session!
*
* @return array see nextmatch_widget::egw_actions()
*/
private function get_actions(Array $query)
{
$actions = array(
'open' => array( // does edit if allowed, otherwise view
'caption' => 'Open',
'default' => true,
'allowOnMultiple' => false,
'url' => 'menuaction=timesheet.timesheet_ui.edit&ts_id=$id',
'popup' => egw_link::get_registry('timesheet', 'add_popup'),
'group' => $group=1,
'disableClass' => 'th',
),
/*
'view' => array(
'caption' => 'View',
'default' => true,
'allowOnMultiple' => false,
'url' => 'menuaction=timesheet.timesheet_ui.view&ts_id=$id',
'popup' => egw_link::get_registry('timesheet', 'view_popup'),
'group' => $group=1,
),
'edit' => array(
'caption' => 'Edit',
'allowOnMultiple' => false,
'url' => 'menuaction=timesheet.timesheet_ui.edit&ts_id=$id',
'popup' => egw_link::get_registry('timesheet', 'add_popup'),
'group' => $group,
'disableClass' => 'rowNoEdit',
),
*/
'add' => array(
'caption' => 'Add',
'url' => 'menuaction=timesheet.timesheet_ui.edit',
'popup' => egw_link::get_registry('timesheet', 'add_popup'),
'group' => $group,
),
'cat' => etemplate_widget_nextmatch::category_action(
'timesheet',++$group,'Change category','cat_'
),
'status' => array(
'icon' => 'apply',
'caption' => 'Modify status',
'group' => $group,
'children' => $this->status_labels,
'prefix' => 'to_status_',
'enabled' => (boolean)$this->status_labels,
),
);
// Other applications
$group++;
if ($GLOBALS['egw_info']['user']['apps']['filemanager'])
{
$actions['filemanager'] = array(
'icon' => 'filemanager/navbar',
'caption' => 'Filemanager',
'url' => 'menuaction=filemanager.filemanager_ui.index&path=/apps/timesheet/$id&ajax=true',
'allowOnMultiple' => false,
'group' => $group,
'disableClass' => 'th',
);
}
$group++;
$actions += array(
'documents' => timesheet_merge::document_action(
$GLOBALS['egw_info']['user']['preferences']['timesheet']['document_dir'],
$group, 'Insert in document', 'document_',
$GLOBALS['egw_info']['user']['preferences']['timesheet']['default_document']
),
'delete' => array(
'caption' => 'Delete',
'confirm' => 'Delete this entry',
'confirm_multiple' => 'Delete these entries',
'group' => ++$group,
'disableClass' => 'rowNoDelete',
),
);
if ($query['col_filter']['ts_status'] == self::DELETED_STATUS)
{
$actions['undelete'] = array(
'caption' => 'Un-Delete',
'confirm' => 'Recover this entry',
'confirm_multiple' => 'Recover these entries',
'icon' => 'revert',
'group' => $group,
'disableClass' => 'rowNoUndelete',
);
}
// enable additonal edit check for following actions, if they are generally available
foreach(array('cat','status') as $action)
{
if ($actions[$action]['enabled'])
{
$actions[$action]['disableClass'] = 'rowNoEdit';
}
}
//_debug_array($actions);
return $actions;
}
/**
* apply an action to multiple timesheets
*
* @param string/int $action 'status_to',set status to timeshhets
* @param array $checked timesheet id's to use if !$use_all
* @param boolean $use_all if true use all$key timesheets of the current selection (in the session)
* @param int &$success number of succeded actions
* @param int &$failed number of failed actions (not enought permissions)
* @param string &$action_msg translated verb for the actions, to be used in a message like %1 timesheets 'deleted'
* @param string/array $session_name 'index' or 'email', or array with session-data depending if we are in the main list or the popup
* @return boolean true if all actions succeded, false otherwise
*/
function action($action,$checked,$use_all,&$success,&$failed,&$action_msg,$session_name,&$msg)
{
$success = $failed = 0;
if ($use_all)
{
// get the whole selection
$query = is_array($session_name) ? $session_name : $GLOBALS['egw']->session->appsession($session_name,'timesheet');
if ($use_all)
{
@set_time_limit(0); // switch off the execution time limit, as it's for big selections to small
$query['num_rows'] = -1; // all
$readonlys = null;
$this->get_rows($query,$checked,$readonlys,true); // true = only return the id's
}
}
//error_log(__METHOD__."('$action', ".array2string($checked).', '.array2string($use_all).",,, '$session_name')");
if (substr($action,0,9) == 'to_status')
{
$to_status = (int)substr($action,10);
$action = 'to_status';
}
else
{
// Dialogs to get options
list($action, $settings) = explode('_', $action, 2);
}
switch($action)
{
case 'delete':
$action_msg = lang('deleted');
foreach((array)$checked as $n => $id)
{
if ($this->delete($id))
{
$success++;
}
else
{
$failed++;
}
}
break;
case 'undelete':
$action_msg =lang('recovered');
foreach((array)$checked as $n => $id)
{
if ($this->set_status($id,''))
{
$success++;
}
else
{
$failed++;
}
}
break;
case 'to_status':
$action_msg =lang('changed status');
foreach((array)$checked as $n => $id)
{
if ($this->set_status($id,$to_status))
{
$success++;
}
else
{
$failed++;
}
}
break;
case 'cat':
$cat_name = categories::id2name($settings);
$action_msg = lang('changed category to %1', $cat_name);
foreach((array)$checked as $n => $id) {
$entry = $this->read($id);
$entry['cat_id'] = $settings;
if($this->save($entry) == 0)
{
$success++;
}
else
{
$failed++;
}
}
break;
case 'document':
if (!$settings) $settings = $GLOBALS['egw_info']['user']['preferences']['timesheet']['default_document'];
$document_merge = new timesheet_merge();
$msg = $document_merge->download($settings, $checked, '', $GLOBALS['egw_info']['user']['preferences']['timesheet']['document_dir']);
$failed = count($checked);
return false;
}
return !$failed;
}
/**
* function for setting individual Status
*
* @param conetnt
* @param view
*/
function editstatus($content = null,$msg='')
{
// this function requires admin rights
$GLOBALS['egw_info']['flags']['admin_only'] = true;
$GLOBALS['egw']->check_app_rights();
if (is_array($content))
{
list($button) = @each($content['button']);
unset ($content['button']);
switch($button)
{
case 'delete':
break;
case 'apply':
case 'save':
foreach($content['statis'] as &$cat)
{
$id = $cat['id'];
if (($cat ['name'] !== $this->status_labels_config[$id]) && ($cat ['name'] !== '') || ($cat ['parent'] !== $this->status_labels_config[$id]['parent']) && ($cat ['parent'] !== ''))
{
$this->status_labels_config[$id] = array(
'name' => trim(str_replace('&nbsp;', '', $cat['name'])),
'parent' => $cat['parent'],
'admin' => $cat['admin']);
$need_update = true;
}
}
if ($need_update)
{
config::save_value('status_labels',$this->status_labels_config,TIMESHEET_APP);
$this->config_data = config::read(TIMESHEET_APP);
$this->load_statuses();
$msg .= lang('Status updated.');
}
if ($button == 'apply') break;
// fall-through
case 'cancel':
$GLOBALS['egw']->redirect_link('/index.php',array(
'menuaction' => 'timesheet.timesheet_ui.index',
'msg' => $msg,
));
break;
}
}
if (isset($content['statis']['delete']))
{
list($id) = each($content['statis']['delete']);
if (isset($this->status_labels_config[$id]))
{
unset($this->status_labels_config[$id]);
config::save_value('status_labels',$this->status_labels_config,TIMESHEET_APP);
unset($this->status_labels[$id]);
$msg .= lang('Status deleted.');
}
}
$i = 1;
$max_id = 0;
unset($content['statis']);
foreach($this->status_labels_config as $id => $label)
{
$content['statis'][$i]['name']= $label['name'];
$content['statis'][$i]['id']= $id;
$content['statis'][$i]['parent']= $label['parent'];
$content['statis'][$i]['admin']= $label['admin'];
$i++;
$max_id = max($id, $max_id);
}
$content['statis'][$i]['name'] = '';
$content['statis'][$i]['admin'] = '';
$content['statis'][$i]['id'] = ++$max_id;
$content['msg'] = $msg;
$preserv = $content;
$sel_options['parent'] = $this->status_labels;
$etpl = new etemplate_new('timesheet.editstatus');
$etpl->exec('timesheet.timesheet_ui.editstatus',$content,$sel_options,array(),$preserv);
}
}