Overview

Packages

  • Horde
    • Form
    • MIME
      • Viewer
    • Scheduler
  • None
  • Whups
    • UnitTests

Classes

  • Horde_Core_Ui_VarRenderer_whups
  • Whups
  • Whups_Ajax_Imple_ContactAutoCompleter
  • Whups_Api
  • Whups_Driver
  • Whups_Driver_Sql
  • Whups_Form_AddComment
  • Whups_Form_Admin_AddAttribute
  • Whups_Form_Admin_AddPriority
  • Whups_Form_Admin_AddQueue
  • Whups_Form_Admin_AddReply
  • Whups_Form_Admin_AddState
  • Whups_Form_Admin_AddType
  • Whups_Form_Admin_AddUser
  • Whups_Form_Admin_AddVersion
  • Whups_Form_Admin_CloneType
  • Whups_Form_Admin_DefaultPriority
  • Whups_Form_Admin_DefaultState
  • Whups_Form_Admin_DeleteAttribute
  • Whups_Form_Admin_DeletePriority
  • Whups_Form_Admin_DeleteQueue
  • Whups_Form_Admin_DeleteReply
  • Whups_Form_Admin_DeleteState
  • Whups_Form_Admin_DeleteType
  • Whups_Form_Admin_DeleteVersion
  • Whups_Form_Admin_EditAttributeStepOne
  • Whups_Form_Admin_EditAttributeStepTwo
  • Whups_Form_Admin_EditPriorityStepOne
  • Whups_Form_Admin_EditPriorityStepTwo
  • Whups_Form_Admin_EditQueueStepOne
  • Whups_Form_Admin_EditQueueStepTwo
  • Whups_Form_Admin_EditReplyStepOne
  • Whups_Form_Admin_EditReplyStepTwo
  • Whups_Form_Admin_EditStateStepOne
  • Whups_Form_Admin_EditStateStepTwo
  • Whups_Form_Admin_EditTypeStepOne
  • Whups_Form_Admin_EditTypeStepTwo
  • Whups_Form_Admin_EditUser
  • Whups_Form_Admin_EditVersionStepOne
  • Whups_Form_Admin_EditVersionStepTwo
  • Whups_Form_InsertBranch
  • Whups_Form_Query_AttributeCriterion
  • Whups_Form_Query_ChooseNameForLoad
  • Whups_Form_Query_ChooseNameForSave
  • Whups_Form_Query_DateCriterion
  • Whups_Form_Query_Delete
  • Whups_Form_Query_GroupCriterion
  • Whups_Form_Query_Parameter
  • Whups_Form_Query_PropertyCriterion
  • Whups_Form_Query_TextCriterion
  • Whups_Form_Query_UserCriterion
  • Whups_Form_Renderer_Comment
  • Whups_Form_Search
  • Whups_Form_SendReminder
  • Whups_Form_Ticket_CreateStepFour
  • Whups_Form_Ticket_CreateStepOne
  • Whups_Form_Ticket_CreateStepThree
  • Whups_Form_Ticket_CreateStepTwo
  • Whups_Form_Ticket_Edit
  • Whups_Form_TicketDetails
  • Whups_LoginTasks_SystemTask_Upgrade
  • Whups_Mail
  • Whups_Query
  • Whups_Query_Manager
  • Whups_Reports
  • Whups_Ticket
  • Whups_View_Base
  • Whups_View_Results
  • Whups_View_SavedQueries
  • Overview
  • Package
  • Class
  • Tree
   1: <?php
   2: /**
   3:  * Copyright 2001-2002 Robert E. Coyle <robertecoyle@hotmail.com>
   4:  * Copyright 2001-2012 Horde LLC (http://www.horde.org/)
   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:  * @package Whups
  10:  */
  11: 
  12: /**
  13:  * The Whups class provides functionality that all of Whups needs, or that
  14:  * should be encapsulated from other parts of the Whups system.
  15:  *
  16:  * @author  Robert E. Coyle <robertecoyle@hotmail.com>
  17:  * @author  Jan Schneider <jan@horde.org>
  18:  * @package Whups
  19:  */
  20: class Whups
  21: {
  22:     /**
  23:      * Path to ticket attachments in the VFS.
  24:      */
  25:     const VFS_ATTACH_PATH = '.horde/whups/attachments';
  26: 
  27:     /**
  28:      * The current sort field.
  29:      *
  30:      * @see sortBy()
  31:      * @var string
  32:      */
  33:     static protected $_sortBy;
  34: 
  35:     /**
  36:      * The current sort direction.
  37:      *
  38:      * @see sortDir()
  39:      * @var integer
  40:      */
  41:     static protected $_sortDir;
  42: 
  43:     /**
  44:      * Cached list of user information.
  45:      *
  46:      * @see getUserAttributes()
  47:      * @var array
  48:      */
  49:     static protected $_users = array();
  50: 
  51:     /**
  52:      * All available form field types including all type information
  53:      * from the Horde_Form classes.
  54:      *
  55:      * @see fieldTypes()
  56:      * @var array
  57:      */
  58:     static protected $_fieldTypes = array();
  59: 
  60:     /**
  61:      * URL factory.
  62:      *
  63:      * @param string $controller       The controller to link to, one of
  64:      *                                 'queue', 'ticket', 'ticket_rss',
  65:      *                                 'ticket_action', 'query', 'query_rss'.
  66:      * @param array|string $data       URL data, depending on the controller.
  67:      * @param boolean $full            @see Horde::url()
  68:      * @param integer $append_session  @see Horde::url()
  69:      *
  70:      * @return Horde_Url  The generated URL.
  71:      */
  72:     static public function urlFor($controller, $data, $full = false,
  73:                                   $append_session = 0)
  74:     {
  75:         $rewrite = isset($GLOBALS['conf']['urls']['pretty']) &&
  76:             $GLOBALS['conf']['urls']['pretty'] == 'rewrite';
  77: 
  78:         switch ($controller) {
  79:         case 'queue':
  80:             if ($rewrite) {
  81:                 if (is_array($data)) {
  82:                     if (empty($data['slug'])) {
  83:                         $slug = (int)$data['id'];
  84:                     } else {
  85:                         $slug = $data['slug'];
  86:                     }
  87:                 } else {
  88:                     $slug = (int)$data;
  89:                 }
  90:                 return Horde::url('queue/' . $slug, $full, $append_session);
  91:             } else {
  92:                 if (is_array($data)) {
  93:                     $id = $data['id'];
  94:                 } else {
  95:                     $id = $data;
  96:                 }
  97:                 return Horde::url('queue/?id=' . $id, $full, $append_session);
  98:             }
  99:             break;
 100: 
 101:         case 'ticket':
 102:             $id = (int)$data;
 103:             if ($rewrite) {
 104:                 return Horde::url('ticket/' . $id, $full, $append_session);
 105:             } else {
 106:                 return Horde::url('ticket/?id=' . $id, $full, $append_session);
 107:             }
 108:             break;
 109: 
 110:         case 'ticket_rss':
 111:             $id = (int)$data;
 112:             if ($rewrite) {
 113:                 return Horde::url('ticket/' . $id . '/rss', $full, $append_session);
 114:             } else {
 115:                 return Horde::url('ticket/rss.php?id=' . $id, $full, $append_session);
 116:             }
 117:             break;
 118: 
 119:         case 'ticket_action':
 120:             list($controller, $id) = $data;
 121:             if ($rewrite) {
 122:                 return Horde::url('ticket/' . $id . '/' . $controller, $full, $append_session = 0);
 123:             } else {
 124:                 return Horde::url('ticket/' . $controller . '.php?id=' . $id, $full, $append_session = 0);
 125:             }
 126: 
 127:         case 'query':
 128:         case 'query_rss':
 129:             if ($rewrite) {
 130:                 if (is_array($data)) {
 131:                     if (isset($data['slug'])) {
 132:                         $slug = $data['slug'];
 133:                     } else {
 134:                         $slug = $data['id'];
 135:                     }
 136:                 } else {
 137:                     $slug = (int)$data;
 138:                 }
 139:                 $url = 'query/' . $slug;
 140:                 if ($controller == 'query_rss') {
 141:                     $url .= '/rss';
 142:                 }
 143:                 return Horde::url($url, $full, $append_session);
 144:             } else {
 145:                 if (is_array($data)) {
 146:                     if (isset($data['slug'])) {
 147:                         $param = array('slug' => $data['slug']);
 148:                     } else {
 149:                         $param = array('query' => $data['id']);
 150:                     }
 151:                 } else {
 152:                     $param = array('query' => $data);
 153:                 }
 154:                 $url = $controller == 'query' ? 'query/run.php' : 'query/rss.php';
 155:                 $url = Horde_Util::addParameter($url, $param);
 156:                 return Horde::url($url, $full, $append_session);
 157:             }
 158:             break;
 159:         }
 160:     }
 161: 
 162:     /**
 163:      * Sorts tickets by requested direction and fields.
 164:      *
 165:      * @param array $tickets  The list of tickets to sort.
 166:      * @param string $by      The field to sort by. If omitted, obtain from
 167:      *                        preferences.
 168:      * @param string $dir     The direction to sort. If omitted, obtain from
 169:      *                        preferences.
 170:      */
 171:     static public function sortTickets(&$tickets, $by = null, $dir = null)
 172:     {
 173:         if (is_null($by)) {
 174:             $by = $GLOBALS['prefs']->getValue('sortby');
 175:         }
 176:         if (is_null($dir)) {
 177:             $dir = $GLOBALS['prefs']->getValue('sortdir');
 178:         }
 179: 
 180:         self::sortBy($by);
 181:         self::sortDir($dir);
 182: 
 183:         // Do some prep for sorting.
 184:         $tickets = array_map(array('Whups', '_prepareSort'), $tickets);
 185: 
 186:         usort($tickets, array('Whups', '_sort'));
 187:     }
 188: 
 189:     /**
 190:      * Sets or returns the current sort field.
 191:      *
 192:      * @param string $b  The field to sort by.
 193:      *
 194:      * @return string  If $b is null, returns the previously set value.
 195:      */
 196:     static public function sortBy($b = null)
 197:     {
 198:         if (!is_null($b)) {
 199:             self::$_sortBy = $b;
 200:         } else {
 201:             return self::$_sortBy;
 202:         }
 203:     }
 204: 
 205:     /**
 206:      * Sets or returns the current sort direction.
 207:      *
 208:      * @param integer $d  The direction to sort by.
 209:      *
 210:      * @return integer  If $d is null, returns the previously set value.
 211:      */
 212:     static public function sortDir($d = null)
 213:     {
 214:         if (!is_null($d)) {
 215:             self::$_sortDir = $d;
 216:         } else {
 217:             return self::$_sortDir;
 218:         }
 219:     }
 220: 
 221:     /**
 222:      * Helper method to prepare an array of tickets for sorting.
 223:      *
 224:      * Adds a sort_by key to each ticket array, with values lowercased. Used as
 225:      * a callback to array_map().
 226:      *
 227:      * @param array $ticket  The ticket array to prepare.
 228:      *
 229:      * @return array  The altered $ticket array
 230:      */
 231:     static protected function _prepareSort(array $ticket)
 232:     {
 233:         $by = self::sortBy();
 234:         $ticket['sort_by'] = array();
 235:         if (is_array($by)) {
 236:             foreach ($by as $field) {
 237:                 if (!isset($ticket[$field])) {
 238:                     $ticket['sort_by'][$field] = '';
 239:                 } else {
 240:                     $ticket['sort_by'][$field] = Horde_String::lower($ticket[$field], true, 'UTF-8');
 241:                 }
 242:             }
 243:         } else {
 244:             if (!isset($ticket[$by])) {
 245:                 $ticket['sort_by'][$by] = '';
 246:             } elseif (is_array($ticket[$by])) {
 247:                 natcasesort($ticket[$by]);
 248:                 $ticket['sort_by'][$by] = implode('', $ticket[$by]);
 249:             } else {
 250:                 $ticket['sort_by'][$by] = Horde_String::lower($ticket[$by], true, 'UTF-8');
 251:             }
 252:         }
 253:         return $ticket;
 254:     }
 255: 
 256:     /**
 257:      * Helper method to sort an array of tickets.
 258:      *
 259:      * Used as callback to usort().
 260:      *
 261:      * @param array $a         The first ticket to compare.
 262:      * @param array $b         The second ticket to compare.
 263:      * @param string $sortby   The field to sort by. If null, uses the field
 264:      *                         from self::sortBy().
 265:      * @param string $sortdir  The direction to sort. If null, uses the value
 266:      *                         from self::sortDir().
 267:      *
 268:      * @return integer
 269:      */
 270:     static protected function _sort($a, $b, $sortby = null, $sortdir = null)
 271:     {
 272:         if (is_null($sortby)) {
 273:             $sortby = self::$_sortBy;
 274:         }
 275:         if (is_null($sortdir)) {
 276:             $sortdir = self::$_sortDir;
 277:         }
 278: 
 279:         if (is_array($sortby)) {
 280:             if (!isset($a[$sortby[0]])) {
 281:                 $a[$sortby[0]] = null;
 282:             }
 283:             if (!isset($b[$sortby[0]])) {
 284:                 $b[$sortby[0]] = null;
 285:             }
 286: 
 287:             if (!count($sortby)) {
 288:                 return 0;
 289:             }
 290:             if ($a['sort_by'][$sortby[0]] > $b['sort_by'][$sortby[0]]) {
 291:                 return $sortdir[0] ? -1 : 1;
 292:             }
 293:             if ($a['sort_by'][$sortby[0]] === $b['sort_by'][$sortby[0]]) {
 294:                 array_shift($sortby);
 295:                 array_shift($sortdir);
 296:                 return self::_sort($a, $b, $sortby, $sortdir);
 297:             }
 298:             return $sortdir[0] ? 1 : -1;
 299:         }
 300: 
 301:         $a_val = isset($a['sort_by'][$sortby]) ? $a['sort_by'][$sortby] : null;
 302:         $b_val = isset($b['sort_by'][$sortby]) ? $b['sort_by'][$sortby] : null;
 303: 
 304:         // Take care of the simplest case first
 305:         if ($a_val === $b_val) {
 306:             return 0;
 307:         }
 308: 
 309:         if ((is_numeric($a_val) || is_null($a_val)) &&
 310:             (is_numeric($b_val) || is_null($b_val))) {
 311:             // Numeric comparison
 312:             return (int)($sortdir ? ($b_val > $a_val) : ($a_val > $b_val));
 313:         }
 314: 
 315:         // Some special case sorting
 316:         if (is_array($a_val) || is_array($b_val)) {
 317:             $a_val = implode('', $a_val);
 318:             $b_val = implode('', $b_val);
 319:         }
 320: 
 321:         // String comparison
 322:         return $sortdir ? strcoll($b_val, $a_val) : strcoll($a_val, $b_val);
 323:     }
 324: 
 325:     /**
 326:      * Returns a new or the current CAPTCHA string.
 327:      *
 328:      * @param boolean $new  If true, a new CAPTCHA is created and returned.
 329:      *                      The current, to-be-confirmed string otherwise.
 330:      *
 331:      * @return string  A CAPTCHA string.
 332:      */
 333:     static public function getCAPTCHA($new = false)
 334:     {
 335:         global $session;
 336: 
 337:         if ($new || !$session->get('whups', 'captcha')) {
 338:             $captcha = '';
 339:             for ($i = 0; $i < 5; ++$i) {
 340:                 $captcha .= chr(rand(65, 90));
 341:             }
 342:             $session->set('whups', 'captcha', $captcha);
 343:         }
 344: 
 345:         return $session->get('whups', 'captcha');
 346:     }
 347: 
 348:     /**
 349:      * Lists all templates of a given type.
 350:      *
 351:      * @param string $type  The kind of template ('searchresults', etc.) to
 352:      *                      list.
 353:      *
 354:      * @return array  All templates of the requested type.
 355:      */
 356:     static public function listTemplates($type)
 357:     {
 358:         $templates = array();
 359: 
 360:         $_templates = Horde::loadConfiguration('templates.php', '_templates', 'whups');
 361:         foreach ($_templates as $name => $info) {
 362:             if ($info['type'] == $type) {
 363:                 $templates[$name] = $info['name'];
 364:             }
 365:         }
 366: 
 367:         return $templates;
 368:     }
 369: 
 370:     /**
 371:      * Returns the current ticket.
 372:      *
 373:      * Uses the 'id' request variable to determine what to look for. Will
 374:      * redirect to the default view if the ticket isn't found or if permissions
 375:      * checks fail.
 376:      *
 377:      * @return Whups_Ticket  The current ticket.
 378:      */
 379:     static public function getCurrentTicket()
 380:     {
 381:         $default = Horde::url($GLOBALS['prefs']->getValue('whups_default_view') . '.php', true);
 382: 
 383:         $id = preg_replace('|\D|', '', Horde_Util::getFormData('id'));
 384:         if (!$id) {
 385:             $GLOBALS['notification']->push(_("Invalid Ticket Id"), 'horde.error');
 386:             $default->redirect();
 387:         }
 388: 
 389:         try {
 390:             return Whups_Ticket::makeTicket($id);
 391:         } catch (Whups_Exception $e) {
 392:             if ($ticket->code === 0) {
 393:                 // No permissions to this ticket.
 394:                 $GLOBALS['notification']->push($e->getMessage(), 'horde.warning');
 395:                 $default->redirect();
 396:             }
 397:         } catch (Exception $e) {
 398:             $GLOBALS['notification']->push($e);
 399:             $default->redirect();
 400:         }
 401:     }
 402: 
 403:     /**
 404:      * Returns the tabs for navigating between ticket actions.
 405:      */
 406:     static public function getTicketTabs(&$vars, $id)
 407:     {
 408:         $tabs = new Horde_Core_Ui_Tabs(null, $vars);
 409:         $queue = $vars->get('queue');
 410: 
 411:         $tabs->addTab(_("_History"), self::urlFor('ticket', $id), 'history');
 412:         if (self::hasPermission($queue, 'queue', 'update')) {
 413:             $tabs->addTab(_("_Update"),
 414:                           self::urlFor('ticket_action', array('update', $id)),
 415:                           'update');
 416:         } else {
 417:             $tabs->addTab(_("_Comment"),
 418:                           self::urlFor('ticket_action', array('comment', $id)),
 419:                           'comment');
 420:         }
 421:         $tabs->addTab(_("_Watch"),
 422:                       self::urlFor('ticket_action', array('watch', $id)),
 423:                       'watch');
 424:         if (self::hasPermission($queue, 'queue', Horde_Perms::DELETE)) {
 425:             $tabs->addTab(_("S_et Queue"),
 426:                           self::urlFor('ticket_action', array('queue', $id)),
 427:                           'queue');
 428:         }
 429:         if (self::hasPermission($queue, 'queue', 'update')) {
 430:             $tabs->addTab(_("Set _Type"),
 431:                           self::urlFor('ticket_action', array('type', $id)),
 432:                           'type');
 433:         }
 434:         if (self::hasPermission($queue, 'queue', Horde_Perms::DELETE)) {
 435:             $tabs->addTab(_("_Delete"),
 436:                           self::urlFor('ticket_action', array('delete', $id)),
 437:                           'delete');
 438:         }
 439: 
 440:         return $tabs;
 441:     }
 442: 
 443:     /**
 444:      * Returns whether a user has a certain permission on a single resource.
 445:      *
 446:      * @param mixed $in                   A single resource to check.
 447:      * @param string $filter              The kind of resource specified in
 448:      *                                    $in, currently only 'queue'.
 449:      * @param string|integer $permission  A permission, either 'assign' or
 450:      *                                    'update', 'requester', or one of the
 451:      *                                    PERM_* constants.
 452:      * @param string $user                A user name.
 453:      *
 454:      * @return boolean  True if the user has the specified permission.
 455:      */
 456:     static public function hasPermission($in, $filter, $permission, $user = null)
 457:     {
 458:         if (is_null($user)) {
 459:             $user = $GLOBALS['registry']->getAuth();
 460:         }
 461: 
 462:         if ($permission == 'update' ||
 463:             $permission == 'assign' ||
 464:             $permission == 'requester') {
 465:             $admin_perm = Horde_Perms::EDIT;
 466:         } else {
 467:             $admin_perm = $permission;
 468:         }
 469: 
 470:         $admin = $GLOBALS['registry']->isAdmin(array('permission' => 'whups:admin', 'permlevel' => $admin_perm, 'user' => $user));
 471:         $perms = $GLOBALS['injector']->getInstance('Horde_Perms');
 472: 
 473:         switch ($filter) {
 474:         case 'queue':
 475:             if ($admin) {
 476:                 return true;
 477:             }
 478:             switch ($permission) {
 479:             case Horde_Perms::SHOW:
 480:             case Horde_Perms::READ:
 481:             case Horde_Perms::EDIT:
 482:             case Horde_Perms::DELETE:
 483:                 if ($perms->hasPermission('whups:queues:' . $in, $user,
 484:                                           $permission)) {
 485:                     return true;
 486:                 }
 487:                 break;
 488: 
 489:             default:
 490:                 if ($perms->exists('whups:queues:' . $in . ':' . $permission)) {
 491:                     if (($permission == 'update' ||
 492:                          $permission == 'assign' ||
 493:                          $permission == 'requester') &&
 494:                         $perms->getPermissions(
 495:                             'whups:queues:' . $in . ':' . $permission, $user)) {
 496:                         return true;
 497:                     }
 498:                 } else {
 499:                     // If the sub-permission doesn't exist, use the queue
 500:                     // permission at an EDIT level and lock out guests.
 501:                     if ($permission != 'requester' &&
 502:                         $GLOBALS['registry']->getAuth() &&
 503:                         $perms->hasPermission('whups:queues:' . $in, $user,
 504:                                               Horde_Perms::EDIT)) {
 505:                         return true;
 506:                     }
 507:                 }
 508:                 break;
 509:             }
 510:             break;
 511:         }
 512: 
 513:         return false;
 514:     }
 515: 
 516:     /**
 517:      * Filters a list of resources based on whether a user has certain
 518:      * permissions on it.
 519:      *
 520:      * @param array $in            A list of resources to check.
 521:      * @param string $filter       The kind of resource specified in $in,
 522:      *                             one of 'queue', 'queue_id', 'reply', or
 523:      *                             'comment'.
 524:      * @param integer $permission  A permission, one of the PERM_* constants.
 525:      * @param string $user         A user name.
 526:      * @param string $creator      The creator of an object in the resource,
 527:      *                             e.g. a ticket creator.
 528:      *
 529:      * @return array  The list of resources matching the permission criteria.
 530:      */
 531:     static public function permissionsFilter($in, $filter,
 532:                                              $permission = Horde_Perms::READ,
 533:                                              $user = null, $creator = null)
 534:     {
 535:         if (is_null($user)) {
 536:             $user = $GLOBALS['registry']->getAuth();
 537:         }
 538: 
 539:         $admin = $GLOBALS['registry']->isAdmin(array('permission' => 'whups:admin', 'permlevel' => $permission, 'user' => $user));
 540:         $perms = $GLOBALS['injector']->getInstance('Horde_Perms');
 541:         $out = array();
 542: 
 543:         switch ($filter) {
 544:         case 'queue':
 545:             if ($admin) {
 546:                 return $in;
 547:             }
 548:             foreach ($in as $queueID => $name) {
 549:                 if ($perms->hasPermission('whups:queues:' . $queueID, $user,
 550:                                           $permission, $creator)) {
 551:                     $out[$queueID] = $name;
 552:                 }
 553:             }
 554:             break;
 555: 
 556:         case 'queue_id':
 557:             if ($admin) {
 558:                 return $in;
 559:             }
 560:             foreach ($in as $queueID) {
 561:                 if ($perms->hasPermission('whups:queues:' . $queueID, $user,
 562:                                           $permission, $creator)) {
 563:                     $out[] = $queueID;
 564:                 }
 565:             }
 566:             break;
 567: 
 568:         case 'reply':
 569:             if ($admin) {
 570:                 return $in;
 571:             }
 572:             foreach ($in as $replyID => $name) {
 573:                 if (!$perms->exists('whups:replies:' . $replyID) ||
 574:                     $perms->hasPermission('whups:replies:' . $replyID,
 575:                                           $user, $permission, $creator)) {
 576:                     $out[$replyID] = $name;
 577:                 }
 578:             }
 579:             break;
 580: 
 581:         case 'comment':
 582:             foreach ($in as $key => $row) {
 583:                 foreach ($row as $rkey => $rval) {
 584:                     if ($rkey != 'changes') {
 585:                         $out[$key][$rkey] = $rval;
 586:                         continue;
 587:                     }
 588:                     foreach ($rval as $i => $change) {
 589:                         if ($change['type'] != 'comment' ||
 590:                             !$perms->exists('whups:comments:' . $change['value'])) {
 591:                             $out[$key][$rkey][$i] = $change;
 592:                             if (isset($change['comment'])) {
 593:                                 $out[$key]['comment_text'] = $change['comment'];
 594:                             }
 595:                         } elseif ($perms->exists('whups:comments:' . $change['value'])) {
 596:                             $change['private'] = true;
 597:                             $out[$key][$rkey][$i] = $change;
 598:                             if (isset($change['comment'])) {
 599:                                 if ($admin ||
 600:                                     $perms->hasPermission('whups:comments:' . $change['value'],
 601:                                                           $user, Horde_Perms::READ, $creator)) {
 602:                                     $out[$key]['comment_text'] = $change['comment'];
 603:                                 } else {
 604:                                     $out[$key][$rkey][$i]['comment'] = _("[Hidden]");
 605:                                 }
 606:                             }
 607:                         }
 608:                     }
 609:                 }
 610:             }
 611:             break;
 612: 
 613:         default:
 614:             $out = $in;
 615:             break;
 616:         }
 617: 
 618:         return $out;
 619:     }
 620: 
 621:     /**
 622:      * Builds a list of criteria for Whups_Driver#getTicketsByProperties() that
 623:      * match a certain user.
 624:      *
 625:      * Merges the user's groups with the user name.
 626:      *
 627:      * @param string $user  A user name.
 628:      *
 629:      * @return array  A list of criteria that would match the user.
 630:      */
 631:     static public function getOwnerCriteria($user)
 632:     {
 633:         $criteria = array('user:' . $user);
 634:         $mygroups = $GLOBALS['injector']
 635:             ->getInstance('Horde_Group')
 636:             ->listGroups($GLOBALS['registry']->getAuth());
 637:         foreach ($mygroups as $id => $group) {
 638:             $criteria[] = 'group:' . $id;
 639:         }
 640:         return $criteria;
 641:     }
 642: 
 643:     /**
 644:      * Returns a hash with user information.
 645:      *
 646:      * @param string $user  A (Whups) user name, defaults to the current user.
 647:      *
 648:      * @return array  An information hash with 'user', 'name', 'email', and
 649:      *                'type' values.
 650:      */
 651:     static public function getUserAttributes($user = null)
 652:     {
 653:         if (is_null($user)) {
 654:             $user = $GLOBALS['registry']->getAuth();
 655:         } elseif (empty($user)) {
 656:             return array('user' => '',
 657:                          'name' => '',
 658:                          'email' => '');
 659:         }
 660: 
 661:         if (isset(self::$_users[$user])) {
 662:             return self::$_users[$user];
 663:         }
 664: 
 665:         if (strpos($user, ':') !== false) {
 666:             list($type, $user) = explode(':', $user, 2);
 667:         } else {
 668:             $type = 'user';
 669:         }
 670: 
 671:         // Default this; some of the cases below might change it.
 672:         self::$_users[$user]['user'] = $user;
 673:         self::$_users[$user]['type'] = $type;
 674: 
 675:         switch ($type) {
 676:         case 'user':
 677:             if (substr($user, 0, 2) == '**') {
 678:                 unset(self::$_users[$user]);
 679:                 $user = substr($user, 2);
 680: 
 681:                 self::$_users[$user]['user'] = $user;
 682:                 self::$_users[$user]['name'] = '';
 683:                 self::$_users[$user]['email'] = '';
 684: 
 685:                 try {
 686:                     $addr_arr = Horde_Mime_Address::parseAddressList($user);
 687:                     if (isset($addr_arr[0])) {
 688:                         self::$_users[$user]['name'] = isset($addr_arr[0]['personal'])
 689:                             ? $addr_arr[0]['personal'] : '';
 690:                         self::$_users[$user]['email'] = $addr_arr[0]['mailbox'] . '@'
 691:                             . $addr_arr[0]['host'];
 692:                     }
 693:                 } catch (Horde_Mime_Exception $e) {
 694:                 }
 695:             } elseif ($user < 0) {
 696:                 global $whups_driver;
 697: 
 698:                 self::$_users[$user]['user'] = '';
 699:                 self::$_users[$user]['name'] = '';
 700:                 self::$_users[$user]['email'] = $whups_driver->getGuestEmail($user);
 701: 
 702:                 try {
 703:                     $addr_arr = Horde_Mime_Address::parseAddressList(self::$_users[$user]['email']);
 704:                     if (isset($addr_arr[0])) {
 705:                         self::$_users[$user]['name'] = isset($addr_arr[0]['personal'])
 706:                             ? $addr_arr[0]['personal'] : '';
 707:                         self::$_users[$user]['email'] = $addr_arr[0]['mailbox'] . '@'
 708:                             . $addr_arr[0]['host'];
 709:                     }
 710:                 } catch (Horde_Mime_Exception $e) {
 711:                 }
 712:             } else {
 713:                 $identity = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create($user);
 714: 
 715:                 self::$_users[$user]['name'] = $identity->getValue('fullname');
 716:                 self::$_users[$user]['email'] = $identity->getValue('from_addr');
 717:             }
 718:             break;
 719: 
 720:         case 'group':
 721:             try {
 722:                 $group = $GLOBALS['injector']
 723:                     ->getInstance('Horde_Group')
 724:                     ->getData($user);
 725:                 self::$_users[$user]['user'] = $group['name'];
 726:                 self::$_users[$user]['name'] = $group['name'];
 727:                 self::$_users[$user]['email'] = $group['email'];
 728:             } catch (Horde_Exception $e) {
 729:                 self::$_users['user']['name'] = '';
 730:                 self::$_users['user']['email'] = '';
 731:             }
 732:             break;
 733:         }
 734: 
 735:         return self::$_users[$user];
 736:     }
 737: 
 738:     /**
 739:      * Returns a user string from the user's name and email address.
 740:      *
 741:      * @param string|array $user  A user name or a hash as returned from
 742:      *                            {@link self::getUserAttributes()}.
 743:      * @param boolean $showemail  Whether to include the email address.
 744:      * @param boolean $showname   Whether to include the full name.
 745:      * @param boolean $html       Whether to "prettify" the result. If true,
 746:      *                            email addresses are obscured, the result is
 747:      *                            escaped for HTML output, and a group icon
 748:      *                            might be added.
 749:      */
 750:     static public function formatUser($user = null, $showemail = true,
 751:                                       $showname = true, $html = false)
 752:     {
 753:         if (!is_null($user) && empty($user)) {
 754:             return '';
 755:         }
 756: 
 757:         if (is_array($user)) {
 758:             $details = $user;
 759:         } else {
 760:             $details = self::getUserAttributes($user);
 761:         }
 762:         if (!empty($details['name'])) {
 763:             $name = $details['name'];
 764:         } else {
 765:             $name = $details['user'];
 766:         }
 767:         if (($showemail || empty($name) || !$showname) &&
 768:             !empty($details['email'])) {
 769:             if ($html && strpos($details['email'], '@') !== false) {
 770:                 $details['email'] = str_replace(array('@', '.'),
 771:                                                 array(' (at) ', ' (dot) '),
 772:                                                 $details['email']);
 773:             }
 774: 
 775:             if (!empty($name) && $showname) {
 776:                 $name .= ' <' . $details['email'] . '>';
 777:             } else {
 778:                 $name = $details['email'];
 779:             }
 780:         }
 781: 
 782:         if ($html) {
 783:             $name = htmlspecialchars($name);
 784:             if ($details['type'] == 'group') {
 785:                 $name = Horde::img('group.png',
 786:                                    !empty($details['name'])
 787:                                    ? $details['name']
 788:                                    : $details['user'])
 789:                     . $name;
 790:             }
 791:         }
 792: 
 793:         return $name;
 794:     }
 795: 
 796:     /**
 797:      * Returns the set of columns and their associated parameter from the
 798:      * backend that should be displayed to the user.
 799:      *
 800:      * The results can depend on the current user preferences and which search
 801:      * function was executed.
 802:      *
 803:      * @param integer $search_type  The type of search that was executed.
 804:      *                              Currently only 'block' is supported.
 805:      */
 806:     static public function getSearchResultColumns($search_type = null)
 807:     {
 808:         if ($search_type == 'block') {
 809:             return array(
 810:                 _("Id")       => 'id',
 811:                 _("Summary")  => 'summary',
 812:                 _("Priority") => 'priority_name',
 813:                 _("State")    => 'state_name');
 814:         }
 815: 
 816:         return array(
 817:             _("Id")        => 'id',
 818:             _("Summary")   => 'summary',
 819:             _("State")     => 'state_name',
 820:             _("Type")      => 'type_name',
 821:             _("Priority")  => 'priority_name',
 822:             _("Queue")     => 'queue_name',
 823:             _("Requester") => 'user_id_requester',
 824:             _("Owners")    => 'owners',
 825:             _("Created")   => 'timestamp',
 826:             _("Updated")   => 'date_updated',
 827:             _("Assigned")  => 'date_assigned',
 828:             _("Resolved")  => 'date_resolved',
 829:         );
 830:     }
 831: 
 832:     /**
 833:      * Sends reminders, one email per user.
 834:      *
 835:      * @param Horde_Variables $vars  The selection criteria:
 836:      *                               - 'id' (integer) for individual tickets
 837:      *                               - 'queue' (integer) for tickets of a queue.
 838:      *                                 - 'category' (array) for ticket
 839:      *                                   categories, defaults to unresolved
 840:      *                                   tickets.
 841:      *                               - 'unassigned' (boolean) for unassigned
 842:      *                                 tickets.
 843:      *
 844:      * @throws Whups_Exception
 845:      */
 846:     static public function sendReminders($vars)
 847:     {
 848:         global $whups_driver;
 849: 
 850:         if ($vars->get('id')) {
 851:             $info = array('id' => $vars->get('id'));
 852:         } elseif ($vars->get('queue')) {
 853:             $info['queue'] = $vars->get('queue');
 854:             if ($vars->get('category')) {
 855:                 $info['category'] = $vars->get('category');
 856:             } else {
 857:                 // Make sure that resolved tickets aren't returned.
 858:                 $info['category'] = array('unconfirmed', 'new', 'assigned');
 859:             }
 860:         } else {
 861:             throw new Whups_Exception(_("You must select at least one queue to send reminders for."));
 862:         }
 863: 
 864:         $tickets = $whups_driver->getTicketsByProperties($info);
 865:         self::sortTickets($tickets);
 866:         if (!count($tickets)) {
 867:             throw new Whups_Exception(_("No tickets matched your search criteria."));
 868:         }
 869: 
 870:         $unassigned = $vars->get('unassigned');
 871:         $remind = array();
 872:         foreach ($tickets as $info) {
 873:             $info['link'] = self::urlFor('ticket', $info['id'], true, -1);
 874:             $owners = current($whups_driver->getOwners($info['id']));
 875:             if (!empty($owners)) {
 876:                 foreach ($owners as $owner) {
 877:                     $remind[$owner][] = $info;
 878:                 }
 879:             } elseif (!empty($unassigned)) {
 880:                 $remind['**' . $unassigned][] = $info;
 881:             }
 882:         }
 883: 
 884:         /* Build message template. */
 885:         $view = new Horde_View(array('templatePath' => WHUPS_BASE . '/config'));
 886:         $view->date = strftime($GLOBALS['prefs']->getValue('date_format'));
 887: 
 888:         /* Get queue specific notification message text, if available. */
 889:         $message_file = WHUPS_BASE . '/config/reminder_email.plain';
 890:         if (file_exists($message_file . '.local.php')) {
 891:             $message_file .= '.local.php';
 892:         } else {
 893:             $message_file .= '.php';
 894:         }
 895:         $message_file = basename($message_file);
 896: 
 897:         foreach ($remind as $user => $utickets) {
 898:             if (empty($user) || !count($utickets)) {
 899:                 continue;
 900:             }
 901:             $view->tickets = $utickets;
 902:             $subject = _("Reminder: Your open tickets");
 903:             $whups_driver->mail(array('recipients' => array($user => 'owner'),
 904:                                       'subject' => $subject,
 905:                                       'view' => $view,
 906:                                       'template' => $message_file,
 907:                                       'from' => $user));
 908:         }
 909:     }
 910: 
 911:     /**
 912:      * Returns attachment information hashes from the VFS backend.
 913:      *
 914:      * @param integer $ticket  A ticket ID.
 915:      * @param string $name     An attachment name.
 916:      *
 917:      * @return array  If $name is empty a list of all attachments' information
 918:      *                hashes, otherwise only the hash for the attachment of
 919:      *                that name.
 920:      *
 921:      * @throws Whups_Exception if the VFS object cannot be created.
 922:      */
 923:     static public function getAttachments($ticket, $name = null)
 924:     {
 925:         if (empty($GLOBALS['conf']['vfs']['type'])) {
 926:             return;
 927:         }
 928: 
 929:         try {
 930:             $vfs = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Vfs')->create();
 931:         } catch (Horde_Vfs_Exception $e) {
 932:             throw new Whups_Exception($e);
 933:         }
 934: 
 935:         if (!$vfs->isFolder(self::VFS_ATTACH_PATH, $ticket)) {
 936:             return;
 937:         }
 938: 
 939:         try {
 940:             $files = $vfs->listFolder(self::VFS_ATTACH_PATH . '/' . $ticket);
 941:         } catch (Horde_Vfs_Exception $e) {
 942:             $files = array();
 943:         }
 944:         if (is_null($name)) {
 945:             return $files;
 946:         }
 947:         foreach ($files as $file) {
 948:             if ($file['name'] == $name) {
 949:                 return $file;
 950:             }
 951:         }
 952:     }
 953: 
 954:     /**
 955:      * Returns the links to view, download, and delete an attachment.
 956:      *
 957:      * @param integer $ticket  A ticket ID.
 958:      * @param string $file     An attachment name.
 959:      * @param integer $queue   The ticket's queue ID.
 960:      */
 961:     static public function attachmentUrl($ticket, $file, $queue)
 962:     {
 963:         $link = '';
 964: 
 965:         // Can we view the attachment online?
 966:         $mime_part = new Horde_Mime_Part();
 967:         $mime_part->setType(Horde_Mime_Magic::extToMime($file['type']));
 968:         $viewer = $GLOBALS['injector']->getInstance('Horde_Core_Factory_MimeViewer')->create($mime_part);
 969:         if ($viewer && !($viewer instanceof Horde_Mime_Viewer_Default)) {
 970:             $url = Horde_Util::addParameter(Horde::url('view.php'),
 971:                                       array('actionID' => 'view_file',
 972:                                             'type' => $file['type'],
 973:                                             'file' => $file['name'],
 974:                                             'ticket' => $ticket));
 975:             $link .= Horde::link($url, $file['name'], null, '_blank') . $file['name'] . '</a>';
 976:         } else {
 977:             $link .= $file['name'];
 978:         }
 979: 
 980:         // We can always download attachments.
 981:         $url_params = array('actionID' => 'download_file',
 982:                             'file' => $file['name'],
 983:                             'ticket' => $ticket);
 984:         $link .= ' ' . Horde::link(Horde::downloadUrl($file['name'], $url_params), $file['name']) . Horde::img('download.png', _("Download")) . '</a>';
 985: 
 986:         // Admins can delete attachments.
 987:         if (self::hasPermission($queue, 'queue', Horde_Perms::DELETE)) {
 988:             $url = Horde_Util::addParameter(
 989:                 Horde::url('ticket/delete_attachment.php'),
 990:                 array('file' => $file['name'],
 991:                       'id' => $ticket,
 992:                       'url' => Horde::selfUrl(true, false, true)));
 993:             $link .= ' ' . Horde::link($url, sprintf(_("Delete %s"), $file['name']), '', '', 'return window.confirm(\'' . addslashes(sprintf(_("Permanently delete %s?"), $file['name'])) . '\');') .
 994:                 Horde::img('delete.png', sprintf(_("Delete %s"), $file['name'])) . '</a>';
 995:         }
 996: 
 997:         return $link;
 998:     }
 999: 
1000:     /**
1001:      * Returns formatted owner names of a ticket.
1002:      *
1003:      * @param integer $ticket    A ticket id. Only used if $owners is null.
1004:      * @param boolean $showmail  Should we include the email address in the
1005:      *                           output?
1006:      * @param boolean $showname  Should we include the name in the output?
1007:      * @param array $owners      An array of owners as returned from
1008:      *                           Whups_Driver::getOwners() to be formatted. If
1009:      *                           this is provided, they are used instead of
1010:      *                           the owners from $ticket.
1011:      *
1012:      * @return string  The formatted owner string.
1013:      */
1014:     static public function getOwners($ticket, $showemail = true,
1015:                                      $showname = true, $owners = null)
1016:     {
1017:         if (is_null($owners)) {
1018:             $owners = $GLOBALS['whups_driver']->getOwners($ticket);
1019:         }
1020: 
1021:         $results = array();
1022:         $owners = reset($owners);
1023:         if ($owners !== false) {
1024:             foreach ($owners as $owner) {
1025:                 $results[] = self::formatUser($owner, $showemail, $showname);
1026:             }
1027:         }
1028:         return implode(', ', $results);
1029:     }
1030: 
1031:     /**
1032:      * Returns all available form field types including all type information
1033:      * from the Horde_Form classes.
1034:      *
1035:      * @todo Doesn't work with autoloading.
1036:      *
1037:      * @return array  The full field types array.
1038:      */
1039:     static public function fieldTypes()
1040:     {
1041:         if (!empty(self::$_fieldTypes)) {
1042:             return self::$_fieldTypes;
1043:         }
1044: 
1045:         /* Fetch all declared classes. */
1046:         $classes = get_declared_classes();
1047: 
1048:         /* Filter for the Horde_Form_Type classes. */
1049:         $blacklist = array('invalid', 'addresslink', 'spacer', 'description',
1050:                            'captcha', 'figlet', 'header');
1051:         foreach ($classes as $class) {
1052:             if (stripos($class, 'horde_form_type_') !== false) {
1053:                 $field_type = substr($class, 16);
1054:                 /* Don't bother including the types that cannot be handled
1055:                  * usefully. */
1056:                 if (in_array($field_type, $blacklist)) {
1057:                     continue;
1058:                 }
1059:                 self::$_fieldTypes[$field_type] = @call_user_func(
1060:                     array('Horde_Form_Type_' . $field_type, 'about'));
1061:             }
1062:         }
1063: 
1064:         return self::$_fieldTypes;
1065:     }
1066: 
1067:     /**
1068:      * Returns the available field type names from the Horde_Form classes.
1069:      *
1070:      * @return array  A hash The with available field types and names.
1071:      */
1072:     static public function fieldTypeNames()
1073:     {
1074:         /* Fetch the field type information from the Horde_Form classes. */
1075:         $fields = self::fieldTypes();
1076: 
1077:         /* Strip out the name element from the array. */
1078:         $available_fields = array();
1079:         foreach ($fields as $field_type => $info) {
1080:             $available_fields[$field_type] = $info['name'];
1081:         }
1082: 
1083:         /* Sort for display purposes. */
1084:         asort($available_fields);
1085: 
1086:         return $available_fields;
1087:     }
1088: 
1089:     /**
1090:      * Returns the parameters for a certain Horde_Form field type.
1091:      *
1092:      * @param string $field_type  A field type.
1093:      *
1094:      * @return array  A list of field type parameters.
1095:      */
1096:     static public function fieldTypeParams($field_type)
1097:     {
1098:         $fields = self::fieldTypes();
1099: 
1100:         return isset($fields[$field_type]['params'])
1101:             ? $fields[$field_type]['params']
1102:             : array();
1103:     }
1104: 
1105:     /**
1106:      * Returns the parameters necessary to run an address search.
1107:      *
1108:      * @return array  An array with two keys: 'sources' and 'fields'.
1109:      */
1110:     static public function getAddressbookSearchParams()
1111:     {
1112:         $src = json_decode($GLOBALS['prefs']->getValue('search_sources'));
1113:         if (!is_array($src)) {
1114:             $src = array();
1115:         }
1116: 
1117:         $fields = json_decode($GLOBALS['prefs']->getValue('search_fields'), true);
1118:         if (!is_array($fields)) {
1119:             $fields = array();
1120:         }
1121: 
1122:         return array(
1123:             'fields' => $fields,
1124:             'sources' => $src
1125:         );
1126:     }
1127: }
1128: 
API documentation generated by ApiGen