Fixed fav reports to include paid status.
[timetracker.git] / WEB-INF / lib / ttReportHelper.class.php
1 <?php
2 // +----------------------------------------------------------------------+
3 // | Anuko Time Tracker
4 // +----------------------------------------------------------------------+
5 // | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
6 // +----------------------------------------------------------------------+
7 // | LIBERAL FREEWARE LICENSE: This source code document may be used
8 // | by anyone for any purpose, and freely redistributed alone or in
9 // | combination with other software, provided that the license is obeyed.
10 // |
11 // | There are only two ways to violate the license:
12 // |
13 // | 1. To redistribute this code in source form, with the copyright
14 // |    notice or license removed or altered. (Distributing in compiled
15 // |    forms without embedded copyright notices is permitted).
16 // |
17 // | 2. To redistribute modified versions of this code in *any* form
18 // |    that bears insufficient indications that the modifications are
19 // |    not the work of the original author(s).
20 // |
21 // | This license applies to this document only, not any other software
22 // | that it may be combined with.
23 // |
24 // +----------------------------------------------------------------------+
25 // | Contributors:
26 // | https://www.anuko.com/time_tracker/credits.htm
27 // +----------------------------------------------------------------------+
28
29 import('ttClientHelper');
30 import('DateAndTime');
31 import('Period');
32 import('ttTimeHelper');
33
34 require_once(dirname(__FILE__).'/../../plugins/CustomFields.class.php');
35
36 // Class ttReportHelper is used for help with reports.
37 class ttReportHelper {
38
39   // getWhere prepares a WHERE clause for a report query.
40   static function getWhere($bean) {
41     global $user;
42
43     // Prepare dropdown parts.
44     $dropdown_parts = '';
45     if ($bean->getAttribute('client'))
46       $dropdown_parts .= ' and l.client_id = '.$bean->getAttribute('client');
47     elseif ($user->isClient() && $user->client_id)
48       $dropdown_parts .= ' and l.client_id = '.$user->client_id;
49     if ($bean->getAttribute('option')) $dropdown_parts .= ' and l.id in(select log_id from tt_custom_field_log where status = 1 and option_id = '.$bean->getAttribute('option').')';
50     if ($bean->getAttribute('project')) $dropdown_parts .= ' and l.project_id = '.$bean->getAttribute('project');
51     if ($bean->getAttribute('task')) $dropdown_parts .= ' and l.task_id = '.$bean->getAttribute('task');
52     if ($bean->getAttribute('include_records')=='1') $dropdown_parts .= ' and l.billable = 1';
53     if ($bean->getAttribute('include_records')=='2') $dropdown_parts .= ' and l.billable = 0';
54     if ($bean->getAttribute('invoice')=='1') $dropdown_parts .= ' and l.invoice_id is not NULL';
55     if ($bean->getAttribute('invoice')=='2') $dropdown_parts .= ' and l.invoice_id is NULL';
56     if ($bean->getAttribute('paid_status')=='1') $dropdown_parts .= ' and l.paid = 1';
57     if ($bean->getAttribute('paid_status')=='2') $dropdown_parts .= ' and l.paid = 0';
58
59     // Prepare user list part.
60     $userlist = -1;
61     if (($user->canManageTeam() || $user->isClient()) && is_array($bean->getAttribute('users')))
62       $userlist = join(',', $bean->getAttribute('users'));
63     // Prepare sql query part for user list.
64     $user_list_part = null;
65     if ($user->canManageTeam() || $user->isClient())
66       $user_list_part = " and l.user_id in ($userlist)";
67     else
68       $user_list_part = " and l.user_id = ".$user->id;
69
70     // Prepare sql query part for where.
71     if ($bean->getAttribute('period'))
72       $period = new Period($bean->getAttribute('period'), new DateAndTime($user->date_format));
73     else {
74       $period = new Period();
75       $period->setPeriod(
76         new DateAndTime($user->date_format, $bean->getAttribute('start_date')),
77         new DateAndTime($user->date_format, $bean->getAttribute('end_date')));
78     }
79     $where = " where l.status = 1 and l.date >= '".$period->getStartDate(DB_DATEFORMAT)."' and l.date <= '".$period->getEndDate(DB_DATEFORMAT)."'".
80       " $user_list_part $dropdown_parts";
81     return $where;
82   }
83
84   // getFavWhere prepares a WHERE clause for a favorite report query.
85   static function getFavWhere($report) {
86     global $user;
87
88     // Prepare dropdown parts.
89     $dropdown_parts = '';
90     if ($report['client_id'])
91       $dropdown_parts .= ' and l.client_id = '.$report['client_id'];
92     elseif ($user->isClient() && $user->client_id)
93       $dropdown_parts .= ' and l.client_id = '.$user->client_id;
94     if ($report['cf_1_option_id']) $dropdown_parts .= ' and l.id in(select log_id from tt_custom_field_log where status = 1 and option_id = '.$report['cf_1_option_id'].')';
95     if ($report['project_id']) $dropdown_parts .= ' and l.project_id = '.$report['project_id'];
96     if ($report['task_id']) $dropdown_parts .= ' and l.task_id = '.$report['task_id'];
97     if ($report['billable']=='1') $dropdown_parts .= ' and l.billable = 1';
98     if ($report['billable']=='2') $dropdown_parts .= ' and l.billable = 0';
99     if ($report['invoice']=='1') $dropdown_parts .= ' and l.invoice_id is not NULL';
100     if ($report['invoice']=='2') $dropdown_parts .= ' and l.invoice_id is NULL';
101     if ($report['paid_status']=='1') $dropdown_parts .= ' and l.paid = 1';
102     if ($report['paid_status']=='2') $dropdown_parts .= ' and l.paid = 0';
103
104     // Prepare user list part.
105     $userlist = -1;
106     if (($user->canManageTeam() || $user->isClient())) {
107       if ($report['users'])
108         $userlist = $report['users'];
109       else {
110         $active_users = ttTeamHelper::getActiveUsers();
111         foreach ($active_users as $single_user)
112           $users[] = $single_user['id'];
113         $userlist = join(',', $users);
114       }
115     }
116     // Prepare sql query part for user list.
117     $user_list_part = null;
118     if ($user->canManageTeam() || $user->isClient())
119       $user_list_part = " and l.user_id in ($userlist)";
120     else
121       $user_list_part = " and l.user_id = ".$user->id;
122
123     // Prepare sql query part for where.
124     if ($report['period'])
125       $period = new Period($report['period'], new DateAndTime($user->date_format));
126     else {
127       $period = new Period();
128       $period->setPeriod(
129         new DateAndTime($user->date_format, $report['period_start']),
130         new DateAndTime($user->date_format, $report['period_end']));
131     }
132     $where = " where l.status = 1 and l.date >= '".$period->getStartDate(DB_DATEFORMAT)."' and l.date <= '".$period->getEndDate(DB_DATEFORMAT)."'".
133       " $user_list_part $dropdown_parts";
134     return $where;
135   }
136
137   // getExpenseWhere prepares WHERE clause for expenses query in a report.
138   static function getExpenseWhere($bean) {
139     global $user;
140
141     // Prepare dropdown parts.
142     $dropdown_parts = '';
143     if ($bean->getAttribute('client'))
144       $dropdown_parts .= ' and ei.client_id = '.$bean->getAttribute('client');
145     elseif ($user->isClient() && $user->client_id)
146       $dropdown_parts .= ' and ei.client_id = '.$user->client_id;
147     if ($bean->getAttribute('project')) $dropdown_parts .= ' and ei.project_id = '.$bean->getAttribute('project');
148     if ($bean->getAttribute('invoice')=='1') $dropdown_parts .= ' and ei.invoice_id is not NULL';
149     if ($bean->getAttribute('invoice')=='2') $dropdown_parts .= ' and ei.invoice_id is NULL';
150
151     // Prepare user list part.
152     $userlist = -1;
153     if (($user->canManageTeam() || $user->isClient()) && is_array($bean->getAttribute('users')))
154       $userlist = join(',', $bean->getAttribute('users'));
155     // Prepare sql query part for user list.
156     $user_list_part = null;
157     if ($user->canManageTeam() || $user->isClient())
158       $user_list_part = " and ei.user_id in ($userlist)";
159     else
160       $user_list_part = " and ei.user_id = ".$user->id;
161
162     // Prepare sql query part for where.
163     if ($bean->getAttribute('period'))
164       $period = new Period($bean->getAttribute('period'), new DateAndTime($user->date_format));
165     else {
166       $period = new Period();
167       $period->setPeriod(
168         new DateAndTime($user->date_format, $bean->getAttribute('start_date')),
169         new DateAndTime($user->date_format, $bean->getAttribute('end_date')));
170     }
171     $where = " where ei.status = 1 and ei.date >= '".$period->getStartDate(DB_DATEFORMAT)."' and ei.date <= '".$period->getEndDate(DB_DATEFORMAT)."'".
172       " $user_list_part $dropdown_parts";
173     return $where;
174   }
175
176   // getFavExpenseWhere prepares a WHERE clause for expenses query in a favorite report.
177   static function getFavExpenseWhere($report) {
178     global $user;
179
180     // Prepare dropdown parts.
181     $dropdown_parts = '';
182     if ($report['client_id'])
183       $dropdown_parts .= ' and ei.client_id = '.$report['client_id'];
184     elseif ($user->isClient() && $user->client_id)
185       $dropdown_parts .= ' and ei.client_id = '.$user->client_id;
186     if ($report['project_id']) $dropdown_parts .= ' and ei.project_id = '.$report['project_id'];
187     if ($report['invoice']=='1') $dropdown_parts .= ' and ei.invoice_id is not NULL';
188     if ($report['invoice']=='2') $dropdown_parts .= ' and ei.invoice_id is NULL';
189
190     // Prepare user list part.
191     $userlist = -1;
192     if (($user->canManageTeam() || $user->isClient())) {
193       if ($report['users'])
194         $userlist = $report['users'];
195       else {
196         $active_users = ttTeamHelper::getActiveUsers();
197         foreach ($active_users as $single_user)
198           $users[] = $single_user['id'];
199         $userlist = join(',', $users);
200       }
201     }
202     // Prepare sql query part for user list.
203     $user_list_part = null;
204     if ($user->canManageTeam() || $user->isClient())
205       $user_list_part = " and ei.user_id in ($userlist)";
206     else
207       $user_list_part = " and ei.user_id = ".$user->id;
208
209     // Prepare sql query part for where.
210     if ($report['period'])
211       $period = new Period($report['period'], new DateAndTime($user->date_format));
212     else {
213       $period = new Period();
214       $period->setPeriod(
215         new DateAndTime($user->date_format, $report['period_start']),
216         new DateAndTime($user->date_format, $report['period_end']));
217     }
218     $where = " where ei.status = 1 and ei.date >= '".$period->getStartDate(DB_DATEFORMAT)."' and ei.date <= '".$period->getEndDate(DB_DATEFORMAT)."'".
219       " $user_list_part $dropdown_parts";
220     return $where;
221   }
222
223   // getItems retrieves all items associated with a report.
224   // It combines tt_log and tt_expense_items in one array for presentation in one table using mysql union all.
225   // Expense items use the "note" field for item name.
226   static function getItems($bean) {
227     global $user;
228     $mdb2 = getConnection();
229
230     $group_by_option = $bean->getAttribute('group_by');
231     $convertTo12Hour = ('%I:%M %p' == $user->time_format) && ($bean->getAttribute('chstart') || $bean->getAttribute('chfinish'));
232
233     // Prepare a query for time items in tt_log table.
234     $fields = array(); // An array of fields for database query.
235     array_push($fields, 'l.id as id');
236     array_push($fields, '1 as type'); // Type 1 is for tt_log entries.
237     array_push($fields, 'l.date as date');
238     if($user->canManageTeam() || $user->isClient())
239       array_push($fields, 'u.name as user');
240     // Add client name if it is selected.
241     if ($bean->getAttribute('chclient') || 'client' == $group_by_option)
242       array_push($fields, 'c.name as client');
243     // Add project name if it is selected.
244     if ($bean->getAttribute('chproject') || 'project' == $group_by_option)
245       array_push($fields, 'p.name as project');
246     // Add task name if it is selected.
247     if ($bean->getAttribute('chtask') || 'task' == $group_by_option)
248       array_push($fields, 't.name as task');
249     // Add custom field.
250     $include_cf_1 = $bean->getAttribute('chcf_1') || 'cf_1' == $group_by_option;
251     if ($include_cf_1) {
252       $custom_fields = new CustomFields($user->team_id);
253       $cf_1_type = $custom_fields->fields[0]['type'];
254       if ($cf_1_type == CustomFields::TYPE_TEXT) {
255         array_push($fields, 'cfl.value as cf_1');
256       } elseif ($cf_1_type == CustomFields::TYPE_DROPDOWN) {
257         array_push($fields, 'cfo.value as cf_1');
258       }
259     }
260     // Add start time.
261     if ($bean->getAttribute('chstart')) {
262       array_push($fields, "l.start as unformatted_start");
263       array_push($fields, "TIME_FORMAT(l.start, '%k:%i') as start");
264     }
265     // Add finish time.
266     if ($bean->getAttribute('chfinish'))
267       array_push($fields, "TIME_FORMAT(sec_to_time(time_to_sec(l.start) + time_to_sec(l.duration)), '%k:%i') as finish");
268     // Add duration.
269     if ($bean->getAttribute('chduration'))
270       array_push($fields, "TIME_FORMAT(l.duration, '%k:%i') as duration");
271     // Add note.
272     if ($bean->getAttribute('chnote'))
273       array_push($fields, 'l.comment as note');
274     // Handle cost.
275     $includeCost = $bean->getAttribute('chcost');
276     if ($includeCost) {
277       if (MODE_TIME == $user->tracking_mode)
278         array_push($fields, "cast(l.billable * coalesce(u.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2)) as cost");   // Use default user rate.
279       else
280         array_push($fields, "cast(l.billable * coalesce(upb.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2)) as cost"); // Use project rate for user.
281       array_push($fields, "null as expense"); 
282     }
283     // Add paid status.
284     if ($user->canManageTeam() && $bean->getAttribute('chpaid'))
285       array_push($fields, 'l.paid as paid');
286
287     // Add invoice name if it is selected.
288     if (($user->canManageTeam() || $user->isClient()) && $bean->getAttribute('chinvoice'))
289       array_push($fields, 'i.name as invoice');
290
291     // Prepare sql query part for left joins.
292     $left_joins = null;
293     if ($bean->getAttribute('chclient') || 'client' == $group_by_option)
294       $left_joins .= " left join tt_clients c on (c.id = l.client_id)";
295     if (($user->canManageTeam() || $user->isClient()) && $bean->getAttribute('chinvoice'))
296       $left_joins .= " left join tt_invoices i on (i.id = l.invoice_id and i.status = 1)";
297     if ($user->canManageTeam() || $user->isClient() || $user->isPluginEnabled('ex'))
298        $left_joins .= " left join tt_users u on (u.id = l.user_id)";
299     if ($bean->getAttribute('chproject') || 'project' == $group_by_option)
300       $left_joins .= " left join tt_projects p on (p.id = l.project_id)";
301     if ($bean->getAttribute('chtask') || 'task' == $group_by_option)
302       $left_joins .= " left join tt_tasks t on (t.id = l.task_id)";
303     if ($include_cf_1) {
304       if ($cf_1_type == CustomFields::TYPE_TEXT)
305         $left_joins .= " left join tt_custom_field_log cfl on (l.id = cfl.log_id and cfl.status = 1)";
306       elseif ($cf_1_type == CustomFields::TYPE_DROPDOWN) {
307         $left_joins .=  " left join tt_custom_field_log cfl on (l.id = cfl.log_id and cfl.status = 1)".
308           " left join tt_custom_field_options cfo on (cfl.option_id = cfo.id)";
309       }
310     }
311     if ($includeCost && MODE_TIME != $user->tracking_mode)
312       $left_joins .= " left join tt_user_project_binds upb on (l.user_id = upb.user_id and l.project_id = upb.project_id)";
313
314     $where = ttReportHelper::getWhere($bean);
315
316     // Construct sql query for tt_log items.
317     $sql = "select ".join(', ', $fields)." from tt_log l $left_joins $where";
318     // If we don't have expense items (such as when the Expenses plugin is desabled), the above is all sql we need,
319     // with an exception of sorting part, that is added in the end.
320
321     // However, when we have expenses, we need to do a union with a separate query for expense items from tt_expense_items table.
322     if ($bean->getAttribute('chcost') && $user->isPluginEnabled('ex')) { // if ex(penses) plugin is enabled
323
324       $fields = array(); // An array of fields for database query.
325       array_push($fields, 'ei.id');
326       array_push($fields, '2 as type'); // Type 2 is for tt_expense_items entries.
327       array_push($fields, 'ei.date');
328       if($user->canManageTeam() || $user->isClient())
329         array_push($fields, 'u.name as user');
330       // Add client name if it is selected.
331       if ($bean->getAttribute('chclient') || 'client' == $group_by_option)
332         array_push($fields, 'c.name as client');
333       // Add project name if it is selected.
334       if ($bean->getAttribute('chproject') || 'project' == $group_by_option)
335         array_push($fields, 'p.name as project');
336       if ($bean->getAttribute('chtask') || 'task' == $group_by_option)
337         array_push($fields, 'null'); // null for task name. We need to match column count for union.
338       if ($bean->getAttribute('chcf_1') || 'cf_1' == $group_by_option)
339         array_push($fields, 'null'); // null for cf_1.
340       if ($bean->getAttribute('chstart')) {
341         array_push($fields, 'null'); // null for unformatted_start.
342         array_push($fields, 'null'); // null for start.
343       }
344       if ($bean->getAttribute('chfinish'))
345         array_push($fields, 'null'); // null for finish.
346       if ($bean->getAttribute('chduration'))
347         array_push($fields, 'null'); // null for duration.
348       // Use the note field to print item name.
349       if ($bean->getAttribute('chnote'))
350         array_push($fields, 'ei.name as note');
351       array_push($fields, 'ei.cost as cost');
352       array_push($fields, 'ei.cost as expense');
353       // Add invoice name if it is selected.
354       if (($user->canManageTeam() || $user->isClient()) && $bean->getAttribute('chinvoice'))
355         array_push($fields, 'i.name as invoice');
356
357       // Prepare sql query part for left joins.
358       $left_joins = null;
359       if ($user->canManageTeam() || $user->isClient())
360         $left_joins .= " left join tt_users u on (u.id = ei.user_id)";
361       if ($bean->getAttribute('chclient') || 'client' == $group_by_option)
362         $left_joins .= " left join tt_clients c on (c.id = ei.client_id)";
363       if ($bean->getAttribute('chproject') || 'project' == $group_by_option)
364         $left_joins .= " left join tt_projects p on (p.id = ei.project_id)";
365       if (($user->canManageTeam() || $user->isClient()) && $bean->getAttribute('chinvoice'))
366         $left_joins .= " left join tt_invoices i on (i.id = ei.invoice_id and i.status = 1)";
367
368       $where = ttReportHelper::getExpenseWhere($bean);
369
370       // Construct sql query for expense items.
371       $sql_for_expense_items = "select ".join(', ', $fields)." from tt_expense_items ei $left_joins $where";
372
373       // Construct a union.
374       $sql = "($sql) union all ($sql_for_expense_items)";
375     }
376
377     // Determine sort part.
378     $sort_part = ' order by ';
379     if ('no_grouping' == $group_by_option || 'date' == $group_by_option)
380       $sort_part .= 'date';
381     else
382       $sort_part .= $group_by_option.', date';
383     if (($user->canManageTeam() || $user->isClient()) && is_array($bean->getAttribute('users')) && 'user' != $group_by_option)
384       $sort_part .= ', user, type';
385     if ($bean->getAttribute('chstart'))
386       $sort_part .= ', unformatted_start';
387     $sort_part .= ', id';
388
389     $sql .= $sort_part;
390     // By now we are ready with sql.
391
392     // Obtain items for report.
393     $res = $mdb2->query($sql);
394     if (is_a($res, 'PEAR_Error')) die($res->getMessage());
395
396     while ($val = $res->fetchRow()) {
397       if ($convertTo12Hour) {
398         if($val['start'] != '')
399           $val['start'] = ttTimeHelper::to12HourFormat($val['start']);
400         if($val['finish'] != '')
401           $val['finish'] = ttTimeHelper::to12HourFormat($val['finish']);
402       }
403       if (isset($val['cost'])) {
404         if ('.' != $user->decimal_mark)
405           $val['cost'] = str_replace('.', $user->decimal_mark, $val['cost']);
406       }
407       if (isset($val['expense'])) {
408         if ('.' != $user->decimal_mark)
409           $val['expense'] = str_replace('.', $user->decimal_mark, $val['expense']);
410       }
411       if ('no_grouping' != $group_by_option) {
412         $val['grouped_by'] = $val[$group_by_option];
413         if ('date' == $group_by_option) {
414           // This is needed to get the date in user date format.
415           $o_date = new DateAndTime(DB_DATEFORMAT, $val['grouped_by']);
416           $val['grouped_by'] = $o_date->toString($user->date_format);
417           unset($o_date);
418         }
419       }
420
421       // This is needed to get the date in user date format.
422       $o_date = new DateAndTime(DB_DATEFORMAT, $val['date']);
423       $val['date'] = $o_date->toString($user->date_format);
424       unset($o_date);
425
426       $row = $val;
427       $report_items[] = $row;
428     }
429
430     return $report_items;
431   }
432
433   // getFavItems retrieves all items associated with a favorite report.
434   // It combines tt_log and tt_expense_items in one array for presentation in one table using mysql union all.
435   // Expense items use the "note" field for item name.
436   static function getFavItems($report) {
437     global $user;
438     $mdb2 = getConnection();
439
440     $group_by_option = $report['group_by'];
441     $convertTo12Hour = ('%I:%M %p' == $user->time_format) && ($report['show_start'] || $report['show_end']);
442
443     // Prepare a query for time items in tt_log table.
444     $fields = array(); // An array of fields for database query.
445     array_push($fields, 'l.id as id');
446     array_push($fields, '1 as type'); // Type 1 is for tt_log entries.
447     array_push($fields, 'l.date as date');
448     if($user->canManageTeam() || $user->isClient())
449       array_push($fields, 'u.name as user');
450     // Add client name if it is selected.
451     if ($report['show_client'] || 'client' == $group_by_option)
452       array_push($fields, 'c.name as client');
453     // Add project name if it is selected.
454     if ($report['show_project'] || 'project' == $group_by_option)
455       array_push($fields, 'p.name as project');
456     // Add task name if it is selected.
457     if ($report['show_task'] || 'task' == $group_by_option)
458       array_push($fields, 't.name as task');
459     // Add custom field.
460     $include_cf_1 = $report['show_custom_field_1'] || 'cf_1' == $group_by_option;
461     if ($include_cf_1) {
462       $custom_fields = new CustomFields($user->team_id);
463       $cf_1_type = $custom_fields->fields[0]['type'];
464       if ($cf_1_type == CustomFields::TYPE_TEXT) {
465         array_push($fields, 'cfl.value as cf_1');
466       } elseif ($cf_1_type == CustomFields::TYPE_DROPDOWN) {
467         array_push($fields, 'cfo.value as cf_1');
468       }
469     }
470     // Add start time.
471     if ($report['show_start']) {
472       array_push($fields, "l.start as unformatted_start");
473       array_push($fields, "TIME_FORMAT(l.start, '%k:%i') as start");
474     }
475     // Add finish time.
476     if ($report['show_end'])
477       array_push($fields, "TIME_FORMAT(sec_to_time(time_to_sec(l.start) + time_to_sec(l.duration)), '%k:%i') as finish");
478     // Add duration.
479     if ($report['show_duration'])
480       array_push($fields, "TIME_FORMAT(l.duration, '%k:%i') as duration");
481     // Add note.
482     if ($report['show_note'])
483       array_push($fields, 'l.comment as note');
484     // Handle cost.
485     $includeCost = $report['show_cost'];
486     if ($includeCost) {
487       if (MODE_TIME == $user->tracking_mode)
488         array_push($fields, "cast(l.billable * coalesce(u.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2)) as cost");   // Use default user rate.
489       else
490         array_push($fields, "cast(l.billable * coalesce(upb.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2)) as cost"); // Use project rate for user.
491       array_push($fields, "null as expense"); 
492     }
493     // Add invoice name if it is selected.
494     if (($user->canManageTeam() || $user->isClient()) && $report['show_invoice'])
495       array_push($fields, 'i.name as invoice');
496
497     // Prepare sql query part for left joins.
498     $left_joins = null;
499     if ($report['show_client'] || 'client' == $group_by_option)
500       $left_joins .= " left join tt_clients c on (c.id = l.client_id)";
501     if (($user->canManageTeam() || $user->isClient()) && $report['show_invoice'])
502       $left_joins .= " left join tt_invoices i on (i.id = l.invoice_id and i.status = 1)";
503     if ($user->canManageTeam() || $user->isClient() || $user->isPluginEnabled('ex'))
504        $left_joins .= " left join tt_users u on (u.id = l.user_id)";
505     if ($report['show_project'] || 'project' == $group_by_option)
506       $left_joins .= " left join tt_projects p on (p.id = l.project_id)";
507     if ($report['show_task'] || 'task' == $group_by_option)
508       $left_joins .= " left join tt_tasks t on (t.id = l.task_id)";
509     if ($include_cf_1) {
510       if ($cf_1_type == CustomFields::TYPE_TEXT)
511         $left_joins .= " left join tt_custom_field_log cfl on (l.id = cfl.log_id and cfl.status = 1)";
512       elseif ($cf_1_type == CustomFields::TYPE_DROPDOWN) {
513         $left_joins .=  " left join tt_custom_field_log cfl on (l.id = cfl.log_id and cfl.status = 1)".
514           " left join tt_custom_field_options cfo on (cfl.option_id = cfo.id)";
515       }
516     }
517     if ($includeCost && MODE_TIME != $user->tracking_mode)
518       $left_joins .= " left join tt_user_project_binds upb on (l.user_id = upb.user_id and l.project_id = upb.project_id)";
519
520     $where = ttReportHelper::getFavWhere($report);
521
522     // Construct sql query for tt_log items.
523     $sql = "select ".join(', ', $fields)." from tt_log l $left_joins $where";
524     // If we don't have expense items (such as when the Expenses plugin is desabled), the above is all sql we need,
525     // with an exception of sorting part, that is added in the end.
526
527     // However, when we have expenses, we need to do a union with a separate query for expense items from tt_expense_items table.
528     if ($report['show_cost'] && $user->isPluginEnabled('ex')) { // if ex(penses) plugin is enabled
529
530       $fields = array(); // An array of fields for database query.
531       array_push($fields, 'ei.id');
532       array_push($fields, '2 as type'); // Type 2 is for tt_expense_items entries.
533       array_push($fields, 'ei.date');
534       if($user->canManageTeam() || $user->isClient())
535         array_push($fields, 'u.name as user');
536       // Add client name if it is selected.
537       if ($report['show_client'] || 'client' == $group_by_option)
538         array_push($fields, 'c.name as client');
539       // Add project name if it is selected.
540       if ($report['show_project'] || 'project' == $group_by_option)
541         array_push($fields, 'p.name as project');
542       if ($report['show_task'] || 'task' == $group_by_option)
543         array_push($fields, 'null'); // null for task name. We need to match column count for union.
544       if ($report['show_custom_field_1'] || 'cf_1' == $group_by_option)
545         array_push($fields, 'null'); // null for cf_1.
546       if ($report['show_start']) {
547         array_push($fields, 'null'); // null for unformatted_start.
548         array_push($fields, 'null'); // null for start.
549       }
550       if ($report['show_end'])
551         array_push($fields, 'null'); // null for finish.
552       if ($report['show_duration'])
553         array_push($fields, 'null'); // null for duration.
554       // Use the note field to print item name.
555       if ($report['show_note'])
556         array_push($fields, 'ei.name as note');
557       array_push($fields, 'ei.cost as cost');
558       array_push($fields, 'ei.cost as expense');
559       // Add invoice name if it is selected.
560       if (($user->canManageTeam() || $user->isClient()) && $report['show_invoice'])
561         array_push($fields, 'i.name as invoice');
562
563       // Prepare sql query part for left joins.
564       $left_joins = null;
565       if ($user->canManageTeam() || $user->isClient())
566         $left_joins .= " left join tt_users u on (u.id = ei.user_id)";
567       if ($report['show_client'] || 'client' == $group_by_option)
568         $left_joins .= " left join tt_clients c on (c.id = ei.client_id)";
569       if ($report['show_project'] || 'project' == $group_by_option)
570         $left_joins .= " left join tt_projects p on (p.id = ei.project_id)";
571       if (($user->canManageTeam() || $user->isClient()) && $report['show_invoice'])
572         $left_joins .= " left join tt_invoices i on (i.id = ei.invoice_id and i.status = 1)";
573
574       $where = ttReportHelper::getFavExpenseWhere($report);
575
576       // Construct sql query for expense items.
577       $sql_for_expense_items = "select ".join(', ', $fields)." from tt_expense_items ei $left_joins $where";
578
579       // Construct a union.
580       $sql = "($sql) union all ($sql_for_expense_items)";
581     }
582
583     // Determine sort part.
584     $sort_part = ' order by ';
585     if ($group_by_option == null || 'no_grouping' == $group_by_option || 'date' == $group_by_option) // TODO: fix DB for NULL values in group_by field.
586       $sort_part .= 'date';
587     else
588       $sort_part .= $group_by_option.', date';
589     if (($user->canManageTeam() || $user->isClient()) /*&& is_array($bean->getAttribute('users'))*/ && 'user' != $group_by_option)
590       $sort_part .= ', user, type';
591     if ($report['show_start'])
592       $sort_part .= ', unformatted_start';
593     $sort_part .= ', id';
594
595     $sql .= $sort_part;
596     // By now we are ready with sql.
597
598     // Obtain items for report.
599     $res = $mdb2->query($sql);
600     if (is_a($res, 'PEAR_Error')) die($res->getMessage());
601
602     while ($val = $res->fetchRow()) {
603       if ($convertTo12Hour) {
604         if($val['start'] != '')
605           $val['start'] = ttTimeHelper::to12HourFormat($val['start']);
606         if($val['finish'] != '')
607           $val['finish'] = ttTimeHelper::to12HourFormat($val['finish']);
608       }
609       if (isset($val['cost'])) {
610         if ('.' != $user->decimal_mark)
611           $val['cost'] = str_replace('.', $user->decimal_mark, $val['cost']);
612       }
613       if (isset($val['expense'])) {
614         if ('.' != $user->decimal_mark)
615           $val['expense'] = str_replace('.', $user->decimal_mark, $val['expense']);
616       }
617       if ('no_grouping' != $group_by_option) {
618         $val['grouped_by'] = $val[$group_by_option];
619         if ('date' == $group_by_option) {
620           // This is needed to get the date in user date format.
621           $o_date = new DateAndTime(DB_DATEFORMAT, $val['grouped_by']);
622           $val['grouped_by'] = $o_date->toString($user->date_format);
623           unset($o_date);
624         }
625       }
626
627       // This is needed to get the date in user date format.
628       $o_date = new DateAndTime(DB_DATEFORMAT, $val['date']);
629       $val['date'] = $o_date->toString($user->date_format);
630       unset($o_date);
631
632       $row = $val;
633       $report_items[] = $row;
634     }
635
636     return $report_items;
637   }
638
639   // getSubtotals calculates report items subtotals when a report is grouped by.
640   // Without expenses, it's a simple select with group by.
641   // With expenses, it becomes a select with group by from a combined set of records obtained with "union all".
642   static function getSubtotals($bean) {
643     global $user;
644
645     $group_by_option = $bean->getAttribute('group_by');
646     if ('no_grouping' == $group_by_option) return null;
647
648     $mdb2 = getConnection();
649
650     // Start with sql to obtain subtotals for time items. This simple sql will be used when we have no expenses.
651
652     // Determine group by field and a required join.
653     switch ($group_by_option) {
654       case 'date':
655         $group_field = 'l.date';
656         $group_join = '';
657         break;
658       case 'user':
659         $group_field = 'u.name';
660         $group_join = 'left join tt_users u on (l.user_id = u.id) ';
661         break;
662       case 'client':
663         $group_field = 'c.name';
664         $group_join = 'left join tt_clients c on (l.client_id = c.id) ';
665         break;
666       case 'project':
667         $group_field = 'p.name';
668         $group_join = 'left join tt_projects p on (l.project_id = p.id) ';
669         break;
670       case 'task':
671         $group_field = 't.name';
672         $group_join = 'left join tt_tasks t on (l.task_id = t.id) ';
673         break;
674       case 'cf_1':
675         $group_field = 'cfo.value';
676         $custom_fields = new CustomFields($user->team_id);
677         if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_TEXT)
678           $group_join = 'left join tt_custom_field_log cfl on (l.id = cfl.log_id and cfl.status = 1) left join tt_custom_field_options cfo on (cfl.value = cfo.id) ';
679         elseif ($custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN)
680           $group_join = 'left join tt_custom_field_log cfl on (l.id = cfl.log_id and cfl.status = 1) left join tt_custom_field_options cfo on (cfl.option_id = cfo.id) ';
681         break;
682     }
683
684     $where = ttReportHelper::getWhere($bean);
685     if ($bean->getAttribute('chcost')) {
686       if (MODE_TIME == $user->tracking_mode) {
687         if ($group_by_option != 'user')
688           $left_join = 'left join tt_users u on (l.user_id = u.id)';
689         $sql = "select $group_field as group_field, sum(time_to_sec(l.duration)) as time, 
690           sum(cast(l.billable * coalesce(u.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10, 2))) as cost,
691           null as expenses from tt_log l
692           $group_join $left_join $where group by $group_field";
693       } else {
694         // If we are including cost and tracking projects, our query (the same as above) needs to join the tt_user_project_binds table.
695         $sql = "select $group_field as group_field, sum(time_to_sec(l.duration)) as time, 
696           sum(cast(l.billable * coalesce(upb.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2))) as cost,
697           null as expenses from tt_log l
698           $group_join
699           left join tt_user_project_binds upb on (l.user_id = upb.user_id and l.project_id = upb.project_id) $where group by $group_field";
700       }
701     } else {
702       $sql = "select $group_field as group_field, sum(time_to_sec(l.duration)) as time, null as expenses from tt_log l
703          $group_join $where group by $group_field";
704     }
705     // By now we have sql for time items.
706
707     // However, when we have expenses, we need to do a union with a separate query for expense items from tt_expense_items table.
708     if ($bean->getAttribute('chcost') && $user->isPluginEnabled('ex')) { // if ex(penses) plugin is enabled
709
710       // Determine group by field and a required join.
711       $group_join = null;
712       $group_field = 'null';
713       switch ($group_by_option) {
714         case 'date':
715           $group_field = 'ei.date';
716           $group_join = '';
717           break;
718         case 'user':
719           $group_field = 'u.name';
720           $group_join = 'left join tt_users u on (ei.user_id = u.id) ';
721           break;
722         case 'client':
723           $group_field = 'c.name';
724           $group_join = 'left join tt_clients c on (ei.client_id = c.id) ';
725           break;
726         case 'project':
727           $group_field = 'p.name';
728           $group_join = 'left join tt_projects p on (ei.project_id = p.id) ';
729           break;
730       }
731
732       $where = ttReportHelper::getExpenseWhere($bean);
733       $sql_for_expenses = "select $group_field as group_field, null as time, sum(ei.cost) as cost, sum(ei.cost) as expenses from tt_expense_items ei 
734         $group_join $where";
735       // Add a "group by" clause if we are grouping.
736       if ('null' != $group_field) $sql_for_expenses .= " group by $group_field";
737
738       // Create a combined query.
739       $sql = "select group_field, sum(time) as time, sum(cost) as cost, sum(expenses) as expenses from (($sql) union all ($sql_for_expenses)) t group by group_field";
740     }
741
742     // Execute query.
743     $res = $mdb2->query($sql);
744     if (is_a($res, 'PEAR_Error')) die($res->getMessage());
745
746     while ($val = $res->fetchRow()) {
747       if ('date' == $group_by_option) {
748         // This is needed to get the date in user date format.
749         $o_date = new DateAndTime(DB_DATEFORMAT, $val['group_field']);
750         $val['group_field'] = $o_date->toString($user->date_format);
751         unset($o_date);
752       }
753       $time = $val['time'] ? sec_to_time_fmt_hm($val['time']) : null;
754       if ($bean->getAttribute('chcost')) {
755         if ('.' != $user->decimal_mark) {
756           $val['cost'] = str_replace('.', $user->decimal_mark, $val['cost']);
757           $val['expenses'] = str_replace('.', $user->decimal_mark, $val['expenses']);
758         }
759         $subtotals[$val['group_field']] = array('name'=>$val['group_field'],'time'=>$time,'cost'=>$val['cost'],'expenses'=>$val['expenses']);
760       } else
761         $subtotals[$val['group_field']] = array('name'=>$val['group_field'],'time'=>$time);
762     }
763
764     return $subtotals;
765   }
766
767   // getFavSubtotals calculates report items subtotals when a favorite report is grouped by.
768   // Without expenses, it's a simple select with group by.
769   // With expenses, it becomes a select with group by from a combined set of records obtained with "union all".
770   static function getFavSubtotals($report) {
771     global $user;
772
773     $group_by_option = $report['group_by'];
774     if ('no_grouping' == $group_by_option) return null;
775
776     $mdb2 = getConnection();
777
778     // Start with sql to obtain subtotals for time items. This simple sql will be used when we have no expenses.
779
780     // Determine group by field and a required join.
781     switch ($group_by_option) {
782       case 'date':
783         $group_field = 'l.date';
784         $group_join = '';
785         break;
786       case 'user':
787         $group_field = 'u.name';
788         $group_join = 'left join tt_users u on (l.user_id = u.id) ';
789         break;
790       case 'client':
791         $group_field = 'c.name';
792         $group_join = 'left join tt_clients c on (l.client_id = c.id) ';
793         break;
794       case 'project':
795         $group_field = 'p.name';
796         $group_join = 'left join tt_projects p on (l.project_id = p.id) ';
797         break;
798       case 'task':
799         $group_field = 't.name';
800         $group_join = 'left join tt_tasks t on (l.task_id = t.id) ';
801         break;
802       case 'cf_1':
803         $group_field = 'cfo.value';
804         $custom_fields = new CustomFields($user->team_id);
805         if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_TEXT)
806           $group_join = 'left join tt_custom_field_log cfl on (l.id = cfl.log_id and cfl.status = 1) left join tt_custom_field_options cfo on (cfl.value = cfo.id) ';
807         elseif ($custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN)
808           $group_join = 'left join tt_custom_field_log cfl on (l.id = cfl.log_id and cfl.status = 1) left join tt_custom_field_options cfo on (cfl.option_id = cfo.id) ';
809         break;
810     }
811
812     $where = ttReportHelper::getFavWhere($report);
813     if ($report['show_cost']) {
814       if (MODE_TIME == $user->tracking_mode) {
815         if ($group_by_option != 'user')
816           $left_join = 'left join tt_users u on (l.user_id = u.id)';
817         $sql = "select $group_field as group_field, sum(time_to_sec(l.duration)) as time, 
818           sum(cast(l.billable * coalesce(u.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10, 2))) as cost,
819           null as expenses from tt_log l
820           $group_join $left_join $where group by $group_field";
821       } else {
822         // If we are including cost and tracking projects, our query (the same as above) needs to join the tt_user_project_binds table.
823         $sql = "select $group_field as group_field, sum(time_to_sec(l.duration)) as time, 
824           sum(cast(l.billable * coalesce(upb.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2))) as cost,
825           null as expenses from tt_log l 
826           $group_join
827           left join tt_user_project_binds upb on (l.user_id = upb.user_id and l.project_id = upb.project_id) $where group by $group_field";
828       }
829     } else {
830       $sql = "select $group_field as group_field, sum(time_to_sec(l.duration)) as time, null as expenses from tt_log l 
831          $group_join $where group by $group_field";
832     }
833     // By now we have sql for time items.
834
835     // However, when we have expenses, we need to do a union with a separate query for expense items from tt_expense_items table.
836     if ($report['show_cost'] && $user->isPluginEnabled('ex')) { // if ex(penses) plugin is enabled
837
838       // Determine group by field and a required join.
839       $group_join = null;
840       $group_field = 'null';
841       switch ($group_by_option) {
842         case 'date':
843           $group_field = 'ei.date';
844           $group_join = '';
845           break;
846         case 'user':
847           $group_field = 'u.name';
848           $group_join = 'left join tt_users u on (ei.user_id = u.id) ';
849           break;
850         case 'client':
851           $group_field = 'c.name';
852           $group_join = 'left join tt_clients c on (ei.client_id = c.id) ';
853           break;
854         case 'project':
855           $group_field = 'p.name';
856           $group_join = 'left join tt_projects p on (ei.project_id = p.id) ';
857           break;
858       }
859
860       $where = ttReportHelper::getFavExpenseWhere($report);
861       $sql_for_expenses = "select $group_field as group_field, null as time, sum(ei.cost) as cost, sum(ei.cost) as expenses from tt_expense_items ei 
862         $group_join $where";
863       // Add a "group by" clause if we are grouping.
864       if ('null' != $group_field) $sql_for_expenses .= " group by $group_field";
865
866       // Create a combined query.
867       $sql = "select group_field, sum(time) as time, sum(cost) as cost, sum(expenses) as expenses from (($sql) union all ($sql_for_expenses)) t group by group_field";
868     }
869
870     // Execute query.
871     $res = $mdb2->query($sql);
872     if (is_a($res, 'PEAR_Error')) die($res->getMessage());
873
874     while ($val = $res->fetchRow()) {
875       if ('date' == $group_by_option) {
876         // This is needed to get the date in user date format.
877         $o_date = new DateAndTime(DB_DATEFORMAT, $val['group_field']);
878         $val['group_field'] = $o_date->toString($user->date_format);
879         unset($o_date);
880       }
881       $time = $val['time'] ? sec_to_time_fmt_hm($val['time']) : null;
882       if ($report['show_cost']) {
883         if ('.' != $user->decimal_mark) {
884           $val['cost'] = str_replace('.', $user->decimal_mark, $val['cost']);
885           $val['expenses'] = str_replace('.', $user->decimal_mark, $val['expenses']);
886         }
887         $subtotals[$val['group_field']] = array('name'=>$val['group_field'],'time'=>$time,'cost'=>$val['cost'],'expenses'=>$val['expenses']);
888       } else
889         $subtotals[$val['group_field']] = array('name'=>$val['group_field'],'time'=>$time);
890     }
891
892     return $subtotals;
893   }
894
895   // getTotals calculates total hours and cost for all report items.
896   static function getTotals($bean)
897   {
898     global $user;
899
900     $mdb2 = getConnection();
901
902     $where = ttReportHelper::getWhere($bean);
903
904     // Start with a query for time items.
905     if ($bean->getAttribute('chcost')) {
906       if (MODE_TIME == $user->tracking_mode) {
907         $sql = "select sum(time_to_sec(l.duration)) as time,
908           sum(cast(l.billable * coalesce(u.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2))) as cost,
909           null as expenses 
910           from tt_log l
911           left join tt_users u on (l.user_id = u.id) $where";
912       } else {
913         $sql = "select sum(time_to_sec(l.duration)) as time,
914           sum(cast(l.billable * coalesce(upb.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2))) as cost,
915           null as expenses
916           from tt_log l
917           left join tt_user_project_binds upb on (l.user_id = upb.user_id and l.project_id = upb.project_id) $where";
918       }
919     } else
920       $sql = "select sum(time_to_sec(l.duration)) as time, null as cost, null as expenses from tt_log l $where";
921
922     // If we have expenses, query becomes a bit more complex.
923     if ($bean->getAttribute('chcost') && $user->isPluginEnabled('ex')) {
924       $where = ttReportHelper::getExpenseWhere($bean);
925       $sql_for_expenses = "select null as time, sum(cost) as cost, sum(cost) as expenses from tt_expense_items ei $where";
926       // Create a combined query.
927       $sql = "select sum(time) as time, sum(cost) as cost, sum(expenses) as expenses from (($sql) union all ($sql_for_expenses)) t";
928     }
929
930     // Execute query.
931     $res = $mdb2->query($sql);
932     if (is_a($res, 'PEAR_Error')) die($res->getMessage());
933
934     $val = $res->fetchRow();
935     $total_time = $val['time'] ? sec_to_time_fmt_hm($val['time']) : null;
936     if ($bean->getAttribute('chcost')) {
937       $total_cost = $val['cost'];
938       if (!$total_cost) $total_cost = '0.00';
939       if ('.' != $user->decimal_mark)
940         $total_cost = str_replace('.', $user->decimal_mark, $total_cost);
941       $total_expenses = $val['expenses'];
942       if (!$total_expenses) $total_expenses = '0.00';
943       if ('.' != $user->decimal_mark)
944         $total_expenses = str_replace('.', $user->decimal_mark, $total_expenses);
945     }
946
947     if ($bean->getAttribute('period'))
948       $period = new Period($bean->getAttribute('period'), new DateAndTime($user->date_format));
949     else {
950       $period = new Period();
951       $period->setPeriod(
952         new DateAndTime($user->date_format, $bean->getAttribute('start_date')),
953         new DateAndTime($user->date_format, $bean->getAttribute('end_date')));
954     }
955
956     $totals['start_date'] = $period->getStartDate();
957     $totals['end_date'] = $period->getEndDate();
958     $totals['time'] = $total_time;
959     $totals['cost'] = $total_cost;
960     $totals['expenses'] = $total_expenses;
961
962     return $totals;
963   }
964
965   // getFavTotals calculates total hours and cost for all favorite report items.
966   static function getFavTotals($report)
967   {
968     global $user;
969
970     $mdb2 = getConnection();
971
972     $where = ttReportHelper::getFavWhere($report);
973
974     // Start with a query for time items.
975     if ($report['show_cost']) {
976       if (MODE_TIME == $user->tracking_mode) {
977         $sql = "select sum(time_to_sec(l.duration)) as time,
978           sum(cast(l.billable * coalesce(u.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2))) as cost,
979           null as expenses 
980           from tt_log l
981           left join tt_users u on (l.user_id = u.id) $where";
982       } else {
983         $sql = "select sum(time_to_sec(l.duration)) as time,
984           sum(cast(l.billable * coalesce(upb.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2))) as cost,
985           null as expenses
986           from tt_log l
987           left join tt_user_project_binds upb on (l.user_id = upb.user_id and l.project_id = upb.project_id) $where";
988       }
989     } else
990       $sql = "select sum(time_to_sec(l.duration)) as time, null as cost, null as expenses from tt_log l $where";
991
992     // If we have expenses, query becomes a bit more complex.
993     if ($report['show_cost'] && $user->isPluginEnabled('ex')) {
994       $where = ttReportHelper::getFavExpenseWhere($report);
995       $sql_for_expenses = "select null as time, sum(cost) as cost, sum(cost) as expenses from tt_expense_items ei $where";
996       // Create a combined query.
997       $sql = "select sum(time) as time, sum(cost) as cost, sum(expenses) as expenses from (($sql) union all ($sql_for_expenses)) t";
998     }
999
1000     // Execute query.
1001     $res = $mdb2->query($sql);
1002     if (is_a($res, 'PEAR_Error')) die($res->getMessage());
1003
1004     $val = $res->fetchRow();
1005     $total_time = $val['time'] ? sec_to_time_fmt_hm($val['time']) : null;
1006     if ($report['show_cost']) {
1007       $total_cost = $val['cost'];
1008       if (!$total_cost) $total_cost = '0.00';
1009       if ('.' != $user->decimal_mark)
1010         $total_cost = str_replace('.', $user->decimal_mark, $total_cost);
1011       $total_expenses = $val['expenses'];
1012       if (!$total_expenses) $total_expenses = '0.00';
1013       if ('.' != $user->decimal_mark)
1014         $total_expenses = str_replace('.', $user->decimal_mark, $total_expenses);
1015     }
1016
1017     if ($report['period'])
1018       $period = new Period($report['period'], new DateAndTime($user->date_format));
1019     else {
1020       $period = new Period();
1021       $period->setPeriod(
1022         new DateAndTime($user->date_format, $report['period_start']),
1023         new DateAndTime($user->date_format, $report['period_end']));
1024     }
1025
1026     $totals['start_date'] = $period->getStartDate();
1027     $totals['end_date'] = $period->getEndDate();
1028     $totals['time'] = $total_time;
1029     $totals['cost'] = $total_cost;
1030     $totals['expenses'] = $total_expenses;
1031
1032     return $totals;
1033   }
1034
1035   // The assignToInvoice assigns a set of records to a specific invoice.
1036   static function assignToInvoice($invoice_id, $time_log_ids, $expense_item_ids)
1037   {
1038     $mdb2 = getConnection();
1039     if ($time_log_ids) {
1040       $sql = "update tt_log set invoice_id = ".$mdb2->quote($invoice_id).
1041         " where id in(".join(', ', $time_log_ids).")";
1042       $affected = $mdb2->exec($sql);
1043       if (is_a($affected, 'PEAR_Error')) die($affected->getMessage());
1044     }
1045     if ($expense_item_ids) {
1046       $sql = "update tt_expense_items set invoice_id = ".$mdb2->quote($invoice_id).
1047         " where id in(".join(', ', $expense_item_ids).")";
1048       $affected = $mdb2->exec($sql);
1049       if (is_a($affected, 'PEAR_Error')) die($affected->getMessage());
1050     }
1051   }
1052
1053   // prepareReportBody - prepares an email body for report.
1054   static function prepareReportBody($bean, $comment)
1055   {
1056     global $user;
1057     global $i18n;
1058
1059     $items = ttReportHelper::getItems($bean);
1060     $group_by = $bean->getAttribute('group_by');
1061     if ($group_by && 'no_grouping' != $group_by)
1062       $subtotals = ttReportHelper::getSubtotals($bean);
1063     $totals = ttReportHelper::getTotals($bean);
1064
1065     // Use custom fields plugin if it is enabled.
1066     if ($user->isPluginEnabled('cf'))
1067       $custom_fields = new CustomFields($user->team_id);
1068
1069     // Define some styles to use in email.
1070     $style_title = 'text-align: center; font-size: 15pt; font-family: Arial, Helvetica, sans-serif;';
1071     $tableHeader = 'font-weight: bold; background-color: #a6ccf7; text-align: left;';
1072     $tableHeaderCentered = 'font-weight: bold; background-color: #a6ccf7; text-align: center;';
1073     $rowItem = 'background-color: #ffffff;';
1074     $rowItemAlt = 'background-color: #f5f5f5;';
1075     $rowSubtotal = 'background-color: #e0e0e0;';
1076     $cellLeftAligned = 'text-align: left; vertical-align: top;';
1077     $cellRightAligned = 'text-align: right; vertical-align: top;';
1078     $cellLeftAlignedSubtotal = 'font-weight: bold; text-align: left; vertical-align: top;';
1079     $cellRightAlignedSubtotal = 'font-weight: bold; text-align: right; vertical-align: top;';
1080
1081     // Start creating email body.
1082     $body = '<html>';
1083     $body .= '<head><meta http-equiv="content-type" content="text/html; charset='.CHARSET.'"></head>';
1084     $body .= '<body>';
1085
1086     // Output title.
1087     $body .= '<p style="'.$style_title.'">'.$i18n->getKey('form.mail.report_subject').': '.$totals['start_date'].' - '.$totals['end_date'].'</p>';
1088
1089     // Output comment.
1090     if ($comment) $body .= '<p>'.htmlspecialchars($comment).'</p>';
1091
1092     if ($bean->getAttribute('chtotalsonly')) {
1093       // Totals only report. Output subtotals.
1094
1095       // Determine group_by header.
1096       if ('cf_1' == $group_by)
1097         $group_by_header = htmlspecialchars($custom_fields->fields[0]['label']);
1098       else {
1099         $key = 'label.'.$group_by;
1100         $group_by_header = $i18n->getKey($key);
1101       }
1102
1103       $body .= '<table border="0" cellpadding="4" cellspacing="0" width="100%">';
1104       $body .= '<tr>';
1105       $body .= '<td style="'.$tableHeader.'">'.$group_by_header.'</td>';
1106       if ($bean->getAttribute('chduration'))
1107         $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->getKey('label.duration').'</td>';
1108       if ($bean->getAttribute('chcost'))
1109         $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->getKey('label.cost').'</td>';
1110       $body .= '</tr>';
1111       foreach($subtotals as $subtotal) {
1112         $body .= '<tr style="'.$rowSubtotal.'">';
1113         $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($subtotal['name'] ? htmlspecialchars($subtotal['name']) : '&nbsp;').'</td>';
1114         if ($bean->getAttribute('chduration')) {
1115           $body .= '<td style="'.$cellRightAlignedSubtotal.'">';
1116           if ($subtotal['time'] <> '0:00') $body .= $subtotal['time'];
1117           $body .= '</td>';
1118         }
1119         if ($bean->getAttribute('chcost')) {
1120           $body .= '<td style="'.$cellRightAlignedSubtotal.'">';
1121           $body .= ($user->canManageTeam() || $user->isClient()) ? $subtotal['cost'] : $subtotal['expenses'];
1122           $body .= '</td>';
1123         }
1124         $body .= '</tr>';
1125       }
1126
1127       // Print totals.
1128       $body .= '<tr><td>&nbsp;</td></tr>';
1129       $body .= '<tr style="'.$rowSubtotal.'">';
1130       $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.$i18n->getKey('label.total').'</td>';
1131       if ($bean->getAttribute('chduration')) {
1132         $body .= '<td style="'.$cellRightAlignedSubtotal.'">';
1133         if ($totals['time'] <> '0:00') $body .= $totals['time'];
1134         $body .= '</td>';
1135       }
1136       if ($bean->getAttribute('chcost')) {
1137         $body .= '<td nowrap style="'.$cellRightAlignedSubtotal.'">'.htmlspecialchars($user->currency).' ';
1138         $body .= ($user->canManageTeam() || $user->isClient()) ? $totals['cost'] : $totals['expenses'];
1139         $body .= '</td>';
1140       }
1141       $body .= '</tr>';
1142
1143       $body .= '</table>';
1144     } else {
1145       // Regular report.
1146
1147       // Print table header.
1148       $body .= '<table border="0" cellpadding="4" cellspacing="0" width="100%">';
1149       $body .= '<tr>';
1150       $body .= '<td style="'.$tableHeader.'">'.$i18n->getKey('label.date').'</td>';
1151       if ($user->canManageTeam() || $user->isClient())
1152         $body .= '<td style="'.$tableHeader.'">'.$i18n->getKey('label.user').'</td>';
1153       if ($bean->getAttribute('chclient'))
1154         $body .= '<td style="'.$tableHeader.'">'.$i18n->getKey('label.client').'</td>';
1155       if ($bean->getAttribute('chproject'))
1156         $body .= '<td style="'.$tableHeader.'">'.$i18n->getKey('label.project').'</td>';
1157       if ($bean->getAttribute('chtask'))
1158         $body .= '<td style="'.$tableHeader.'">'.$i18n->getKey('label.task').'</td>';
1159       if ($bean->getAttribute('chcf_1'))
1160         $body .= '<td style="'.$tableHeader.'">'.htmlspecialchars($custom_fields->fields[0]['label']).'</td>';
1161       if ($bean->getAttribute('chstart'))
1162         $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->getKey('label.start').'</td>';
1163       if ($bean->getAttribute('chfinish'))
1164         $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->getKey('label.finish').'</td>';
1165       if ($bean->getAttribute('chduration'))
1166         $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->getKey('label.duration').'</td>';
1167       if ($bean->getAttribute('chnote'))
1168         $body .= '<td style="'.$tableHeader.'">'.$i18n->getKey('label.note').'</td>';
1169       if ($bean->getAttribute('chcost'))
1170         $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->getKey('label.cost').'</td>';
1171       if ($bean->getAttribute('chpaid'))
1172         $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->getKey('label.paid').'</td>';
1173       if ($bean->getAttribute('chinvoice'))
1174         $body .= '<td style="'.$tableHeader.'">'.$i18n->getKey('label.invoice').'</td>';
1175       $body .= '</tr>';
1176
1177       // Initialize variables to print subtotals.
1178       if ($items && 'no_grouping' != $group_by) {
1179         $print_subtotals = true;
1180         $first_pass = true;
1181         $prev_grouped_by = '';
1182         $cur_grouped_by = '';
1183       }
1184       // Initialize variables to alternate color of rows for different dates.
1185       $prev_date = '';
1186       $cur_date = '';
1187       $row_style = $rowItem;
1188
1189       // Print report items.
1190       if (is_array($items)) {
1191         foreach ($items as $record) {
1192           $cur_date = $record['date'];
1193           // Print a subtotal row after a block of grouped items.
1194           if ($print_subtotals) {
1195             $cur_grouped_by = $record['grouped_by'];
1196             if ($cur_grouped_by != $prev_grouped_by && !$first_pass) {
1197               $body .= '<tr style="'.$rowSubtotal.'">';
1198               $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.$i18n->getKey('label.subtotal').'</td>';
1199               $subtotal_name = htmlspecialchars($subtotals[$prev_grouped_by]['name']);
1200               if ($user->canManageTeam() || $user->isClient()) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'user' ? $subtotal_name : '').'</td>';
1201               if ($bean->getAttribute('chclient')) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'client' ? $subtotal_name : '').'</td>';
1202               if ($bean->getAttribute('chproject')) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'project' ? $subtotal_name : '').'</td>';
1203               if ($bean->getAttribute('chtask')) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'task' ? $subtotal_name : '').'</td>';
1204               if ($bean->getAttribute('chcf_1')) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'cf_1' ? $subtotal_name : '').'</td>';
1205               if ($bean->getAttribute('chstart')) $body .= '<td></td>';
1206               if ($bean->getAttribute('chfinish')) $body .= '<td></td>';
1207               if ($bean->getAttribute('chduration')) $body .= '<td style="'.$cellRightAlignedSubtotal.'">'.$subtotals[$prev_grouped_by]['time'].'</td>';
1208               if ($bean->getAttribute('chnote')) $body .= '<td></td>';
1209               if ($bean->getAttribute('chcost')) {
1210                 $body .= '<td style="'.$cellRightAlignedSubtotal.'">';
1211                 $body .= ($user->canManageTeam() || $user->isClient()) ? $subtotals[$prev_grouped_by]['cost'] : $subtotals[$prev_grouped_by]['expenses'];
1212                 $body .= '</td>';
1213               }
1214               if ($bean->getAttribute('chpaid')) $body .= '<td></td>';
1215               if ($bean->getAttribute('chinvoice')) $body .= '<td></td>';
1216               $body .= '</tr>';
1217               $body .= '<tr><td>&nbsp;</td></tr>';
1218             }
1219             $first_pass = false;
1220           }
1221
1222           // Print a regular row.
1223           if ($cur_date != $prev_date)
1224             $row_style = ($row_style == $rowItem) ? $rowItemAlt : $rowItem;
1225           $body .= '<tr style="'.$row_style.'">';
1226           $body .= '<td style="'.$cellLeftAligned.'">'.$record['date'].'</td>';
1227           if ($user->canManageTeam() || $user->isClient())
1228             $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['user']).'</td>';
1229           if ($bean->getAttribute('chclient'))
1230             $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['client']).'</td>';
1231           if ($bean->getAttribute('chproject'))
1232             $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['project']).'</td>';
1233           if ($bean->getAttribute('chtask'))
1234             $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['task']).'</td>';
1235           if ($bean->getAttribute('chcf_1'))
1236             $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['cf_1']).'</td>';
1237           if ($bean->getAttribute('chstart'))
1238             $body .= '<td nowrap style="'.$cellRightAligned.'">'.$record['start'].'</td>';
1239           if ($bean->getAttribute('chfinish'))
1240             $body .= '<td nowrap style="'.$cellRightAligned.'">'.$record['finish'].'</td>';
1241           if ($bean->getAttribute('chduration'))
1242             $body .= '<td style="'.$cellRightAligned.'">'.$record['duration'].'</td>';
1243           if ($bean->getAttribute('chnote'))
1244             $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['note']).'</td>';
1245           if ($bean->getAttribute('chcost'))
1246             $body .= '<td style="'.$cellRightAligned.'">'.$record['cost'].'</td>';
1247           if ($bean->getAttribute('chpaid')) {
1248             $body .= '<td style="'.$cellRightAligned.'">';
1249             $body .= $record['paid'] == 1 ? $i18n->getKey('label.yes') : $i18n->getKey('label.no');
1250             $body .= '</td>';
1251           }
1252           if ($bean->getAttribute('chinvoice'))
1253             $body .= '<td style="'.$cellRightAligned.'">'.htmlspecialchars($record['invoice']).'</td>';
1254           $body .= '</tr>';
1255
1256           $prev_date = $record['date'];
1257           if ($print_subtotals)
1258             $prev_grouped_by = $record['grouped_by'];
1259         }
1260       }
1261
1262       // Print a terminating subtotal.
1263       if ($print_subtotals) {
1264         $body .= '<tr style="'.$rowSubtotal.'">';
1265         $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.$i18n->getKey('label.subtotal').'</td>';
1266         $subtotal_name = htmlspecialchars($subtotals[$cur_grouped_by]['name']);
1267         if ($user->canManageTeam() || $user->isClient()) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'user' ? $subtotal_name : '').'</td>';
1268         if ($bean->getAttribute('chclient')) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'client' ? $subtotal_name : '').'</td>';
1269         if ($bean->getAttribute('chproject')) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'project' ? $subtotal_name : '').'</td>';
1270         if ($bean->getAttribute('chtask')) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'task' ? $subtotal_name : '').'</td>';
1271         if ($bean->getAttribute('chcf_1')) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'cf_1' ? $subtotal_name : '').'</td>';
1272         if ($bean->getAttribute('chstart')) $body .= '<td></td>';
1273         if ($bean->getAttribute('chfinish')) $body .= '<td></td>';
1274         if ($bean->getAttribute('chduration')) $body .= '<td style="'.$cellRightAlignedSubtotal.'">'.$subtotals[$cur_grouped_by]['time'].'</td>';
1275         if ($bean->getAttribute('chnote')) $body .= '<td></td>';
1276         if ($bean->getAttribute('chcost')) {
1277           $body .= '<td style="'.$cellRightAlignedSubtotal.'">';
1278           $body .= ($user->canManageTeam() || $user->isClient()) ? $subtotals[$cur_grouped_by]['cost'] : $subtotals[$cur_grouped_by]['expenses'];
1279           $body .= '</td>';
1280         }
1281         if ($bean->getAttribute('chpaid')) $body .= '<td></td>';
1282         if ($bean->getAttribute('chinvoice')) $body .= '<td></td>';
1283         $body .= '</tr>';
1284       }
1285
1286       // Print totals.
1287       $body .= '<tr><td>&nbsp;</td></tr>';
1288       $body .= '<tr style="'.$rowSubtotal.'">';
1289       $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.$i18n->getKey('label.total').'</td>';
1290       if ($user->canManageTeam() || $user->isClient()) $body .= '<td></td>';
1291       if ($bean->getAttribute('chclient')) $body .= '<td></td>';
1292       if ($bean->getAttribute('chproject')) $body .= '<td></td>';
1293       if ($bean->getAttribute('chtask')) $body .= '<td></td>';
1294       if ($bean->getAttribute('chcf_1')) $body .= '<td></td>';
1295       if ($bean->getAttribute('chstart')) $body .= '<td></td>';
1296       if ($bean->getAttribute('chfinish')) $body .= '<td></td>';
1297       if ($bean->getAttribute('chduration')) $body .= '<td style="'.$cellRightAlignedSubtotal.'">'.$totals['time'].'</td>';
1298       if ($bean->getAttribute('chnote')) $body .= '<td></td>';
1299       if ($bean->getAttribute('chcost')) {
1300         $body .= '<td nowrap style="'.$cellRightAlignedSubtotal.'">'.htmlspecialchars($user->currency).' ';
1301         $body .= ($user->canManageTeam() || $user->isClient()) ? $totals['cost'] : $totals['expenses'];
1302         $body .= '</td>';
1303       }
1304       if ($bean->getAttribute('chpaid')) $body .= '<td></td>';
1305       if ($bean->getAttribute('chinvoice')) $body .= '<td></td>';
1306       $body .= '</tr>';
1307
1308       $body .= '</table>';
1309     }
1310
1311     // Output footer.
1312     if (!defined('REPORT_FOOTER') || !(REPORT_FOOTER == false))
1313       $body .= '<p style="text-align: center;">'.$i18n->getKey('form.mail.footer').'</p>';
1314
1315     // Finish creating email body.
1316     $body .= '</body></html>';
1317
1318     return $body;
1319   }
1320
1321   // checkFavReportCondition - checks whether it is okay to send fav report.
1322   static function checkFavReportCondition($report, $condition)
1323   {
1324     $items = ttReportHelper::getFavItems($report);
1325
1326     $condition = str_replace('count', '', $condition);
1327     $count_required = intval(trim(str_replace('>', '', $condition)));
1328
1329     if (count($items) > $count_required)
1330       return true; // Condition ok.
1331
1332     return false;
1333   }
1334
1335   // prepareFavReportBody - prepares an email body for a favorite report.
1336   static function prepareFavReportBody($report)
1337   {
1338     global $user;
1339     global $i18n;
1340
1341     $items = ttReportHelper::getFavItems($report);
1342     $group_by = $report['group_by'];
1343     if ($group_by && 'no_grouping' != $group_by)
1344       $subtotals = ttReportHelper::getFavSubtotals($report);
1345     $totals = ttReportHelper::getFavTotals($report);
1346
1347     // Use custom fields plugin if it is enabled.
1348     if ($user->isPluginEnabled('cf'))
1349       $custom_fields = new CustomFields($user->team_id);
1350
1351     // Define some styles to use in email.
1352     $style_title = 'text-align: center; font-size: 15pt; font-family: Arial, Helvetica, sans-serif;';
1353     $tableHeader = 'font-weight: bold; background-color: #a6ccf7; text-align: left;';
1354     $tableHeaderCentered = 'font-weight: bold; background-color: #a6ccf7; text-align: center;';
1355     $rowItem = 'background-color: #ffffff;';
1356     $rowItemAlt = 'background-color: #f5f5f5;';
1357     $rowSubtotal = 'background-color: #e0e0e0;';
1358     $cellLeftAligned = 'text-align: left; vertical-align: top;';
1359     $cellRightAligned = 'text-align: right; vertical-align: top;';
1360     $cellLeftAlignedSubtotal = 'font-weight: bold; text-align: left; vertical-align: top;';
1361     $cellRightAlignedSubtotal = 'font-weight: bold; text-align: right; vertical-align: top;';
1362
1363     // Start creating email body.
1364     $body = '<html>';
1365     $body .= '<head><meta http-equiv="content-type" content="text/html; charset='.CHARSET.'"></head>';
1366     $body .= '<body>';
1367
1368     // Output title.
1369     $body .= '<p style="'.$style_title.'">'.$i18n->getKey('form.mail.report_subject').': '.$totals['start_date'].' - '.$totals['end_date'].'</p>';
1370
1371     // Output comment.
1372     // if ($comment) $body .= '<p>'.htmlspecialchars($comment).'</p>'; // No comment for fav. reports.
1373
1374     if ($report['show_totals_only']) {
1375       // Totals only report. Output subtotals.
1376
1377       // Determine group_by header.
1378       if ('cf_1' == $group_by)
1379         $group_by_header = htmlspecialchars($custom_fields->fields[0]['label']);
1380       else {
1381         $key = 'label.'.$group_by;
1382         $group_by_header = $i18n->getKey($key);
1383       }
1384
1385       $body .= '<table border="0" cellpadding="4" cellspacing="0" width="100%">';
1386       $body .= '<tr>';
1387       $body .= '<td style="'.$tableHeader.'">'.$group_by_header.'</td>';
1388       if ($report['show_duration'])
1389         $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->getKey('label.duration').'</td>';
1390       if ($report['show_cost'])
1391         $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->getKey('label.cost').'</td>';
1392       $body .= '</tr>';
1393       foreach($subtotals as $subtotal) {
1394         $body .= '<tr style="'.$rowSubtotal.'">';
1395         $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($subtotal['name'] ? htmlspecialchars($subtotal['name']) : '&nbsp;').'</td>';
1396         if ($report['show_duration']) {
1397           $body .= '<td style="'.$cellRightAlignedSubtotal.'">';
1398           if ($subtotal['time'] <> '0:00') $body .= $subtotal['time'];
1399           $body .= '</td>';
1400         }
1401         if ($report['show_cost']) {
1402           $body .= '<td style="'.$cellRightAlignedSubtotal.'">';
1403           $body .= ($user->canManageTeam() || $user->isClient()) ? $subtotal['cost'] : $subtotal['expenses'];
1404           $body .= '</td>';
1405         }
1406         $body .= '</tr>';
1407       }
1408
1409       // Print totals.
1410       $body .= '<tr><td>&nbsp;</td></tr>';
1411       $body .= '<tr style="'.$rowSubtotal.'">';
1412       $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.$i18n->getKey('label.total').'</td>';
1413       if ($report['show_duration']) {
1414         $body .= '<td style="'.$cellRightAlignedSubtotal.'">';
1415         if ($totals['time'] <> '0:00') $body .= $totals['time'];
1416         $body .= '</td>';
1417       }
1418       if ($report['show_cost']) {
1419         $body .= '<td nowrap style="'.$cellRightAlignedSubtotal.'">'.htmlspecialchars($user->currency).' ';
1420         $body .= ($user->canManageTeam() || $user->isClient()) ? $totals['cost'] : $totals['expenses'];
1421         $body .= '</td>';
1422       }
1423       $body .= '</tr>';
1424
1425       $body .= '</table>';
1426     } else {
1427       // Regular report.
1428
1429       // Print table header.
1430       $body .= '<table border="0" cellpadding="4" cellspacing="0" width="100%">';
1431       $body .= '<tr>';
1432       $body .= '<td style="'.$tableHeader.'">'.$i18n->getKey('label.date').'</td>';
1433       if ($user->canManageTeam() || $user->isClient())
1434         $body .= '<td style="'.$tableHeader.'">'.$i18n->getKey('label.user').'</td>';
1435       if ($report['show_client'])
1436         $body .= '<td style="'.$tableHeader.'">'.$i18n->getKey('label.client').'</td>';
1437       if ($report['show_project'])
1438         $body .= '<td style="'.$tableHeader.'">'.$i18n->getKey('label.project').'</td>';
1439       if ($report['show_task'])
1440         $body .= '<td style="'.$tableHeader.'">'.$i18n->getKey('label.task').'</td>';
1441       if ($report['show_custom_field_1'])
1442         $body .= '<td style="'.$tableHeader.'">'.htmlspecialchars($custom_fields->fields[0]['label']).'</td>';
1443       if ($report['show_start'])
1444         $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->getKey('label.start').'</td>';
1445       if ($report['show_end'])
1446         $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->getKey('label.finish').'</td>';
1447       if ($report['show_duration'])
1448         $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->getKey('label.duration').'</td>';
1449       if ($report['show_note'])
1450         $body .= '<td style="'.$tableHeader.'">'.$i18n->getKey('label.note').'</td>';
1451       if ($report['show_cost'])
1452         $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->getKey('label.cost').'</td>';
1453       if ($report['show_paid'])
1454         $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->getKey('label.paid').'</td>';
1455       if ($report['show_invoice'])
1456         $body .= '<td style="'.$tableHeader.'">'.$i18n->getKey('label.invoice').'</td>';
1457       $body .= '</tr>';
1458
1459       // Initialize variables to print subtotals.
1460       if ($items && 'no_grouping' != $group_by) {
1461         $print_subtotals = true;
1462         $first_pass = true;
1463         $prev_grouped_by = '';
1464         $cur_grouped_by = '';
1465       }
1466       // Initialize variables to alternate color of rows for different dates.
1467       $prev_date = '';
1468       $cur_date = '';
1469       $row_style = $rowItem;
1470
1471       // Print report items.
1472       if (is_array($items)) {
1473         foreach ($items as $record) {
1474           $cur_date = $record['date'];
1475           // Print a subtotal row after a block of grouped items.
1476           if ($print_subtotals) {
1477             $cur_grouped_by = $record['grouped_by'];
1478             if ($cur_grouped_by != $prev_grouped_by && !$first_pass) {
1479               $body .= '<tr style="'.$rowSubtotal.'">';
1480               $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.$i18n->getKey('label.subtotal').'</td>';
1481               $subtotal_name = htmlspecialchars($subtotals[$prev_grouped_by]['name']);
1482               if ($user->canManageTeam() || $user->isClient()) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'user' ? $subtotal_name : '').'</td>';
1483               if ($report['show_client']) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'client' ? $subtotal_name : '').'</td>';
1484               if ($report['show_project']) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'project' ? $subtotal_name : '').'</td>';
1485               if ($report['show_task']) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'task' ? $subtotal_name : '').'</td>';
1486               if ($report['show_custom_field_1']) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'cf_1' ? $subtotal_name : '').'</td>';
1487               if ($report['show_start']) $body .= '<td></td>';
1488               if ($report['show_end']) $body .= '<td></td>';
1489               if ($report['show_duration']) $body .= '<td style="'.$cellRightAlignedSubtotal.'">'.$subtotals[$prev_grouped_by]['time'].'</td>';
1490               if ($report['show_note']) $body .= '<td></td>';
1491               if ($report['show_cost']) {
1492                 $body .= '<td style="'.$cellRightAlignedSubtotal.'">';
1493                 $body .= ($user->canManageTeam() || $user->isClient()) ? $subtotals[$prev_grouped_by]['cost'] : $subtotals[$prev_grouped_by]['expenses'];
1494                 $body .= '</td>';
1495               }
1496               if ($report['show_paid']) $body .= '<td></td>';
1497               if ($report['show_invoice']) $body .= '<td></td>';
1498               $body .= '</tr>';
1499               $body .= '<tr><td>&nbsp;</td></tr>';
1500             }
1501             $first_pass = false;
1502           }
1503
1504           // Print a regular row.
1505           if ($cur_date != $prev_date)
1506             $row_style = ($row_style == $rowItem) ? $rowItemAlt : $rowItem;
1507           $body .= '<tr style="'.$row_style.'">';
1508           $body .= '<td style="'.$cellLeftAligned.'">'.$record['date'].'</td>';
1509           if ($user->canManageTeam() || $user->isClient())
1510             $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['user']).'</td>';
1511           if ($report['show_client'])
1512             $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['client']).'</td>';
1513           if ($report['show_project'])
1514             $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['project']).'</td>';
1515           if ($report['show_task'])
1516             $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['task']).'</td>';
1517           if ($report['show_custom_field_1'])
1518             $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['cf_1']).'</td>';
1519           if ($report['show_start'])
1520             $body .= '<td nowrap style="'.$cellRightAligned.'">'.$record['start'].'</td>';
1521           if ($report['show_end'])
1522             $body .= '<td nowrap style="'.$cellRightAligned.'">'.$record['finish'].'</td>';
1523           if ($report['show_duration'])
1524             $body .= '<td style="'.$cellRightAligned.'">'.$record['duration'].'</td>';
1525           if ($report['show_note'])
1526             $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['note']).'</td>';
1527           if ($report['show_cost'])
1528             $body .= '<td style="'.$cellRightAligned.'">'.$record['cost'].'</td>';
1529           if ($report['show_paid']) {
1530             $body .= '<td style="'.$cellRightAligned.'">';
1531             $body .= $record['paid'] == 1 ? $i18n->getKey('label.yes') : $i18n->getKey('label.no');
1532             $body .= '</td>';
1533           }
1534           if ($report['show_invoice'])
1535             $body .= '<td style="'.$cellRightAligned.'">'.htmlspecialchars($record['invoice']).'</td>';
1536           $body .= '</tr>';
1537
1538           $prev_date = $record['date'];
1539           if ($print_subtotals)
1540             $prev_grouped_by = $record['grouped_by'];
1541         }
1542       }
1543
1544       // Print a terminating subtotal.
1545       if ($print_subtotals) {
1546         $body .= '<tr style="'.$rowSubtotal.'">';
1547         $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.$i18n->getKey('label.subtotal').'</td>';
1548         $subtotal_name = htmlspecialchars($subtotals[$cur_grouped_by]['name']);
1549         if ($user->canManageTeam() || $user->isClient()) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'user' ? $subtotal_name : '').'</td>';
1550         if ($report['show_client']) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'client' ? $subtotal_name : '').'</td>';
1551         if ($report['show_project']) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'project' ? $subtotal_name : '').'</td>';
1552         if ($report['show_task']) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'task' ? $subtotal_name : '').'</td>';
1553         if ($report['show_custom_field_1']) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'cf_1' ? $subtotal_name : '').'</td>';
1554         if ($report['show_start']) $body .= '<td></td>';
1555         if ($report['show_end']) $body .= '<td></td>';
1556         if ($report['show_duration']) $body .= '<td style="'.$cellRightAlignedSubtotal.'">'.$subtotals[$cur_grouped_by]['time'].'</td>';
1557         if ($report['show_note']) $body .= '<td></td>';
1558         if ($report['show_cost']) {
1559           $body .= '<td style="'.$cellRightAlignedSubtotal.'">';
1560           $body .= ($user->canManageTeam() || $user->isClient()) ? $subtotals[$cur_grouped_by]['cost'] : $subtotals[$cur_grouped_by]['expenses'];
1561           $body .= '</td>';
1562         }
1563         if ($report['show_paid']) $body .= '<td></td>';
1564         if ($report['show_invoice']) $body .= '<td></td>';
1565         $body .= '</tr>';
1566       }
1567
1568       // Print totals.
1569       $body .= '<tr><td>&nbsp;</td></tr>';
1570       $body .= '<tr style="'.$rowSubtotal.'">';
1571       $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.$i18n->getKey('label.total').'</td>';
1572       if ($user->canManageTeam() || $user->isClient()) $body .= '<td></td>';
1573       if ($report['show_client']) $body .= '<td></td>';
1574       if ($report['show_project']) $body .= '<td></td>';
1575       if ($report['show_task']) $body .= '<td></td>';
1576       if ($report['show_custom_field_1']) $body .= '<td></td>';
1577       if ($report['show_start']) $body .= '<td></td>';
1578       if ($report['show_end']) $body .= '<td></td>';
1579       if ($report['show_duration']) $body .= '<td style="'.$cellRightAlignedSubtotal.'">'.$totals['time'].'</td>';
1580       if ($report['show_note']) $body .= '<td></td>';
1581       if ($report['show_cost']) {
1582         $body .= '<td nowrap style="'.$cellRightAlignedSubtotal.'">'.htmlspecialchars($user->currency).' ';
1583         $body .= ($user->canManageTeam() || $user->isClient()) ? $totals['cost'] : $totals['expenses'];
1584         $body .= '</td>';
1585       }
1586       if ($report['show_paid']) $body .= '<td></td>';
1587       if ($report['show_invoice']) $body .= '<td></td>';
1588       $body .= '</tr>';
1589
1590       $body .= '</table>';
1591     }
1592
1593     // Output footer.
1594     if (!defined('REPORT_FOOTER') || !(REPORT_FOOTER == false))
1595       $body .= '<p style="text-align: center;">'.$i18n->getKey('form.mail.footer').'</p>';
1596
1597     // Finish creating email body.
1598     $body .= '</body></html>';
1599
1600     return $body;
1601   }
1602
1603   // sendFavReport - sends a favorite report to a specified email, called from cron.php
1604   static function sendFavReport($report, $subject, $email, $cc) {
1605     // We are called from cron.php, we have no $bean in session.
1606     // cron.php sets global $user and $i18n objects to match our favorite report user.
1607     global $user;
1608     global $i18n;
1609
1610     // Prepare report body.
1611     $body = ttReportHelper::prepareFavReportBody($report);
1612
1613     import('mail.Mailer');
1614     $mailer = new Mailer();
1615     $mailer->setCharSet(CHARSET);
1616     $mailer->setContentType('text/html');
1617     $mailer->setSender(SENDER);
1618     if (!empty($cc))
1619       $mailer->setReceiverCC($cc);
1620     if (!empty($user->bcc_email))
1621       $mailer->setReceiverBCC($user->bcc_email);
1622     $mailer->setReceiver($email);
1623     $mailer->setMailMode(MAIL_MODE);
1624     if (empty($subject)) $subject = $report['name'];
1625     if (!$mailer->send($subject, $body))
1626       return false;
1627
1628     return true;
1629   }
1630 }