1<?php 2 3//if(!defined('DOKU_INC')) die(); 4 5//require_once 'entity.php'; 6 7namespace dokuwiki\plugin\bez\mdl; 8 9use dokuwiki\plugin\bez\meta\Mailer; 10use dokuwiki\plugin\bez\meta\PermissionDeniedException; 11use dokuwiki\plugin\bez\meta\ValidationException; 12 13class Thread extends Entity { 14 15 protected $id; 16 17 protected $original_poster, $coordinator, $closed_by; 18 19 protected $private, $lock; 20 21 protected $type, $state; 22 23 protected $create_date, $last_activity_date, $last_modification_date, $close_date; 24 25 protected $title, $content, $content_html; 26 27 protected $priority; 28 29 protected $task_count, $task_count_closed, $task_sum_cost; 30 31 /*new labels to add when object saved*/ 32 //protected $new_label_ids; 33 protected $labels; 34 35 public static function get_columns() { 36 return array('id', 37 'original_poster', 'coordinator', 'closed_by', 38 'private', 'lock', 39 'type', 'state', 40 'create_date', 'last_activity_date', 'last_modification_date', 'close_date', 41 'title', 'content', 'content_html', 42 'priority', 43 'task_count', 'task_count_closed', 'task_sum_cost'); 44 } 45 46 public static function get_select_columns() { 47 $cols = parent::get_select_columns(); 48 array_push($cols, 'label_id', 'label_name'); 49 return $cols; 50 } 51 52 public static function get_states() { 53 return array('proposal', 'opened', 'done', 'closed', 'rejected'); 54 } 55 56 57// private function state_string() { 58// if ($this->state === '2') { 59// return 'state_rejected'; 60// } else if ($this->coordinator === '-proposal') { 61// return 'state_proposal'; 62// } else if ( $this->state === '0' && 63// (int)$this->assigned_tasks_count > 0 && 64// (int)$this->opened_tasks_count === 0) { 65// return 'state_done'; 66// } else if ($this->state === '0') { 67// return 'state_opened'; 68// } else if ($this->state === '1') { 69// return 'state_closed'; 70// } 71// } 72// 73// private function type_string() { 74// if ($this->type === '') { 75// return ''; 76// } 77// $issuetype = $this->model->issuetypes->get_one($this->type)->get_assoc(); 78// return $issuetype[$this->model->conf['lang']]; 79// } 80// 81// private function priority() { 82// if ($this->state === '2') { 83// return '3'; 84// } 85// $min_pr = $this->model->tasks->min_priority(array('issue' => $this->id)); 86// if ($min_pr === NULL) { 87// return 'None'; 88// } 89// return $min_pr; 90// } 91 92 public function user_is_coordinator() { 93 if ($this->coordinator === $this->model->user_nick || 94 $this->model->acl->get_level() >= BEZ_AUTH_ADMIN) { 95 return true; 96 } 97 } 98 99 public function __construct($model, $defaults=array()) { 100 parent::__construct($model); 101 102 $this->validator->set_rules(array( 103 'coordinator' => array(array('dw_user'), 'NULL'), 104 'title' => array(array('length', 200), 'NOT NULL'), 105 'content' => array(array('length', 10000), 'NOT NULL') 106 )); 107 108// $this->validator->set_rules(array( 109// 'title' => array(array('length', 200), 'NOT NULL'), 110// 'description' => array(array('length', 10000), 'NOT NULL'), 111// 'state' => array(array('select', array('0', '1', '2')), 'NULL'), 112// 'opinion' => array(array('length', 10000), 'NOT NULL'), 113// 'type' => array(array('numeric'), 'NULL'), 114// 'coordinator' => array(array('dw_user'), 'NOT NULL'), 115// 'reporter' => array(array('dw_user'), 'NOT NULL'), 116// 'date' => array(array('unix_timestamp'), 'NOT NULL'), 117// 'last_mod' => array(array('unix_timestamp'), 'NULL'), 118// 'last_activity' => array(array('sqlite_datetime'), 'NOT NULL') 119// )); 120 121// $this->validator->set_rules(array( 122 //'coordinator' => array(array('dw_user'), 'NULL'), 123// 'original_poster' => array(array('dw_user'), 'NOT NULL'), 124// 'title' => array(array('length', 200), 'NOT NULL'), 125// 'content' => array(array('length', 10000), 'NOT NULL'), 126// 'state' => array(array('select', array('0', '1', '2')), 'NULL'), 127// 'opinion' => array(array('length', 10000), 'NOT NULL'), 128// 'type' => array(array('select'), 'NULL'), 129 130// 'create_date' => array(array('sqlite_datetime'), 'NOT NULL'), 131// 'last_mod' => array(array('sqlite_datetime'), 'NULL'), 132// 'last_activity' => array(array('sqlite_datetime'), 'NOT NULL') 133// )); 134 135 //we've created empty object (new record) 136 if ($this->id === NULL) { 137 $this->original_poster = $this->model->user_nick; 138 $this->create_date = date('c'); 139 $this->last_activity_date = $this->create_date; 140 $this->last_modification_date = $this->create_date; 141 142 $this->state = 'proposal'; 143 144// $this->close_date = ''; 145 146// $this->lock = '0'; 147// $this->private = '0'; 148// $this->type = '1';//type 1 - issue 149// $this->state = '0';//state 0 - proposal 150 151 //$this->update_last_activity(); 152 153 //$this->state = '0'; 154 155 if ($this->model->acl->get_level() >= BEZ_AUTH_LEADER) { 156// $this->validator->add_rule('cooridnator', array(array('dw_user'), 'NOT NULL')); 157 //throws ValidationException 158// $this->coordinator = $this->validator->validate_field('coordinator', $defaults['coordinator']); 159 if (!$this->model->userFactory->exists($defaults['coordinator'])) { 160 throw new ValidationException('thread', array('coordinator' => 'is_null')); 161 } 162 $this->coordinator = $defaults['coordinator']; 163 $this->state = 'opened'; 164 } 165// } else { 166// $this->coordinator = '-proposal'; 167// } 168 169 170// $this->add_participant($this->reporter); 171// $this->add_subscribent($this->reporter); 172// if ($this->coordinator !== '-proposal') { 173// $this->add_participant($this->coordinator); 174// $this->add_subscribent($this->coordinator); 175// } 176 177 } 178 //close_date required 179// if ($this->state !== 'state_proposal' && $this->state !== 'state_opened') { 180// $this->validator->set_rules(array( 181// 'close_date' => array(array('unix_timestamp'), 'NOT NULL') 182// )); 183// } 184 185 186// if ($this->participants !== NULL) { 187// $exp_part = explode(',', $this->participants); 188// foreach ($exp_part as $participant) { 189// $this->participants_array[$participant] = $participant; 190// } 191// } 192// 193// if ($this->subscribents !== NULL) { 194// $exp_part = explode(',', $this->subscribents); 195// foreach ($exp_part as $subscribent) { 196// $this->subscribents_array[$subscribent] = $subscribent; 197// } 198// } 199 } 200 201 public function set_data($data, $filter=NULL) { 202 $input = array('title', 'content', 'coordinator'); 203 $val_data = $this->validator->validate($data, $input); 204 205 if ($val_data === false) { 206 throw new ValidationException('thread', $this->validator->get_errors()); 207 } 208 209 210 //change coordinator at the end(!) 211 if (isset($val_data['coordinator'])) { 212 $val_coordinator = $val_data['coordinator']; 213 unset($val_data['coordinator']); 214 } 215 216 $this->set_property_array($val_data); 217 218 if (isset($val_coordinator)) { 219 $this->set_property('coordinator', $val_coordinator); 220 } 221 222 $this->content_html = p_render('xhtml',p_get_instructions($this->content), $ignore); 223 224 //update dates 225 $this->last_modification_date = date('c'); 226 $this->last_activity_date = $this->last_modification_date; 227 } 228 229 public function set_state($state) { 230 if ($this->acl_of('state') < BEZ_PERMISSION_CHANGE) { 231 throw new PermissionDeniedException(); 232 } 233 234 if (!in_array($state, array('opened', 'closed', 'rejected'))) { 235 throw new ValidationException('task', array('sholud be opened, closed or rejected')); 236 } 237 238 //nothing to do 239 if ($state == $this->state) { 240 return; 241 } 242 243 if ($state == 'closed' || $state == 'rejected') { 244 $this->model->sqlite->query("UPDATE {$this->get_table_name()} SET state=?, closed_by=?, close_date=? WHERE id=?", 245 $state, 246 $this->model->user_nick, 247 date('c'), 248 $this->id); 249 //reopen the task 250 } else { 251 $this->model->sqlite->query("UPDATE {$this->get_table_name()} SET state=? WHERE id=?", $state, $this->id); 252 } 253 254 $this->state = $state; 255 } 256 257 public function update_last_activity() { 258 $this->last_activity_date = date('c'); 259 $this->model->sqlite->query('UPDATE thread SET last_activity_date=? WHERE id=?', 260 $this->last_activity_date, $this->id); 261 } 262 263// public function set_labels($labels_ids = array()) { 264// 265// } 266 267// public function get_meta_fields() { 268// return array('reporter', 'date', 'last_mod', 'last_activity'); 269// } 270// 271// public function set_meta($post) { 272// 273// if (isset($post['date'])) { 274// $unix = strtotime($post['date']); 275// //if $unix === false validator will catch it 276// if ($unix !== false) { 277// $post['date'] = (string)$unix; 278// } 279// } 280// 281// if (isset($post['last_mod'])) { 282// $unix = strtotime($post['last_mod']); 283// //if $unix === false validator will catch it 284// if ($unix !== false) { 285// $post['last_mod'] = (string)$unix; 286// } 287// } 288// 289// parent::set_data($post, $this->get_meta_fields()); 290// } 291 292// public function update_cache() { 293// if ($this->model->acl->get_level() < BEZ_AUTH_ADMIN) { 294// return false; 295// } 296// $this->description_cache = $this->helper->wiki_parse($this->description); 297// $this->opinion_cache = $this->helper->wiki_parse($this->opinion); 298// } 299// 300// public function set_state($data) { 301// 302// $input = array('state', 'opinion'); 303// $val_data = $this->validator->validate($data, $input); 304// 305// if ($val_data === false) { 306// throw new ValidationException('issues', $this->validator->get_errors()); 307// } 308// 309// $this->set_property_array($val_data); 310// 311// if (count($this->validator->get_errors()) > 0) { 312// throw new ValidationException('issues', $this->validator->get_errors()); 313// } 314// 315// //update activity on state update 316// $this->last_mod = time(); 317// $this->update_last_activity(); 318// $this->opinion_cache = $this->helper->wiki_parse($this->opinion); 319// 320// //update virtuals 321// //$this->update_virtual_columns(); 322// } 323 324// public function update_last_activity() { 325// $this->last_activity = $this->sqlite_date(); 326// } 327 328 //private $participants; 329 public function get_participants($filter='') { 330 if ($this->acl_of('participants') < BEZ_PERMISSION_VIEW) { 331 throw new PermissionDeniedException(); 332 } 333 if ($this->id === NULL) { 334 return array(); 335 } 336 337 $sql = 'SELECT * FROM thread_participant WHERE'; 338 $possible_flags = array('original_poster', 'coordinator', 'commentator', 'task_assignee', 'subscribent'); 339 if ($filter != '') { 340 if (!in_array($filter, $possible_flags)) { 341 throw new \Exception("unknown flag $filter"); 342 } 343 $sql .= " $filter=1 AND"; 344 } 345 $sql .= ' thread_id=? ORDER BY user_id'; 346 347 $r = $this->model->sqlite->query($sql, $this->id); 348 $pars = $this->model->sqlite->res2arr($r); 349 $participants = array(); 350 foreach ($pars as $par) { 351 $participants[$par['user_id']] = $par; 352 } 353 354 return $participants; 355 } 356 357 public function get_participant($user_id) { 358 if ($this->acl_of('participants') < BEZ_PERMISSION_VIEW) { 359 throw new PermissionDeniedException(); 360 } 361 if ($this->id === NULL) { 362 return array(); 363 } 364 365 $r = $this->model->sqlite->query('SELECT * FROM thread_participant WHERE thread_id=? AND user_id=?', $this->id, $user_id); 366 $par = $this->model->sqlite->res2row($r); 367 if (!is_array($par)) { 368 return false; 369 } 370 371 return $par; 372 } 373 374 public function is_subscribent($user_id=null) { 375 if ($user_id == null) { 376 $user_id = $this->model->user_nick; 377 } 378 $par = $this->get_participant($user_id); 379 if ($par['subscribent'] == 1) { 380 return true; 381 } 382 return false; 383 } 384 385 public function remove_participant_flags($user_id, $flags) { 386 if ($this->acl_of('participants') < BEZ_PERMISSION_CHANGE) { 387 throw new PermissionDeniedException(); 388 } 389 390 //thread not saved yet 391 if ($this->id === NULL) { 392 throw new \Exception('cannot remove flags from not saved thread'); 393 } 394 395 $possible_flags = array('original_poster', 'coordinator', 'commentator', 'task_assignee', 'subscribent'); 396 if (array_intersect($flags, $possible_flags) != $flags) { 397 throw new \Exception('unknown flags'); 398 } 399 400 $set = implode(',', array_map(function ($v) { return "$v=0"; }, $flags)); 401 402 $sql = "UPDATE thread_participant SET $set WHERE thread_id=? AND user_id=?"; 403 $this->model->sqlite->query($sql, $this->id, $user_id); 404 405 } 406 407 public function set_participant_flags($user_id, $flags=array()) { 408 if ($this->acl_of('participants') < BEZ_PERMISSION_CHANGE) { 409 throw new PermissionDeniedException(); 410 } 411 412 //thread not saved yet 413 if ($this->id === NULL) { 414 throw new \Exception('cannot add flags to not saved thread'); 415 } 416 417 //validate user 418 if (!$this->model->userFactory->exists($user_id)) { 419 throw new \Exception("$user_id isn't dokuwiki user"); 420 } 421 422 $possible_flags = array('original_poster', 'coordinator', 'commentator', 'task_assignee', 'subscribent'); 423 if (array_intersect($flags, $possible_flags) != $flags) { 424 throw new \Exception('unknown flags'); 425 } 426 427 $participant = $this->get_participant($user_id); 428 if ($participant == false) { 429 $participant = array_fill_keys($possible_flags, 0); 430 431 $participant['thread_id'] = $this->id; 432 $participant['user_id'] = $user_id; 433 $participant['added_by'] = $this->model->user_nick; 434 $participant['added_date'] = date('c'); 435 } 436 $values = array_merge($participant, array_fill_keys($flags, 1)); 437 438 $keys = join(',', array_keys($values)); 439 $vals = join(',', array_fill(0,count($values),'?')); 440 441 $sql = "REPLACE INTO thread_participant ($keys) VALUES ($vals)"; 442 $this->model->sqlite->query($sql, array_values($values)); 443 444 445 446// if (! ( $this->user_is_coordinator() || 447// $participant === $this->model->user_nick || 448// $participant === $this->coordinator) //dodajemy nowego koordynatora 449// ) { 450// throw new PermissionDeniedException(); 451// } 452// if ($this->model->users->exists($participant)) { 453// $this->participants_array[$participant] = $participant; 454// $this->participants = implode(',', $this->participants_array); 455// } 456 } 457 458 459 public function invite($client) { 460 $this->set_participant_flags($client, array('subscribent')); 461 $this->mail_notify_invite($client); 462 } 463 464 public function get_labels() { 465 if ($this->acl_of('labels') < BEZ_PERMISSION_VIEW) { 466 throw new PermissionDeniedException(); 467 } 468 469 //record not saved 470 if ($this->id === NULL) { 471 return array(); 472 } 473 474 $labels = array(); 475 $r = $this->model->sqlite->query('SELECT * FROM label JOIN thread_label ON label.id = thread_label.label_id 476 WHERE thread_label.thread_id=?', $this->id); 477 $arr = $this->model->sqlite->res2arr($r); 478 foreach ($arr as $label) { 479 $labels[$label['id']] = $label; 480 } 481 482 return $labels; 483 } 484 485// public function get_label_id($name) { 486// $labels = $this->get_labels(); 487// 488// foreach ($labels as $label) { 489// if ($label['name'] == $name) { 490// return $label['label_id']; 491// } 492// } 493// return false; 494// } 495 496 public function add_label($label_id) { 497 if ($this->acl_of('labels') < BEZ_PERMISSION_CHANGE) { 498 throw new PermissionDeniedException(); 499 } 500 501 502 //issue not saved yet 503 if ($this->id === NULL) { 504 throw new \Exception('cannot add labels to not saved thread. use initial_save() instead'); 505 } 506 507 //label already assigned, nothing to do 508// if ($this->get_label_id($name)) return; 509 510 $r = $this->model->sqlite->query('SELECT id FROM label WHERE id=?', $label_id); 511 $label_id = $this->model->sqlite->res2single($r); 512 if (!$label_id) { 513 throw new \Exception("label($label_id) doesn't exist"); 514 } 515 516 517 $this->model->sqlite->storeEntry('thread_label', 518 array('thread_id' => $this->id, 519 'label_id' => $label_id)); 520 521 } 522 523 public function remove_label($label_id) { 524 if ($this->acl_of('labels') < BEZ_PERMISSION_CHANGE) { 525 throw new PermissionDeniedException(); 526 } 527 528 //issue not saved yet 529 if ($this->id === NULL) { 530 throw new \Exception('cannot remove labels from not saved thread. use initial_save() instead'); 531 } 532 533 /** @var \PDOStatement $r */ 534 $r = $this->model->sqlite->query('DELETE FROM thread_label WHERE thread_id=? AND label_id=?',$this->id, $label_id); 535 if ($r->rowCount() != 1) { 536 throw new \Exception('label was not assigned to this thread'); 537 } 538 539 } 540 541 public function get_causes() { 542 $r = $this->model->sqlite->query("SELECT id FROM thread_comment WHERE type LIKE 'cause_%' AND thread_id=?", 543 $this->id); 544 $arr = $this->model->sqlite->res2arr($r); 545 $causes = array(); 546 foreach ($arr as $cause) { 547 $causes[] = $cause['id']; 548 } 549 550 return $causes; 551 } 552 553 public function can_be_closed() { 554 $res = $this->model->sqlite->query("SELECT thread_comment.id FROM thread_comment 555 LEFT JOIN task ON thread_comment.id = task.thread_comment_id 556 WHERE thread_comment.thread_id = ? AND 557 thread_comment.type LIKE 'cause_%' AND task.id IS NULL", $this->id); 558 559 $causes_without_tasks = $this->model->sqlite->res2row($res) ? true : false; 560 return $this->state == 'opened' && 561 ($this->task_count - $this->task_count_closed == 0) && 562 $this->state != 'proposal' && 563 ! $causes_without_tasks && 564 $this->task_count > 0; 565 566 } 567 568 public function can_be_rejected() { 569 return $this->state == 'opened' && $this->task_count == 0; 570 } 571 572 public function closing_comment() { 573 $r = $this->model->thread_commentFactory->get_from_thread($this, array(), 'id', true, 1); 574 $thread_comment = $r->fetch(); 575 576 return $thread_comment->content_html; 577 } 578 579// public function remove_label($name) { 580// if ($this->acl_of('labels') < BEZ_PERMISSION_CHANGE) { 581// throw new PermissionDeniedException(); 582// } 583// //label not assigned 584// $label_id = $this->get_label($name); 585// 586// if ($label_id === false) { 587// throw new \Exception('label don not exists'); 588// } 589// 590// $this->model->sqlite->query('DELETE FROM thread_label WHERE thread_id=?, label_id=?', $this->id, $label_id); 591// } 592 593// public function add_subscribent($subscribent) { 594// if (! ( $this->user_is_coordinator() || 595// $subscribent === $this->model->user_nick || 596// $subscribent === $this->coordinator) //dodajemy nowego koordynatora) 597// ) { 598// throw new PermissionDeniedException(); 599// } 600// 601// if ($this->model->users->exists($subscribent) && 602// !in_array($subscribent, $this->subscribents_array)) { 603// $this->subscribents_array[$subscribent] = $subscribent; 604// $this->subscribents = implode(',', $this->subscribents_array); 605// return true; 606// } 607// return false; 608// } 609// 610// public function remove_subscribent($subscribent) { 611// if (! ( $this->user_is_coordinator() || 612// $subscribent === $this->model->user_nick) 613// ) { 614// throw new PermissionDeniedException(); 615// } 616// unset($this->subscribents_array[$subscribent]); 617// $this->subscribents = implode(',', $this->subscribents_array); 618// } 619// 620// public function get_subscribents() { 621// return $this->subscribents_array; 622// } 623 624// public function get_participants() { 625// $full_names = []; 626// 627// $involved = array_merge($this->subscribents_array, $this->participants_array); 628// foreach ($involved as $par) { 629// $name = $this->model->users->get_user_full_name($par); 630// if ($name == '') { 631// $full_names[$par] = $par; 632// } else { 633// $full_names[$par] = $name; 634// } 635// } 636// //coordinator on top 637// uksort($full_names, function ($a, $b) use($full_names) { 638// if ($a === $this->coordinator) { 639// return -1; 640// } else if ($b === $this->coordinator) { 641// return 1; 642// } 643// return $full_names[$a] > $full_names[$b]; 644// }); 645// 646// return $full_names; 647// } 648 649// public function is_subscribent($user=NULL) { 650// if ($user === NULL) { 651// $user = $this->model->user_nick; 652// } 653// if (in_array($user, $this->subscribents_array)) { 654// return true; 655// } 656// return false; 657// } 658// 659// public function is_task_executor($user=NULL) { 660// if ($user === NULL) { 661// $user = $this->model->user_nick; 662// } 663// $sth = $this->model->db->prepare('SELECT COUNT(*) FROM tasks 664// WHERE issue=:issue AND executor=:executor'); 665// $sth->execute(array(':issue' => $this->id, ':executor' => $user)); 666// $fetch = $sth->fetch(); 667// if ($fetch[0] === '0') { 668// return false; 669// } else { 670// return true; 671// } 672// } 673// 674// public function is_commentator($user=NULL) { 675// if ($user === NULL) { 676// $user = $this->model->user_nick; 677// } 678// $sth = $this->model->db->prepare('SELECT COUNT(*) FROM commcauses 679// WHERE issue=:issue AND reporter=:reporter'); 680// $sth->execute(array(':issue' => $this->id, ':reporter' => $user)); 681// $fetch = $sth->fetch(); 682// if ($fetch[0] === '0') { 683// return false; 684// } else { 685// return true; 686// } 687// } 688// 689// private $causes_without_tasks = -1; 690// public function causes_without_tasks_count() { 691// if ($this->causes_without_tasks === -1) { 692// $sth = $this->model->db->prepare('SELECT COUNT(*) FROM 693// (SELECT tasks.id 694// FROM commcauses LEFT JOIN tasks ON commcauses.id = tasks.cause 695// WHERE commcauses.type > 0 AND commcauses.issue = ? 696// GROUP BY commcauses.id) 697// WHERE id IS NULL'); 698// $sth->execute(array($this->id)); 699// $count = $sth->fetchColumn(); 700// 701// $this->causes_without_tasks = (int)$count; 702// } 703// return $this->causes_without_tasks; 704// } 705 706 //http://data.agaric.com/capture-all-sent-mail-locally-postfix 707 //https://askubuntu.com/questions/192572/how-do-i-read-local-email-in-thunderbird 708 public function mail_notify($replacements=array(), $users=false) { 709 $plain = io_readFile($this->model->action->localFN('thread-notification')); 710 $html = io_readFile($this->model->action->localFN('thread-notification', 'html')); 711 712 $thread_reps = array( 713 'thread_id' => $this->id, 714 'thread_link' => $this->model->action->url('thread', 'id', $this->id), 715 'thread_unsubscribe' => 716 $this->model->action->url('thread', 'id', $this->id, 'action', 'unsubscribe'), 717 'custom_content' => false, 718 'action_border_color' => 'transparent', 719 'action_color' => 'transparent', 720 ); 721 722 //$replacements can override $issue_reps 723 $rep = array_merge($thread_reps, $replacements); 724 //auto title 725 if (!isset($rep['subject'])) { 726 $rep['subject'] = '#'.$this->id. ' ' .$this->title; 727 } 728 if (!isset($rep['content_html'])) { 729 $rep['content_html'] = $rep['content']; 730 } 731 if (!isset($rep['who_full_name'])) { 732 $rep['who_full_name'] = 733 $this->model->userFactory->get_user_full_name($rep['who']); 734 } 735 736 //format when 737 $rep['when'] = dformat(strtotime($rep['when']), '%Y-%m-%d %H:%M'); 738 739 if ($rep['custom_content'] === false) { 740 $html = str_replace('@CONTENT_HTML@', ' 741 <div style="margin: 5px 0;"> 742 <strong>@WHO_FULL_NAME@</strong> <br> 743 <span style="color: #888">@WHEN@</span> 744 </div> 745 @CONTENT_HTML@ 746 ', $html); 747 } 748 749 //we must do it manually becouse Mailer uses htmlspecialchars() 750 $html = str_replace('@CONTENT_HTML@', $rep['content_html'], $html); 751 752 $mailer = new Mailer(); 753 $mailer->setBody($plain, $rep, $rep, $html, false); 754 755 if ($users == FALSE) { 756 $users = array_map(function($par) { 757 return $par['user_id']; 758 }, $this->get_participants('subscribent')); 759 760 //don't notify myself 761 unset($users[$this->model->user_nick]); 762 } 763 764 $emails = array_map(function($user_id) { 765 return $this->model->userFactory->get_user_email($user_id); 766 }, $users); 767 768 769 $mailer->to($emails); 770 $mailer->subject($rep['subject']); 771 772 $send = $mailer->send(); 773 if ($send === false) { 774 //this may mean empty $emails 775 //throw new Exception("can't send email"); 776 } 777 } 778 779 protected function mail_issue_box_reps($replacements=array()) { 780 $replacements['custom_content'] = true; 781 782 $html = '<h2 style="font-size: 1.2em;">'; 783 $html .= '<a style="font-size:115%" href="@THREAD_LINK@">#@THREAD_ID@</a> '; 784 785 if ( ! empty($this->type_string)) { 786 $html .= $this->type_string; 787 } else { 788 $html .= '<i style="color: #777"> '. 789 $this->model->action->getLang('issue_type_no_specified'). 790 '</i>'; 791 } 792 793 $html .= ' ('. $this->model->action->getLang('state_' . $this->state ) .') '; 794 795 $html .= '<span style="color: #777; font-weight: normal; font-size: 90%;">'; 796 $html .= $this->model->action->getLang('coordinator') . ': '; 797 $html .= '<span style="font-weight: bold;">'; 798 799 if ($this->state == 'proposal') { 800 $html .= '<i style="font-weight: normal;">' . 801 $this->model->action->getLang('proposal') . 802 '</i>'; 803 } else { 804 $html .= $this->model->userFactory->get_user_full_name($this->coordinator); 805 } 806 $html .= '</span></span></h2>'; 807 808 $html .= '<h2 style="font-size: 1.2em;border-bottom: 1px solid @ACTION_BORDER_COLOR@">' . $this->title . '</h2>'; 809 810 $html .= $this->content_html; 811 812// if ($this->state !== '0') { 813// $html .= '<h3 style="font-size:100%; border-bottom: 1px dotted #bbb">'; 814// if ($this->state === '1') { 815// $html .= $this->model->action->getLang('opinion'); 816// } else { 817// $html .= $this->model->action->getLang('reason'); 818// } 819// $html .= '</h3>'; 820// $html .= $this->opinion_cache; 821// } 822 823 $replacements['content_html'] = $html; 824 825 826 switch ($this->priority) { 827 case '0': 828 $replacements['action_color'] = '#F8E8E8'; 829 $replacements['action_border_color'] = '#F0AFAD'; 830 break; 831 case '1': 832 $replacements['action_color'] = '#ffd'; 833 $replacements['action_border_color'] = '#dd9'; 834 break; 835 case '2': 836 $replacements['action_color'] = '#EEF6F0'; 837 $replacements['action_border_color'] = '#B0D2B6'; 838 break; 839 case 'None': 840 $replacements['action_color'] = '#e7f1ff'; 841 $replacements['action_border_color'] = '#a3c8ff'; 842 break; 843 default: 844 $replacements['action_color'] = '#fff'; 845 $replacements['action_border_color'] = '#bbb'; 846 break; 847 } 848 849 return $replacements; 850 } 851 852 public function mail_notify_change_state() { 853 $this->mail_notify($this->mail_issue_box_reps(array( 854 'who' => $this->model->user_nick, 855 'action' => $this->model->action->getLang('mail_mail_notify_change_state_action'), 856 //'subject' => $this->model->action->getLang('mail_mail_notify_change_state_subject') . ' #'.$this->id 857 ))); 858 } 859 860 public function mail_notify_invite($client) { 861 $this->mail_notify($this->mail_issue_box_reps(array( 862 'who' => $this->model->user_nick, 863 'action' => $this->model->action->getLang('mail_mail_notify_invite_action'), 864 //'subject' => $this->model->action->getLang('mail_mail_notify_invite_subject') . ' #'.$this->id 865 )), array($client)); 866 } 867 868 public function mail_inform_coordinator() { 869 $this->mail_notify($this->mail_issue_box_reps(array( 870 'who' => $this->model->user_nick, 871 'action' => $this->model->action->getLang('mail_mail_inform_coordinator_action'), 872 //'subject' => $this->model->action->getLang('mail_mail_inform_coordinator_subject') . ' #'.$this->id 873 )), array($this->coordinator)); 874 } 875 876 public function mail_notify_issue_inactive($users=false) { 877 $this->mail_notify($this->mail_issue_box_reps(array( 878 'who' => '', 879 'action' => $this->model->action->getLang('mail_mail_notify_issue_inactive'), 880 )), $users); 881 } 882 883} 884