xref: /plugin/bez/mdl/Task.php (revision 16c7b168a60daa2c9b9ddcfa51e05d123a2a17dc)
1<?php
2
3namespace dokuwiki\plugin\bez\mdl;
4
5use dokuwiki\plugin\bez\meta\Mailer;
6use dokuwiki\plugin\bez\meta\PermissionDeniedException;
7use dokuwiki\plugin\bez\meta\ValidationException;
8
9class Task extends Entity {
10
11    protected $id;
12
13	protected $original_poster, $assignee, $closed_by;
14
15	protected $private, $lock;
16
17	protected $state, $type;
18
19	protected $create_date, $last_activity_date, $last_modification_date, $close_date;
20
21	protected $cost, $plan_date, $all_day_event, $start_time, $finish_time;
22
23	protected $content, $content_html;
24
25	protected $thread_id, $thread_comment_id, $task_program_id;
26
27	/** @var \dokuwiki\plugin\bez\mdl\Thread */
28	protected $thread;
29
30	/** @var Thread_comment */
31	protected $thread_comment;
32
33	//virtual
34    protected $task_program_name, $priority, $coordinator;
35
36	public static function get_columns() {
37		return array('id',
38            'original_poster', 'assignee', 'closed_by',
39            'private', 'lock',
40            'state', 'type',
41            'create_date', 'last_activity_date', 'last_modification_date', 'close_date',
42            'cost', 'plan_date', 'all_day_event', 'start_time', 'finish_time',
43            'content', 'content_html',
44            'thread_id', 'thread_comment_id', 'task_program_id');
45	}
46
47	public static function get_types() {
48	    return array('correction', 'corrective', 'preventive', 'program');
49    }
50
51    public static function get_states() {
52        return array('opened', 'done');
53    }
54
55    public function __get($property) {
56        if ($property == 'thread') {
57            if ($this->thread_id == null) {
58                return null;
59            }
60            if ($this->thread == null) {
61                $this->thread = $this->model->threadFactory->get_one($this->thread_id);
62            }
63            return $this->thread;
64
65        } elseif($property == 'thread_comment') {
66            if ($this->thread_comment_id == null) {
67                return null;
68            }
69            if ($this->thread_comment == null) {
70                $this->thread = $this->model->thread_commentFactory->get_one($this->thread_comment_id);
71            }
72            return $this->thread_comment;
73
74        } elseif($property == 'priority' || $property == 'coordinator' || $property == 'task_program_name') {
75            return $this->$property;
76        }
77        return parent::__get($property);
78    }
79
80	public function __construct($model, $defaults=array()) {
81		parent::__construct($model, $defaults);
82
83		$this->validator->set_rules(array(
84            'assignee' => array(array('dw_user'), 'NOT NULL'),
85            'cost' => array(array('numeric'), 'NULL'),
86			'plan_date' => array(array('iso_date'), 'NOT NULL'),
87			'all_day_event' => array(array('select', array('0', '1')), 'NOT NULL'),
88			'start_time' => array(array('time'), 'NULL'),
89			'finish_time' => array(array('time'), 'NULL'),
90            'content' => array(array('length', 10000), 'NOT NULL'),
91            'thread_comment_id' => array(array('numeric'), 'NULL'),
92            'task_program_id' => array(array('numeric'), 'NULL')
93		));
94
95		//we've created empty object
96		if ($this->id === NULL) {
97            $this->original_poster = $this->model->user_nick;
98            $this->create_date = date('c');
99            $this->last_activity_date = $this->create_date;
100            $this->last_modification_date = $this->create_date;
101
102            $this->state = 'opened';
103
104            if (isset($defaults['thread'])) {
105                $this->thread = $defaults['thread'];
106                $this->thread_id = $this->thread->id;
107                $this->type = 'correction';
108
109                if (isset($defaults['thread_comment'])) {
110                    $this->thread_comment = $defaults['thread_comment'];
111                    $this->thread_comment_id = $this->thread_comment->id;
112
113                    if ($this->thread_comment->type == 'cause_real') {
114                        $this->type = 'corrective';
115                    } else {
116                        $this->type = 'preventive';
117                    }
118                }
119            } else {
120                $this->type = 'program';
121            }
122        //we get object form db
123		} else {
124
125            if (isset($defaults['thread']) && $this->thread_id == $defaults['thread']->id) {
126                $this->thread = $defaults['thread'];
127            }
128
129            if (isset($defaults['thread_comment']) && $this->thread_comment_id == $defaults['thread_comment']->id) {
130                $this->thread_comment = $defaults['thread_comment'];
131            }
132
133        }
134
135		if ($this->thread_id == '') {
136			$this->validator->set_rules(array(
137				'task_program_id' => array(array('numeric'), 'NOT NULL'),
138			));
139		    //this field is unused in program tasks
140            $this->validator->delete_rule('thread_comment_id');
141        }
142	}
143
144
145	public function set_data($post, $filter=NULL) {
146        parent::set_data($post);
147
148        $this->content_html = p_render('xhtml',p_get_instructions($this->content), $ignore);
149
150        //update dates
151        $this->last_modification_date = date('c');
152        $this->last_activity_date = $this->last_modification_date;
153
154        //all day event
155        if (!isset($post['all_day_event'])) {
156            $post['all_day_event'] = '0';
157        }
158
159		return true;
160	}
161
162    public function set_state($state) {
163	    if ($this->acl_of('state') < BEZ_PERMISSION_CHANGE) {
164	        throw new PermissionDeniedException();
165        }
166
167        if (!in_array($state, array('opened', 'done'))) {
168	        throw new ValidationException('task', array('sholud be opened or done'));
169        }
170
171        //nothing to do
172        if ($state == $this->state) {
173	        return;
174        }
175
176        if ($state == 'done') {
177            $this->model->sqlite->query("UPDATE {$this->get_table_name()} SET state=?, closed_by=?, close_date=? WHERE id=?",
178                $state,
179                $this->model->user_nick,
180                date('c'),
181                $this->id);
182        //reopen the task
183        } else {
184            $this->model->sqlite->query("UPDATE {$this->get_table_name()} SET state=? WHERE id=?", $state, $this->id);
185        }
186
187        $this->state = $state;
188    }
189
190    public function update_last_activity() {
191        $this->last_activity_date = date('c');
192        $this->model->sqlite->query('UPDATE task SET last_activity_date=? WHERE id=?',
193                                    $this->last_activity_date, $this->id);
194    }
195
196    public function get_participants($filter='') {
197        if ($this->acl_of('participants') < BEZ_PERMISSION_VIEW) {
198            throw new PermissionDeniedException();
199        }
200        if ($this->id === NULL) {
201            return array();
202        }
203
204        $sql = 'SELECT * FROM task_participant WHERE';
205        $possible_flags = array('original_poster', 'assignee', 'commentator', 'subscribent');
206        if ($filter != '') {
207            if (!in_array($filter, $possible_flags)) {
208                throw new \Exception("unknown flag $filter");
209            }
210            $sql .= " $filter=1 AND";
211        }
212        $sql .= ' task_id=? ORDER BY user_id';
213
214        $r = $this->model->sqlite->query($sql, $this->id);
215        $pars = $this->model->sqlite->res2arr($r);
216        $participants = array();
217        foreach ($pars as $par) {
218            $participants[$par['user_id']] = $par;
219        }
220
221        return $participants;
222    }
223
224    public function get_participant($user_id) {
225        if ($this->acl_of('participants') < BEZ_PERMISSION_VIEW) {
226            throw new PermissionDeniedException();
227        }
228        if ($this->id === NULL) {
229            return array();
230        }
231
232        $r = $this->model->sqlite->query('SELECT * FROM task_participant WHERE task_id=? AND user_id=?', $this->id, $user_id);
233        $par = $this->model->sqlite->res2row($r);
234        if (!is_array($par)) {
235            return false;
236        }
237
238        return $par;
239    }
240
241    public function is_subscribent($user_id=null) {
242        if ($user_id == null) {
243            $user_id = $this->model->user_nick;
244        }
245        $par = $this->get_participant($user_id);
246        if ($par['subscribent'] == 1) {
247            return true;
248        }
249        return false;
250    }
251
252    public function remove_participant_flags($user_id, $flags) {
253        if ($this->acl_of('participants') < BEZ_PERMISSION_CHANGE) {
254            throw new PermissionDeniedException();
255        }
256
257        //thread not saved yet
258        if ($this->id === NULL) {
259            throw new \Exception('cannot remove flags from not saved thread');
260        }
261
262        $possible_flags = array('original_poster', 'assignee', 'commentator', 'subscribent');
263        if (array_intersect($flags, $possible_flags) != $flags) {
264            throw new \Exception('unknown flags');
265        }
266
267        $set = implode(',', array_map(function ($v) { return "$v=0"; }, $flags));
268
269        $sql = "UPDATE task_participant SET $set WHERE task_id=? AND user_id=?";
270        $this->model->sqlite->query($sql, $this->id, $user_id);
271
272    }
273
274    public function set_participant_flags($user_id, $flags=array()) {
275        if ($this->acl_of('participants') < BEZ_PERMISSION_CHANGE) {
276            throw new PermissionDeniedException();
277        }
278
279        //thread not saved yet
280        if ($this->id === NULL) {
281            throw new \Exception('cannot add flags to not saved thread');
282        }
283
284        //validate user
285        if (!$this->model->userFactory->exists($user_id)) {
286            throw new \Exception("$user_id isn't dokuwiki user");
287        }
288
289        $possible_flags = array('original_poster', 'assignee', 'commentator', 'subscribent');
290        if (array_intersect($flags, $possible_flags) != $flags) {
291            throw new \Exception('unknown flags');
292        }
293
294        $participant = $this->get_participant($user_id);
295        if ($participant == false) {
296            $participant = array_fill_keys($possible_flags, 0);
297
298            $participant['task_id'] = $this->id;
299            $participant['user_id'] = $user_id;
300            $participant['added_by'] = $this->model->user_nick;
301            $participant['added_date'] = date('c');
302        }
303        $values = array_merge($participant, array_fill_keys($flags, 1));
304
305        $keys = join(',', array_keys($values));
306        $vals = join(',', array_fill(0,count($values),'?'));
307
308        $sql = "REPLACE INTO task_participant ($keys) VALUES ($vals)";
309        $this->model->sqlite->query($sql, array_values($values));
310    }
311
312    public function invite($client) {
313        $this->set_participant_flags($client, array('subscribent'));
314        $this->mail_notify_invite($client);
315    }
316
317    private function mail_notify($replacements=array(), $users=false) {
318        $plain = io_readFile($this->model->action->localFN('task-notification'));
319        $html = io_readFile($this->model->action->localFN('task-notification', 'html'));
320
321        $task_link = $this->model->action->url('task', 'tid', $this->id);
322
323        $reps = array(
324                        'task_id' => $this->id,
325                        'task_link' => $task_link,
326                        'who' => $this->original_poster
327                     );
328
329        //$replacements can override $reps
330        $rep = array_merge($reps, $replacements);
331
332        if (!isset($rep['who_full_name'])) {
333            $rep['who_full_name'] =
334                $this->model->userFactory->get_user_full_name($rep['who']);
335        }
336
337        //auto title
338        if (!isset($rep['subject'])) {
339//            if (isset($rep['content'])) {
340//                $rep['subject'] =  array_shift(explode('.', $rep['content'], 2));
341//            }
342            $rep['subject'] = '#z'.$this->id. ' ' . $this->task_program_name;
343        }
344
345        //we must do it manually becouse Mailer uses htmlspecialchars()
346        $html = str_replace('@TASK_TABLE@', $rep['task_table'], $html);
347
348        $mailer = new Mailer();
349        $mailer->setBody($plain, $rep, $rep, $html, false);
350
351        if ($users === FALSE) {
352            $users = $this->get_participants('subscribent');
353
354            //don't notify current user
355            unset($users[$this->model->user_nick]);
356        }
357
358        $emails = array_map(function($user) {
359            return $this->model->userFactory->get_user_email($user);
360        }, $users);
361
362        $mailer->to($emails);
363        $mailer->subject($rep['subject']);
364
365        $send = $mailer->send();
366        if ($send === false) {
367            //this may mean empty $emails
368            //throw new Exception("can't send email");
369        }
370    }
371
372    protected function bez_html_array_to_style_list($arr) {
373        $output = '';
374        foreach ($arr as $k => $v) {
375            $output .= $k.': '. $v . ';';
376        }
377        return $output;
378    }
379
380    protected function bez_html_irrtable($style) {
381        $argv = func_get_args();
382        $argc = func_num_args();
383        if (isset($style['table'])) {
384            $output = '<table style="'.self::bez_html_array_to_style_list($style['table']).'">';
385        } else {
386            $output = '<table>';
387        }
388
389        $tr_style  = '';
390        if (isset($style['tr'])) {
391            $tr_style = 'style="'.self::bez_html_array_to_style_list($style['tr']).'"';
392        }
393
394        $td_style  = '';
395        if (isset($style['td'])) {
396            $td_style = 'style="'.self::bez_html_array_to_style_list($style['td']).'"';
397        }
398
399        $row_max = 0;
400
401        for ($i = 1; $i < $argc; $i++) {
402            $row = $argv[$i];
403            $c = count($row);
404            if ($c > $row_max) {
405                $row_max = $c;
406            }
407        }
408
409        for ($j = 1; $j < $argc; $j++) {
410            $row = $argv[$j];
411            $output .= '<tr '.$tr_style.'>' . NL;
412            $c = count($row);
413            for ($i = 0; $i < $c; $i++) {
414                //last element
415                if ($i === $c - 1 && $c < $row_max) {
416                    $output .= '<td '.$td_style.' colspan="' . ( $row_max - $c + 1 ) . '">' . NL;
417                } else {
418                    $output .= '<td '.$td_style.'>' . NL;
419                }
420                $output .= $row[$i] . NL;
421                $output .= '</td>' . NL;
422            }
423            $output .= '</tr>' . NL;
424        }
425        $output .= '</table>' . NL;
426        return $output;
427    }
428
429    public function mail_notify_task_box($users=false, $replacements=array()) {
430       $top_row = array(
431            '<strong>'.$this->model->action->getLang('executor').': </strong>' .
432            $this->model->userFactory->get_user_full_name($this->assignee),
433
434            '<strong>'.$this->model->action->getLang('reporter').': </strong>' .
435            $this->model->userFactory->get_user_full_name($this->original_poster)
436        );
437
438        if ($this->task_program_name != '') {
439            $top_row[] =
440                '<strong>'.$this->model->action->getLang('task_type').': </strong>' .
441                $this->task_program_name;
442        }
443
444        if ($this->cost != '') {
445            $top_row[] =
446                '<strong>'.$this->model->action->getLang('cost').': </strong>' .
447                $this->cost;
448        }
449
450        //BOTTOM ROW
451        $bottom_row = array(
452            '<strong>'.$this->model->action->getLang('plan_date').': </strong>' .
453            $this->plan_date
454        );
455
456        if ($this->all_day_event == '0') {
457            $bottom_row[] =
458                '<strong>'.$this->model->action->getLang('start_time').': </strong>' .
459                $this->start_time;
460            $bottom_row[] =
461                '<strong>'.$this->model->action->getLang('finish_time').': </strong>' .
462                $this->finish_time;
463        }
464
465        $rep = array(
466            'content' => $this->content,
467            'content_html' =>
468                '<h2 style="font-size: 1.2em;">'.
469	               '<a href="'.$this->model->action->url('task', 'tid', $this->id).'">' .
470		              '#z'.$this->id .
471	               '</a> ' .
472	lcfirst($this->model->action->getLang('task_type_' . $this->type)) . ' ' .
473    '(' .
474        lcfirst($this->model->action->getLang('task_' . $this->state)) .
475    ')' .
476                '</h2>' .
477                self::bez_html_irrtable(array(
478                    'table' => array(
479                        'border-collapse' => 'collapse',
480                        'font-size' => '0.8em',
481                        'width' => '100%'
482                    ),
483                    'td' => array(
484                        'border-top' => '1px solid #8bbcbc',
485                        'border-bottom' => '1px solid #8bbcbc',
486                        'padding' => '.3em .5em'
487                    )
488                ), $top_row, $bottom_row) . $this->content_html,
489            'who' => $this->model->user_nick,
490            'when' => $this->create_date,
491            'custom_content' => true
492        );
493
494        $rep['action_color'] = '#e4f4f4';
495        $rep['action_border_color'] = '#8bbcbc';
496
497        //$replacements can override $reps
498        $rep = array_merge($rep, $replacements);
499
500//        if ($this->thread == NULL) {
501//            $this->mail_notify($rep, $users);
502//        } else {
503//            $this->thread->mail_notify($rep);
504//        }
505        $this->mail_notify($rep, $users);
506    }
507
508    public function mail_notify_subscribents($replacements=array()) {
509        $this->mail_notify_task_box(false, $replacements);
510    }
511
512    public function mail_notify_add($users=false, $replacements=array()) {
513        $replacements['action'] = $this->model->action->getLang('mail_task_added');
514        $this->mail_notify_task_box($users, $replacements);
515    }
516
517    public function mail_notify_remind($users=false) {
518        $replacements = array();
519
520        $replacements['action'] = $this->model->action->getLang('mail_task_remind');
521        //we don't want any who
522        $replacements['who_full_name'] = '';
523
524        //$users = array($this->executor);
525        $this->mail_notify_task_box($users, $replacements);
526    }
527
528    public function mail_notify_invite($client) {
529        $replacements = array();
530
531        $replacements['action'] = $this->model->action->getLang('mail_task_invite');
532
533        $users = array($client);
534        $this->mail_notify_task_box($users, $replacements);
535    }
536}
537