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 Base Class.
  4:  *
  5:  *
  6:  * See the enclosed file LICENSE for license information (BSD). If you
  7:  * did not receive this file, see http://www.horde.org/licenses/bsdl.php.
  8:  *
  9:  * @author  Chuck Hagenbuch <chuck@horde.org>
 10:  * @package Hermes
 11:  */
 12: class Hermes
 13: {
 14:     /**
 15:      * Get a list of available clients
 16:      *
 17:      * @param string $name  The string to search for in the client name
 18:      *
 19:      * @staticvar array $clients
 20:      * @return array  A hash of client_id => client_name
 21:      */
 22:     public static function listClients($name = '')
 23:     {
 24:         static $clients;
 25: 
 26:         if (is_null($clients) || empty($clients[$name])) {
 27:             try {
 28:                 $result = $GLOBALS['registry']->clients->searchClients(array($name), array('name'), true);
 29:             } catch (Horde_Exception $e) {
 30:                 // No client backend
 31:             }
 32:             $client_name_field = $GLOBALS['conf']['client']['field'];
 33:             $clients = is_null($clients) ?  array() : $clients;
 34:             if (!empty($result)) {
 35:                 $result = $result[$name];
 36:                 foreach ($result as $client) {
 37:                     $clients[$name][$client['id']] = $client[$client_name_field];
 38:                 }
 39:             }
 40:             if (!empty($clients[$name])) {
 41:                 uasort($clients[$name], 'strcoll');
 42:             } else {
 43:                 $clients[$name] = array();
 44:             }
 45:         }
 46: 
 47:         return $clients[$name];
 48:     }
 49: 
 50:     public static function getClientSelect($id)
 51:     {
 52:         $clients = self::listClients();
 53:         $select = '<select name="client" id="' . $id . '">';
 54:         $select .= '<option value="">' . _("--- Select A Client ---") . '</option>';
 55:         foreach ($clients as $cid => $client) {
 56:             $select .= '<option value="' . $cid . '">' . $client . '</option>';
 57:         }
 58: 
 59:         return $select . '</select>';
 60:     }
 61: 
 62:     /**
 63:      * @TODO: Build these via ajax once we have UI support for editing jobtypes
 64:      * @return <type>
 65:      */
 66:     public static function getJobTypeSelect($id)
 67:     {
 68:         $types = $GLOBALS['injector']->getInstance('Hermes_Driver')->listJobTypes(array('enabled' => true));
 69:         $select = '<select name="type" id="' . $id . '">';
 70:         foreach ($types as $tid => $type) {
 71:             $select .= '<option value="' . $tid . '">' . $type['name'] . '</option>';
 72:         }
 73: 
 74:         return $select . '</select>';
 75:     }
 76: 
 77:     /**
 78:      * Determines if the current user can edit a specific timeslice according to
 79:      * the following rules: 'hermes:review' perms may edit any slice, the
 80:      * current user can edit his/her own slice prior to submitting it. Otherwise
 81:      * no editing allowed.
 82:      *
 83:      * @param <type> $id
 84:      * @return <type>
 85:      */
 86:     public static function canEditTimeslice($id)
 87:     {
 88:         $perms = $GLOBALS['injector']->getInstance('Horde_Perms');
 89: 
 90:         if ($perms->hasPermission('hermes:review', $GLOBALS['registry']->getAuth(), Horde_Perms::EDIT)) {
 91:             return true;
 92:         }
 93: 
 94:         $hours = $GLOBALS['injector']->getInstance('Hermes_Driver')->getHours(array('id' => $id));
 95:         if (!is_array($hours) || count($hours) != 1) {
 96:             return false;
 97:         }
 98:         $slice = $hours[0];
 99: 
100:         // We can edit our own time if it hasn't been submitted.
101:         if ($slice['employee'] == $GLOBALS['registry']->getAuth() && !$slice['submitted']) {
102:             return true;
103:         }
104: 
105:         return false;
106:     }
107: 
108:     /**
109:      * Rewrite an hours array into a format useable by Horde_Data::
110:      *
111:      * @param array $hours  This is an array of the results from
112:      *                      $driver->getHours().
113:      *
114:      * @return array an array suitable for Horde_Data::
115:      */
116:     public static function makeExportHours($hours)
117:     {
118:         if (is_null($hours)) {
119:             return null;
120:         }
121: 
122:         $clients = Hermes::listClients();
123:         $namecache = array();
124:         for ($i = 0; $i < count($hours); $i++) {
125:             $timeentry = &$hours[$i];
126:             $timeentry['item'] = $timeentry['_type_name'];
127:             if (isset($clients[$timeentry['client']])) {
128:                 $timeentry['client'] = $clients[$timeentry['client']];
129:             }
130: 
131:             $emp = &$timeentry['employee'];
132:             if (isset($namecache[$emp])) {
133:                 $emp = $namecache[$emp];
134:             } else {
135:                 $ident = $identity = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create($emp);
136:                 $fullname = $ident->getValue('fullname');
137:                 if ($fullname) {
138:                     $namecache[$emp] = $emp = $fullname;
139:                 } else {
140:                     $namecache[$emp] = $emp;
141:                 }
142:             }
143:         }
144: 
145:         return $hours;
146:     }
147: 
148:     /**
149:      * Get form control type for users.
150:      *
151:      * What type of control we use depends on whether the Auth driver has list
152:      * capability.
153:      *
154:      * @param string $enumtype  The type to return if we have list capability
155:      *                          (should be either 'enum' or 'multienum').
156:      *
157:      * @return array A two-element array of the type and the type's parameters.
158:      */
159:     public static function getEmployeesType($enumtype = 'multienum')
160:     {
161:         $auth = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Auth')->create();
162:         if (!$auth->hasCapability('list')) {
163:             return array('text', array());
164:         }
165:         try {
166:             $users = $auth->listUsers();
167:         } catch (Exception $e) {
168:             return array('invalid',
169:                          array(sprintf(_("An error occurred listing users: %s"), $e->getMessage())));
170:         }
171: 
172:         $employees = array();
173:         foreach ($users as $user) {
174:             $identity = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create($user);
175:             $label = $identity->getValue('fullname');
176:             if (empty($label)) {
177:                 $label = $user;
178:             }
179:             $employees[$user] = $label;
180:         }
181: 
182:         return array($enumtype, array($employees));
183:     }
184: 
185:     public static function getCostObjectByID($id)
186:     {
187:         static $cost_objects;
188: 
189:         if (strpos($id, ':') !== false) {
190:             list($app, $app_id) = explode(':', $id, 2);
191: 
192:             if (!isset($cost_objects[$app])) {
193:                 $results = $GLOBALS['registry']->callByPackage($app, 'listCostObjects', array(array()));
194:                 $cost_objects[$app] = $results;
195:             }
196: 
197:             foreach (array_keys($cost_objects[$app]) as $catkey) {
198:                 foreach (array_keys($cost_objects[$app][$catkey]['objects']) as $objkey) {
199:                     if ($cost_objects[$app][$catkey]['objects'][$objkey]['id'] == $app_id) {
200:                         return $cost_objects[$app][$catkey]['objects'][$objkey];
201:                     }
202:                 }
203:             }
204:         }
205: 
206:         throw new Horde_Exception_NotFound();
207:     }
208: 
209:     /**
210:      */
211:     public static function getCostObjectType($clientID = null)
212:     {
213:         global $registry;
214: 
215:         /* Check to see if any other active applications are exporting cost
216:          * objects to which we might want to bill our time. */
217:         $criteria = array('user'   => $GLOBALS['registry']->getAuth(),
218:                           'active' => true);
219:         if (!empty($clientID)) {
220:             $criteria['client_id'] = $clientID;
221:         }
222: 
223:         $costobjects = array();
224:         foreach ($registry->listApps() as $app) {
225:             if (!$registry->hasMethod('listCostObjects', $app)) {
226:                 continue;
227:             }
228: 
229:             try {
230:                 $result = $registry->callByPackage($app, 'listCostObjects', array($criteria));
231:             } catch (Horde_Exception $e) {
232:                 $GLOBALS['notification']->push(sprintf(_("Error retrieving cost objects from \"%s\": %s"), $registry->get('name', $app), $e->getMessage()), 'horde.error');
233:                 continue;
234:             }
235: 
236:             foreach (array_keys($result) as $catkey) {
237:                 foreach (array_keys($result[$catkey]['objects']) as $okey){
238:                     $result[$catkey]['objects'][$okey]['id'] = $app . ':' .
239:                         $result[$catkey]['objects'][$okey]['id'];
240:                 }
241:             }
242: 
243:             if ($app == $registry->getApp()) {
244:                 $costobjects = array_merge($result, $costobjects);
245:             } else {
246:                 $costobjects = array_merge($costobjects, $result);
247:             }
248:         }
249: 
250:         $elts = array('' => _("--- No Cost Object ---"));
251:         $counter = 0;
252:         foreach ($costobjects as $category) {
253:             Horde_Array::arraySort($category['objects'], 'name');
254:             $elts['category%' . $counter++] = sprintf('--- %s ---', $category['category']);
255:             foreach ($category['objects'] as $object) {
256:                 $name = $object['name'];
257:                 if (Horde_String::length($name) > 80) {
258:                     $name = Horde_String::substr($name, 0, 76) . ' ...';
259:                 }
260: 
261:                 $hours = 0.0;
262:                 $filter = array('costobject' => $object['id']);
263:                 if (!empty($GLOBALS['conf']['time']['sum_billable_only'])) {
264:                     $filter['billable'] = true;
265:                 }
266:                 $result = $GLOBALS['injector']->getInstance('Hermes_Driver')->getHours($filter, array('hours'));
267:                 foreach ($result as $entry) {
268:                     if (!empty($entry['hours'])) {
269:                         $hours += $entry['hours'];
270:                     }
271:                 }
272: 
273:                 /* Show summary of hours versus estimate for this
274:                  * deliverable. */
275:                 if (empty($object['estimate'])) {
276:                     $name .= sprintf(_(" (%0.2f hours)"), $hours);
277:                 } else {
278:                     $name .= sprintf(_(" (%d%%, %0.2f of %0.2f hours)"),
279:                                      (int)($hours / $object['estimate'] * 100),
280:                                      $hours, $object['estimate']);
281:                 }
282: 
283:                 $elts[$object['id']] = $name;
284:             }
285:         }
286: 
287:         return $elts;
288:     }
289: 
290:     public static function tabs()
291:     {
292:         /* Build search mode tabs. */
293:         $sUrl = Horde::selfUrl();
294:         $tabs = new Horde_Core_Ui_Tabs('search_mode', Horde_Variables::getDefaultVariables());
295:         $tabs->addTab(_("Summary"), $sUrl, 'summary');
296:         $tabs->addTab(_("By Date"), $sUrl, 'date');
297:         $tabs->addTab(_("By Employee"), $sUrl, 'employee');
298:         $tabs->addTab(_("By Client"), $sUrl, 'client');
299:         $tabs->addTab(_("By Job Type"), $sUrl, 'jobtype');
300:         $tabs->addTab(_("By Cost Object"), $sUrl, 'costobject');
301:         if ($mode = Horde_Util::getFormData('search_mode')) {
302:             $GLOBALS['session']->set('hermes', 'search_mode', $mode);
303:         } elseif (!$GLOBALS['session']->exists('hermes', 'search_mode')) {
304:             $GLOBALS['session']->set('hermes', 'search_mode', 'summary');
305:         }
306:         return $tabs->render($GLOBALS['session']->get('hermes', 'search_mode'));
307:     }
308: 
309:     /**
310:      * Output everything for the AJAX interface up to but not including the
311:      * <body> tag.
312:      */
313:     public static function header()
314:     {
315:         // Need to include script files before we start output
316:         $datejs = str_replace('_', '-', $GLOBALS['language']) . '.js';
317:         if (!file_exists($GLOBALS['registry']->get('jsfs', 'horde') . '/date/' . $datejs)) {
318:             $datejs = 'en-US.js';
319:         }
320:         Horde::addScriptFile('effects.js', 'horde');
321:         Horde::addScriptFile('horde.js', 'horde');
322:         Horde::addScriptFile('growler.js', 'horde');
323:         Horde::addScriptFile('redbox.js', 'horde');
324:         Horde::addScriptFile('tooltips.js', 'horde');
325:         Horde::addScriptFile('date/' . $datejs, 'horde');
326:         Horde::addScriptFile('date/date.js', 'horde');
327:         Horde::addScriptFile('quickfinder.js', 'horde');
328:         Horde::addScriptFile('hermes.js', 'hermes');
329:         Horde_Core_Ui_JsCalendar::init(array('short_weekdays' => true));
330: 
331:         if (isset($GLOBALS['language'])) {
332:             header('Content-type: text/html; charset=UTF-8');
333:             header('Vary: Accept-Language');
334:         }
335: 
336:         echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "DTD/xhtml1-transitional.dtd">' . "\n" .
337:              (!empty($GLOBALS['language']) ? '<html lang="' . strtr($GLOBALS['language'], '_', '-') . '"' : '<html') . ">\n".
338:              "<head>\n" .
339:              '<title>' . htmlspecialchars($GLOBALS['registry']->get('name')) . "</title>\n";
340: 
341:         Horde::includeFavicon();
342:         echo Horde::wrapInlineScript(self::includeJSVars());
343:         Horde::includeStylesheetFiles();
344: 
345:         echo "</head>\n";
346: 
347:         // Send what we have currently output so the browser can start
348:         // loading CSS/JS. See:
349:         // http://developer.yahoo.com/performance/rules.html#flush
350:         echo Horde::endBuffer();
351:         flush();
352:     }
353: 
354:     public static function includeJSVars()
355:     {
356:         global $prefs, $registry;
357: 
358:         $hermes_webroot = $registry->get('webroot');
359:         $horde_webroot = $registry->get('webroot', 'horde');
360:         $has_tasks = $registry->hasInterface('tasks');
361:         $app_urls = array();
362:         if (isset($GLOBALS['conf']['menu']['apps']) &&
363:             is_array($GLOBALS['conf']['menu']['apps'])) {
364:             foreach ($GLOBALS['conf']['menu']['apps'] as $app) {
365:                 $app_urls[$app] = (string)Horde::url($registry->getInitialPage($app), true)->add('ajaxui', 1);
366:             }
367:         }
368: 
369:         /* Variables used in core javascript files. */
370:         $code['conf'] = array(
371:             'URI_AJAX' => (string)Horde::getServiceLink('ajax', 'hermes'),
372:             'SESSION_ID' => defined('SID') ? SID : '',
373:             'images' => array(
374:                 'timerlog' => (string)Horde_Themes::img('log.png'),
375:                 'timerplay' => (string)Horde_Themes::img('play.png'),
376:                 'timerpause' => (string)Horde_Themes::img('pause.png')
377:             ),
378:             'user' => $GLOBALS['registry']->convertUsername($GLOBALS['registry']->getAuth(), false),
379:             'prefs_url' => (string)Horde::getServiceLink('prefs', 'hermes')->setRaw(true)->add('ajaxui', 1),
380:             'app_urls' => $app_urls,
381:             'name' => $registry->get('name'),
382:             'login_view' => 'time',
383:             'date_format' => str_replace(array('%e', '%d', '%a', '%A', '%m', '%h', '%b', '%B', '%y', '%Y'),
384:                              array('d', 'dd', 'ddd', 'dddd', 'MM', 'MMM', 'MMM', 'MMMM', 'yy', 'yyyy'),
385:                              Horde_Nls::getLangInfo(D_FMT)),
386:             'client_name_field' => $GLOBALS['conf']['client']['field']
387:         );
388:         if (!empty($GLOBALS['conf']['logo']['link'])) {
389:             $code['conf']['URI_HOME'] = $GLOBALS['conf']['logo']['link'];
390:         }
391: 
392:         /* Gettext strings used in core javascript files. */
393:         $code['text'] = array(
394:             'ajax_error' => _("Error when communicating with the server."),
395:             'ajax_timeout' => _("There has been no contact with the server for several minutes. The server may be temporarily unavailable or network problems may be interrupting your session. You will not see any updates until the connection is restored."),
396:             'ajax_recover' => _("The connection to the server has been restored."),
397:             'noalerts' => _("No Notifications"),
398:             'alerts' => sprintf(_("%s notifications"), '#{count}'),
399:             'hidelog' => _("Hide Notifications"),
400:             'growlerinfo' => _("This is the notification backlog"),
401:             'more' => _("more..."),
402:             'prefs' => _("Preferences"),
403:             'wrong_auth' => _("The authentication information you specified wasn't accepted."),
404:             'fix_form_values' => _("Please enter correct values in the form first."),
405:         );
406: 
407:         return Horde::addInlineJsVars(array(
408:             'var Hermes' => $code
409:         ), array('ret_vars' => true));
410:     }
411: 
412:     /**
413:      * Returns whether to display the ajax view.
414:      *
415:      * return boolean  True if the ajax view should be displayed.
416:      */
417:     public static function showAjaxView()
418:     {
419:         global $prefs, $session;
420: 
421:         $mode = $session->get('horde', 'mode');
422:         return ($mode == 'dynamic' || ($prefs->getValue('dynamic_view') && $mode == 'auto')) && Horde::ajaxAvailable();
423:     }
424: 
425:     /**
426:      * Create a new timer and save it to storage.
427:      *
428:      * @param string $description  The timer description.
429:      *
430:      * @return integer  The timer id.
431:      */
432:     public static function newTimer($description)
433:     {
434:         $now = time();
435:         $timer = array(
436:             'name' => $description,
437:             'time' => $now,
438:             'paused' => false,
439:             'elapsed' => 0);
440: 
441:         self::updateTimer($now, $timer);
442: 
443:         return $now;
444:     }
445: 
446:     public static function getTimer($id)
447:     {
448:         global $prefs;
449: 
450:         $timers = $prefs->getValue('running_timers');
451:         if (!empty($timers)) {
452:             $timers = @unserialize($timers);
453:         } else {
454:             $timers = array();
455:         }
456: 
457:         if (empty($timers[$id])) {
458:             return false;
459:         }
460: 
461:         return $timers[$id];
462:     }
463: 
464:     public static function clearTimer($id)
465:     {
466:         global $prefs;
467: 
468:         $timers = @unserialize($prefs->getValue('running_timers'));
469:          if (!is_array($timers)) {
470:             $timers = array();
471:          } else {
472:             unset($timers[$id]);
473:         }
474:         $prefs->setValue('running_timers', serialize($timers));
475:     }
476: 
477:     public static function updateTimer($id, $timer)
478:     {
479:          global $prefs;
480: 
481:          $timers = @unserialize($prefs->getValue('running_timers'));
482:          if (!is_array($timers)) {
483:             $timers = array();
484:          }
485:          $timers[$id] = $timer;
486:          $prefs->setValue('running_timers', serialize($timers));
487:     }
488: 
489: }
490: 
API documentation generated by ApiGen