Overview

Packages

  • Hermes
  • Horde
    • Data
  • Kronolith
  • None

Classes

  • Hermes
  • Hermes_Ajax_Application
  • Hermes_Api
  • Hermes_Driver
  • Hermes_Driver_Sql
  • Hermes_Factory_Driver
  • Hermes_Form_Admin_AddJobType
  • Hermes_Form_Admin_DeleteJobType
  • Hermes_Form_Admin_EditClientStepOne
  • Hermes_Form_Admin_EditClientStepTwo
  • Hermes_Form_Admin_EditJobTypeStepOne
  • Hermes_Form_Admin_EditJobTypeStepTwo
  • Hermes_Form_Deliverable
  • Hermes_Form_Deliverable_ClientSelector
  • Hermes_Form_Export
  • Hermes_Form_JobType_Edit_Step1
  • Hermes_Form_Search
  • Hermes_Form_Time
  • Hermes_Form_Time_Entry
  • Hermes_LoginTasks_SystemTask_Upgrade
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * Hermes external API interface.
  4:  *
  5:  * This file defines Hermes's external API interface. Other applications
  6:  * can interact with Hermes through this API.
  7:  *
  8:  * See the enclosed file LICENSE for license information (BSD). If you
  9:  * did not receive this file, see http://www.horde.org/licenses/bsdl.php.
 10:  *
 11:  * @package Hermes
 12:  */
 13: class Hermes_Api extends Horde_Registry_Api
 14: {
 15:     /**
 16:      * @TODO
 17:      *
 18:      * @param <type> $name
 19:      * @param <type> $params
 20:      *
 21:      * @return <type>
 22:      */
 23:     public static function getTableMetaData($name, $params)
 24:     {
 25:         switch ($name) {
 26:         case 'hours':
 27:             $emptype = Hermes::getEmployeesType('enum');
 28:             $clients = Hermes::listClients();
 29:             $hours = $GLOBALS['injector']->getInstance('Hermes_Driver')->getHours($params);
 30:             $yesno = array(1 => _("Yes"),
 31:                            0 => _("No"));
 32: 
 33:             $columns = array(
 34:                 array('name'   => 'icons',
 35:                       'title'  => '',
 36:                       'type'   => '%html',
 37:                       'nobr'   => true),
 38:                 array('name'   => 'checkbox',
 39:                       'title'  => '',
 40:                       'type'   => '%html',
 41:                       'nobr'   => true),
 42:                 array('name'   => 'date',
 43:                       'title'  => _("Date"),
 44:                       'type'   => 'date',
 45:                       'params' => array($GLOBALS['prefs']->getValue('date_format')),
 46:                       'nobr'   => true),
 47:                 array('name'   => 'employee',
 48:                       'title'  => _("Employee"),
 49:                       'type'   => $emptype[0],
 50:                       'params' => $emptype[1]),
 51:                 array('name'   => '_client_name',
 52:                       'title'  => _("Client"),
 53:                       'type'   => 'enum',
 54:                       'params' => array($clients)),
 55:                 array('name'   => '_type_name',
 56:                       'title'  => _("Job Type")),
 57:                 array('name'   => '_costobject_name',
 58:                       'title'  => _("Cost Object")),
 59:                 array('name'   => 'hours',
 60:                       'title'  => _("Hours"),
 61:                       'type'   => 'number',
 62:                       'align'  => 'right'));
 63:             if ($GLOBALS['conf']['time']['choose_ifbillable']) {
 64:                 $columns[] =
 65:                     array('name'   => 'billable',
 66:                           'title'  => _("Bill?"),
 67:                           'type'   => 'enum',
 68:                           'params' => array($yesno));
 69:             }
 70:             $columns = array_merge($columns, array(
 71:                 array('name'  => 'description',
 72:                       'title' => _("Description")),
 73:                 array('name'  => 'note',
 74:                       'title' => _("Notes"))));
 75: 
 76:             $colspan = 6;
 77:             if ($GLOBALS['conf']['time']['choose_ifbillable']) {
 78:                 $colspan++;
 79:             }
 80:             $fColumns = array(
 81:                 array('name'    => 'approval',
 82:                       'colspan' => $colspan,
 83:                       'type'    => '%html',
 84:                       'align'   => 'right'),
 85:                 array('name'    => 'hours',
 86:                       'type'    => 'number',
 87:                       'align'   => 'right'));
 88:             if ($GLOBALS['conf']['time']['choose_ifbillable']) {
 89:                 $fColumns[] =
 90:                     array('name'   => 'billable',
 91:                           'type'   => 'enum',
 92:                           'params' => array($yesno));
 93:             }
 94:             $fColumns = array_merge($fColumns, array(
 95:                 array('name' => 'description'),
 96:                 array('name' => 'blank2')));
 97: 
 98:             return array('title' => _("Search Results"),
 99:                          'sections' => array(
100:                              'data' => array(
101:                                  'rows' => count($hours),
102:                                  'columns' => $columns),
103:                              'footer' => array(
104:                                  'rows' => 3,
105:                                  'strong' => true,
106:                                  'columns' => $fColumns)));
107: 
108:         default:
109:             throw new Hermes_Exception(sprintf(_("\"%s\" is not a defined table."), $name));
110:         }
111:     }
112: 
113:     /**
114:      * @TODO
115:      *
116:      * @param <type> $name
117:      * @param <type> $params
118:      * @return string
119:      */
120:     public static function getTableData($name, $params)
121:     {
122:         switch ($name) {
123:         case 'hours':
124:             $time_data = $GLOBALS['injector']->getInstance('Hermes_Driver')->getHours($params);
125:             $subtotal_column = null;
126:             if ($search_mode = $GLOBALS['session']->get('hermes', 'search_mode')) {
127:                 switch ($search_mode) {
128:                 case 'date':
129:                     $subtotal_column = 'date';
130:                     break;
131: 
132:                 case 'employee':
133:                     $subtotal_column = 'employee';
134:                     break;
135: 
136:                 case 'client':
137:                     $subtotal_column = '_client_name';
138:                     break;
139: 
140:                 case 'jobtype':
141:                     $subtotal_column = '_type_name';
142:                     break;
143: 
144:                 case 'costobject':
145:                     $subtotal_column = '_costobject_name';
146:                     break;
147:                 }
148: 
149:                 $clients = Hermes::listClients();
150:                 $column = array();
151:                 foreach ($time_data as $key => $row) {
152:                     if (empty($row['client'])) {
153:                         $time_data[$key]['_client_name'] = _("no client");
154:                     } elseif (isset($clients[$row['client']])) {
155:                         $time_data[$key]['_client_name'] = $clients[$row['client']];
156:                     } else {
157:                         $time_data[$key]['_client_name'] = $row['client'];
158:                     }
159:                     if (!is_null($subtotal_column)) {
160:                       $column[$key] = $time_data[$key][$subtotal_column];
161:                     }
162:                 }
163:                 if (!is_null($subtotal_column)) {
164:                     array_multisort($column, SORT_ASC, $time_data);
165:                 }
166:             }
167: 
168:             $total_hours = 0.0;
169:             $total_billable_hours = 0.0;
170:             $subtotal_hours = 0.0;
171:             $subtotal_billable_hours = 0.0;
172:             $subtotal_control = null;
173: 
174:             $result['data'] = array();
175:             foreach ($time_data as $k => $vals) {
176:                 // Initialize subtotal break value.
177:                 if (is_null($subtotal_control) && isset($vals[$subtotal_column])) {
178:                     $subtotal_control = $vals[$subtotal_column];
179:                 }
180: 
181:                 if (!empty($subtotal_column) &&
182:                     $vals[$subtotal_column] != $subtotal_control) {
183:                     Hermes_Api::renderSubtotals($result['data'], $subtotal_hours, $subtotal_billable_hours,
184:                                     $subtotal_column == 'date' ? strftime("%m/%d/%Y", $subtotal_control) :
185:                                     $subtotal_control);
186:                     $subtotal_hours = 0.0;
187:                     $subtotal_billable_hours = 0.0;
188:                     $subtotal_control = $vals[$subtotal_column];
189:                 }
190: 
191:                 // Set up edit/delete icons.
192:                 if (Hermes::canEditTimeslice($vals['id'])) {
193:                     $edit_link = Horde::url('entry.php', true)
194:                       ->add(array(
195:                         'id' => $vals['id'],
196:                         'url' => Horde::selfUrl(true, true, true)));
197:                     $vals['icons'] =
198:                         Horde::link($edit_link, _("Edit Entry")) .
199:                         Horde::img('edit.png', _("Edit Entry"), '') . '</a>';
200: 
201:                     if (empty($vals['submitted'])) {
202:                         $vals['checkbox'] =
203:                             '<input type="checkbox" name="item[' .
204:                             htmlspecialchars($vals['id']) .
205:                             ']" checked="checked" />';
206:                     } else {
207:                         $vals['checkbox'] = '';
208:                     }
209:                 }
210: 
211:                 // Add to totals.
212:                 $subtotal_hours += (double)$vals['hours'];
213:                 $total_hours += (double)$vals['hours'];
214:                 if ($vals['billable']) {
215:                     $subtotal_billable_hours += (double)$vals['hours'];
216:                     $total_billable_hours += (double)$vals['hours'];
217:                 }
218: 
219:                 // Localize hours.
220:                 $vals['hours'] = sprintf('%.02f', $vals['hours']);
221: 
222:                 $result['data'][] = $vals;
223:             }
224: 
225:             if (!empty($subtotal_column)) {
226:                 Hermes_Api::renderSubtotals($result['data'], $subtotal_hours, $subtotal_billable_hours,
227:                                 $subtotal_column == 'date' ? strftime("%m/%d/%Y", $subtotal_control) :
228:                                 $subtotal_control);
229:             }
230: 
231:             // Avoid a divide by zero.
232:             if ($total_hours == 0.0) {
233:                 $billable_pct = 0.0;
234:             } else {
235:                 $billable_pct = round($total_billable_hours / $total_hours * 100.0);
236:             }
237: 
238:             $descr = _("Billable Hours") . ' (' . $billable_pct . '%)';
239:             $result['footer'] = array();
240:             $result['footer'][] = array(
241:                 'hours'       => sprintf('%.02f', $total_billable_hours),
242:                 'description' => $descr);
243: 
244:             $descr = _("Non-billable Hours") . ' (' . (100.0 - $billable_pct) . '%)';
245:             $result['footer'][] = array(
246:                 'hours'       => sprintf('%.02f', $total_hours - $total_billable_hours),
247:                 'description' => $descr);
248:             $result['footer'][] = array(
249:                 'hours'       => sprintf('%.02f', $total_hours),
250:                 'description' => _("Total Hours"),
251:                 'approval'    => '<div id="approval">' . _("Approved By:") .
252:                                  ' ________________________________________ ' .
253:                                  '&nbsp;</div>');
254:             break;
255:         }
256: 
257:         return $result;
258:     }
259: 
260:     /**
261:      * @TODO
262:      *
263:      * @param <type> $table_data
264:      * @param <type> $hours
265:      * @param <type> $billable_hours
266:      * @param <type> $value
267:      * @return <type>
268:      */
269:     public static function renderSubtotals(&$table_data, $hours, $billable_hours, $value)
270:     {
271:         $billable_pct = ($hours == 0.0) ? 0.0 :
272:              round($billable_hours / $hours * 100.0);
273:         $descr = _("Billable Hours") . ' (' . $billable_pct . '%)';
274:         $table_data[] = array(
275:             'date'             => '',
276:             'employee'         => '',
277:             'client'           => '',
278:             'billable'         => '',
279:             'note'             => '',
280:             '_type_name'       => '',
281:             '_costobject_name' => '',
282:             'hours'            => sprintf('%.02f', $billable_hours),
283:             'description'      => $descr);
284:          $descr = _("Non-billable Hours") . ' (' . (100.0 - $billable_pct) . '%)';
285:          $table_data[] = array(
286:             'hours'       => sprintf('%.02f', $hours - $billable_hours),
287:             'description' => $descr);
288:          $table_data[] = array(
289:             'hours'       => sprintf('%.02f', $hours),
290:             'description' => sprintf(_("Total Hours for %s"), $value),
291:              );
292: 
293:         return;
294:     }
295: 
296:     /**
297:      * listCostObjects API
298:      *
299:      * @param array $criteria  The search criteria
300:      *
301:      * @return array  A listCostObjects result array.
302:      */
303:     public function listCostObjects($criteria)
304:     {
305:         if (!$GLOBALS['conf']['time']['deliverables']) {
306:             return array();
307:         }
308: 
309:         $deliverables = $GLOBALS['injector']->getInstance('Hermes_Driver')->listDeliverables($criteria);
310:         if (empty($criteria['id'])) {
311:             /* Build heirarchical tree. */
312:             $levels = array();
313:             $hash = array();
314:             foreach ($deliverables as $deliverable) {
315:                 if (empty($deliverable['parent'])) {
316:                     $parent = -1;
317:                 } else {
318:                     $parent = $deliverable['parent'];
319:                 }
320:                 $levels[$parent][$deliverable['id']] = $deliverable['name'];
321:                 $hash[$deliverable['id']] = $deliverable;
322:             }
323: 
324:             /* Sort levels alphabetically, keeping keys intact. */
325:             foreach ($levels as $key => $level) {
326:                 asort($levels[$key]);
327:             }
328: 
329:             /* Traverse the tree and glue them back together. Lots of magic
330:              * involved, so don't try to understand. */
331:             $elts = array();
332:             $stack = empty($levels[-1]) ? array() : array(-1);
333:             while (count($stack)) {
334:                 if (!(list($key, $val) = each($levels[$stack[count($stack) - 1]]))) {
335:                     array_pop($stack);
336:                     continue;
337:                 }
338:                 $elts[$key] = str_repeat(' + ', count($stack)-1) . $val;
339:                 if (!empty($levels[$key])) {
340:                     $stack[] = $key;
341:                 }
342:             }
343: 
344:             $results = array();
345:             foreach ($elts as $key => $value) {
346:                 $results[] = array('id'       => $key,
347:                                    'active'   => $hash[$key]['active'],
348:                                    'estimate' => $hash[$key]['estimate'],
349:                                    'name'     => $value);
350:             }
351:         } else {
352:             $results = $deliverables;
353:         }
354: 
355:         if (!$results) {
356:             return array();
357:         }
358: 
359:         return array(array('category' => _("Deliverables"),
360:                            'objects'  => $results));
361:     }
362: 
363:     /**
364:      * Retrieve list of job types.
365:      *
366:      * @param array $criteria  Hash of filter criteria:
367:      *
368:      *                      'enabled' => If present, only retrieve enabled
369:      *                                   or disabled job types.
370:      *
371:      * @return array Associative array of job types
372:      */
373:     public function listJobTypes($criteria = array())
374:     {
375:         return $GLOBALS['injector']->getInstance('Hermes_Driver')->listJobTypes($criteria);
376:     }
377: 
378:     /**
379:      *
380:      * @see Hermes::listClients
381:      */
382:     public function listClients()
383:     {
384:         return Hermes::listClients();
385:     }
386: 
387:     /**
388:      * Record a time slice
389:      *
390:      * @param array $data  Slice attributes
391:      *<pre>
392:      *  date          - The slice date (required).
393:      *  client        - The client id (required).
394:      *  type          - The jobType id (required).
395:      *  costobject    - The costObject id [none]
396:      *  hours         - Number of hours (required).
397:      *  billable      - Time billable? [true]
398:      *  description   - Description (required)
399:      *  note          - Note [blank]
400:      *</pre>
401:      *
402:      * @return <type>
403:      */
404:     public function recordTime(array $data)
405:     {
406:         $data = new Horde_Support_Array($data);
407:         // Check for required
408:         if (!$data->date || !$data->client || !$data->type || !$data->hours || !$data->description) {
409:           throw new Hermes_Exception(_("Missing required values: check data and retry"));
410:         }
411: 
412:         // Parse date
413:         $dateobj = new Horde_Date($data->date);
414:         $date['year'] = $dateobj->year;
415:         $date['month'] = $dateobj->month;
416:         $date['day'] = $dateobj->mday;
417:         $data->date = $date;
418: 
419:         if (!$data->billable) {
420:           $data->billable = true;
421:         }
422: 
423:         return $GLOBALS['injector']->getInstance('Hermes_Driver')->enterTime($GLOBALS['registry']->getAuth(), $data);
424:     }
425: 
426:     /**
427:      * Retrieve information about a costobject's hours. Includes number of hours
428:      * worked on for each employee, total billed time etc...
429:      *
430:      * @param string $costobject  The costobject id (e.g., "whups:15").
431:      *
432:      * @return array  An array of data with the following structure:
433:      *</pre>
434:      *  employees  - an array of employee ids as keys, number of hours as values.
435:      *  total      - total number of hours
436:      *  billable   - total number of billable hours.
437:      *</pre>
438:      */
439:     public function getCostObjectInfo($costobject)
440:     {
441:         $filter = array('costobject' => $costobject);
442:         $slices = $GLOBALS['injector']->getInstance('Hermes_Driver')->getHours($filter);
443:         $billable = $time = 0;
444:         $employees = array();
445:         foreach ($slices as $slice) {
446:           $time += $slice['hours'];
447:           if ($slice['billable']) {
448:             $billable += $slice['hours'];
449:           }
450: 
451:           $employees[$slice['employee']] += $slice['hours'];
452:         }
453: 
454:         return array('employees' => $employees,
455:                     'total' => $time,
456:                     'billable' => $billable);
457: 
458:     }
459: 
460: }
461: 
API documentation generated by ApiGen