forked from extern/egroupware
8d2194f191
At the date when the change occured - yesterday ;-) - starttimes where wrong by one hour, because difference of timestamp of daystart and current time was one hour more (because of the switch)
744 lines
23 KiB
PHP
744 lines
23 KiB
PHP
<?php
|
|
/**
|
|
* TimeSheet - business object
|
|
*
|
|
* @link http://www.egroupware.org
|
|
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
|
* @package timesheet
|
|
* @copyright (c) 2005-9 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
|
* @version $Id$
|
|
*/
|
|
|
|
if (!defined('TIMESHEET_APP'))
|
|
{
|
|
define('TIMESHEET_APP','timesheet');
|
|
}
|
|
|
|
/**
|
|
* Business object of the TimeSheet
|
|
*
|
|
* Uses eTemplate's so_sql as storage object (Table: egw_timesheet).
|
|
*
|
|
* @todo Implement sorting&filtering by and searching of custom fields
|
|
*/
|
|
class timesheet_bo extends so_sql_cf
|
|
{
|
|
/**
|
|
* Timesheets config data
|
|
*
|
|
* @var array
|
|
*/
|
|
var $config_data = array();
|
|
/**
|
|
* Should we show a quantity sum, makes only sense if we sum up identical units (can be used to sum up negative (over-)time)
|
|
*
|
|
* @var boolean
|
|
*/
|
|
var $quantity_sum=false;
|
|
/**
|
|
* Timestaps that need to be adjusted to user-time on reading or saving
|
|
*
|
|
* @var array
|
|
*/
|
|
var $user;
|
|
/**
|
|
* current user
|
|
*
|
|
* @var int
|
|
*/
|
|
var $timestamps = array(
|
|
'ts_start','ts_modified'
|
|
);
|
|
/**
|
|
* Start of today in user-time
|
|
*
|
|
* @var int
|
|
*/
|
|
var $today;
|
|
/**
|
|
* Filter for search limiting the date-range
|
|
*
|
|
* @var array
|
|
*/
|
|
var $date_filters = array( // Start: year,month,day,week, End: year,month,day,week
|
|
'Today' => array(0,0,0,0, 0,0,1,0),
|
|
'Yesterday' => array(0,0,-1,0, 0,0,0,0),
|
|
'This week' => array(0,0,0,0, 0,0,0,1),
|
|
'Last week' => array(0,0,0,-1, 0,0,0,0),
|
|
'This month' => array(0,0,0,0, 0,1,0,0),
|
|
'Last month' => array(0,-1,0,0, 0,0,0,0),
|
|
'2 month ago' => array(0,-2,0,0, 0,-1,0,0),
|
|
'This year' => array(0,0,0,0, 1,0,0,0),
|
|
'Last year' => array(-1,0,0,0, 0,0,0,0),
|
|
'2 years ago' => array(-2,0,0,0, -1,0,0,0),
|
|
'3 years ago' => array(-3,0,0,0, -2,0,0,0),
|
|
);
|
|
/**
|
|
* Grants: $GLOBALS['egw']->acl->get_grants(TIMESHEET_APP);
|
|
*
|
|
* @var array
|
|
*/
|
|
var $grants;
|
|
/**
|
|
* Sums of the last search in keys duration and price
|
|
*
|
|
* @var array
|
|
*/
|
|
var $summary;
|
|
/**
|
|
* Array with boolean values in keys 'day', 'week' or 'month', for the sums to return in the search
|
|
*
|
|
* @var array
|
|
*/
|
|
var $show_sums;
|
|
/**
|
|
* Array with custom fileds
|
|
*
|
|
* @var array
|
|
*/
|
|
var $customfields=array();
|
|
/**
|
|
* Array with status label
|
|
*
|
|
* @var array
|
|
*/
|
|
var $status_labels = array();
|
|
/**
|
|
* Array with status label configuration
|
|
*
|
|
* @var array
|
|
*/
|
|
var $status_labels_config = array();
|
|
/**
|
|
* Array with substatus of label configuration
|
|
*/
|
|
var $status_labels_substatus = array();
|
|
/**
|
|
* Instance of the timesheet_tracking object
|
|
*
|
|
* @var timesheet_tracking
|
|
*/
|
|
var $tracking;
|
|
/**
|
|
* Translates field / acl-names to labels
|
|
*
|
|
* @var array
|
|
*/
|
|
var $field2label = array(
|
|
'ts_project' => 'Project',
|
|
'ts_title' => 'Title',
|
|
'cat_id' => 'Category',
|
|
'ts_description' => 'Description',
|
|
'ts_start' => 'Start',
|
|
'ts_duration' => 'Duration',
|
|
'ts_quantity' => 'Quantity',
|
|
'ts_unitprice' => 'Unitprice',
|
|
'ts_owner' => 'Owner',
|
|
'ts_modifier' => 'Modifier',
|
|
'ts_status' => 'Status',
|
|
'pm_id' => 'Projectid',
|
|
// pseudo fields used in edit
|
|
//'link_to' => 'Attachments & Links',
|
|
'customfields' => 'Custom fields',
|
|
);
|
|
/**
|
|
* setting field-name from DB to history status
|
|
*
|
|
* @var array
|
|
*/
|
|
var $field2history = array();
|
|
/**
|
|
* Name of the timesheet table storing custom fields
|
|
*/
|
|
const EXTRA_TABLE = 'egw_timesheet_extra';
|
|
|
|
function __construct()
|
|
{
|
|
parent::__construct(TIMESHEET_APP,'egw_timesheet',self::EXTRA_TABLE,'','ts_extra_name','ts_extra_value','ts_id');
|
|
|
|
$this->config_data = config::read(TIMESHEET_APP);
|
|
$this->quantity_sum = $this->config_data['quantity_sum'] == 'true';
|
|
if($this->config_data['status_labels'])
|
|
{
|
|
$this->status_labels =& $this->config_data['status_labels'];
|
|
if (!is_array($this->status_labels)) $this->status_labels= array($this->status_labels);
|
|
foreach ($this->status_labels as $status_id => $label)
|
|
{
|
|
if (!is_array($label))
|
|
{ //old values, bevor parent status
|
|
$name = $label;
|
|
$label=array();
|
|
$label['name'] = $name;
|
|
$label['parent'] = '';
|
|
}
|
|
$sort_labels[$status_id][] = $label['name'];
|
|
$this->status_labels_config[$status_id] = $label;
|
|
if ($label['parent']!='')
|
|
{
|
|
$sort = $label['parent']?$label['parent']:$status_id;
|
|
$sort_labels[$sort][$status_id]= $label['name'];
|
|
$this->status_labels_substatus[$sort][$status_id] = $status_id;
|
|
if (!isset($this->status_labels_substatus[$sort][$sort]))
|
|
{
|
|
$this->status_labels_substatus[$sort][$sort]= $sort;
|
|
}
|
|
}
|
|
}
|
|
unset($this->status_labels);
|
|
foreach ($sort_labels as $status_id => $label_sub)
|
|
{
|
|
if (!isset($this->status_labels[$status_id])) $this->status_labels[$status_id] = $this->status_labels_config[$status_id]['name'];
|
|
foreach ($sort_labels[$status_id] as $sub_status_id => $sub_label)
|
|
{
|
|
if (isset($this->status_labels_config[$sub_status_id]))
|
|
{
|
|
$level = ' ';
|
|
if (isset($this->status_labels_substatus[$sub_status_id])) $this->status_labels_substatus['2level'][$sub_status_id] = $sub_status_id ;
|
|
if (isset($this->status_labels_substatus['2level'][$status_id])) $level = ' ';
|
|
$this->status_labels[$sub_status_id]= $level.$this->status_labels_config[$sub_status_id]['name'];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
$this->today = mktime(0,0,0,date('m',$this->now),date('d',$this->now),date('Y',$this->now));
|
|
|
|
// save us in $GLOBALS['timesheet_bo'] for ExecMethod used in hooks
|
|
if (!is_object($GLOBALS['timesheet_bo']))
|
|
{
|
|
$GLOBALS['timesheet_bo'] =& $this;
|
|
}
|
|
$this->grants = $GLOBALS['egw']->acl->get_grants(TIMESHEET_APP);
|
|
//set fields for tracking
|
|
$this->field2history = array_keys($this->db_cols);
|
|
$this->field2history = array_diff(array_combine($this->field2history,$this->field2history),array('ts_modified'));
|
|
$this->field2history += array('customfields' => '#c'); //add custom fields for history
|
|
}
|
|
|
|
/**
|
|
* get list of specified grants as uid => Username pairs
|
|
*
|
|
* @param int $required=EGW_ACL_READ
|
|
* @return array with uid => Username pairs
|
|
*/
|
|
function grant_list($required=EGW_ACL_READ)
|
|
{
|
|
$result = array();
|
|
foreach($this->grants as $uid => $grant)
|
|
{
|
|
if ($grant & $required)
|
|
{
|
|
$result[$uid] = common::grab_owner_name($uid);
|
|
}
|
|
}
|
|
natcasesort($result);
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* checks if the user has enough rights for a certain operation
|
|
*
|
|
* Rights are given via owner grants or role based acl
|
|
*
|
|
* @param int $required EGW_ACL_READ, EGW_ACL_WRITE, EGW_ACL_ADD, EGW_ACL_DELETE, EGW_ACL_BUDGET, EGW_ACL_EDIT_BUDGET
|
|
* @param array/int $data=null project or project-id to use, default the project in $this->data
|
|
* @return boolean true if the rights are ok, null if not found, false if no rights
|
|
*/
|
|
function check_acl($required,$data=null)
|
|
{
|
|
//error_log(__METHOD__."($required,".array2string($data).")");
|
|
if (is_null($data) || (int)$data == $this->data['ts_id'])
|
|
{
|
|
$data =& $this->data;
|
|
}
|
|
if (!is_array($data))
|
|
{
|
|
$save_data = $this->data;
|
|
$data = $this->read($data,true);
|
|
$this->data = $save_data;
|
|
|
|
if (!$data) return null; // entry not found
|
|
}
|
|
$rights = $this->grants[$data['ts_owner']];
|
|
|
|
return $data && !!($rights & $required);
|
|
}
|
|
|
|
/**
|
|
* return SQL implementing filtering by date
|
|
*
|
|
* @param string $name
|
|
* @param int &$start
|
|
* @param int &$end_param
|
|
* @return string
|
|
*/
|
|
function date_filter($name,&$start,&$end_param)
|
|
{
|
|
$end = $end_param;
|
|
|
|
if ($name == 'custom' && $start)
|
|
{
|
|
if ($end)
|
|
{
|
|
$end += 24*60*60;
|
|
}
|
|
else
|
|
{
|
|
$end = $start + 8*24*60*60;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!isset($this->date_filters[$name]))
|
|
{
|
|
return '1=1';
|
|
}
|
|
$year = (int) date('Y',$this->today);
|
|
$month = (int) date('m',$this->today);
|
|
$day = (int) date('d',$this->today);
|
|
|
|
list($syear,$smonth,$sday,$sweek,$eyear,$emonth,$eday,$eweek) = $this->date_filters[$name];
|
|
|
|
if ($syear || $eyear)
|
|
{
|
|
$start = mktime(0,0,0,1,1,$syear+$year);
|
|
$end = mktime(0,0,0,1,1,$eyear+$year);
|
|
}
|
|
elseif ($smonth || $emonth)
|
|
{
|
|
$start = mktime(0,0,0,$smonth+$month,1,$year);
|
|
$end = mktime(0,0,0,$emonth+$month,1,$year);
|
|
}
|
|
elseif ($sday || $eday)
|
|
{
|
|
$start = mktime(0,0,0,$month,$sday+$day,$year);
|
|
$end = mktime(0,0,0,$month,$eday+$day,$year);
|
|
}
|
|
elseif ($sweek || $eweek)
|
|
{
|
|
$wday = (int) date('w',$this->today); // 0=sun, ..., 6=sat
|
|
switch($GLOBALS['egw_info']['user']['preferences']['calendar']['weekdaystarts'])
|
|
{
|
|
case 'Sunday':
|
|
$weekstart = $this->today - $wday * 24*60*60;
|
|
break;
|
|
case 'Saturday':
|
|
$weekstart = $this->today - (6-$wday) * 24*60*60;
|
|
break;
|
|
case 'Moday':
|
|
default:
|
|
$weekstart = $this->today - ($wday ? $wday-1 : 6) * 24*60*60;
|
|
break;
|
|
}
|
|
$start = $weekstart + $sweek*7*24*60*60;
|
|
$end = $weekstart + $eweek*7*24*60*60;
|
|
}
|
|
$end_param = $end - 24*60*60;
|
|
}
|
|
//echo "<p align='right'>date_filter($name,$start,$end) today=".date('l, Y-m-d H:i',$this->today)." ==> ".date('l, Y-m-d H:i:s',$start)." <= date < ".date('l, Y-m-d H:i:s',$end)."</p>\n";
|
|
// convert start + end from user to servertime for the filter
|
|
return '('.($start-$this->tz_offset_s).' <= ts_start AND ts_start < '.($end-$this->tz_offset_s).')';
|
|
}
|
|
|
|
/**
|
|
* search the timesheet
|
|
*
|
|
* reimplemented to limit result to users we have grants from
|
|
*
|
|
* @param array/string $criteria array of key and data cols, OR a SQL query (content for WHERE), fully quoted (!)
|
|
* @param boolean/string $only_keys=true True returns only keys, False returns all cols. comma seperated list of keys to return
|
|
* @param string $order_by='' fieldnames + {ASC|DESC} separated by colons ',', can also contain a GROUP BY (if it contains ORDER BY)
|
|
* @param string/array $extra_cols='' string or array of strings to be added to the SELECT, eg. "count(*) as num"
|
|
* @param string $wildcard='' appended befor and after each criteria
|
|
* @param boolean $empty=false False=empty criteria are ignored in query, True=empty have to be empty in row
|
|
* @param string $op='AND' defaults to 'AND', can be set to 'OR' too, then criteria's are OR'ed together
|
|
* @param mixed $start=false if != false, return only maxmatch rows begining with start, or array($start,$num)
|
|
* @param array $filter=null if set (!=null) col-data pairs, to be and-ed (!) into the query without wildcards
|
|
* @param string $join='' sql to do a join, added as is after the table-name, eg. ", table2 WHERE x=y" or
|
|
* "LEFT JOIN table2 ON (x=y)", Note: there's no quoting done on $join!
|
|
* @param boolean $need_full_no_count=false If true an unlimited query is run to determine the total number of rows, default false
|
|
* @param boolean $only_summary=false If true only return the sums as array with keys duration and price, default false
|
|
* @return array of matching rows (the row is an array of the cols) or False
|
|
*/
|
|
function &search($criteria,$only_keys=True,$order_by='',$extra_cols='',$wildcard='',$empty=False,$op='AND',$start=false,$filter=null,$join='',$need_full_no_count=false,$only_summary=false)
|
|
{
|
|
//error_log(__METHOD__."(".print_r($criteria,true).",'$only_keys','$order_by',".print_r($extra_cols,true).",'$wildcard','$empty','$op','$start',".print_r($filter,true).",'$join')");
|
|
//echo "<p>".__METHOD__."(".print_r($criteria,true).",'$only_keys','$order_by',".print_r($extra_cols,true).",'$wildcard','$empty','$op','$start',".print_r($filter,true).",'$join')</p>\n";
|
|
// postgres can't round from double precission, only from numeric ;-)
|
|
$total_sql = $this->db->Type != 'pgsql' ? "round(ts_quantity*ts_unitprice,2)" : "round(cast(ts_quantity*ts_unitprice AS numeric),2)";
|
|
|
|
if (!is_array($extra_cols))
|
|
{
|
|
$extra_cols = $extra_cols ? explode(',',$extra_cols) : array();
|
|
}
|
|
if ($only_keys === false || $this->show_sums && strpos($order_by,'ts_start') !== false)
|
|
{
|
|
$extra_cols[] = $total_sql.' AS ts_total';
|
|
}
|
|
if (!isset($filter['ts_owner']) || !count($filter['ts_owner']))
|
|
{
|
|
$filter['ts_owner'] = array_keys($this->grants);
|
|
}
|
|
else
|
|
{
|
|
if (!is_array($filter['ts_owner'])) $filter['ts_owner'] = array($filter['ts_owner']);
|
|
|
|
foreach($filter['ts_owner'] as $key => $owner)
|
|
{
|
|
if (!isset($this->grants[$owner]))
|
|
{
|
|
unset($filter['ts_owner'][$key]);
|
|
}
|
|
}
|
|
}
|
|
if (!count($filter['ts_owner']))
|
|
{
|
|
$this->total = 0;
|
|
$this->summary = array();
|
|
return array();
|
|
}
|
|
$this->summary = parent::search($criteria,"SUM(ts_duration) AS duration,SUM($total_sql) AS price".
|
|
($this->quantity_sum ? ",SUM(ts_quantity) AS quantity" : ''),
|
|
'','',$wildcard,$empty,$op,false,$filter,$join);
|
|
$this->summary = $this->summary[0];
|
|
|
|
if ($only_summary) return $this->summary;
|
|
|
|
if ($this->show_sums && strpos($order_by,'ts_start') !== false && // sums only make sense if ordered by ts_start
|
|
$this->db->capabilities['union'] && ($from_unixtime_ts_start = $this->db->from_unixtime('ts_start')))
|
|
{
|
|
$sum_sql = array(
|
|
'year' => $this->db->date_format($from_unixtime_ts_start,'%Y'),
|
|
'month' => $this->db->date_format($from_unixtime_ts_start,'%Y%m'),
|
|
'week' => $this->db->date_format($from_unixtime_ts_start,$GLOBALS['egw_info']['user']['preferences']['calendar']['weekdaystarts'] == 'Sunday' ? '%X%V' : '%x%v'),
|
|
'day' => $this->db->date_format($from_unixtime_ts_start,'%Y-%m-%d'),
|
|
);
|
|
foreach($this->show_sums as $type)
|
|
{
|
|
$extra_cols[] = $sum_sql[$type].' AS ts_'.$type;
|
|
$extra_cols[] = '0 AS is_sum_'.$type;
|
|
$sum_extra_cols[] = str_replace('ts_start','MIN(ts_start)',$sum_sql[$type]); // as we dont group by ts_start
|
|
$sum_extra_cols[$type] = '0 AS is_sum_'.$type;
|
|
}
|
|
// regular entries
|
|
parent::search($criteria,$only_keys,$order_by,$extra_cols,$wildcard,$empty,$op,'UNION',$filter,$join,$need_full_no_count);
|
|
|
|
$sort = substr($order_by,8);
|
|
$union_order = array();
|
|
$sum_ts_id = array('year' => -3,'month' => -2,'week' => -1,'day' => 0);
|
|
foreach($this->show_sums as $type)
|
|
{
|
|
$union_order[] = 'ts_'.$type . ' ' . $sort;
|
|
$union_order[] = 'is_sum_'.$type;
|
|
$sum_extra_cols[$type]{0} = '1';
|
|
// the $type sum
|
|
parent::search($criteria,$sum_ts_id[$type].",'','','',MIN(ts_start),SUM(ts_duration) AS ts_duration,".
|
|
($this->quantity_sum ? "SUM(ts_quantity) AS ts_quantity" : '0').",0,NULL,0,0,0,0,0,SUM($total_sql) AS ts_total",
|
|
'GROUP BY '.$sum_sql[$type],$sum_extra_cols,$wildcard,$empty,$op,'UNION',$filter,$join,$need_full_no_count);
|
|
$sum_extra_cols[$type]{0} = '0';
|
|
}
|
|
$union_order[] = 'ts_start '.$sort;
|
|
return parent::search('','',implode(',',$union_order),'','',false,'',$start);
|
|
}
|
|
return parent::search($criteria,$only_keys,$order_by,$extra_cols,$wildcard,$empty,$op,$start,$filter,$join,$need_full_no_count);
|
|
}
|
|
|
|
/**
|
|
* read a timesheet entry
|
|
*
|
|
* @param int $ts_id
|
|
* @param boolean $ignore_acl=false should the acl be checked
|
|
* @return array/boolean array with timesheet entry, null if timesheet not found or false if no rights
|
|
*/
|
|
function read($ts_id,$ignore_acl=false)
|
|
{
|
|
//error_log(__METHOD__."($ts_id,$ignore_acl) ".function_backtrace());
|
|
if (!(int)$ts_id || (int)$ts_id != $this->data['ts_id'] && !parent::read($ts_id))
|
|
{
|
|
return null; // entry not found
|
|
}
|
|
if (!$ignore_acl && !($ret = $this->check_acl(EGW_ACL_READ)))
|
|
{
|
|
return false; // no read rights
|
|
}
|
|
return $this->data;
|
|
}
|
|
|
|
/**
|
|
* saves a timesheet entry
|
|
*
|
|
* reimplemented to notify the link-class
|
|
*
|
|
* @param array $keys if given $keys are copied to data before saveing => allows a save as
|
|
* @param boolean $touch_modified=true should modification date+user be set, default yes
|
|
* @param boolean $ignore_acl=false should the acl be checked, returns true if no edit-rigts
|
|
* @return int 0 on success and errno != 0 else
|
|
*/
|
|
function save($keys=null,$touch_modified=true,$ignore_acl=false)
|
|
{
|
|
if ($keys) $this->data_merge($keys);
|
|
|
|
if (!$ignore_acl && $this->data['ts_id'] && !$this->check_acl(EGW_ACL_EDIT))
|
|
{
|
|
return true;
|
|
}
|
|
if ($touch_modified)
|
|
{
|
|
$this->data['ts_modifier'] = $GLOBALS['egw_info']['user']['account_id'];
|
|
$this->data['ts_modified'] = $this->now;
|
|
$this->user = $this->data['ts_modifier'];
|
|
}
|
|
|
|
// check if we have a real modification
|
|
// read the old record
|
|
$new =& $this->data;
|
|
unset($this->data);
|
|
$this->read($new['ts_id']);
|
|
$old =& $this->data;
|
|
$this->data =& $new;
|
|
$changed[] = array();
|
|
if (isset($old)) foreach($old as $name => $value)
|
|
{
|
|
if (isset($new[$name]) && $new[$name] != $value) $changed[] = $name;
|
|
}
|
|
if (!$changed)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!is_object($this->tracking))
|
|
{
|
|
$this->tracking = new timesheet_tracking($this);
|
|
|
|
$this->tracking->html_content_allow = true;
|
|
}
|
|
if ($this->customfields)
|
|
{
|
|
$data_custom = $old_custom = array();
|
|
foreach($this->customfields as $name => $custom)
|
|
{
|
|
if (isset($this->data['#'.$name]) && (string)$this->data['#'.$name]!=='') $data_custom[] = $custom['label'].': '.$this->data['#'.$name];
|
|
if (isset($old['#'.$name]) && (string)$old['#'.$name]!=='') $old_custom[] = $custom['label'].': '.$old['#'.$name];
|
|
}
|
|
$this->data['customfields'] = implode("\n",$data_custom);
|
|
$old['customfields'] = implode("\n",$old_custom);
|
|
}
|
|
if (!$this->tracking->track($this->data,$old,$this->user))
|
|
{
|
|
return implode(', ',$this->tracking->errors);
|
|
}
|
|
if (!($err = parent::save()))
|
|
{
|
|
// notify the link-class about the update, as other apps may be subscribt to it
|
|
egw_link::notify_update(TIMESHEET_APP,$this->data['ts_id'],$this->data);
|
|
}
|
|
|
|
|
|
return $err;
|
|
}
|
|
|
|
/**
|
|
* deletes a timesheet entry identified by $keys or the loaded one, reimplemented to notify the link class (unlink)
|
|
*
|
|
* @param array $keys if given array with col => value pairs to characterise the rows to delete
|
|
* @param boolean $ignore_acl=false should the acl be checked, returns false if no delete-rigts
|
|
* @return int affected rows, should be 1 if ok, 0 if an error
|
|
*/
|
|
function delete($keys=null,$ignore_acl=false)
|
|
{
|
|
if (!is_array($keys) && (int) $keys)
|
|
{
|
|
$keys = array('ts_id' => (int) $keys);
|
|
}
|
|
$ts_id = is_null($keys) ? $this->data['ts_id'] : $keys['ts_id'];
|
|
|
|
if (!$this->check_acl(EGW_ACL_DELETE,$ts_id))
|
|
{
|
|
return false;
|
|
}
|
|
if (($ret = parent::delete($keys)) && $ts_id)
|
|
{
|
|
// delete all links to timesheet entry $ts_id
|
|
egw_link::unlink(0,TIMESHEET_APP,$ts_id);
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* set a status for timesheet entry identified by $keys
|
|
*
|
|
* @param array $keys if given array with col => value pairs to characterise the rows to delete
|
|
* @param boolean $status
|
|
* @return int affected rows, should be 1 if ok, 0 if an error
|
|
*/
|
|
function set_status($keys=null,$status)
|
|
{
|
|
$ret = true;
|
|
if (!is_array($keys) && (int) $keys)
|
|
{
|
|
$keys = array('ts_id' => (int) $keys);
|
|
}
|
|
$ts_id = is_null($keys) ? $this->data['ts_id'] : $keys['ts_id'];
|
|
|
|
if (!$this->check_acl(EGW_ACL_EDIT,$ts_id))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
$this->read($keys,true);
|
|
$this->data['ts_status'] = $status;
|
|
if ($this->save($ts_id)!=0) $ret = false;
|
|
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Get the time- and pricesum for the given timesheet entries
|
|
*
|
|
* @param array $ids array of timesheet id's
|
|
* @return array with keys time and price
|
|
*/
|
|
function sum($ids)
|
|
{
|
|
return $this->search(array('ts_id'=>$ids),true,'','','',false,'AND',false,null,'',false,true);
|
|
}
|
|
|
|
/**
|
|
* get title for a timesheet entry identified by $entry
|
|
*
|
|
* Is called as hook to participate in the linking
|
|
*
|
|
* @param int/array $entry int ts_id or array with timesheet entry
|
|
* @return string/boolean string with title, null if timesheet not found, false if no perms to view it
|
|
*/
|
|
function link_title( $entry )
|
|
{
|
|
if (!is_array($entry))
|
|
{
|
|
$entry = $this->read( $entry,false,false);
|
|
}
|
|
if (!$entry)
|
|
{
|
|
return $entry;
|
|
}
|
|
$format = $GLOBALS['egw_info']['user']['preferences']['common']['dateformat'];
|
|
if (date('H:i',$entry['ts_start']) != '00:00') // dont show 00:00 time, as it means date only
|
|
{
|
|
$format .= ' '.($GLOBALS['egw_info']['user']['preferences']['common']['timeformat'] == 12 ? 'h:i a' : 'H:i');
|
|
}
|
|
return date($format,$entry['ts_start']).': '.$entry['ts_title'];
|
|
}
|
|
|
|
/**
|
|
* get title for multiple timesheet entries identified by $ids
|
|
*
|
|
* Is called as hook to participate in the linking
|
|
*
|
|
* @param array $ids array with ts_id's
|
|
* @return array with titles, see link_title
|
|
*/
|
|
function link_titles( array $ids )
|
|
{
|
|
$titles = array();
|
|
if (($entries = $this->search(array('ts_id' => $ids),'ts_id,ts_title,ts_start')))
|
|
{
|
|
foreach($entries as $entry)
|
|
{
|
|
$titles[$entry['ts_id']] = $this->link_title($entry);
|
|
}
|
|
}
|
|
// we assume all not returned entries are not readable by the user, as we notify egw_link about all deletes
|
|
foreach($ids as $id)
|
|
{
|
|
if (!isset($titles[$id]))
|
|
{
|
|
$titles[$id] = false;
|
|
}
|
|
}
|
|
return $titles;
|
|
}
|
|
|
|
/**
|
|
* query timesheet for entries matching $pattern
|
|
*
|
|
* Is called as hook to participate in the linking
|
|
*
|
|
* @param string $pattern pattern to search
|
|
* @return array with ts_id - title pairs of the matching entries
|
|
*/
|
|
function link_query( $pattern )
|
|
{
|
|
$criteria = array();
|
|
foreach(array('ts_project','ts_title','ts_description') as $col)
|
|
{
|
|
$criteria[$col] = $pattern;
|
|
}
|
|
$result = array();
|
|
foreach((array) $this->search($criteria,false,'','','%',false,'OR') as $ts )
|
|
{
|
|
if ($ts) $result[$ts['ts_id']] = $this->link_title($ts);
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Check access to the projects file store
|
|
*
|
|
* @param int $id id of entry
|
|
* @param int $check EGW_ACL_READ for read and EGW_ACL_EDIT for write or delete access
|
|
* @return boolean true if access is granted or false otherwise
|
|
*/
|
|
function file_access($id,$check,$rel_path)
|
|
{
|
|
return $this->check_acl($check,$id);
|
|
}
|
|
|
|
/**
|
|
* updates the project titles in the timesheet application (called whenever a project name is changed in the project manager)
|
|
*
|
|
* Todo: implement via notification
|
|
*
|
|
* @param string $oldtitle => the origin title of the project
|
|
* @param string $newtitle => the new title of the project
|
|
* @return boolean true for success, false for invalid parameters
|
|
*/
|
|
function update_ts_project($oldtitle='', $newtitle='')
|
|
{
|
|
if(strlen($oldtitle) > 0 && strlen($newtitle) > 0)
|
|
{
|
|
$this->db->update('egw_timesheet',array(
|
|
'ts_project' => $newtitle,
|
|
'ts_title' => $newtitle,
|
|
),array(
|
|
'ts_project' => $oldtitle,
|
|
),__LINE__,__FILE__,TIMESHEET_APP);
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* returns array with relation link_id and ts_id (necessary for project-selection)
|
|
*
|
|
* @param int $pm_id ID of selected project
|
|
* @return array containing link_id and ts_id
|
|
*/
|
|
function get_ts_links($pm_id=0)
|
|
{
|
|
if($pm_id && isset($GLOBALS['egw_info']['user']['apps']['projectmanager']))
|
|
{
|
|
$pm_ids = ExecMethod('projectmanager.projectmanager_bo.children',$pm_id);
|
|
$pm_ids[] = $pm_id;
|
|
$links = solink::get_links('projectmanager',$pm_ids,'timesheet'); // solink::get_links not egw_links::get_links!
|
|
if ($links)
|
|
{
|
|
$links = array_unique(call_user_func_array('array_merge',$links));
|
|
}
|
|
return $links;
|
|
}
|
|
return array();
|
|
}
|
|
} |