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