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.
 
  11 // | There are only two ways to violate the license:
 
  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).
 
  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).
 
  21 // | This license applies to this document only, not any other software
 
  22 // | that it may be combined with.
 
  24 // +----------------------------------------------------------------------+
 
  26 // | https://www.anuko.com/time_tracker/credits.htm
 
  27 // +----------------------------------------------------------------------+
 
  29 import('ttClientHelper');
 
  30 import('DateAndTime');
 
  32 import('ttTimeHelper');
 
  34 require_once(dirname(__FILE__).'/../../plugins/CustomFields.class.php');
 
  36 // Class ttReportHelper is used for help with reports.
 
  37 class ttReportHelper {
 
  39   // getWhere prepares a WHERE clause for a report query.
 
  40   static function getWhere($bean) {
 
  43     // Prepare 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';
 
  59     // Prepare user list part.
 
  61     if (($user->can('view_reports') || $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->can('view_reports') || $user->isClient())
 
  66       $user_list_part = " and l.user_id in ($userlist)";
 
  68       $user_list_part = " and l.user_id = ".$user->id;
 
  70     // Prepare sql query part for where.
 
  71     if ($bean->getAttribute('period'))
 
  72       $period = new Period($bean->getAttribute('period'), new DateAndTime($user->date_format));
 
  74       $period = new Period();
 
  76         new DateAndTime($user->date_format, $bean->getAttribute('start_date')),
 
  77         new DateAndTime($user->date_format, $bean->getAttribute('end_date')));
 
  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";
 
  84   // getFavWhere prepares a WHERE clause for a favorite report query.
 
  85   static function getFavWhere($report) {
 
  88     // Prepare 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';
 
 104     // Prepare user list part.
 
 106     if (($user->can('view_reports') || $user->isClient())) {
 
 107       if ($report['users'])
 
 108         $userlist = $report['users'];
 
 110         $active_users = ttTeamHelper::getActiveUsers();
 
 111         foreach ($active_users as $single_user)
 
 112           $users[] = $single_user['id'];
 
 113         $userlist = join(',', $users);
 
 116     // Prepare sql query part for user list.
 
 117     $user_list_part = null;
 
 118     if ($user->can('view_reports') || $user->isClient())
 
 119       $user_list_part = " and l.user_id in ($userlist)";
 
 121       $user_list_part = " and l.user_id = ".$user->id;
 
 123     // Prepare sql query part for where.
 
 124     if ($report['period'])
 
 125       $period = new Period($report['period'], new DateAndTime($user->date_format));
 
 127       $period = new Period();
 
 129         new DateAndTime($user->date_format, $report['period_start']),
 
 130         new DateAndTime($user->date_format, $report['period_end']));
 
 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";
 
 137   // getExpenseWhere prepares WHERE clause for expenses query in a report.
 
 138   static function getExpenseWhere($bean) {
 
 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     if ($bean->getAttribute('paid_status')=='1') $dropdown_parts .= ' and ei.paid = 1';
 
 151     if ($bean->getAttribute('paid_status')=='2') $dropdown_parts .= ' and ei.paid = 0';
 
 153     // Prepare user list part.
 
 155     if (($user->can('view_reports') || $user->isClient()) && is_array($bean->getAttribute('users')))
 
 156       $userlist = join(',', $bean->getAttribute('users'));
 
 157     // Prepare sql query part for user list.
 
 158     $user_list_part = null;
 
 159     if ($user->can('view_reports') || $user->isClient())
 
 160       $user_list_part = " and ei.user_id in ($userlist)";
 
 162       $user_list_part = " and ei.user_id = ".$user->id;
 
 164     // Prepare sql query part for where.
 
 165     if ($bean->getAttribute('period'))
 
 166       $period = new Period($bean->getAttribute('period'), new DateAndTime($user->date_format));
 
 168       $period = new Period();
 
 170         new DateAndTime($user->date_format, $bean->getAttribute('start_date')),
 
 171         new DateAndTime($user->date_format, $bean->getAttribute('end_date')));
 
 173     $where = " where ei.status = 1 and ei.date >= '".$period->getStartDate(DB_DATEFORMAT)."' and ei.date <= '".$period->getEndDate(DB_DATEFORMAT)."'".
 
 174       " $user_list_part $dropdown_parts";
 
 178   // getFavExpenseWhere prepares a WHERE clause for expenses query in a favorite report.
 
 179   static function getFavExpenseWhere($report) {
 
 182     // Prepare dropdown parts.
 
 183     $dropdown_parts = '';
 
 184     if ($report['client_id'])
 
 185       $dropdown_parts .= ' and ei.client_id = '.$report['client_id'];
 
 186     elseif ($user->isClient() && $user->client_id)
 
 187       $dropdown_parts .= ' and ei.client_id = '.$user->client_id;
 
 188     if ($report['project_id']) $dropdown_parts .= ' and ei.project_id = '.$report['project_id'];
 
 189     if ($report['invoice']=='1') $dropdown_parts .= ' and ei.invoice_id is not NULL';
 
 190     if ($report['invoice']=='2') $dropdown_parts .= ' and ei.invoice_id is NULL';
 
 191     if ($report['paid_status']=='1') $dropdown_parts .= ' and ei.paid = 1';
 
 192     if ($report['paid_status']=='2') $dropdown_parts .= ' and ei.paid = 0';
 
 194     // Prepare user list part.
 
 196     if (($user->can('view_reports') || $user->isClient())) {
 
 197       if ($report['users'])
 
 198         $userlist = $report['users'];
 
 200         $active_users = ttTeamHelper::getActiveUsers();
 
 201         foreach ($active_users as $single_user)
 
 202           $users[] = $single_user['id'];
 
 203         $userlist = join(',', $users);
 
 206     // Prepare sql query part for user list.
 
 207     $user_list_part = null;
 
 208     if ($user->can('view_reports') || $user->isClient())
 
 209       $user_list_part = " and ei.user_id in ($userlist)";
 
 211       $user_list_part = " and ei.user_id = ".$user->id;
 
 213     // Prepare sql query part for where.
 
 214     if ($report['period'])
 
 215       $period = new Period($report['period'], new DateAndTime($user->date_format));
 
 217       $period = new Period();
 
 219         new DateAndTime($user->date_format, $report['period_start']),
 
 220         new DateAndTime($user->date_format, $report['period_end']));
 
 222     $where = " where ei.status = 1 and ei.date >= '".$period->getStartDate(DB_DATEFORMAT)."' and ei.date <= '".$period->getEndDate(DB_DATEFORMAT)."'".
 
 223       " $user_list_part $dropdown_parts";
 
 227   // getItems retrieves all items associated with a report.
 
 228   // It combines tt_log and tt_expense_items in one array for presentation in one table using mysql union all.
 
 229   // Expense items use the "note" field for item name.
 
 230   static function getItems($bean) {
 
 232     $mdb2 = getConnection();
 
 234     // Determine these once as they are used in multiple places in this function.
 
 235     $canViewReports = $user->can('view_reports');
 
 236     $isClient = $user->isClient();
 
 238     $group_by_option = $bean->getAttribute('group_by');
 
 239     $convertTo12Hour = ('%I:%M %p' == $user->time_format) && ($bean->getAttribute('chstart') || $bean->getAttribute('chfinish'));
 
 241     // Prepare a query for time items in tt_log table.
 
 242     $fields = array(); // An array of fields for database query.
 
 243     array_push($fields, 'l.id as id');
 
 244     array_push($fields, '1 as type'); // Type 1 is for tt_log entries.
 
 245     array_push($fields, 'l.date as date');
 
 246     if($canViewReports || $isClient)
 
 247       array_push($fields, 'u.name as user');
 
 248     // Add client name if it is selected.
 
 249     if ($bean->getAttribute('chclient') || 'client' == $group_by_option)
 
 250       array_push($fields, 'c.name as client');
 
 251     // Add project name if it is selected.
 
 252     if ($bean->getAttribute('chproject') || 'project' == $group_by_option)
 
 253       array_push($fields, 'p.name as project');
 
 254     // Add task name if it is selected.
 
 255     if ($bean->getAttribute('chtask') || 'task' == $group_by_option)
 
 256       array_push($fields, 't.name as task');
 
 258     $include_cf_1 = $bean->getAttribute('chcf_1') || 'cf_1' == $group_by_option;
 
 260       $custom_fields = new CustomFields($user->group_id);
 
 261       $cf_1_type = $custom_fields->fields[0]['type'];
 
 262       if ($cf_1_type == CustomFields::TYPE_TEXT) {
 
 263         array_push($fields, 'cfl.value as cf_1');
 
 264       } elseif ($cf_1_type == CustomFields::TYPE_DROPDOWN) {
 
 265         array_push($fields, 'cfo.value as cf_1');
 
 269     if ($bean->getAttribute('chstart')) {
 
 270       array_push($fields, "l.start as unformatted_start");
 
 271       array_push($fields, "TIME_FORMAT(l.start, '%k:%i') as start");
 
 274     if ($bean->getAttribute('chfinish'))
 
 275       array_push($fields, "TIME_FORMAT(sec_to_time(time_to_sec(l.start) + time_to_sec(l.duration)), '%k:%i') as finish");
 
 277     if ($bean->getAttribute('chduration'))
 
 278       array_push($fields, "TIME_FORMAT(l.duration, '%k:%i') as duration");
 
 280     if ($bean->getAttribute('chnote'))
 
 281       array_push($fields, 'l.comment as note');
 
 283     $includeCost = $bean->getAttribute('chcost');
 
 285       if (MODE_TIME == $user->tracking_mode)
 
 286         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.
 
 288         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.
 
 289       array_push($fields, "null as expense"); 
 
 292     if ($canViewReports && $bean->getAttribute('chpaid'))
 
 293       array_push($fields, 'l.paid as paid');
 
 295     if ($canViewReports && $bean->getAttribute('chip')) {
 
 296       array_push($fields, 'l.created as created');
 
 297       array_push($fields, 'l.created_ip as created_ip');
 
 298       array_push($fields, 'l.modified as modified');
 
 299       array_push($fields, 'l.modified_ip as modified_ip');
 
 302     // Add invoice name if it is selected.
 
 303     if (($canViewReports || $isClient) && $bean->getAttribute('chinvoice'))
 
 304       array_push($fields, 'i.name as invoice');
 
 307     if ($bean->getAttribute('chunits'))
 
 308       array_push($fields, "if(time_to_sec(duration)/60 < $user->first_unit_threshold, 0, ceil(time_to_sec(duration)/60/$user->minutes_in_unit)) as units");
 
 310     // Prepare sql query part for left joins.
 
 312     if ($bean->getAttribute('chclient') || 'client' == $group_by_option)
 
 313       $left_joins .= " left join tt_clients c on (c.id = l.client_id)";
 
 314     if (($canViewReports || $isClient) && $bean->getAttribute('chinvoice'))
 
 315       $left_joins .= " left join tt_invoices i on (i.id = l.invoice_id and i.status = 1)";
 
 316     if ($canViewReports || $isClient || $user->isPluginEnabled('ex'))
 
 317        $left_joins .= " left join tt_users u on (u.id = l.user_id)";
 
 318     if ($bean->getAttribute('chproject') || 'project' == $group_by_option)
 
 319       $left_joins .= " left join tt_projects p on (p.id = l.project_id)";
 
 320     if ($bean->getAttribute('chtask') || 'task' == $group_by_option)
 
 321       $left_joins .= " left join tt_tasks t on (t.id = l.task_id)";
 
 323       if ($cf_1_type == CustomFields::TYPE_TEXT)
 
 324         $left_joins .= " left join tt_custom_field_log cfl on (l.id = cfl.log_id and cfl.status = 1)";
 
 325       elseif ($cf_1_type == CustomFields::TYPE_DROPDOWN) {
 
 326         $left_joins .=  " left join tt_custom_field_log cfl on (l.id = cfl.log_id and cfl.status = 1)".
 
 327           " left join tt_custom_field_options cfo on (cfl.option_id = cfo.id)";
 
 330     if ($includeCost && MODE_TIME != $user->tracking_mode)
 
 331       $left_joins .= " left join tt_user_project_binds upb on (l.user_id = upb.user_id and l.project_id = upb.project_id)";
 
 333     $where = ttReportHelper::getWhere($bean);
 
 335     // Construct sql query for tt_log items.
 
 336     $sql = "select ".join(', ', $fields)." from tt_log l $left_joins $where";
 
 337     // If we don't have expense items (such as when the Expenses plugin is desabled), the above is all sql we need,
 
 338     // with an exception of sorting part, that is added in the end.
 
 340     // However, when we have expenses, we need to do a union with a separate query for expense items from tt_expense_items table.
 
 341     if ($bean->getAttribute('chcost') && $user->isPluginEnabled('ex')) { // if ex(penses) plugin is enabled
 
 343       $fields = array(); // An array of fields for database query.
 
 344       array_push($fields, 'ei.id');
 
 345       array_push($fields, '2 as type'); // Type 2 is for tt_expense_items entries.
 
 346       array_push($fields, 'ei.date');
 
 347       if($canViewReports || $isClient)
 
 348         array_push($fields, 'u.name as user');
 
 349       // Add client name if it is selected.
 
 350       if ($bean->getAttribute('chclient') || 'client' == $group_by_option)
 
 351         array_push($fields, 'c.name as client');
 
 352       // Add project name if it is selected.
 
 353       if ($bean->getAttribute('chproject') || 'project' == $group_by_option)
 
 354         array_push($fields, 'p.name as project');
 
 355       if ($bean->getAttribute('chtask') || 'task' == $group_by_option)
 
 356         array_push($fields, 'null'); // null for task name. We need to match column count for union.
 
 357       if ($bean->getAttribute('chcf_1') || 'cf_1' == $group_by_option)
 
 358         array_push($fields, 'null'); // null for cf_1.
 
 359       if ($bean->getAttribute('chstart')) {
 
 360         array_push($fields, 'null'); // null for unformatted_start.
 
 361         array_push($fields, 'null'); // null for start.
 
 363       if ($bean->getAttribute('chfinish'))
 
 364         array_push($fields, 'null'); // null for finish.
 
 365       if ($bean->getAttribute('chduration'))
 
 366         array_push($fields, 'null'); // null for duration.
 
 367       // Use the note field to print item name.
 
 368       if ($bean->getAttribute('chnote'))
 
 369         array_push($fields, 'ei.name as note');
 
 370       array_push($fields, 'ei.cost as cost');
 
 371       array_push($fields, 'ei.cost as expense');
 
 373       if ($canViewReports && $bean->getAttribute('chpaid'))
 
 374         array_push($fields, 'ei.paid as paid');
 
 376       if ($canViewReports && $bean->getAttribute('chip')) {
 
 377         array_push($fields, 'ei.created as created');
 
 378         array_push($fields, 'ei.created_ip as created_ip');
 
 379         array_push($fields, 'ei.modified as modified');
 
 380         array_push($fields, 'ei.modified_ip as modified_ip');
 
 383       // Add invoice name if it is selected.
 
 384       if (($canViewReports || $isClient) && $bean->getAttribute('chinvoice'))
 
 385         array_push($fields, 'i.name as invoice');
 
 388       if ($bean->getAttribute('chunits'))
 
 389         array_push($fields, 'null'); // null for work units.
 
 391       // Prepare sql query part for left joins.
 
 393       if ($canViewReports || $isClient)
 
 394         $left_joins .= " left join tt_users u on (u.id = ei.user_id)";
 
 395       if ($bean->getAttribute('chclient') || 'client' == $group_by_option)
 
 396         $left_joins .= " left join tt_clients c on (c.id = ei.client_id)";
 
 397       if ($bean->getAttribute('chproject') || 'project' == $group_by_option)
 
 398         $left_joins .= " left join tt_projects p on (p.id = ei.project_id)";
 
 399       if (($canViewReports || $isClient) && $bean->getAttribute('chinvoice'))
 
 400         $left_joins .= " left join tt_invoices i on (i.id = ei.invoice_id and i.status = 1)";
 
 402       $where = ttReportHelper::getExpenseWhere($bean);
 
 404       // Construct sql query for expense items.
 
 405       $sql_for_expense_items = "select ".join(', ', $fields)." from tt_expense_items ei $left_joins $where";
 
 407       // Construct a union.
 
 408       $sql = "($sql) union all ($sql_for_expense_items)";
 
 411     // Determine sort part.
 
 412     $sort_part = ' order by ';
 
 413     if ('no_grouping' == $group_by_option || 'date' == $group_by_option)
 
 414       $sort_part .= 'date';
 
 416       $sort_part .= $group_by_option.', date';
 
 417     if (($canViewReports || $isClient) && is_array($bean->getAttribute('users')) && 'user' != $group_by_option)
 
 418       $sort_part .= ', user, type';
 
 419     if ($bean->getAttribute('chstart'))
 
 420       $sort_part .= ', unformatted_start';
 
 421     $sort_part .= ', id';
 
 424     // By now we are ready with sql.
 
 426     // Obtain items for report.
 
 427     $res = $mdb2->query($sql);
 
 428     if (is_a($res, 'PEAR_Error')) die($res->getMessage());
 
 430     while ($val = $res->fetchRow()) {
 
 431       if ($convertTo12Hour) {
 
 432         if($val['start'] != '')
 
 433           $val['start'] = ttTimeHelper::to12HourFormat($val['start']);
 
 434         if($val['finish'] != '')
 
 435           $val['finish'] = ttTimeHelper::to12HourFormat($val['finish']);
 
 437       if (isset($val['cost'])) {
 
 438         if ('.' != $user->decimal_mark)
 
 439           $val['cost'] = str_replace('.', $user->decimal_mark, $val['cost']);
 
 441       if (isset($val['expense'])) {
 
 442         if ('.' != $user->decimal_mark)
 
 443           $val['expense'] = str_replace('.', $user->decimal_mark, $val['expense']);
 
 445       if ('no_grouping' != $group_by_option) {
 
 446         $val['grouped_by'] = $val[$group_by_option];
 
 447         if ('date' == $group_by_option) {
 
 448           // This is needed to get the date in user date format.
 
 449           $o_date = new DateAndTime(DB_DATEFORMAT, $val['grouped_by']);
 
 450           $val['grouped_by'] = $o_date->toString($user->date_format);
 
 455       // This is needed to get the date in user date format.
 
 456       $o_date = new DateAndTime(DB_DATEFORMAT, $val['date']);
 
 457       $val['date'] = $o_date->toString($user->date_format);
 
 461       $report_items[] = $row;
 
 464     return $report_items;
 
 467   // putInSession stores tt_log and tt_expense_items ids from a report in user session
 
 468   // as 2 comma-separated lists.
 
 469   static function putInSession($report_items) {
 
 470     unset($_SESSION['report_item_ids']);
 
 471     unset($_SESSION['report_item_expense_ids']);
 
 473     // Iterate through records and build 2 comma-separated lists.
 
 474     foreach($report_items as $item) {
 
 475       if ($item['type'] == 1)
 
 476         $report_item_ids .= ','.$item['id'];
 
 477       else if ($item['type'] == 2)
 
 478          $report_item_expense_ids .= ','.$item['id'];
 
 480     $report_item_ids = trim($report_item_ids, ',');
 
 481     $report_item_expense_ids = trim($report_item_expense_ids, ',');
 
 483     // The lists are reqdy. Put them in session.
 
 484     if ($report_item_ids) $_SESSION['report_item_ids'] = $report_item_ids;
 
 485     if ($report_item_expense_ids) $_SESSION['report_item_expense_ids'] = $report_item_expense_ids;
 
 488   // getFromSession obtains tt_log and tt_expense_items ids stored in user session.
 
 489   static function getFromSession() {
 
 491     $report_item_ids = $_SESSION['report_item_ids'];
 
 492     if ($report_item_ids)
 
 493       $items['report_item_ids'] = explode(',', $report_item_ids);
 
 494     $report_item_expense_ids = $_SESSION['report_item_expense_ids'];
 
 495     if ($report_item_expense_ids)
 
 496       $items['report_item_expense_ids'] = explode(',', $report_item_expense_ids);
 
 500   // getFavItems retrieves all items associated with a favorite report.
 
 501   // It combines tt_log and tt_expense_items in one array for presentation in one table using mysql union all.
 
 502   // Expense items use the "note" field for item name.
 
 503   static function getFavItems($report) {
 
 505     $mdb2 = getConnection();
 
 507     // Determine these once as they are used in multiple places in this function.
 
 508     $canViewReports = $user->can('view_reports');
 
 509     $isClient = $user->isClient();
 
 511     $group_by_option = $report['group_by'];
 
 512     $convertTo12Hour = ('%I:%M %p' == $user->time_format) && ($report['show_start'] || $report['show_end']);
 
 514     // Prepare a query for time items in tt_log table.
 
 515     $fields = array(); // An array of fields for database query.
 
 516     array_push($fields, 'l.id as id');
 
 517     array_push($fields, '1 as type'); // Type 1 is for tt_log entries.
 
 518     array_push($fields, 'l.date as date');
 
 519     if($canViewReports || $isClient)
 
 520       array_push($fields, 'u.name as user');
 
 521     // Add client name if it is selected.
 
 522     if ($report['show_client'] || 'client' == $group_by_option)
 
 523       array_push($fields, 'c.name as client');
 
 524     // Add project name if it is selected.
 
 525     if ($report['show_project'] || 'project' == $group_by_option)
 
 526       array_push($fields, 'p.name as project');
 
 527     // Add task name if it is selected.
 
 528     if ($report['show_task'] || 'task' == $group_by_option)
 
 529       array_push($fields, 't.name as task');
 
 531     $include_cf_1 = $report['show_custom_field_1'] || 'cf_1' == $group_by_option;
 
 533       $custom_fields = new CustomFields($user->group_id);
 
 534       $cf_1_type = $custom_fields->fields[0]['type'];
 
 535       if ($cf_1_type == CustomFields::TYPE_TEXT) {
 
 536         array_push($fields, 'cfl.value as cf_1');
 
 537       } elseif ($cf_1_type == CustomFields::TYPE_DROPDOWN) {
 
 538         array_push($fields, 'cfo.value as cf_1');
 
 542     if ($report['show_start']) {
 
 543       array_push($fields, "l.start as unformatted_start");
 
 544       array_push($fields, "TIME_FORMAT(l.start, '%k:%i') as start");
 
 547     if ($report['show_end'])
 
 548       array_push($fields, "TIME_FORMAT(sec_to_time(time_to_sec(l.start) + time_to_sec(l.duration)), '%k:%i') as finish");
 
 550     if ($report['show_duration'])
 
 551       array_push($fields, "TIME_FORMAT(l.duration, '%k:%i') as duration");
 
 553     if ($report['show_note'])
 
 554       array_push($fields, 'l.comment as note');
 
 556     $includeCost = $report['show_cost'];
 
 558       if (MODE_TIME == $user->tracking_mode)
 
 559         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.
 
 561         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.
 
 562       array_push($fields, "null as expense"); 
 
 565     if ($canViewReports && $report['show_paid'])
 
 566       array_push($fields, 'l.paid as paid');
 
 568     if ($canViewReports && $report['show_ip']) {
 
 569       array_push($fields, 'l.created as created');
 
 570       array_push($fields, 'l.created_ip as created_ip');
 
 571       array_push($fields, 'l.modified as modified');
 
 572       array_push($fields, 'l.modified_ip as modified_ip');
 
 574     // Add invoice name if it is selected.
 
 575     if (($canViewReports || $isClient) && $report['show_invoice'])
 
 576       array_push($fields, 'i.name as invoice');
 
 578     // Prepare sql query part for left joins.
 
 580     if ($report['show_client'] || 'client' == $group_by_option)
 
 581       $left_joins .= " left join tt_clients c on (c.id = l.client_id)";
 
 582     if (($canViewReports || $isClient) && $report['show_invoice'])
 
 583       $left_joins .= " left join tt_invoices i on (i.id = l.invoice_id and i.status = 1)";
 
 584     if ($canViewReports || $isClient || $user->isPluginEnabled('ex'))
 
 585        $left_joins .= " left join tt_users u on (u.id = l.user_id)";
 
 586     if ($report['show_project'] || 'project' == $group_by_option)
 
 587       $left_joins .= " left join tt_projects p on (p.id = l.project_id)";
 
 588     if ($report['show_task'] || 'task' == $group_by_option)
 
 589       $left_joins .= " left join tt_tasks t on (t.id = l.task_id)";
 
 591       if ($cf_1_type == CustomFields::TYPE_TEXT)
 
 592         $left_joins .= " left join tt_custom_field_log cfl on (l.id = cfl.log_id and cfl.status = 1)";
 
 593       elseif ($cf_1_type == CustomFields::TYPE_DROPDOWN) {
 
 594         $left_joins .=  " left join tt_custom_field_log cfl on (l.id = cfl.log_id and cfl.status = 1)".
 
 595           " left join tt_custom_field_options cfo on (cfl.option_id = cfo.id)";
 
 598     if ($includeCost && MODE_TIME != $user->tracking_mode)
 
 599       $left_joins .= " left join tt_user_project_binds upb on (l.user_id = upb.user_id and l.project_id = upb.project_id)";
 
 601     $where = ttReportHelper::getFavWhere($report);
 
 603     // Construct sql query for tt_log items.
 
 604     $sql = "select ".join(', ', $fields)." from tt_log l $left_joins $where";
 
 605     // If we don't have expense items (such as when the Expenses plugin is desabled), the above is all sql we need,
 
 606     // with an exception of sorting part, that is added in the end.
 
 608     // However, when we have expenses, we need to do a union with a separate query for expense items from tt_expense_items table.
 
 609     if ($report['show_cost'] && $user->isPluginEnabled('ex')) { // if ex(penses) plugin is enabled
 
 611       $fields = array(); // An array of fields for database query.
 
 612       array_push($fields, 'ei.id');
 
 613       array_push($fields, '2 as type'); // Type 2 is for tt_expense_items entries.
 
 614       array_push($fields, 'ei.date');
 
 615       if($canViewReports || $isClient)
 
 616         array_push($fields, 'u.name as user');
 
 617       // Add client name if it is selected.
 
 618       if ($report['show_client'] || 'client' == $group_by_option)
 
 619         array_push($fields, 'c.name as client');
 
 620       // Add project name if it is selected.
 
 621       if ($report['show_project'] || 'project' == $group_by_option)
 
 622         array_push($fields, 'p.name as project');
 
 623       if ($report['show_task'] || 'task' == $group_by_option)
 
 624         array_push($fields, 'null'); // null for task name. We need to match column count for union.
 
 625       if ($report['show_custom_field_1'] || 'cf_1' == $group_by_option)
 
 626         array_push($fields, 'null'); // null for cf_1.
 
 627       if ($report['show_start']) {
 
 628         array_push($fields, 'null'); // null for unformatted_start.
 
 629         array_push($fields, 'null'); // null for start.
 
 631       if ($report['show_end'])
 
 632         array_push($fields, 'null'); // null for finish.
 
 633       if ($report['show_duration'])
 
 634         array_push($fields, 'null'); // null for duration.
 
 635       // Use the note field to print item name.
 
 636       if ($report['show_note'])
 
 637         array_push($fields, 'ei.name as note');
 
 638       array_push($fields, 'ei.cost as cost');
 
 639       array_push($fields, 'ei.cost as expense');
 
 641       if ($canViewReports && $report['show_paid'])
 
 642         array_push($fields, 'ei.paid as paid');
 
 644       if ($canViewReports && $report['show_ip']) {
 
 645         array_push($fields, 'ei.created as created');
 
 646         array_push($fields, 'ei.created_ip as created_ip');
 
 647         array_push($fields, 'ei.modified as modified');
 
 648         array_push($fields, 'ei.modified_ip as modified_ip');
 
 650       // Add invoice name if it is selected.
 
 651       if (($canViewReports || $isClient) && $report['show_invoice'])
 
 652         array_push($fields, 'i.name as invoice');
 
 654       // Prepare sql query part for left joins.
 
 656       if ($canViewReports || $isClient)
 
 657         $left_joins .= " left join tt_users u on (u.id = ei.user_id)";
 
 658       if ($report['show_client'] || 'client' == $group_by_option)
 
 659         $left_joins .= " left join tt_clients c on (c.id = ei.client_id)";
 
 660       if ($report['show_project'] || 'project' == $group_by_option)
 
 661         $left_joins .= " left join tt_projects p on (p.id = ei.project_id)";
 
 662       if (($canViewReports || $isClient) && $report['show_invoice'])
 
 663         $left_joins .= " left join tt_invoices i on (i.id = ei.invoice_id and i.status = 1)";
 
 665       $where = ttReportHelper::getFavExpenseWhere($report);
 
 667       // Construct sql query for expense items.
 
 668       $sql_for_expense_items = "select ".join(', ', $fields)." from tt_expense_items ei $left_joins $where";
 
 670       // Construct a union.
 
 671       $sql = "($sql) union all ($sql_for_expense_items)";
 
 674     // Determine sort part.
 
 675     $sort_part = ' order by ';
 
 676     if ($group_by_option == null || 'no_grouping' == $group_by_option || 'date' == $group_by_option) // TODO: fix DB for NULL values in group_by field.
 
 677       $sort_part .= 'date';
 
 679       $sort_part .= $group_by_option.', date';
 
 680     if (($canViewReports || $isClient) /*&& is_array($bean->getAttribute('users'))*/ && 'user' != $group_by_option)
 
 681       $sort_part .= ', user, type';
 
 682     if ($report['show_start'])
 
 683       $sort_part .= ', unformatted_start';
 
 684     $sort_part .= ', id';
 
 687     // By now we are ready with sql.
 
 689     // Obtain items for report.
 
 690     $res = $mdb2->query($sql);
 
 691     if (is_a($res, 'PEAR_Error')) die($res->getMessage());
 
 693     while ($val = $res->fetchRow()) {
 
 694       if ($convertTo12Hour) {
 
 695         if($val['start'] != '')
 
 696           $val['start'] = ttTimeHelper::to12HourFormat($val['start']);
 
 697         if($val['finish'] != '')
 
 698           $val['finish'] = ttTimeHelper::to12HourFormat($val['finish']);
 
 700       if (isset($val['cost'])) {
 
 701         if ('.' != $user->decimal_mark)
 
 702           $val['cost'] = str_replace('.', $user->decimal_mark, $val['cost']);
 
 704       if (isset($val['expense'])) {
 
 705         if ('.' != $user->decimal_mark)
 
 706           $val['expense'] = str_replace('.', $user->decimal_mark, $val['expense']);
 
 708       if ('no_grouping' != $group_by_option) {
 
 709         $val['grouped_by'] = $val[$group_by_option];
 
 710         if ('date' == $group_by_option) {
 
 711           // This is needed to get the date in user date format.
 
 712           $o_date = new DateAndTime(DB_DATEFORMAT, $val['grouped_by']);
 
 713           $val['grouped_by'] = $o_date->toString($user->date_format);
 
 718       // This is needed to get the date in user date format.
 
 719       $o_date = new DateAndTime(DB_DATEFORMAT, $val['date']);
 
 720       $val['date'] = $o_date->toString($user->date_format);
 
 724       $report_items[] = $row;
 
 727     return $report_items;
 
 730   // getSubtotals calculates report items subtotals when a report is grouped by.
 
 731   // Without expenses, it's a simple select with group by.
 
 732   // With expenses, it becomes a select with group by from a combined set of records obtained with "union all".
 
 733   static function getSubtotals($bean) {
 
 736     $group_by_option = $bean->getAttribute('group_by');
 
 737     if ('no_grouping' == $group_by_option) return null;
 
 739     $mdb2 = getConnection();
 
 741     // Start with sql to obtain subtotals for time items. This simple sql will be used when we have no expenses.
 
 743     // Determine group by field and a required join.
 
 744     switch ($group_by_option) {
 
 746         $group_field = 'l.date';
 
 750         $group_field = 'u.name';
 
 751         $group_join = 'left join tt_users u on (l.user_id = u.id) ';
 
 754         $group_field = 'c.name';
 
 755         $group_join = 'left join tt_clients c on (l.client_id = c.id) ';
 
 758         $group_field = 'p.name';
 
 759         $group_join = 'left join tt_projects p on (l.project_id = p.id) ';
 
 762         $group_field = 't.name';
 
 763         $group_join = 'left join tt_tasks t on (l.task_id = t.id) ';
 
 766         $group_field = 'cfo.value';
 
 767         $custom_fields = new CustomFields($user->group_id);
 
 768         if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_TEXT)
 
 769           $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) ';
 
 770         elseif ($custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN)
 
 771           $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) ';
 
 775     $where = ttReportHelper::getWhere($bean);
 
 776     if ($bean->getAttribute('chcost')) {
 
 777       if (MODE_TIME == $user->tracking_mode) {
 
 778         if ($group_by_option != 'user')
 
 779           $left_join = 'left join tt_users u on (l.user_id = u.id)';
 
 780         $sql = "select $group_field as group_field, sum(time_to_sec(l.duration)) as time,
 
 781           sum(if(time_to_sec(duration)/60 < $user->first_unit_threshold, 0, ceil(time_to_sec(duration)/60/$user->minutes_in_unit))) as units,
 
 782           sum(cast(l.billable * coalesce(u.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10, 2))) as cost,
 
 783           null as expenses from tt_log l
 
 784           $group_join $left_join $where group by $group_field";
 
 786         // If we are including cost and tracking projects, our query (the same as above) needs to join the tt_user_project_binds table.
 
 787         $sql = "select $group_field as group_field, sum(time_to_sec(l.duration)) as time,
 
 788           sum(if(time_to_sec(duration)/60 < $user->first_unit_threshold, 0, ceil(time_to_sec(duration)/60/$user->minutes_in_unit))) as units,
 
 789           sum(cast(l.billable * coalesce(upb.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2))) as cost,
 
 790           null as expenses from tt_log l
 
 792           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";
 
 795       $sql = "select $group_field as group_field, sum(time_to_sec(l.duration)) as time,
 
 796         sum(if(time_to_sec(duration)/60 < $user->first_unit_threshold, 0, ceil(time_to_sec(duration)/60/$user->minutes_in_unit))) as units,
 
 797         null as expenses from tt_log l
 
 798         $group_join $where group by $group_field";
 
 800     // By now we have sql for time items.
 
 802     // However, when we have expenses, we need to do a union with a separate query for expense items from tt_expense_items table.
 
 803     if ($bean->getAttribute('chcost') && $user->isPluginEnabled('ex')) { // if ex(penses) plugin is enabled
 
 805       // Determine group by field and a required join.
 
 807       $group_field = 'null';
 
 808       switch ($group_by_option) {
 
 810           $group_field = 'ei.date';
 
 814           $group_field = 'u.name';
 
 815           $group_join = 'left join tt_users u on (ei.user_id = u.id) ';
 
 818           $group_field = 'c.name';
 
 819           $group_join = 'left join tt_clients c on (ei.client_id = c.id) ';
 
 822           $group_field = 'p.name';
 
 823           $group_join = 'left join tt_projects p on (ei.project_id = p.id) ';
 
 827       $where = ttReportHelper::getExpenseWhere($bean);
 
 828       $sql_for_expenses = "select $group_field as group_field, null as time, null as units, sum(ei.cost) as cost, sum(ei.cost) as expenses from tt_expense_items ei 
 
 830       // Add a "group by" clause if we are grouping.
 
 831       if ('null' != $group_field) $sql_for_expenses .= " group by $group_field";
 
 833       // Create a combined query.
 
 834       $sql = "select group_field, sum(time) as time, sum(units) as units, sum(cost) as cost, sum(expenses) as expenses from (($sql) union all ($sql_for_expenses)) t group by group_field";
 
 838     $res = $mdb2->query($sql);
 
 839     if (is_a($res, 'PEAR_Error')) die($res->getMessage());
 
 841     while ($val = $res->fetchRow()) {
 
 842       if ('date' == $group_by_option) {
 
 843         // This is needed to get the date in user date format.
 
 844         $o_date = new DateAndTime(DB_DATEFORMAT, $val['group_field']);
 
 845         $val['group_field'] = $o_date->toString($user->date_format);
 
 848       $time = $val['time'] ? sec_to_time_fmt_hm($val['time']) : null;
 
 849       if ($bean->getAttribute('chcost')) {
 
 850         if ('.' != $user->decimal_mark) {
 
 851           $val['cost'] = str_replace('.', $user->decimal_mark, $val['cost']);
 
 852           $val['expenses'] = str_replace('.', $user->decimal_mark, $val['expenses']);
 
 854         $subtotals[$val['group_field']] = array('name'=>$val['group_field'],'time'=>$time, 'units'=> $val['units'],'cost'=>$val['cost'],'expenses'=>$val['expenses']);
 
 856         $subtotals[$val['group_field']] = array('name'=>$val['group_field'],'time'=>$time, 'units'=> $val['units']);
 
 862   // getFavSubtotals calculates report items subtotals when a favorite report is grouped by.
 
 863   // Without expenses, it's a simple select with group by.
 
 864   // With expenses, it becomes a select with group by from a combined set of records obtained with "union all".
 
 865   static function getFavSubtotals($report) {
 
 868     $group_by_option = $report['group_by'];
 
 869     if ('no_grouping' == $group_by_option) return null;
 
 871     $mdb2 = getConnection();
 
 873     // Start with sql to obtain subtotals for time items. This simple sql will be used when we have no expenses.
 
 875     // Determine group by field and a required join.
 
 876     switch ($group_by_option) {
 
 878         $group_field = 'l.date';
 
 882         $group_field = 'u.name';
 
 883         $group_join = 'left join tt_users u on (l.user_id = u.id) ';
 
 886         $group_field = 'c.name';
 
 887         $group_join = 'left join tt_clients c on (l.client_id = c.id) ';
 
 890         $group_field = 'p.name';
 
 891         $group_join = 'left join tt_projects p on (l.project_id = p.id) ';
 
 894         $group_field = 't.name';
 
 895         $group_join = 'left join tt_tasks t on (l.task_id = t.id) ';
 
 898         $group_field = 'cfo.value';
 
 899         $custom_fields = new CustomFields($user->group_id);
 
 900         if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_TEXT)
 
 901           $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) ';
 
 902         elseif ($custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN)
 
 903           $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) ';
 
 907     $where = ttReportHelper::getFavWhere($report);
 
 908     if ($report['show_cost']) {
 
 909       if (MODE_TIME == $user->tracking_mode) {
 
 910         if ($group_by_option != 'user')
 
 911           $left_join = 'left join tt_users u on (l.user_id = u.id)';
 
 912         $sql = "select $group_field as group_field, sum(time_to_sec(l.duration)) as time, 
 
 913           sum(cast(l.billable * coalesce(u.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10, 2))) as cost,
 
 914           null as expenses from tt_log l
 
 915           $group_join $left_join $where group by $group_field";
 
 917         // If we are including cost and tracking projects, our query (the same as above) needs to join the tt_user_project_binds table.
 
 918         $sql = "select $group_field as group_field, sum(time_to_sec(l.duration)) as time, 
 
 919           sum(cast(l.billable * coalesce(upb.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2))) as cost,
 
 920           null as expenses from tt_log l 
 
 922           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";
 
 925       $sql = "select $group_field as group_field, sum(time_to_sec(l.duration)) as time, null as expenses from tt_log l 
 
 926          $group_join $where group by $group_field";
 
 928     // By now we have sql for time items.
 
 930     // However, when we have expenses, we need to do a union with a separate query for expense items from tt_expense_items table.
 
 931     if ($report['show_cost'] && $user->isPluginEnabled('ex')) { // if ex(penses) plugin is enabled
 
 933       // Determine group by field and a required join.
 
 935       $group_field = 'null';
 
 936       switch ($group_by_option) {
 
 938           $group_field = 'ei.date';
 
 942           $group_field = 'u.name';
 
 943           $group_join = 'left join tt_users u on (ei.user_id = u.id) ';
 
 946           $group_field = 'c.name';
 
 947           $group_join = 'left join tt_clients c on (ei.client_id = c.id) ';
 
 950           $group_field = 'p.name';
 
 951           $group_join = 'left join tt_projects p on (ei.project_id = p.id) ';
 
 955       $where = ttReportHelper::getFavExpenseWhere($report);
 
 956       $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 
 
 958       // Add a "group by" clause if we are grouping.
 
 959       if ('null' != $group_field) $sql_for_expenses .= " group by $group_field";
 
 961       // Create a combined query.
 
 962       $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";
 
 966     $res = $mdb2->query($sql);
 
 967     if (is_a($res, 'PEAR_Error')) die($res->getMessage());
 
 969     while ($val = $res->fetchRow()) {
 
 970       if ('date' == $group_by_option) {
 
 971         // This is needed to get the date in user date format.
 
 972         $o_date = new DateAndTime(DB_DATEFORMAT, $val['group_field']);
 
 973         $val['group_field'] = $o_date->toString($user->date_format);
 
 976       $time = $val['time'] ? sec_to_time_fmt_hm($val['time']) : null;
 
 977       if ($report['show_cost']) {
 
 978         if ('.' != $user->decimal_mark) {
 
 979           $val['cost'] = str_replace('.', $user->decimal_mark, $val['cost']);
 
 980           $val['expenses'] = str_replace('.', $user->decimal_mark, $val['expenses']);
 
 982         $subtotals[$val['group_field']] = array('name'=>$val['group_field'],'time'=>$time,'cost'=>$val['cost'],'expenses'=>$val['expenses']);
 
 984         $subtotals[$val['group_field']] = array('name'=>$val['group_field'],'time'=>$time);
 
 990   // getTotals calculates total hours and cost for all report items.
 
 991   static function getTotals($bean)
 
 995     $mdb2 = getConnection();
 
 997     $where = ttReportHelper::getWhere($bean);
 
 999     // TODO: build query in parts so the work units inclusion is conditional.
 
1001     // Start with a query for time items.
 
1002     if ($bean->getAttribute('chcost')) {
 
1003       if (MODE_TIME == $user->tracking_mode) {
 
1004         $sql = "select sum(time_to_sec(l.duration)) as time,
 
1005           sum(if(time_to_sec(duration)/60 < $user->first_unit_threshold, 0, ceil(time_to_sec(duration)/60/$user->minutes_in_unit))) as units,
 
1006           sum(cast(l.billable * coalesce(u.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2))) as cost,
 
1009           left join tt_users u on (l.user_id = u.id) $where";
 
1011         $sql = "select sum(time_to_sec(l.duration)) as time,
 
1012           sum(if(time_to_sec(duration)/60 < $user->first_unit_threshold, 0, ceil(time_to_sec(duration)/60/$user->minutes_in_unit))) as units,
 
1013           sum(cast(l.billable * coalesce(upb.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2))) as cost,
 
1016           left join tt_user_project_binds upb on (l.user_id = upb.user_id and l.project_id = upb.project_id) $where";
 
1019       $sql = "select sum(time_to_sec(l.duration)) as time,"
 
1020             ." sum(if(time_to_sec(duration)/60 < $user->first_unit_threshold, 0, ceil(time_to_sec(duration)/60/$user->minutes_in_unit))) as units,"
 
1021             ." null as cost, null as expenses from tt_log l $where";
 
1023     // If we have expenses, query becomes a bit more complex.
 
1024     if ($bean->getAttribute('chcost') && $user->isPluginEnabled('ex')) {
 
1025       $where = ttReportHelper::getExpenseWhere($bean);
 
1026       $sql_for_expenses = "select null as time, null as units, sum(cost) as cost, sum(cost) as expenses from tt_expense_items ei $where";
 
1028       // Create a combined query.
 
1029       $sql = "select sum(time) as time, sum(units) as units, sum(cost) as cost, sum(expenses) as expenses from (($sql) union all ($sql_for_expenses)) t";
 
1033     $res = $mdb2->query($sql);
 
1034     if (is_a($res, 'PEAR_Error')) die($res->getMessage());
 
1036     $val = $res->fetchRow();
 
1037     $total_time = $val['time'] ? sec_to_time_fmt_hm($val['time']) : null;
 
1038     if ($bean->getAttribute('chcost')) {
 
1039       $total_cost = $val['cost'];
 
1040       if (!$total_cost) $total_cost = '0.00';
 
1041       if ('.' != $user->decimal_mark)
 
1042         $total_cost = str_replace('.', $user->decimal_mark, $total_cost);
 
1043       $total_expenses = $val['expenses'];
 
1044       if (!$total_expenses) $total_expenses = '0.00';
 
1045       if ('.' != $user->decimal_mark)
 
1046         $total_expenses = str_replace('.', $user->decimal_mark, $total_expenses);
 
1049     if ($bean->getAttribute('period'))
 
1050       $period = new Period($bean->getAttribute('period'), new DateAndTime($user->date_format));
 
1052       $period = new Period();
 
1054         new DateAndTime($user->date_format, $bean->getAttribute('start_date')),
 
1055         new DateAndTime($user->date_format, $bean->getAttribute('end_date')));
 
1058     $totals['start_date'] = $period->getStartDate();
 
1059     $totals['end_date'] = $period->getEndDate();
 
1060     $totals['time'] = $total_time;
 
1061     $totals['units'] = $val['units'];
 
1062     $totals['cost'] = $total_cost;
 
1063     $totals['expenses'] = $total_expenses;
 
1068   // getFavTotals calculates total hours and cost for all favorite report items.
 
1069   static function getFavTotals($report)
 
1073     $mdb2 = getConnection();
 
1075     $where = ttReportHelper::getFavWhere($report);
 
1077     // Start with a query for time items.
 
1078     if ($report['show_cost']) {
 
1079       if (MODE_TIME == $user->tracking_mode) {
 
1080         $sql = "select sum(time_to_sec(l.duration)) as time,
 
1081           sum(cast(l.billable * coalesce(u.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2))) as cost,
 
1084           left join tt_users u on (l.user_id = u.id) $where";
 
1086         $sql = "select sum(time_to_sec(l.duration)) as time,
 
1087           sum(cast(l.billable * coalesce(upb.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2))) as cost,
 
1090           left join tt_user_project_binds upb on (l.user_id = upb.user_id and l.project_id = upb.project_id) $where";
 
1093       $sql = "select sum(time_to_sec(l.duration)) as time, null as cost, null as expenses from tt_log l $where";
 
1095     // If we have expenses, query becomes a bit more complex.
 
1096     if ($report['show_cost'] && $user->isPluginEnabled('ex')) {
 
1097       $where = ttReportHelper::getFavExpenseWhere($report);
 
1098       $sql_for_expenses = "select null as time, sum(cost) as cost, sum(cost) as expenses from tt_expense_items ei $where";
 
1099       // Create a combined query.
 
1100       $sql = "select sum(time) as time, sum(cost) as cost, sum(expenses) as expenses from (($sql) union all ($sql_for_expenses)) t";
 
1104     $res = $mdb2->query($sql);
 
1105     if (is_a($res, 'PEAR_Error')) die($res->getMessage());
 
1107     $val = $res->fetchRow();
 
1108     $total_time = $val['time'] ? sec_to_time_fmt_hm($val['time']) : null;
 
1109     if ($report['show_cost']) {
 
1110       $total_cost = $val['cost'];
 
1111       if (!$total_cost) $total_cost = '0.00';
 
1112       if ('.' != $user->decimal_mark)
 
1113         $total_cost = str_replace('.', $user->decimal_mark, $total_cost);
 
1114       $total_expenses = $val['expenses'];
 
1115       if (!$total_expenses) $total_expenses = '0.00';
 
1116       if ('.' != $user->decimal_mark)
 
1117         $total_expenses = str_replace('.', $user->decimal_mark, $total_expenses);
 
1120     if ($report['period'])
 
1121       $period = new Period($report['period'], new DateAndTime($user->date_format));
 
1123       $period = new Period();
 
1125         new DateAndTime($user->date_format, $report['period_start']),
 
1126         new DateAndTime($user->date_format, $report['period_end']));
 
1129     $totals['start_date'] = $period->getStartDate();
 
1130     $totals['end_date'] = $period->getEndDate();
 
1131     $totals['time'] = $total_time;
 
1132     $totals['cost'] = $total_cost;
 
1133     $totals['expenses'] = $total_expenses;
 
1138   // The assignToInvoice assigns a set of records to a specific invoice.
 
1139   static function assignToInvoice($invoice_id, $time_log_ids, $expense_item_ids)
 
1141     $mdb2 = getConnection();
 
1142     if ($time_log_ids) {
 
1143       $sql = "update tt_log set invoice_id = ".$mdb2->quote($invoice_id).
 
1144         " where id in(".join(', ', $time_log_ids).")";
 
1145       $affected = $mdb2->exec($sql);
 
1146       if (is_a($affected, 'PEAR_Error')) die($affected->getMessage());
 
1148     if ($expense_item_ids) {
 
1149       $sql = "update tt_expense_items set invoice_id = ".$mdb2->quote($invoice_id).
 
1150         " where id in(".join(', ', $expense_item_ids).")";
 
1151       $affected = $mdb2->exec($sql);
 
1152       if (is_a($affected, 'PEAR_Error')) die($affected->getMessage());
 
1156   // The markPaid marks a set of records as either paid or unpaid.
 
1157   static function markPaid($time_log_ids, $expense_item_ids, $paid = true)
 
1159     $mdb2 = getConnection();
 
1160     $paid_val = (int) $paid;
 
1161     if ($time_log_ids) {
 
1162       $sql = "update tt_log set paid = $paid_val where id in(".join(', ', $time_log_ids).")";
 
1163       $affected = $mdb2->exec($sql);
 
1164       if (is_a($affected, 'PEAR_Error')) die($affected->getMessage());
 
1166     if ($expense_item_ids) {
 
1167       $sql = "update tt_expense_items set paid = $paid_val where id in(".join(', ', $expense_item_ids).")";
 
1168       $affected = $mdb2->exec($sql);
 
1169       if (is_a($affected, 'PEAR_Error')) die($affected->getMessage());
 
1173   // prepareReportBody - prepares an email body for report.
 
1174   static function prepareReportBody($bean, $comment)
 
1179     // Determine these once as they are used in multiple places in this function.
 
1180     $canViewReports = $user->can('view_reports');
 
1181     $isClient = $user->isClient();
 
1183     $items = ttReportHelper::getItems($bean);
 
1184     $group_by = $bean->getAttribute('group_by');
 
1185     if ($group_by && 'no_grouping' != $group_by)
 
1186       $subtotals = ttReportHelper::getSubtotals($bean);
 
1187     $totals = ttReportHelper::getTotals($bean);
 
1189     // Use custom fields plugin if it is enabled.
 
1190     if ($user->isPluginEnabled('cf'))
 
1191       $custom_fields = new CustomFields($user->group_id);
 
1193     // Define some styles to use in email.
 
1194     $style_title = 'text-align: center; font-size: 15pt; font-family: Arial, Helvetica, sans-serif;';
 
1195     $tableHeader = 'font-weight: bold; background-color: #a6ccf7; text-align: left;';
 
1196     $tableHeaderCentered = 'font-weight: bold; background-color: #a6ccf7; text-align: center;';
 
1197     $rowItem = 'background-color: #ffffff;';
 
1198     $rowItemAlt = 'background-color: #f5f5f5;';
 
1199     $rowSubtotal = 'background-color: #e0e0e0;';
 
1200     $cellLeftAligned = 'text-align: left; vertical-align: top;';
 
1201     $cellRightAligned = 'text-align: right; vertical-align: top;';
 
1202     $cellLeftAlignedSubtotal = 'font-weight: bold; text-align: left; vertical-align: top;';
 
1203     $cellRightAlignedSubtotal = 'font-weight: bold; text-align: right; vertical-align: top;';
 
1205     // Start creating email body.
 
1207     $body .= '<head><meta http-equiv="content-type" content="text/html; charset='.CHARSET.'"></head>';
 
1211     $body .= '<p style="'.$style_title.'">'.$i18n->get('form.mail.report_subject').': '.$totals['start_date'].' - '.$totals['end_date'].'</p>';
 
1214     if ($comment) $body .= '<p>'.htmlspecialchars($comment).'</p>';
 
1216     if ($bean->getAttribute('chtotalsonly')) {
 
1217       // Totals only report. Output subtotals.
 
1219       // Determine group_by header.
 
1220       if ('cf_1' == $group_by)
 
1221         $group_by_header = htmlspecialchars($custom_fields->fields[0]['label']);
 
1223         $key = 'label.'.$group_by;
 
1224         $group_by_header = $i18n->get($key);
 
1227       $body .= '<table border="0" cellpadding="4" cellspacing="0" width="100%">';
 
1229       $body .= '<td style="'.$tableHeader.'">'.$group_by_header.'</td>';
 
1230       if ($bean->getAttribute('chduration'))
 
1231         $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->get('label.duration').'</td>';
 
1232       if ($bean->getAttribute('chcost'))
 
1233         $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->get('label.cost').'</td>';
 
1235       foreach($subtotals as $subtotal) {
 
1236         $body .= '<tr style="'.$rowSubtotal.'">';
 
1237         $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($subtotal['name'] ? htmlspecialchars($subtotal['name']) : ' ').'</td>';
 
1238         if ($bean->getAttribute('chduration')) {
 
1239           $body .= '<td style="'.$cellRightAlignedSubtotal.'">';
 
1240           if ($subtotal['time'] <> '0:00') $body .= $subtotal['time'];
 
1243         if ($bean->getAttribute('chcost')) {
 
1244           $body .= '<td style="'.$cellRightAlignedSubtotal.'">';
 
1245           $body .= ($canViewReports || $isClient) ? $subtotal['cost'] : $subtotal['expenses'];
 
1252       $body .= '<tr><td> </td></tr>';
 
1253       $body .= '<tr style="'.$rowSubtotal.'">';
 
1254       $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.$i18n->get('label.total').'</td>';
 
1255       if ($bean->getAttribute('chduration')) {
 
1256         $body .= '<td style="'.$cellRightAlignedSubtotal.'">';
 
1257         if ($totals['time'] <> '0:00') $body .= $totals['time'];
 
1260       if ($bean->getAttribute('chcost')) {
 
1261         $body .= '<td nowrap style="'.$cellRightAlignedSubtotal.'">'.htmlspecialchars($user->currency).' ';
 
1262         $body .= ($canViewReports || $isClient) ? $totals['cost'] : $totals['expenses'];
 
1267       $body .= '</table>';
 
1271       // Print table header.
 
1272       $body .= '<table border="0" cellpadding="4" cellspacing="0" width="100%">';
 
1274       $body .= '<td style="'.$tableHeader.'">'.$i18n->get('label.date').'</td>';
 
1275       if ($canViewReports || $isClient)
 
1276         $body .= '<td style="'.$tableHeader.'">'.$i18n->get('label.user').'</td>';
 
1277       if ($bean->getAttribute('chclient'))
 
1278         $body .= '<td style="'.$tableHeader.'">'.$i18n->get('label.client').'</td>';
 
1279       if ($bean->getAttribute('chproject'))
 
1280         $body .= '<td style="'.$tableHeader.'">'.$i18n->get('label.project').'</td>';
 
1281       if ($bean->getAttribute('chtask'))
 
1282         $body .= '<td style="'.$tableHeader.'">'.$i18n->get('label.task').'</td>';
 
1283       if ($bean->getAttribute('chcf_1'))
 
1284         $body .= '<td style="'.$tableHeader.'">'.htmlspecialchars($custom_fields->fields[0]['label']).'</td>';
 
1285       if ($bean->getAttribute('chstart'))
 
1286         $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->get('label.start').'</td>';
 
1287       if ($bean->getAttribute('chfinish'))
 
1288         $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->get('label.finish').'</td>';
 
1289       if ($bean->getAttribute('chduration'))
 
1290         $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->get('label.duration').'</td>';
 
1291       if ($bean->getAttribute('chnote'))
 
1292         $body .= '<td style="'.$tableHeader.'">'.$i18n->get('label.note').'</td>';
 
1293       if ($bean->getAttribute('chcost'))
 
1294         $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->get('label.cost').'</td>';
 
1295       if ($bean->getAttribute('chpaid'))
 
1296         $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->get('label.paid').'</td>';
 
1297       if ($bean->getAttribute('chip'))
 
1298         $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->get('label.ip').'</td>';
 
1299       if ($bean->getAttribute('chinvoice'))
 
1300         $body .= '<td style="'.$tableHeader.'">'.$i18n->get('label.invoice').'</td>';
 
1303       // Initialize variables to print subtotals.
 
1304       if ($items && 'no_grouping' != $group_by) {
 
1305         $print_subtotals = true;
 
1307         $prev_grouped_by = '';
 
1308         $cur_grouped_by = '';
 
1310       // Initialize variables to alternate color of rows for different dates.
 
1313       $row_style = $rowItem;
 
1315       // Print report items.
 
1316       if (is_array($items)) {
 
1317         foreach ($items as $record) {
 
1318           $cur_date = $record['date'];
 
1319           // Print a subtotal row after a block of grouped items.
 
1320           if ($print_subtotals) {
 
1321             $cur_grouped_by = $record['grouped_by'];
 
1322             if ($cur_grouped_by != $prev_grouped_by && !$first_pass) {
 
1323               $body .= '<tr style="'.$rowSubtotal.'">';
 
1324               $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.$i18n->get('label.subtotal').'</td>';
 
1325               $subtotal_name = htmlspecialchars($subtotals[$prev_grouped_by]['name']);
 
1326               if ($canViewReports || $isClient) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'user' ? $subtotal_name : '').'</td>';
 
1327               if ($bean->getAttribute('chclient')) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'client' ? $subtotal_name : '').'</td>';
 
1328               if ($bean->getAttribute('chproject')) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'project' ? $subtotal_name : '').'</td>';
 
1329               if ($bean->getAttribute('chtask')) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'task' ? $subtotal_name : '').'</td>';
 
1330               if ($bean->getAttribute('chcf_1')) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'cf_1' ? $subtotal_name : '').'</td>';
 
1331               if ($bean->getAttribute('chstart')) $body .= '<td></td>';
 
1332               if ($bean->getAttribute('chfinish')) $body .= '<td></td>';
 
1333               if ($bean->getAttribute('chduration')) $body .= '<td style="'.$cellRightAlignedSubtotal.'">'.$subtotals[$prev_grouped_by]['time'].'</td>';
 
1334               if ($bean->getAttribute('chnote')) $body .= '<td></td>';
 
1335               if ($bean->getAttribute('chcost')) {
 
1336                 $body .= '<td style="'.$cellRightAlignedSubtotal.'">';
 
1337                 $body .= ($canViewReports || $isClient) ? $subtotals[$prev_grouped_by]['cost'] : $subtotals[$prev_grouped_by]['expenses'];
 
1340               if ($bean->getAttribute('chpaid')) $body .= '<td></td>';
 
1341               if ($bean->getAttribute('chip')) $body .= '<td></td>';
 
1342               if ($bean->getAttribute('chinvoice')) $body .= '<td></td>';
 
1344               $body .= '<tr><td> </td></tr>';
 
1346             $first_pass = false;
 
1349           // Print a regular row.
 
1350           if ($cur_date != $prev_date)
 
1351             $row_style = ($row_style == $rowItem) ? $rowItemAlt : $rowItem;
 
1352           $body .= '<tr style="'.$row_style.'">';
 
1353           $body .= '<td style="'.$cellLeftAligned.'">'.$record['date'].'</td>';
 
1354           if ($canViewReports || $isClient)
 
1355             $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['user']).'</td>';
 
1356           if ($bean->getAttribute('chclient'))
 
1357             $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['client']).'</td>';
 
1358           if ($bean->getAttribute('chproject'))
 
1359             $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['project']).'</td>';
 
1360           if ($bean->getAttribute('chtask'))
 
1361             $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['task']).'</td>';
 
1362           if ($bean->getAttribute('chcf_1'))
 
1363             $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['cf_1']).'</td>';
 
1364           if ($bean->getAttribute('chstart'))
 
1365             $body .= '<td nowrap style="'.$cellRightAligned.'">'.$record['start'].'</td>';
 
1366           if ($bean->getAttribute('chfinish'))
 
1367             $body .= '<td nowrap style="'.$cellRightAligned.'">'.$record['finish'].'</td>';
 
1368           if ($bean->getAttribute('chduration'))
 
1369             $body .= '<td style="'.$cellRightAligned.'">'.$record['duration'].'</td>';
 
1370           if ($bean->getAttribute('chnote'))
 
1371             $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['note']).'</td>';
 
1372           if ($bean->getAttribute('chcost'))
 
1373             $body .= '<td style="'.$cellRightAligned.'">'.$record['cost'].'</td>';
 
1374           if ($bean->getAttribute('chpaid')) {
 
1375             $body .= '<td style="'.$cellRightAligned.'">';
 
1376             $body .= $record['paid'] == 1 ? $i18n->get('label.yes') : $i18n->get('label.no');
 
1379           if ($bean->getAttribute('chip')) {
 
1380             $body .= '<td style="'.$cellRightAligned.'">';
 
1381             $body .= $record['modified'] ? $record['modified_ip'].' '.$record['modified'] : $record['created_ip'].' '.$record['created'];
 
1384           if ($bean->getAttribute('chinvoice'))
 
1385             $body .= '<td style="'.$cellRightAligned.'">'.htmlspecialchars($record['invoice']).'</td>';
 
1388           $prev_date = $record['date'];
 
1389           if ($print_subtotals)
 
1390             $prev_grouped_by = $record['grouped_by'];
 
1394       // Print a terminating subtotal.
 
1395       if ($print_subtotals) {
 
1396         $body .= '<tr style="'.$rowSubtotal.'">';
 
1397         $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.$i18n->get('label.subtotal').'</td>';
 
1398         $subtotal_name = htmlspecialchars($subtotals[$cur_grouped_by]['name']);
 
1399         if ($canViewReports || $isClient) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'user' ? $subtotal_name : '').'</td>';
 
1400         if ($bean->getAttribute('chclient')) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'client' ? $subtotal_name : '').'</td>';
 
1401         if ($bean->getAttribute('chproject')) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'project' ? $subtotal_name : '').'</td>';
 
1402         if ($bean->getAttribute('chtask')) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'task' ? $subtotal_name : '').'</td>';
 
1403         if ($bean->getAttribute('chcf_1')) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'cf_1' ? $subtotal_name : '').'</td>';
 
1404         if ($bean->getAttribute('chstart')) $body .= '<td></td>';
 
1405         if ($bean->getAttribute('chfinish')) $body .= '<td></td>';
 
1406         if ($bean->getAttribute('chduration')) $body .= '<td style="'.$cellRightAlignedSubtotal.'">'.$subtotals[$cur_grouped_by]['time'].'</td>';
 
1407         if ($bean->getAttribute('chnote')) $body .= '<td></td>';
 
1408         if ($bean->getAttribute('chcost')) {
 
1409           $body .= '<td style="'.$cellRightAlignedSubtotal.'">';
 
1410           $body .= ($canViewReports || $isClient) ? $subtotals[$cur_grouped_by]['cost'] : $subtotals[$cur_grouped_by]['expenses'];
 
1413         if ($bean->getAttribute('chpaid')) $body .= '<td></td>';
 
1414         if ($bean->getAttribute('chip')) $body .= '<td></td>';
 
1415         if ($bean->getAttribute('chinvoice')) $body .= '<td></td>';
 
1420       $body .= '<tr><td> </td></tr>';
 
1421       $body .= '<tr style="'.$rowSubtotal.'">';
 
1422       $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.$i18n->get('label.total').'</td>';
 
1423       if ($canViewReports || $isClient) $body .= '<td></td>';
 
1424       if ($bean->getAttribute('chclient')) $body .= '<td></td>';
 
1425       if ($bean->getAttribute('chproject')) $body .= '<td></td>';
 
1426       if ($bean->getAttribute('chtask')) $body .= '<td></td>';
 
1427       if ($bean->getAttribute('chcf_1')) $body .= '<td></td>';
 
1428       if ($bean->getAttribute('chstart')) $body .= '<td></td>';
 
1429       if ($bean->getAttribute('chfinish')) $body .= '<td></td>';
 
1430       if ($bean->getAttribute('chduration')) $body .= '<td style="'.$cellRightAlignedSubtotal.'">'.$totals['time'].'</td>';
 
1431       if ($bean->getAttribute('chnote')) $body .= '<td></td>';
 
1432       if ($bean->getAttribute('chcost')) {
 
1433         $body .= '<td nowrap style="'.$cellRightAlignedSubtotal.'">'.htmlspecialchars($user->currency).' ';
 
1434         $body .= ($canViewReports || $isClient) ? $totals['cost'] : $totals['expenses'];
 
1437       if ($bean->getAttribute('chpaid')) $body .= '<td></td>';
 
1438       if ($bean->getAttribute('chip')) $body .= '<td></td>';
 
1439       if ($bean->getAttribute('chinvoice')) $body .= '<td></td>';
 
1442       $body .= '</table>';
 
1446     if (!defined('REPORT_FOOTER') || !(REPORT_FOOTER == false))
 
1447       $body .= '<p style="text-align: center;">'.$i18n->get('form.mail.footer').'</p>';
 
1449     // Finish creating email body.
 
1450     $body .= '</body></html>';
 
1455   // checkFavReportCondition - checks whether it is okay to send fav report.
 
1456   static function checkFavReportCondition($report, $condition)
 
1458     $items = ttReportHelper::getFavItems($report);
 
1460     $condition = str_replace('count', '', $condition);
 
1461     $count_required = (int) trim(str_replace('>', '', $condition));
 
1463     if (count($items) > $count_required)
 
1464       return true; // Condition ok.
 
1469   // prepareFavReportBody - prepares an email body for a favorite report.
 
1470   static function prepareFavReportBody($report)
 
1475     // Determine these once as they are used in multiple places in this function.
 
1476     $canViewReports = $user->can('view_reports');
 
1477     $isClient = $user->isClient();
 
1479     $items = ttReportHelper::getFavItems($report);
 
1480     $group_by = $report['group_by'];
 
1481     if ($group_by && 'no_grouping' != $group_by)
 
1482       $subtotals = ttReportHelper::getFavSubtotals($report);
 
1483     $totals = ttReportHelper::getFavTotals($report);
 
1485     // Use custom fields plugin if it is enabled.
 
1486     if ($user->isPluginEnabled('cf'))
 
1487       $custom_fields = new CustomFields($user->group_id);
 
1489     // Define some styles to use in email.
 
1490     $style_title = 'text-align: center; font-size: 15pt; font-family: Arial, Helvetica, sans-serif;';
 
1491     $tableHeader = 'font-weight: bold; background-color: #a6ccf7; text-align: left;';
 
1492     $tableHeaderCentered = 'font-weight: bold; background-color: #a6ccf7; text-align: center;';
 
1493     $rowItem = 'background-color: #ffffff;';
 
1494     $rowItemAlt = 'background-color: #f5f5f5;';
 
1495     $rowSubtotal = 'background-color: #e0e0e0;';
 
1496     $cellLeftAligned = 'text-align: left; vertical-align: top;';
 
1497     $cellRightAligned = 'text-align: right; vertical-align: top;';
 
1498     $cellLeftAlignedSubtotal = 'font-weight: bold; text-align: left; vertical-align: top;';
 
1499     $cellRightAlignedSubtotal = 'font-weight: bold; text-align: right; vertical-align: top;';
 
1501     // Start creating email body.
 
1503     $body .= '<head><meta http-equiv="content-type" content="text/html; charset='.CHARSET.'"></head>';
 
1507     $body .= '<p style="'.$style_title.'">'.$i18n->get('form.mail.report_subject').': '.$totals['start_date'].' - '.$totals['end_date'].'</p>';
 
1510     // if ($comment) $body .= '<p>'.htmlspecialchars($comment).'</p>'; // No comment for fav. reports.
 
1512     if ($report['show_totals_only']) {
 
1513       // Totals only report. Output subtotals.
 
1515       // Determine group_by header.
 
1516       if ('cf_1' == $group_by)
 
1517         $group_by_header = htmlspecialchars($custom_fields->fields[0]['label']);
 
1519         $key = 'label.'.$group_by;
 
1520         $group_by_header = $i18n->get($key);
 
1523       $body .= '<table border="0" cellpadding="4" cellspacing="0" width="100%">';
 
1525       $body .= '<td style="'.$tableHeader.'">'.$group_by_header.'</td>';
 
1526       if ($report['show_duration'])
 
1527         $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->get('label.duration').'</td>';
 
1528       if ($report['show_cost'])
 
1529         $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->get('label.cost').'</td>';
 
1531       foreach($subtotals as $subtotal) {
 
1532         $body .= '<tr style="'.$rowSubtotal.'">';
 
1533         $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($subtotal['name'] ? htmlspecialchars($subtotal['name']) : ' ').'</td>';
 
1534         if ($report['show_duration']) {
 
1535           $body .= '<td style="'.$cellRightAlignedSubtotal.'">';
 
1536           if ($subtotal['time'] <> '0:00') $body .= $subtotal['time'];
 
1539         if ($report['show_cost']) {
 
1540           $body .= '<td style="'.$cellRightAlignedSubtotal.'">';
 
1541           $body .= ($canViewReports || $isClient) ? $subtotal['cost'] : $subtotal['expenses'];
 
1548       $body .= '<tr><td> </td></tr>';
 
1549       $body .= '<tr style="'.$rowSubtotal.'">';
 
1550       $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.$i18n->get('label.total').'</td>';
 
1551       if ($report['show_duration']) {
 
1552         $body .= '<td style="'.$cellRightAlignedSubtotal.'">';
 
1553         if ($totals['time'] <> '0:00') $body .= $totals['time'];
 
1556       if ($report['show_cost']) {
 
1557         $body .= '<td nowrap style="'.$cellRightAlignedSubtotal.'">'.htmlspecialchars($user->currency).' ';
 
1558         $body .= ($canViewReports || $isClient) ? $totals['cost'] : $totals['expenses'];
 
1563       $body .= '</table>';
 
1567       // Print table header.
 
1568       $body .= '<table border="0" cellpadding="4" cellspacing="0" width="100%">';
 
1570       $body .= '<td style="'.$tableHeader.'">'.$i18n->get('label.date').'</td>';
 
1571       if ($canViewReports || $isClient)
 
1572         $body .= '<td style="'.$tableHeader.'">'.$i18n->get('label.user').'</td>';
 
1573       if ($report['show_client'])
 
1574         $body .= '<td style="'.$tableHeader.'">'.$i18n->get('label.client').'</td>';
 
1575       if ($report['show_project'])
 
1576         $body .= '<td style="'.$tableHeader.'">'.$i18n->get('label.project').'</td>';
 
1577       if ($report['show_task'])
 
1578         $body .= '<td style="'.$tableHeader.'">'.$i18n->get('label.task').'</td>';
 
1579       if ($report['show_custom_field_1'])
 
1580         $body .= '<td style="'.$tableHeader.'">'.htmlspecialchars($custom_fields->fields[0]['label']).'</td>';
 
1581       if ($report['show_start'])
 
1582         $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->get('label.start').'</td>';
 
1583       if ($report['show_end'])
 
1584         $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->get('label.finish').'</td>';
 
1585       if ($report['show_duration'])
 
1586         $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->get('label.duration').'</td>';
 
1587       if ($report['show_note'])
 
1588         $body .= '<td style="'.$tableHeader.'">'.$i18n->get('label.note').'</td>';
 
1589       if ($report['show_cost'])
 
1590         $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->get('label.cost').'</td>';
 
1591       if ($report['show_paid'])
 
1592         $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->get('label.paid').'</td>';
 
1593       if ($report['show_ip'])
 
1594         $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->get('label.ip').'</td>';
 
1595       if ($report['show_invoice'])
 
1596         $body .= '<td style="'.$tableHeader.'">'.$i18n->get('label.invoice').'</td>';
 
1599       // Initialize variables to print subtotals.
 
1600       if ($items && 'no_grouping' != $group_by) {
 
1601         $print_subtotals = true;
 
1603         $prev_grouped_by = '';
 
1604         $cur_grouped_by = '';
 
1606       // Initialize variables to alternate color of rows for different dates.
 
1609       $row_style = $rowItem;
 
1611       // Print report items.
 
1612       if (is_array($items)) {
 
1613         foreach ($items as $record) {
 
1614           $cur_date = $record['date'];
 
1615           // Print a subtotal row after a block of grouped items.
 
1616           if ($print_subtotals) {
 
1617             $cur_grouped_by = $record['grouped_by'];
 
1618             if ($cur_grouped_by != $prev_grouped_by && !$first_pass) {
 
1619               $body .= '<tr style="'.$rowSubtotal.'">';
 
1620               $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.$i18n->get('label.subtotal').'</td>';
 
1621               $subtotal_name = htmlspecialchars($subtotals[$prev_grouped_by]['name']);
 
1622               if ($canViewReports || $isClient) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'user' ? $subtotal_name : '').'</td>';
 
1623               if ($report['show_client']) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'client' ? $subtotal_name : '').'</td>';
 
1624               if ($report['show_project']) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'project' ? $subtotal_name : '').'</td>';
 
1625               if ($report['show_task']) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'task' ? $subtotal_name : '').'</td>';
 
1626               if ($report['show_custom_field_1']) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'cf_1' ? $subtotal_name : '').'</td>';
 
1627               if ($report['show_start']) $body .= '<td></td>';
 
1628               if ($report['show_end']) $body .= '<td></td>';
 
1629               if ($report['show_duration']) $body .= '<td style="'.$cellRightAlignedSubtotal.'">'.$subtotals[$prev_grouped_by]['time'].'</td>';
 
1630               if ($report['show_note']) $body .= '<td></td>';
 
1631               if ($report['show_cost']) {
 
1632                 $body .= '<td style="'.$cellRightAlignedSubtotal.'">';
 
1633                 $body .= ($canViewReports || $isClient) ? $subtotals[$prev_grouped_by]['cost'] : $subtotals[$prev_grouped_by]['expenses'];
 
1636               if ($report['show_paid']) $body .= '<td></td>';
 
1637               if ($report['show_ip']) $body .= '<td></td>';
 
1638               if ($report['show_invoice']) $body .= '<td></td>';
 
1640               $body .= '<tr><td> </td></tr>';
 
1642             $first_pass = false;
 
1645           // Print a regular row.
 
1646           if ($cur_date != $prev_date)
 
1647             $row_style = ($row_style == $rowItem) ? $rowItemAlt : $rowItem;
 
1648           $body .= '<tr style="'.$row_style.'">';
 
1649           $body .= '<td style="'.$cellLeftAligned.'">'.$record['date'].'</td>';
 
1650           if ($canViewReports || $isClient)
 
1651             $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['user']).'</td>';
 
1652           if ($report['show_client'])
 
1653             $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['client']).'</td>';
 
1654           if ($report['show_project'])
 
1655             $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['project']).'</td>';
 
1656           if ($report['show_task'])
 
1657             $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['task']).'</td>';
 
1658           if ($report['show_custom_field_1'])
 
1659             $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['cf_1']).'</td>';
 
1660           if ($report['show_start'])
 
1661             $body .= '<td nowrap style="'.$cellRightAligned.'">'.$record['start'].'</td>';
 
1662           if ($report['show_end'])
 
1663             $body .= '<td nowrap style="'.$cellRightAligned.'">'.$record['finish'].'</td>';
 
1664           if ($report['show_duration'])
 
1665             $body .= '<td style="'.$cellRightAligned.'">'.$record['duration'].'</td>';
 
1666           if ($report['show_note'])
 
1667             $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['note']).'</td>';
 
1668           if ($report['show_cost'])
 
1669             $body .= '<td style="'.$cellRightAligned.'">'.$record['cost'].'</td>';
 
1670           if ($report['show_paid']) {
 
1671             $body .= '<td style="'.$cellRightAligned.'">';
 
1672             $body .= $record['paid'] == 1 ? $i18n->get('label.yes') : $i18n->get('label.no');
 
1675           if ($report['show_ip']) {
 
1676             $body .= '<td style="'.$cellRightAligned.'">';
 
1677             $body .= $record['modified'] ? $record['modified_ip'].' '.$record['modified'] : $record['created_ip'].' '.$record['created'];
 
1680           if ($report['show_invoice'])
 
1681             $body .= '<td style="'.$cellRightAligned.'">'.htmlspecialchars($record['invoice']).'</td>';
 
1684           $prev_date = $record['date'];
 
1685           if ($print_subtotals)
 
1686             $prev_grouped_by = $record['grouped_by'];
 
1690       // Print a terminating subtotal.
 
1691       if ($print_subtotals) {
 
1692         $body .= '<tr style="'.$rowSubtotal.'">';
 
1693         $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.$i18n->get('label.subtotal').'</td>';
 
1694         $subtotal_name = htmlspecialchars($subtotals[$cur_grouped_by]['name']);
 
1695         if ($canViewReports || $isClient) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'user' ? $subtotal_name : '').'</td>';
 
1696         if ($report['show_client']) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'client' ? $subtotal_name : '').'</td>';
 
1697         if ($report['show_project']) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'project' ? $subtotal_name : '').'</td>';
 
1698         if ($report['show_task']) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'task' ? $subtotal_name : '').'</td>';
 
1699         if ($report['show_custom_field_1']) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'cf_1' ? $subtotal_name : '').'</td>';
 
1700         if ($report['show_start']) $body .= '<td></td>';
 
1701         if ($report['show_end']) $body .= '<td></td>';
 
1702         if ($report['show_duration']) $body .= '<td style="'.$cellRightAlignedSubtotal.'">'.$subtotals[$cur_grouped_by]['time'].'</td>';
 
1703         if ($report['show_note']) $body .= '<td></td>';
 
1704         if ($report['show_cost']) {
 
1705           $body .= '<td style="'.$cellRightAlignedSubtotal.'">';
 
1706           $body .= ($canViewReports || $isClient) ? $subtotals[$cur_grouped_by]['cost'] : $subtotals[$cur_grouped_by]['expenses'];
 
1709         if ($report['show_paid']) $body .= '<td></td>';
 
1710         if ($report['show_ip']) $body .= '<td></td>';
 
1711         if ($report['show_invoice']) $body .= '<td></td>';
 
1716       $body .= '<tr><td> </td></tr>';
 
1717       $body .= '<tr style="'.$rowSubtotal.'">';
 
1718       $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.$i18n->get('label.total').'</td>';
 
1719       if ($canViewReports || $isClient) $body .= '<td></td>';
 
1720       if ($report['show_client']) $body .= '<td></td>';
 
1721       if ($report['show_project']) $body .= '<td></td>';
 
1722       if ($report['show_task']) $body .= '<td></td>';
 
1723       if ($report['show_custom_field_1']) $body .= '<td></td>';
 
1724       if ($report['show_start']) $body .= '<td></td>';
 
1725       if ($report['show_end']) $body .= '<td></td>';
 
1726       if ($report['show_duration']) $body .= '<td style="'.$cellRightAlignedSubtotal.'">'.$totals['time'].'</td>';
 
1727       if ($report['show_note']) $body .= '<td></td>';
 
1728       if ($report['show_cost']) {
 
1729         $body .= '<td nowrap style="'.$cellRightAlignedSubtotal.'">'.htmlspecialchars($user->currency).' ';
 
1730         $body .= ($canViewReports || $isClient) ? $totals['cost'] : $totals['expenses'];
 
1733       if ($report['show_paid']) $body .= '<td></td>';
 
1734       if ($report['show_ip']) $body .= '<td></td>';
 
1735       if ($report['show_invoice']) $body .= '<td></td>';
 
1738       $body .= '</table>';
 
1742     if (!defined('REPORT_FOOTER') || !(REPORT_FOOTER == false))
 
1743       $body .= '<p style="text-align: center;">'.$i18n->get('form.mail.footer').'</p>';
 
1745     // Finish creating email body.
 
1746     $body .= '</body></html>';
 
1751   // sendFavReport - sends a favorite report to a specified email, called from cron.php
 
1752   static function sendFavReport($report, $subject, $email, $cc) {
 
1753     // We are called from cron.php, we have no $bean in session.
 
1754     // cron.php sets global $user and $i18n objects to match our favorite report user.
 
1758     // Prepare report body.
 
1759     $body = ttReportHelper::prepareFavReportBody($report);
 
1761     import('mail.Mailer');
 
1762     $mailer = new Mailer();
 
1763     $mailer->setCharSet(CHARSET);
 
1764     $mailer->setContentType('text/html');
 
1765     $mailer->setSender(SENDER);
 
1767       $mailer->setReceiverCC($cc);
 
1768     if (!empty($user->bcc_email))
 
1769       $mailer->setReceiverBCC($user->bcc_email);
 
1770     $mailer->setReceiver($email);
 
1771     $mailer->setMailMode(MAIL_MODE);
 
1772     if (empty($subject)) $subject = $report['name'];
 
1773     if (!$mailer->send($subject, $body))