1<?php 2/********************************************************************* 3 class.task.php 4 5 Task 6 7 Peter Rotich <peter@osticket.com> 8 Copyright (c) 2014 osTicket 9 http://www.osticket.com 10 11 Released under the GNU General Public License WITHOUT ANY WARRANTY. 12 See LICENSE.TXT for details. 13 14 vim: expandtab sw=4 ts=4 sts=4: 15**********************************************************************/ 16 17include_once INCLUDE_DIR.'class.role.php'; 18 19 20class TaskModel extends VerySimpleModel { 21 static $meta = array( 22 'table' => TASK_TABLE, 23 'pk' => array('id'), 24 'joins' => array( 25 'dept' => array( 26 'constraint' => array('dept_id' => 'Dept.id'), 27 ), 28 'lock' => array( 29 'constraint' => array('lock_id' => 'Lock.lock_id'), 30 'null' => true, 31 ), 32 'staff' => array( 33 'constraint' => array('staff_id' => 'Staff.staff_id'), 34 'null' => true, 35 ), 36 'team' => array( 37 'constraint' => array('team_id' => 'Team.team_id'), 38 'null' => true, 39 ), 40 'thread' => array( 41 'constraint' => array( 42 'id' => 'TaskThread.object_id', 43 "'A'" => 'TaskThread.object_type', 44 ), 45 'list' => false, 46 'null' => false, 47 ), 48 'cdata' => array( 49 'constraint' => array('id' => 'TaskCData.task_id'), 50 'list' => false, 51 ), 52 'entries' => array( 53 'constraint' => array( 54 "'A'" => 'DynamicFormEntry.object_type', 55 'id' => 'DynamicFormEntry.object_id', 56 ), 57 'list' => true, 58 ), 59 60 'ticket' => array( 61 'constraint' => array( 62 'object_id' => 'Ticket.ticket_id', 63 ), 64 'null' => true, 65 ), 66 ), 67 ); 68 69 const PERM_CREATE = 'task.create'; 70 const PERM_EDIT = 'task.edit'; 71 const PERM_ASSIGN = 'task.assign'; 72 const PERM_TRANSFER = 'task.transfer'; 73 const PERM_REPLY = 'task.reply'; 74 const PERM_CLOSE = 'task.close'; 75 const PERM_DELETE = 'task.delete'; 76 77 static protected $perms = array( 78 self::PERM_CREATE => array( 79 'title' => 80 /* @trans */ 'Create', 81 'desc' => 82 /* @trans */ 'Ability to create tasks'), 83 self::PERM_EDIT => array( 84 'title' => 85 /* @trans */ 'Edit', 86 'desc' => 87 /* @trans */ 'Ability to edit tasks'), 88 self::PERM_ASSIGN => array( 89 'title' => 90 /* @trans */ 'Assign', 91 'desc' => 92 /* @trans */ 'Ability to assign tasks to agents or teams'), 93 self::PERM_TRANSFER => array( 94 'title' => 95 /* @trans */ 'Transfer', 96 'desc' => 97 /* @trans */ 'Ability to transfer tasks between departments'), 98 self::PERM_REPLY => array( 99 'title' => 100 /* @trans */ 'Post Reply', 101 'desc' => 102 /* @trans */ 'Ability to post task update'), 103 self::PERM_CLOSE => array( 104 'title' => 105 /* @trans */ 'Close', 106 'desc' => 107 /* @trans */ 'Ability to close tasks'), 108 self::PERM_DELETE => array( 109 'title' => 110 /* @trans */ 'Delete', 111 'desc' => 112 /* @trans */ 'Ability to delete tasks'), 113 ); 114 115 const ISOPEN = 0x0001; 116 const ISOVERDUE = 0x0002; 117 118 119 protected function hasFlag($flag) { 120 return ($this->get('flags') & $flag) !== 0; 121 } 122 123 protected function clearFlag($flag) { 124 return $this->set('flags', $this->get('flags') & ~$flag); 125 } 126 127 protected function setFlag($flag) { 128 return $this->set('flags', $this->get('flags') | $flag); 129 } 130 131 function getId() { 132 return $this->id; 133 } 134 135 function getNumber() { 136 return $this->number; 137 } 138 139 function getStaffId() { 140 return $this->staff_id; 141 } 142 143 function getStaff() { 144 return $this->staff; 145 } 146 147 function getTeamId() { 148 return $this->team_id; 149 } 150 151 function getTeam() { 152 return $this->team; 153 } 154 155 function getDeptId() { 156 return $this->dept_id; 157 } 158 159 function getDept() { 160 return $this->dept; 161 } 162 163 function getCreateDate() { 164 return $this->created; 165 } 166 167 function getDueDate() { 168 return $this->duedate; 169 } 170 171 function getCloseDate() { 172 return $this->isClosed() ? $this->closed : ''; 173 } 174 175 function isOpen() { 176 return $this->hasFlag(self::ISOPEN); 177 } 178 179 function isClosed() { 180 return !$this->isOpen(); 181 } 182 183 function isCloseable() { 184 185 if ($this->isClosed()) 186 return true; 187 188 $warning = null; 189 if ($this->getMissingRequiredFields()) { 190 $warning = sprintf( 191 __( '%1$s is missing data on %2$s one or more required fields %3$s and cannot be closed'), 192 __('This task'), 193 '', ''); 194 } 195 196 return $warning ?: true; 197 } 198 199 protected function close() { 200 return $this->clearFlag(self::ISOPEN); 201 } 202 203 protected function reopen() { 204 return $this->setFlag(self::ISOPEN); 205 } 206 207 function isAssigned($to=null) { 208 if (!$this->isOpen()) 209 return false; 210 211 if (is_null($to)) 212 return ($this->getStaffId() || $this->getTeamId()); 213 214 switch (true) { 215 case $to instanceof Staff: 216 return ($to->getId() == $this->getStaffId() || 217 $to->isTeamMember($this->getTeamId())); 218 break; 219 case $to instanceof Team: 220 return ($to->getId() == $this->getTeamId()); 221 break; 222 } 223 224 return false; 225 } 226 227 function isOverdue() { 228 return $this->hasFlag(self::ISOVERDUE); 229 } 230 231 static function getPermissions() { 232 return self::$perms; 233 } 234 235} 236 237RolePermission::register(/* @trans */ 'Tasks', TaskModel::getPermissions()); 238 239 240class Task extends TaskModel implements RestrictedAccess, Threadable { 241 var $form; 242 var $entry; 243 244 var $_thread; 245 var $_entries; 246 var $_answers; 247 248 var $lastrespondent; 249 250 function __onload() { 251 $this->loadDynamicData(); 252 } 253 254 function loadDynamicData() { 255 if (!isset($this->_answers)) { 256 $this->_answers = array(); 257 foreach (DynamicFormEntryAnswer::objects() 258 ->filter(array( 259 'entry__object_id' => $this->getId(), 260 'entry__object_type' => ObjectModel::OBJECT_TYPE_TASK 261 )) as $answer 262 ) { 263 $tag = mb_strtolower($answer->field->name) 264 ?: 'field.' . $answer->field->id; 265 $this->_answers[$tag] = $answer; 266 } 267 } 268 return $this->_answers; 269 } 270 271 function getStatus() { 272 return $this->isOpen() ? __('Open') : __('Completed'); 273 } 274 275 function getTitle() { 276 return $this->__cdata('title', ObjectModel::OBJECT_TYPE_TASK); 277 } 278 279 function checkStaffPerm($staff, $perm=null) { 280 281 // Must be a valid staff 282 if (!$staff instanceof Staff && !($staff=Staff::lookup($staff))) 283 return false; 284 285 // Check access based on department or assignment 286 if (!$staff->canAccessDept($this->getDept()) 287 && $this->isOpen() 288 && $staff->getId() != $this->getStaffId() 289 && !$staff->isTeamMember($this->getTeamId())) 290 return false; 291 292 // At this point staff has access unless a specific permission is 293 // requested 294 if ($perm === null) 295 return true; 296 297 // Permission check requested -- get role. 298 if (!($role=$staff->getRole($this->getDept()))) 299 return false; 300 301 // Check permission based on the effective role 302 return $role->hasPerm($perm); 303 } 304 305 function getAssignee() { 306 307 if (!$this->isOpen() || !$this->isAssigned()) 308 return false; 309 310 if ($this->staff) 311 return $this->staff; 312 313 if ($this->team) 314 return $this->team; 315 316 return null; 317 } 318 319 function getAssigneeId() { 320 321 if (!($assignee=$this->getAssignee())) 322 return null; 323 324 $id = ''; 325 if ($assignee instanceof Staff) 326 $id = 's'.$assignee->getId(); 327 elseif ($assignee instanceof Team) 328 $id = 't'.$assignee->getId(); 329 330 return $id; 331 } 332 333 function getAssignees() { 334 335 $assignees=array(); 336 if ($this->staff) 337 $assignees[] = $this->staff->getName(); 338 339 //Add team assignment 340 if ($this->team) 341 $assignees[] = $this->team->getName(); 342 343 return $assignees; 344 } 345 346 function getAssigned($glue='/') { 347 $assignees = $this->getAssignees(); 348 349 return $assignees ? implode($glue, $assignees):''; 350 } 351 352 function getLastRespondent() { 353 354 if (!isset($this->lastrespondent)) { 355 $this->lastrespondent = Staff::objects() 356 ->filter(array( 357 'staff_id' => static::objects() 358 ->filter(array( 359 'thread__entries__type' => 'R', 360 'thread__entries__staff_id__gt' => 0 361 )) 362 ->values_flat('thread__entries__staff_id') 363 ->order_by('-thread__entries__id') 364 ->limit('1,1') 365 )) 366 ->first() 367 ?: false; 368 } 369 370 return $this->lastrespondent; 371 } 372 373 function getField($fid) { 374 if (is_numeric($fid)) 375 return $this->getDymanicFieldById($fid); 376 377 // Special fields 378 switch ($fid) { 379 case 'duedate': 380 return DateTimeField::init(array( 381 'id' => $fid, 382 'name' => $fid, 383 'default' => Misc::db2gmtime($this->getDueDate()), 384 'label' => __('Due Date'), 385 'configuration' => array( 386 'min' => Misc::gmtime(), 387 'time' => true, 388 'gmt' => false, 389 'future' => true, 390 ) 391 )); 392 } 393 } 394 395 function getDymanicFieldById($fid) { 396 foreach (DynamicFormEntry::forObject($this->getId(), 397 ObjectModel::OBJECT_TYPE_TASK) as $form) { 398 foreach ($form->getFields() as $field) 399 if ($field->getId() == $fid) 400 return $field; 401 } 402 } 403 404 function getDynamicFields($criteria=array()) { 405 406 $fields = DynamicFormField::objects()->filter(array( 407 'id__in' => $this->entries 408 ->filter($criteria) 409 ->values_flat('answers__field_id'))); 410 411 return ($fields && count($fields)) ? $fields : array(); 412 } 413 414 function getMissingRequiredFields() { 415 416 return $this->getDynamicFields(array( 417 'answers__field__flags__hasbit' => DynamicFormField::FLAG_ENABLED, 418 'answers__field__flags__hasbit' => DynamicFormField::FLAG_CLOSE_REQUIRED, 419 'answers__value__isnull' => true, 420 )); 421 } 422 423 function getParticipants() { 424 $participants = array(); 425 foreach ($this->getThread()->collaborators as $c) 426 $participants[] = $c->getName(); 427 428 return $participants ? implode(', ', $participants) : ' '; 429 } 430 431 function getThreadId() { 432 return $this->thread->getId(); 433 } 434 435 function getThread() { 436 return $this->thread; 437 } 438 439 function getThreadEntry($id) { 440 return $this->getThread()->getEntry($id); 441 } 442 443 function getThreadEntries($type=false) { 444 $thread = $this->getThread()->getEntries(); 445 if ($type && is_array($type)) 446 $thread->filter(array('type__in' => $type)); 447 return $thread; 448 } 449 450 function postThreadEntry($type, $vars, $options=array()) { 451 $errors = array(); 452 $poster = isset($options['poster']) ? $options['poster'] : null; 453 $alert = isset($options['alert']) ? $options['alert'] : true; 454 switch ($type) { 455 case 'N': 456 case 'M': 457 return $this->getThread()->addDescription($vars); 458 break; 459 default: 460 return $this->postNote($vars, $errors, $poster, $alert); 461 } 462 } 463 464 function getForm() { 465 if (!isset($this->form)) { 466 // Look for the entry first 467 if ($this->form = DynamicFormEntry::lookup( 468 array('object_type' => ObjectModel::OBJECT_TYPE_TASK))) { 469 return $this->form; 470 } 471 // Make sure the form is in the database 472 elseif (!($this->form = DynamicForm::lookup( 473 array('type' => ObjectModel::OBJECT_TYPE_TASK)))) { 474 $this->__loadDefaultForm(); 475 return $this->getForm(); 476 } 477 // Create an entry to be saved later 478 $this->form = $this->form->instanciate(); 479 $this->form->object_type = ObjectModel::OBJECT_TYPE_TASK; 480 } 481 482 return $this->form; 483 } 484 485 function getAssignmentForm($source=null, $options=array()) { 486 global $thisstaff; 487 488 $prompt = $assignee = ''; 489 // Possible assignees 490 $dept = $this->getDept(); 491 switch (strtolower($options['target'])) { 492 case 'agents': 493 if (!$source && $this->isOpen() && $this->staff) 494 $assignee = sprintf('s%d', $this->staff->getId()); 495 $prompt = __('Select an Agent'); 496 break; 497 case 'teams': 498 if (!$source && $this->isOpen() && $this->team) 499 $assignee = sprintf('t%d', $this->team->getId()); 500 $prompt = __('Select a Team'); 501 break; 502 } 503 504 // Default to current assignee if source is not set 505 if (!$source) 506 $source = array('assignee' => array($assignee)); 507 508 $form = AssignmentForm::instantiate($source, $options); 509 510 // Field configurations 511 if ($f=$form->getField('assignee')) { 512 $f->configure('dept', $dept); 513 $f->configure('staff', $thisstaff); 514 if ($prompt) 515 $f->configure('prompt', $prompt); 516 if ($options['target']) 517 $f->configure('target', $options['target']); 518 } 519 520 return $form; 521 } 522 523 function getClaimForm($source=null, $options=array()) { 524 global $thisstaff; 525 526 $id = sprintf('s%d', $thisstaff->getId()); 527 if(!$source) 528 $source = array('assignee' => array($id)); 529 530 $form = ClaimForm::instantiate($source, $options); 531 $form->setAssignees(array($id => $thisstaff->getName())); 532 533 return $form; 534 535 } 536 537 538 function getTransferForm($source=null) { 539 540 if (!$source) 541 $source = array('dept' => array($this->getDeptId())); 542 543 return TransferForm::instantiate($source); 544 } 545 546 function addDynamicData($data) { 547 548 $tf = TaskForm::getInstance($this->id, true); 549 foreach ($tf->getFields() as $f) 550 if (isset($data[$f->get('name')])) 551 $tf->setAnswer($f->get('name'), $data[$f->get('name')]); 552 553 $tf->save(); 554 555 return $tf; 556 } 557 558 function getDynamicData($create=true) { 559 if (!isset($this->_entries)) { 560 $this->_entries = DynamicFormEntry::forObject($this->id, 561 ObjectModel::OBJECT_TYPE_TASK)->all(); 562 if (!$this->_entries && $create) { 563 $f = TaskForm::getInstance($this->id, true); 564 $f->save(); 565 $this->_entries[] = $f; 566 } 567 } 568 569 return $this->_entries ?: array(); 570 } 571 572 function setStatus($status, $comments='', &$errors=array()) { 573 global $thisstaff; 574 575 $ecb = null; 576 switch($status) { 577 case 'open': 578 if ($this->isOpen()) 579 return false; 580 581 $this->reopen(); 582 $this->closed = null; 583 584 $ecb = function ($t) use($thisstaff) { 585 $t->logEvent('reopened', false, null, 'closed'); 586 587 if ($t->ticket) { 588 $t->ticket->reopen(); 589 $vars = array( 590 'title' => sprintf('Task %s Reopened', 591 $t->getNumber()), 592 'note' => __('Task reopened') 593 ); 594 $t->ticket->logNote($vars['title'], $vars['note'], $thisstaff); 595 } 596 }; 597 break; 598 case 'closed': 599 if ($this->isClosed()) 600 return false; 601 602 // Check if task is closeable 603 $closeable = $this->isCloseable(); 604 if ($closeable !== true) 605 $errors['err'] = $closeable ?: sprintf(__('%s cannot be closed'), __('This task')); 606 607 if ($errors) 608 return false; 609 610 $this->close(); 611 $this->closed = SqlFunction::NOW(); 612 $ecb = function($t) use($thisstaff) { 613 $t->logEvent('closed'); 614 615 if ($t->ticket) { 616 $vars = array( 617 'title' => sprintf('Task %s Closed', 618 $t->getNumber()), 619 'note' => __('Task closed') 620 ); 621 $t->ticket->logNote($vars['title'], $vars['note'], $thisstaff); 622 } 623 }; 624 break; 625 default: 626 return false; 627 } 628 629 if (!$this->save(true)) 630 return false; 631 632 // Log events via callback 633 if ($ecb) $ecb($this); 634 635 if ($comments) { 636 $errors = array(); 637 $this->postNote(array( 638 'note' => $comments, 639 'title' => sprintf( 640 __('Status changed to %s'), 641 $this->getStatus()) 642 ), 643 $errors, 644 $thisstaff); 645 } 646 647 return true; 648 } 649 650 function to_json() { 651 652 $info = array( 653 'id' => $this->getId(), 654 'title' => $this->getTitle() 655 ); 656 657 return JsonDataEncoder::encode($info); 658 } 659 660 function __cdata($field, $ftype=null) { 661 662 foreach ($this->getDynamicData() as $e) { 663 // Make sure the form type matches 664 if (!$e->form 665 || ($ftype && $ftype != $e->form->get('type'))) 666 continue; 667 668 // Get the named field and return the answer 669 if ($a = $e->getAnswer($field)) 670 return $a; 671 } 672 673 return null; 674 } 675 676 function __toString() { 677 return (string) $this->getTitle(); 678 } 679 680 /* util routines */ 681 682 function logEvent($state, $data=null, $user=null, $annul=null) { 683 switch ($state) { 684 case 'transferred': 685 case 'edited': 686 $type = $data; 687 $type['type'] = $state; 688 break; 689 case 'assigned': 690 break; 691 default: 692 $type = array('type' => $state); 693 break; 694 } 695 if ($type) 696 Signal::send('object.created', $this, $type); 697 $this->getThread()->getEvents()->log($this, $state, $data, $user, $annul); 698 } 699 700 function claim(ClaimForm $form, &$errors) { 701 global $thisstaff; 702 703 $dept = $this->getDept(); 704 $assignee = $form->getAssignee(); 705 if (!($assignee instanceof Staff) 706 || !$thisstaff 707 || $thisstaff->getId() != $assignee->getId()) { 708 $errors['err'] = __('Unknown assignee'); 709 } elseif (!$assignee->isAvailable()) { 710 $errors['err'] = __('Agent is unavailable for assignment'); 711 } elseif (!$dept->canAssign($assignee)) { 712 $errors['err'] = __('Permission denied'); 713 } 714 715 if ($errors) 716 return false; 717 718 $type = array('type' => 'assigned', 'claim' => true); 719 Signal::send('object.edited', $this, $type); 720 721 return $this->assignToStaff($assignee, $form->getComments(), false); 722 } 723 724 function assignToStaff($staff, $note, $alert=true) { 725 726 if(!is_object($staff) && !($staff = Staff::lookup($staff))) 727 return false; 728 729 if (!$staff->isAvailable()) 730 return false; 731 732 $this->staff_id = $staff->getId(); 733 734 if (!$this->save()) 735 return false; 736 737 $this->onAssignment($staff, $note, $alert); 738 739 global $thisstaff; 740 $data = array(); 741 if ($thisstaff && $staff->getId() == $thisstaff->getId()) 742 $data['claim'] = true; 743 else 744 $data['staff'] = $staff->getId(); 745 746 $this->logEvent('assigned', $data); 747 748 return true; 749 } 750 751 752 function assign(AssignmentForm $form, &$errors, $alert=true) { 753 global $thisstaff; 754 755 $evd = array(); 756 $audit = array(); 757 $assignee = $form->getAssignee(); 758 if ($assignee instanceof Staff) { 759 $dept = $this->getDept(); 760 if ($this->getStaffId() == $assignee->getId()) { 761 $errors['assignee'] = sprintf(__('%s already assigned to %s'), 762 __('Task'), 763 __('the agent') 764 ); 765 } elseif(!$assignee->isAvailable()) { 766 $errors['assignee'] = __('Agent is unavailable for assignment'); 767 } elseif (!$dept->canAssign($assignee)) { 768 $errors['err'] = __('Permission denied'); 769 } 770 else { 771 $this->staff_id = $assignee->getId(); 772 if ($thisstaff && $thisstaff->getId() == $assignee->getId()) 773 $evd['claim'] = true; 774 else 775 $evd['staff'] = array($assignee->getId(), $assignee->getName()); 776 $audit = array('staff' => $assignee->getName()->name); 777 } 778 } elseif ($assignee instanceof Team) { 779 if ($this->getTeamId() == $assignee->getId()) { 780 $errors['assignee'] = sprintf(__('%s already assigned to %s'), 781 __('Task'), 782 __('the team') 783 ); 784 } else { 785 $this->team_id = $assignee->getId(); 786 $evd = array('team' => $assignee->getId()); 787 $audit = array('team' => $assignee->getName()); 788 } 789 } else { 790 $errors['assignee'] = __('Unknown assignee'); 791 } 792 793 if ($errors || !$this->save(true)) 794 return false; 795 796 $this->logEvent('assigned', $evd); 797 798 $type = array('type' => 'assigned'); 799 $type += $audit; 800 Signal::send('object.edited', $this, $type); 801 802 $this->onAssignment($assignee, 803 $form->getField('comments')->getClean(), 804 $alert); 805 806 return true; 807 } 808 809 function onAssignment($assignee, $comments='', $alert=true) { 810 global $thisstaff, $cfg; 811 812 if (!is_object($assignee)) 813 return false; 814 815 $assigner = $thisstaff ?: __('SYSTEM (Auto Assignment)'); 816 817 //Assignment completed... post internal note. 818 $note = null; 819 if ($comments) { 820 821 $title = sprintf(__('Task assigned to %s'), 822 (string) $assignee); 823 824 $errors = array(); 825 $note = $this->postNote( 826 array('note' => $comments, 'title' => $title), 827 $errors, 828 $assigner, 829 false); 830 } 831 832 // Send alerts out if enabled. 833 if (!$alert || !$cfg->alertONTaskAssignment()) 834 return false; 835 836 if (!($dept=$this->getDept()) 837 || !($tpl = $dept->getTemplate()) 838 || !($email = $dept->getAlertEmail()) 839 ) { 840 return true; 841 } 842 843 // Recipients 844 $recipients = array(); 845 if ($assignee instanceof Staff) { 846 if ($cfg->alertStaffONTaskAssignment()) 847 $recipients[] = $assignee; 848 } elseif (($assignee instanceof Team) && $assignee->alertsEnabled()) { 849 if ($cfg->alertTeamMembersONTaskAssignment() && ($members=$assignee->getMembersForAlerts())) 850 $recipients = array_merge($recipients, $members); 851 elseif ($cfg->alertTeamLeadONTaskAssignment() && ($lead=$assignee->getTeamLead())) 852 $recipients[] = $lead; 853 } 854 855 if ($recipients 856 && ($msg=$tpl->getTaskAssignmentAlertMsgTemplate())) { 857 858 $msg = $this->replaceVars($msg->asArray(), 859 array('comments' => $comments, 860 'assignee' => $assignee, 861 'assigner' => $assigner 862 ) 863 ); 864 // Send the alerts. 865 $sentlist = array(); 866 $options = $note instanceof ThreadEntry 867 ? array('thread' => $note) 868 : array(); 869 870 foreach ($recipients as $k => $staff) { 871 if (!is_object($staff) 872 || !$staff->isAvailable() 873 || in_array($staff->getEmail(), $sentlist)) { 874 continue; 875 } 876 877 $alert = $this->replaceVars($msg, array('recipient' => $staff)); 878 $email->sendAlert($staff, $alert['subj'], $alert['body'], null, $options); 879 $sentlist[] = $staff->getEmail(); 880 } 881 } 882 883 return true; 884 } 885 886 function transfer(TransferForm $form, &$errors, $alert=true) { 887 global $thisstaff, $cfg; 888 889 $cdept = $this->getDept(); 890 $dept = $form->getDept(); 891 if (!$dept || !($dept instanceof Dept)) 892 $errors['dept'] = __('Department selection is required'); 893 elseif ($dept->getid() == $this->getDeptId()) 894 $errors['dept'] = __('Task already in the department'); 895 else 896 $this->dept_id = $dept->getId(); 897 898 if ($errors || !$this->save(true)) 899 return false; 900 901 // Log transfer event 902 $this->logEvent('transferred', array('dept' => $dept->getName())); 903 904 // Post internal note if any 905 $note = $form->getField('comments')->getClean(); 906 if ($note) { 907 $title = sprintf(__('%1$s transferred from %2$s to %3$s'), 908 __('Task'), 909 $cdept->getName(), 910 $dept->getName()); 911 912 $_errors = array(); 913 $note = $this->postNote( 914 array('note' => $note, 'title' => $title), 915 $_errors, $thisstaff, false); 916 } 917 918 // Send alerts if requested && enabled. 919 if (!$alert || !$cfg->alertONTaskTransfer()) 920 return true; 921 922 if (($email = $dept->getAlertEmail()) 923 && ($tpl = $dept->getTemplate()) 924 && ($msg=$tpl->getTaskTransferAlertMsgTemplate())) { 925 926 $msg = $this->replaceVars($msg->asArray(), 927 array('comments' => $note, 'staff' => $thisstaff)); 928 // Recipients 929 $recipients = array(); 930 // Assigned staff or team... if any 931 if ($this->isAssigned() && $cfg->alertAssignedONTaskTransfer()) { 932 if($this->getStaffId()) 933 $recipients[] = $this->getStaff(); 934 elseif ($this->getTeamId() 935 && ($team=$this->getTeam()) 936 && ($members=$team->getMembersForAlerts()) 937 ) { 938 $recipients = array_merge($recipients, $members); 939 } 940 } elseif ($cfg->alertDeptMembersONTaskTransfer() && !$this->isAssigned()) { 941 // Only alerts dept members if the task is NOT assigned. 942 foreach ($dept->getMembersForAlerts() as $M) 943 $recipients[] = $M; 944 } 945 946 // Always alert dept manager?? 947 if ($cfg->alertDeptManagerONTaskTransfer() 948 && ($manager=$dept->getManager())) { 949 $recipients[] = $manager; 950 } 951 952 $sentlist = $options = array(); 953 if ($note instanceof ThreadEntry) { 954 $options += array('thread'=>$note); 955 } 956 957 foreach ($recipients as $k=>$staff) { 958 if (!is_object($staff) 959 || !$staff->isAvailable() 960 || in_array($staff->getEmail(), $sentlist) 961 ) { 962 continue; 963 } 964 $alert = $this->replaceVars($msg, array('recipient' => $staff)); 965 $email->sendAlert($staff, $alert['subj'], $alert['body'], null, $options); 966 $sentlist[] = $staff->getEmail(); 967 } 968 } 969 970 return true; 971 } 972 973 function postNote($vars, &$errors, $poster='', $alert=true) { 974 global $cfg, $thisstaff; 975 976 $vars['staffId'] = 0; 977 $vars['poster'] = 'SYSTEM'; 978 if ($poster && is_object($poster)) { 979 $vars['staffId'] = $poster->getId(); 980 $vars['poster'] = $poster->getName(); 981 } elseif ($poster) { //string 982 $vars['poster'] = $poster; 983 } 984 985 if (!($note=$this->getThread()->addNote($vars, $errors))) 986 return null; 987 988 $assignee = $this->getStaff(); 989 990 if (isset($vars['task:status'])) 991 $this->setStatus($vars['task:status']); 992 993 $this->onActivity(array( 994 'activity' => $note->getActivity(), 995 'threadentry' => $note, 996 'assignee' => $assignee 997 ), $alert); 998 999 $type = array('type' => 'note'); 1000 Signal::send('object.created', $this, $type); 1001 1002 return $note; 1003 } 1004 1005 /* public */ 1006 function postReply($vars, &$errors, $alert = true) { 1007 global $thisstaff, $cfg; 1008 1009 1010 if (!$vars['poster'] && $thisstaff) 1011 $vars['poster'] = $thisstaff; 1012 1013 if (!$vars['staffId'] && $thisstaff) 1014 $vars['staffId'] = $thisstaff->getId(); 1015 1016 if (!$vars['ip_address'] && $_SERVER['REMOTE_ADDR']) 1017 $vars['ip_address'] = $_SERVER['REMOTE_ADDR']; 1018 1019 if (!($response = $this->getThread()->addResponse($vars, $errors))) 1020 return null; 1021 1022 $assignee = $this->getStaff(); 1023 1024 if (isset($vars['task:status'])) 1025 $this->setStatus($vars['task:status']); 1026 1027 /* 1028 // TODO: add auto claim setting for tasks. 1029 // Claim on response bypasses the department assignment restrictions 1030 if ($thisstaff 1031 && $this->isOpen() 1032 && !$this->getStaffId() 1033 && $cfg->autoClaimTasks) 1034 ) { 1035 $this->staff_id = $thisstaff->getId(); 1036 } 1037 */ 1038 1039 // Send activity alert to agents 1040 $activity = $vars['activity'] ?: $response->getActivity(); 1041 $this->onActivity( array( 1042 'activity' => $activity, 1043 'threadentry' => $response, 1044 'assignee' => $assignee, 1045 )); 1046 1047 $this->lastrespondent = $response->staff; 1048 $this->save(); 1049 1050 // Send alert to collaborators 1051 if ($alert && $vars['emailcollab']) { 1052 $signature = ''; 1053 $this->notifyCollaborators($response, 1054 array('signature' => $signature) 1055 ); 1056 } 1057 1058 $type = array('type' => 'message'); 1059 Signal::send('object.created', $this, $type); 1060 1061 return $response; 1062 } 1063 1064 function pdfExport($options=array()) { 1065 global $thisstaff; 1066 1067 require_once(INCLUDE_DIR.'class.pdf.php'); 1068 if (!isset($options['psize'])) { 1069 if ($_SESSION['PAPER_SIZE']) 1070 $psize = $_SESSION['PAPER_SIZE']; 1071 elseif (!$thisstaff || !($psize = $thisstaff->getDefaultPaperSize())) 1072 $psize = 'Letter'; 1073 1074 $options['psize'] = $psize; 1075 } 1076 1077 $pdf = new Task2PDF($this, $options); 1078 $name = 'Task-'.$this->getNumber().'.pdf'; 1079 Http::download($name, 'application/pdf', $pdf->output($name, 'S')); 1080 //Remember what the user selected - for autoselect on the next print. 1081 $_SESSION['PAPER_SIZE'] = $options['psize']; 1082 exit; 1083 } 1084 1085 /* util routines */ 1086 function replaceVars($input, $vars = array()) { 1087 global $ost; 1088 1089 return $ost->replaceTemplateVariables($input, 1090 array_merge($vars, array('task' => $this))); 1091 } 1092 1093 function asVar() { 1094 return $this->getNumber(); 1095 } 1096 1097 function getVar($tag) { 1098 global $cfg; 1099 1100 if ($tag && is_callable(array($this, 'get'.ucfirst($tag)))) 1101 return call_user_func(array($this, 'get'.ucfirst($tag))); 1102 1103 switch(mb_strtolower($tag)) { 1104 case 'phone': 1105 case 'phone_number': 1106 return $this->getPhoneNumber(); 1107 case 'ticket_link': 1108 if ($ticket = $this->ticket) { 1109 return sprintf('%s/scp/tickets.php?id=%d#tasks', 1110 $cfg->getBaseUrl(), $ticket->getId()); 1111 } 1112 case 'staff_link': 1113 return sprintf('%s/scp/tasks.php?id=%d', $cfg->getBaseUrl(), $this->getId()); 1114 case 'create_date': 1115 return new FormattedDate($this->getCreateDate()); 1116 case 'due_date': 1117 if ($due = $this->getDueDate()) 1118 return new FormattedDate($due); 1119 break; 1120 case 'close_date': 1121 if ($this->isClosed()) 1122 return new FormattedDate($this->getCloseDate()); 1123 break; 1124 case 'last_update': 1125 return new FormattedDate($this->updated); 1126 case 'description': 1127 return Format::display($this->getThread()->getVar('original') ?: ''); 1128 case 'subject': 1129 return Format::htmlchars($this->getTitle()); 1130 default: 1131 if (isset($this->_answers[$tag])) 1132 // The answer object is retrieved here which will 1133 // automatically invoke the toString() method when the 1134 // answer is coerced into text 1135 return $this->_answers[$tag]; 1136 } 1137 return false; 1138 } 1139 1140 static function getVarScope() { 1141 $base = array( 1142 'assigned' => __('Assigned Agent / Team'), 1143 'close_date' => array( 1144 'class' => 'FormattedDate', 'desc' => __('Date Closed'), 1145 ), 1146 'create_date' => array( 1147 'class' => 'FormattedDate', 'desc' => __('Date Created'), 1148 ), 1149 'dept' => array( 1150 'class' => 'Dept', 'desc' => __('Department'), 1151 ), 1152 'description' => __('Description'), 1153 'due_date' => array( 1154 'class' => 'FormattedDate', 'desc' => __('Due Date'), 1155 ), 1156 'number' => __('Task Number'), 1157 'recipients' => array( 1158 'class' => 'UserList', 'desc' => __('List of all recipient names'), 1159 ), 1160 'status' => __('Status'), 1161 'staff' => array( 1162 'class' => 'Staff', 'desc' => __('Assigned/closing agent'), 1163 ), 1164 'subject' => 'Subject', 1165 'team' => array( 1166 'class' => 'Team', 'desc' => __('Assigned/closing team'), 1167 ), 1168 'thread' => array( 1169 'class' => 'TaskThread', 'desc' => __('Task Thread'), 1170 ), 1171 'staff_link' => __('Link to view the task'), 1172 'ticket_link' => __('Link to view the task inside the ticket'), 1173 'last_update' => array( 1174 'class' => 'FormattedDate', 'desc' => __('Time of last update'), 1175 ), 1176 ); 1177 1178 $extra = VariableReplacer::compileFormScope(TaskForm::getInstance()); 1179 return $base + $extra; 1180 } 1181 1182 function onActivity($vars, $alert=true) { 1183 global $cfg, $thisstaff; 1184 1185 if (!$alert // Check if alert is enabled 1186 || !$cfg->alertONTaskActivity() 1187 || !($dept=$this->getDept()) 1188 || !($email=$cfg->getAlertEmail()) 1189 || !($tpl = $dept->getTemplate()) 1190 || !($msg=$tpl->getTaskActivityAlertMsgTemplate()) 1191 ) { 1192 return; 1193 } 1194 1195 // Alert recipients 1196 $recipients = array(); 1197 //Last respondent. 1198 if ($cfg->alertLastRespondentONTaskActivity()) 1199 $recipients[] = $this->getLastRespondent(); 1200 1201 // Assigned staff / team 1202 if ($cfg->alertAssignedONTaskActivity()) { 1203 if (isset($vars['assignee']) 1204 && $vars['assignee'] instanceof Staff) 1205 $recipients[] = $vars['assignee']; 1206 elseif ($this->isOpen() && ($assignee = $this->getStaff())) 1207 $recipients[] = $assignee; 1208 1209 if ($team = $this->getTeam()) 1210 $recipients = array_merge($recipients, $team->getMembersForAlerts()); 1211 } 1212 1213 // Dept manager 1214 if ($cfg->alertDeptManagerONTaskActivity() && $dept && $dept->getManagerId()) 1215 $recipients[] = $dept->getManager(); 1216 1217 $options = array(); 1218 $staffId = $thisstaff ? $thisstaff->getId() : 0; 1219 if ($vars['threadentry'] && $vars['threadentry'] instanceof ThreadEntry) { 1220 $options = array('thread' => $vars['threadentry']); 1221 1222 // Activity details 1223 if (!$vars['message']) 1224 $vars['message'] = $vars['threadentry']; 1225 1226 // Staff doing the activity 1227 $staffId = $vars['threadentry']->getStaffId() ?: $staffId; 1228 } 1229 1230 $msg = $this->replaceVars($msg->asArray(), 1231 array( 1232 'note' => $vars['threadentry'], // For compatibility 1233 'activity' => $vars['activity'], 1234 'message' => $vars['message'])); 1235 1236 $isClosed = $this->isClosed(); 1237 $sentlist=array(); 1238 foreach ($recipients as $k=>$staff) { 1239 if (!is_object($staff) 1240 // Don't bother vacationing staff. 1241 || !$staff->isAvailable() 1242 // No need to alert the poster! 1243 || $staffId == $staff->getId() 1244 // No duplicates. 1245 || isset($sentlist[$staff->getEmail()]) 1246 // Make sure staff has access to task 1247 || ($isClosed && !$this->checkStaffPerm($staff)) 1248 ) { 1249 continue; 1250 } 1251 $alert = $this->replaceVars($msg, array('recipient' => $staff)); 1252 $email->sendAlert($staff, $alert['subj'], $alert['body'], null, $options); 1253 $sentlist[$staff->getEmail()] = 1; 1254 } 1255 1256 } 1257 1258 function addCollaborator($user, $vars, &$errors, $event=true) { 1259 if ($c = $this->getThread()->addCollaborator($user, $vars, $errors, $event)) { 1260 $this->collaborators = null; 1261 $this->recipients = null; 1262 } 1263 return $c; 1264 } 1265 1266 /* 1267 * Notify collaborators on response or new message 1268 * 1269 */ 1270 function notifyCollaborators($entry, $vars = array()) { 1271 global $cfg; 1272 1273 if (!$entry instanceof ThreadEntry 1274 || !($recipients=$this->getThread()->getRecipients()) 1275 || !($dept=$this->getDept()) 1276 || !($tpl=$dept->getTemplate()) 1277 || !($msg=$tpl->getTaskActivityNoticeMsgTemplate()) 1278 || !($email=$dept->getEmail()) 1279 ) { 1280 return; 1281 } 1282 1283 // Who posted the entry? 1284 $skip = array(); 1285 if ($entry instanceof MessageThreadEntry) { 1286 $poster = $entry->getUser(); 1287 // Skip the person who sent in the message 1288 $skip[$entry->getUserId()] = 1; 1289 // Skip all the other recipients of the message 1290 foreach ($entry->getAllEmailRecipients() as $R) { 1291 foreach ($recipients as $R2) { 1292 if (0 === strcasecmp($R2->getEmail(), $R->mailbox.'@'.$R->host)) { 1293 $skip[$R2->getUserId()] = true; 1294 break; 1295 } 1296 } 1297 } 1298 } else { 1299 $poster = $entry->getStaff(); 1300 } 1301 1302 $vars = array_merge($vars, array( 1303 'message' => (string) $entry, 1304 'poster' => $poster ?: _S('A collaborator'), 1305 ) 1306 ); 1307 1308 $msg = $this->replaceVars($msg->asArray(), $vars); 1309 1310 $attachments = $cfg->emailAttachments()?$entry->getAttachments():array(); 1311 $options = array('thread' => $entry); 1312 1313 foreach ($recipients as $recipient) { 1314 // Skip folks who have already been included on this part of 1315 // the conversation 1316 if (isset($skip[$recipient->getUserId()])) 1317 continue; 1318 $notice = $this->replaceVars($msg, array('recipient' => $recipient)); 1319 $email->send($recipient, $notice['subj'], $notice['body'], $attachments, 1320 $options); 1321 } 1322 } 1323 1324 function update($forms, $vars, &$errors) { 1325 global $thisstaff; 1326 1327 if (!$forms || !$this->checkStaffPerm($thisstaff, Task::PERM_EDIT)) 1328 return false; 1329 1330 1331 foreach ($forms as $form) { 1332 $form->setSource($vars); 1333 if (!$form->isValid(function($f) { 1334 return $f->isVisibleToStaff() && $f->isEditableToStaff(); 1335 }, array('mode'=>'edit'))) { 1336 $errors = array_merge($errors, $form->errors()); 1337 } 1338 } 1339 1340 if ($errors) 1341 return false; 1342 1343 // Update dynamic meta-data 1344 $changes = array(); 1345 foreach ($forms as $f) { 1346 $changes += $f->getChanges(); 1347 $f->save(); 1348 } 1349 1350 1351 if ($vars['note']) { 1352 $_errors = array(); 1353 $this->postNote(array( 1354 'note' => $vars['note'], 1355 'title' => _S('Task Updated'), 1356 ), 1357 $_errors, 1358 $thisstaff); 1359 } 1360 1361 $this->updated = SqlFunction::NOW(); 1362 1363 if ($changes) 1364 $this->logEvent('edited', array('fields' => $changes)); 1365 1366 Signal::send('model.updated', $this); 1367 return $this->save(); 1368 } 1369 1370 function updateField($form, &$errors) { 1371 global $thisstaff, $cfg; 1372 1373 if (!($field = $form->getField('field'))) 1374 return null; 1375 1376 if (!($changes = $field->getChanges())) 1377 $errors['field'] = sprintf(__('%s is already assigned this value'), 1378 __($field->getLabel())); 1379 else { 1380 if ($field->answer) { 1381 if (!$field->isEditableToStaff()) 1382 $errors['field'] = sprintf(__('%s can not be edited'), 1383 __($field->getLabel())); 1384 elseif (!$field->save(true)) 1385 $errors['field'] = __('Unable to update field'); 1386 $changes['fields'] = array($field->getId() => $changes); 1387 } else { 1388 $val = $field->getClean(); 1389 $fid = $field->get('name'); 1390 1391 // Convert duedate to DB timezone. 1392 if ($fid == 'duedate') { 1393 if (empty($val)) 1394 $val = null; 1395 elseif ($dt = Format::parseDateTime($val)) { 1396 // Make sure the due date is valid 1397 if (Misc::user2gmtime($val) <= Misc::user2gmtime()) 1398 $errors['field']=__('Due date must be in the future'); 1399 else { 1400 $dt->setTimezone(new DateTimeZone($cfg->getDbTimezone())); 1401 $val = $dt->format('Y-m-d H:i:s'); 1402 } 1403 } 1404 } elseif (is_object($val)) 1405 $val = $val->getId(); 1406 1407 $changes = array(); 1408 $this->{$fid} = $val; 1409 foreach ($this->dirty as $F=>$old) { 1410 switch ($F) { 1411 case 'sla_id': 1412 case 'topic_id': 1413 case 'user_id': 1414 case 'source': 1415 $changes[$F] = array($old, $this->{$F}); 1416 } 1417 } 1418 1419 if (!$errors && !$this->save()) 1420 $errors['field'] = __('Unable to update field'); 1421 } 1422 } 1423 1424 if ($errors) 1425 return false; 1426 1427 // Record the changes 1428 $this->logEvent('edited', $changes); 1429 1430 // Log comments (if any) 1431 if (($comments = $form->getField('comments')->getClean())) { 1432 $title = sprintf(__('%s updated'), __($field->getLabel())); 1433 $_errors = array(); 1434 $this->postNote( 1435 array('note' => $comments, 'title' => $title), 1436 $_errors, $thisstaff, false); 1437 } 1438 1439 $this->updated = SqlFunction::NOW(); 1440 1441 $this->save(); 1442 1443 Signal::send('model.updated', $this); 1444 1445 return true; 1446 } 1447 1448 /* static routines */ 1449 static function lookupIdByNumber($number) { 1450 1451 if (($task = self::lookup(array('number' => $number)))) 1452 return $task->getId(); 1453 1454 } 1455 1456 static function isNumberUnique($number) { 1457 return !self::lookupIdByNumber($number); 1458 } 1459 1460 static function create($vars=false) { 1461 global $thisstaff, $cfg; 1462 1463 if (!is_array($vars) 1464 || !$thisstaff 1465 || !$thisstaff->hasPerm(Task::PERM_CREATE, false)) 1466 return null; 1467 1468 $task = new static(array( 1469 'flags' => self::ISOPEN, 1470 'object_id' => $vars['object_id'], 1471 'object_type' => $vars['object_type'], 1472 'number' => $cfg->getNewTaskNumber(), 1473 'created' => new SqlFunction('NOW'), 1474 'updated' => new SqlFunction('NOW'), 1475 )); 1476 1477 if ($vars['internal_formdata']['dept_id']) 1478 $task->dept_id = $vars['internal_formdata']['dept_id']; 1479 if ($vars['internal_formdata']['duedate']) 1480 $task->duedate = date('Y-m-d G:i', Misc::dbtime($vars['internal_formdata']['duedate'])); 1481 1482 if (!$task->save(true)) 1483 return false; 1484 1485 // Add dynamic data 1486 $task->addDynamicData($vars['default_formdata']); 1487 1488 // Create a thread + message. 1489 $thread = TaskThread::create($task); 1490 $desc = $thread->addDescription($vars); 1491 // Set the ORIGINAL_MESSAGE Flag if Description is added 1492 if ($desc) { 1493 $desc->setFlag(ThreadEntry::FLAG_ORIGINAL_MESSAGE); 1494 $desc->save(); 1495 } 1496 1497 $task->logEvent('created', null, $thisstaff); 1498 1499 // Get role for the dept 1500 $role = $thisstaff->getRole($task->getDept()); 1501 // Assignment 1502 $assignee = $vars['internal_formdata']['assignee']; 1503 if ($assignee 1504 // skip assignment if the user doesn't have perm. 1505 && $role->hasPerm(Task::PERM_ASSIGN)) { 1506 $_errors = array(); 1507 $assigneeId = sprintf('%s%d', 1508 ($assignee instanceof Staff) ? 's' : 't', 1509 $assignee->getId()); 1510 1511 $form = AssignmentForm::instantiate(array('assignee' => $assigneeId)); 1512 1513 $task->assign($form, $_errors); 1514 } 1515 1516 $task->onNewTask(); 1517 1518 Signal::send('task.created', $task); 1519 1520 return $task; 1521 } 1522 1523 function onNewTask($vars=array()) { 1524 global $cfg, $thisstaff; 1525 1526 if (!$cfg->alertONNewTask() // Check if alert is enabled 1527 || !($dept=$this->getDept()) 1528 || ($dept->isGroupMembershipEnabled() == Dept::ALERTS_DISABLED) 1529 || !($email=$cfg->getAlertEmail()) 1530 || !($tpl = $dept->getTemplate()) 1531 || !($msg=$tpl->getNewTaskAlertMsgTemplate()) 1532 ) { 1533 return; 1534 } 1535 1536 // Check if Dept recipients is Admin Only 1537 $adminOnly = ($dept->isGroupMembershipEnabled() == Dept::ALERTS_ADMIN_ONLY); 1538 1539 // Alert recipients 1540 $recipients = array(); 1541 1542 // Department Manager 1543 if ($cfg->alertDeptManagerONNewTask() 1544 && $dept->getManagerId() 1545 && !$adminOnly) 1546 $recipients[] = $dept->getManager(); 1547 1548 // Department Members 1549 if ($cfg->alertDeptMembersONNewTask() && !$adminOnly) 1550 foreach ($dept->getMembersForAlerts() as $M) 1551 $recipients[] = $M; 1552 1553 $options = array(); 1554 $staffId = $thisstaff ? $thisstaff->getId() : 0; 1555 1556 $msg = $this->replaceVars($msg->asArray(), $vars); 1557 1558 $sentlist=array(); 1559 foreach ($recipients as $k=>$staff) { 1560 if (!is_object($staff) 1561 // Don't bother vacationing staff. 1562 || !$staff->isAvailable() 1563 // No need to alert the poster! 1564 || $staffId == $staff->getId() 1565 // No duplicates. 1566 || isset($sentlist[$staff->getEmail()]) 1567 // Make sure staff has access to task 1568 || !$this->checkStaffPerm($staff) 1569 ) { 1570 continue; 1571 } 1572 $alert = $this->replaceVars($msg, array('recipient' => $staff)); 1573 $email->sendAlert($staff, $alert['subj'], $alert['body'], null, $options); 1574 $sentlist[$staff->getEmail()] = 1; 1575 } 1576 1577 // Alert Admin ONLY if not already a staff 1578 if ($cfg->alertAdminONNewTask() 1579 && !in_array($cfg->getAdminEmail(), $sentlist)) { 1580 $alert = $this->replaceVars($msg, array('recipient' => __('Admin'))); 1581 $email->sendAlert($cfg->getAdminEmail(), $alert['subj'], 1582 $alert['body'], null, $options); 1583 } 1584 } 1585 1586 function delete($comments='') { 1587 global $ost, $thisstaff; 1588 1589 $thread = $this->getThread(); 1590 1591 if (!parent::delete()) 1592 return false; 1593 1594 $thread->delete(); 1595 $this->logEvent('deleted'); 1596 Draft::deleteForNamespace('task.%.' . $this->getId()); 1597 1598 foreach (DynamicFormEntry::forObject($this->getId(), ObjectModel::OBJECT_TYPE_TASK) as $form) 1599 $form->delete(); 1600 1601 // Log delete 1602 $log = sprintf(__('Task #%1$s deleted by %2$s'), 1603 $this->getNumber(), 1604 $thisstaff ? $thisstaff->getName() : __('SYSTEM')); 1605 1606 if ($comments) 1607 $log .= sprintf('<hr>%s', $comments); 1608 1609 $ost->logDebug( 1610 sprintf( __('Task #%s deleted'), $this->getNumber()), 1611 $log); 1612 1613 return true; 1614 1615 } 1616 1617 static function __loadDefaultForm() { 1618 1619 require_once INCLUDE_DIR.'class.i18n.php'; 1620 1621 $i18n = new Internationalization(); 1622 $tpl = $i18n->getTemplate('form.yaml'); 1623 foreach ($tpl->getData() as $f) { 1624 if ($f['type'] == ObjectModel::OBJECT_TYPE_TASK) { 1625 $form = DynamicForm::create($f); 1626 $form->save(); 1627 break; 1628 } 1629 } 1630 } 1631 1632 /* Quick staff's stats */ 1633 static function getStaffStats($staff) { 1634 global $cfg; 1635 1636 /* Unknown or invalid staff */ 1637 if (!$staff 1638 || (!is_object($staff) && !($staff=Staff::lookup($staff))) 1639 || !$staff->isStaff()) 1640 return null; 1641 1642 $where = array('(task.staff_id='.db_input($staff->getId()) 1643 .sprintf(' AND task.flags & %d != 0 ', TaskModel::ISOPEN) 1644 .') '); 1645 $where2 = ''; 1646 1647 if(($teams=$staff->getTeams())) 1648 $where[] = ' ( task.team_id IN('.implode(',', db_input(array_filter($teams))) 1649 .') AND ' 1650 .sprintf('task.flags & %d != 0 ', TaskModel::ISOPEN) 1651 .')'; 1652 1653 if(!$staff->showAssignedOnly() && ($depts=$staff->getDepts())) //Staff with limited access just see Assigned tasks. 1654 $where[] = 'task.dept_id IN('.implode(',', db_input($depts)).') '; 1655 1656 $where = implode(' OR ', $where); 1657 if ($where) $where = 'AND ( '.$where.' ) '; 1658 1659 $sql = 'SELECT \'open\', count(task.id ) AS tasks ' 1660 .'FROM ' . TASK_TABLE . ' task ' 1661 . sprintf(' WHERE task.flags & %d != 0 ', TaskModel::ISOPEN) 1662 . $where . $where2 1663 1664 .'UNION SELECT \'overdue\', count( task.id ) AS tasks ' 1665 .'FROM ' . TASK_TABLE . ' task ' 1666 . sprintf(' WHERE task.flags & %d != 0 ', TaskModel::ISOPEN) 1667 . sprintf(' AND task.flags & %d != 0 ', TaskModel::ISOVERDUE) 1668 . $where 1669 1670 .'UNION SELECT \'assigned\', count( task.id ) AS tasks ' 1671 .'FROM ' . TASK_TABLE . ' task ' 1672 . sprintf(' WHERE task.flags & %d != 0 ', TaskModel::ISOPEN) 1673 .'AND task.staff_id = ' . db_input($staff->getId()) . ' ' 1674 . $where 1675 1676 .'UNION SELECT \'closed\', count( task.id ) AS tasks ' 1677 .'FROM ' . TASK_TABLE . ' task ' 1678 . sprintf(' WHERE task.flags & %d = 0 ', TaskModel::ISOPEN) 1679 . $where; 1680 1681 $res = db_query($sql); 1682 $stats = array(); 1683 while ($row = db_fetch_row($res)) 1684 $stats[$row[0]] = $row[1]; 1685 1686 return $stats; 1687 } 1688 1689 static function getAgentActions($agent, $options=array()) { 1690 if (!$agent) 1691 return; 1692 1693 require STAFFINC_DIR.'templates/tasks-actions.tmpl.php'; 1694 } 1695} 1696 1697 1698class TaskCData extends VerySimpleModel { 1699 static $meta = array( 1700 'pk' => array('task_id'), 1701 'table' => TASK_CDATA_TABLE, 1702 'joins' => array( 1703 'task' => array( 1704 'constraint' => array('task_id' => 'TaskModel.task_id'), 1705 ), 1706 ), 1707 ); 1708} 1709 1710 1711class TaskForm extends DynamicForm { 1712 static $instance; 1713 static $defaultForm; 1714 static $internalForm; 1715 1716 static $forms; 1717 1718 static $cdata = array( 1719 'table' => TASK_CDATA_TABLE, 1720 'object_id' => 'task_id', 1721 'object_type' => ObjectModel::OBJECT_TYPE_TASK, 1722 ); 1723 1724 static function objects() { 1725 $os = parent::objects(); 1726 return $os->filter(array('type'=>ObjectModel::OBJECT_TYPE_TASK)); 1727 } 1728 1729 static function getDefaultForm() { 1730 if (!isset(static::$defaultForm)) { 1731 if (($o = static::objects()) && $o[0]) 1732 static::$defaultForm = $o[0]; 1733 } 1734 1735 return static::$defaultForm; 1736 } 1737 1738 static function getInstance($object_id=0, $new=false) { 1739 if ($new || !isset(static::$instance)) 1740 static::$instance = static::getDefaultForm()->instanciate(); 1741 1742 static::$instance->object_type = ObjectModel::OBJECT_TYPE_TASK; 1743 1744 if ($object_id) 1745 static::$instance->object_id = $object_id; 1746 1747 return static::$instance; 1748 } 1749 1750 static function getInternalForm($source=null, $options=array()) { 1751 if (!isset(static::$internalForm)) 1752 static::$internalForm = new TaskInternalForm($source, $options); 1753 1754 return static::$internalForm; 1755 } 1756} 1757 1758class TaskInternalForm 1759extends AbstractForm { 1760 static $layout = 'GridFormLayout'; 1761 1762 function buildFields() { 1763 1764 $fields = array( 1765 'dept_id' => new DepartmentField(array( 1766 'id'=>1, 1767 'label' => __('Department'), 1768 'required' => true, 1769 'layout' => new GridFluidCell(6), 1770 )), 1771 'assignee' => new AssigneeField(array( 1772 'id'=>2, 1773 'label' => __('Assignee'), 1774 'required' => false, 1775 'layout' => new GridFluidCell(6), 1776 )), 1777 'duedate' => new DatetimeField(array( 1778 'id' => 3, 1779 'label' => __('Due Date'), 1780 'required' => false, 1781 'configuration' => array( 1782 'min' => Misc::gmtime(), 1783 'time' => true, 1784 'gmt' => false, 1785 'future' => true, 1786 ), 1787 )), 1788 1789 ); 1790 1791 $mode = @$this->options['mode']; 1792 if ($mode && $mode == 'edit') { 1793 unset($fields['dept_id']); 1794 unset($fields['assignee']); 1795 } 1796 1797 return $fields; 1798 } 1799} 1800 1801// Task thread class 1802class TaskThread extends ObjectThread { 1803 1804 function addDescription($vars, &$errors=array()) { 1805 1806 $vars['threadId'] = $this->getId(); 1807 if (!isset($vars['message']) && $vars['description']) 1808 $vars['message'] = $vars['description']; 1809 unset($vars['description']); 1810 return MessageThreadEntry::add($vars, $errors); 1811 } 1812 1813 static function create($task=false) { 1814 assert($task !== false); 1815 1816 $id = is_object($task) ? $task->getId() : $task; 1817 $thread = parent::create(array( 1818 'object_id' => $id, 1819 'object_type' => ObjectModel::OBJECT_TYPE_TASK 1820 )); 1821 if ($thread->save()) 1822 return $thread; 1823 } 1824 1825} 1826?> 1827