r1473: Because we didn't understand the return val of eval() all our notifications...
[bugdar.git] / includes / class_notification.php
1 <?php
2 /*=====================================================================*\
3 || ###################################################################
4 || # Bugdar [#]version[#]
5 || # Copyright ©2002-[#]year[#] Blue Static
6 || #
7 || # This program is free software; you can redistribute it and/or modify
8 || # it under the terms of the GNU General Public License as published by
9 || # the Free Software Foundation; version [#]gpl[#] of the License.
10 || #
11 || # This program is distributed in the hope that it will be useful, but
12 || # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 || # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 || # more details.
15 || #
16 || # You should have received a copy of the GNU General Public License along
17 || # with this program; if not, write to the Free Software Foundation, Inc.,
18 || # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
19 || ###################################################################
20 \*=====================================================================*/
21
22 /**
23 * Notification Center
24 *
25 * This class determines which emails need to be sent out based on user
26 * options and bug changes, and then it sends said emails.
27 *
28 * @author Blue Static
29 * @copyright Copyright ©2002 - [#]year[#], Blue Static
30 * @version $Revision$
31 * @package Bugdar
32 *
33 */
34 class NotificationCenter
35 {
36 /**
37 * Bug information
38 * @var array
39 * @access private
40 */
41 var $bug = array();
42
43 /**
44 * Original bug data
45 * @var array
46 * @access private
47 */
48 var $original = array();
49
50 /**
51 * Modified bug data
52 * @var array
53 * @access private
54 */
55 var $modified = array();
56
57 /**
58 * Global bugsys registry
59 * @var object
60 * @access private
61 */
62 var $registry = null;
63
64 /**
65 * Role list: a list of user IDs with their relations to the bug
66 * @var array
67 * @access private
68 */
69 var $roles = array(
70 '-notapplicable-' => array(),
71 'reporter' => array(),
72 'assignee' => array(),
73 'favorite' => array(),
74 'voter' => array(),
75 'commenter' => array()
76 );
77
78 /**
79 * User cache list
80 * @var array
81 * @access private
82 */
83 var $users = array();
84
85 /**
86 * A list of notices per-user that are combined together in NotificationCenter::finalize()
87 * @var array
88 * @access private
89 */
90 var $notices = array();
91
92 // ###################################################################
93 /**
94 * Constructor: set database objects
95 *
96 * @access public
97 */
98 function __construct()
99 {
100 global $bugsys;
101
102 $this->registry =& $bugsys;
103 }
104
105 // ###################################################################
106 /**
107 * (PHP 4) Constructor
108 *
109 * @access public
110 */
111 function NotificationCenter()
112 {
113 $this->__construct();
114 }
115
116 // ###################################################################
117 /**
118 * Sets the bug data so that all methods in this class have access to
119 * it when sending emails.
120 *
121 * @access public
122 *
123 * @param array Original bug data
124 * @param array Modified bug data
125 */
126 function set_bug_data($original, $modified = array())
127 {
128 if (sizeof($modified) > 0)
129 {
130 $this->bug = $modified;
131 }
132 else
133 {
134 $this->bug = $original;
135 }
136
137 $this->original = $original;
138 $this->modified = $modified;
139
140 $this->roles['-notapplicable-'] = (sizeof($modified) > 0 ? array($original['assignedto'], $modified['assignedto']) : array($original['assignedto']));
141 $this->roles['reporter'] = array($original['userid']);
142 $this->roles['assignee'] = (sizeof($modified) > 0 ? array($modified['assignedto']) : array($original['assignedto']));
143
144 $this->fetch_user_cache();
145 }
146
147 // ###################################################################
148 /**
149 * Fetches all the users who could be related to the bug and sticks
150 * their information into an array.
151 *
152 * @access private
153 */
154 function fetch_user_cache()
155 {
156 // reset all the data each time we do this, just in case it changes within the lifespan of the object
157 $this->users = array();
158 foreach ($this->roles AS $role => $users)
159 {
160 $this->roles["$role"] = array();
161 }
162
163 $newbuggers = $this->registry->db->query("SELECT userid FROM " . TABLE_PREFIX . "useremail WHERE relation = " . $this->registry->emailoptions['relations']['-notapplicable-'] . " AND mask & " . $this->registry->emailoptions['notifications']['newbug']);
164 while ($newbug = $this->registry->db->fetch_array($newbuggers))
165 {
166 $this->roles['-notapplicable-']["$newbug[userid]"] = $newbug['userid'];
167 }
168
169 $favorites = $this->registry->db->query("SELECT userid FROM " . TABLE_PREFIX . "favorite WHERE bugid = " . $this->registry->clean($this->bug['bugid'], TYPE_UINT));
170 while ($fav = $this->registry->db->fetch_array($favorites))
171 {
172 $this->roles['favorite']["$fav[userid]"] = $fav['userid'];
173 }
174
175 $voters = $this->registry->db->query_first("SELECT userids FROM " . TABLE_PREFIX . "vote WHERE bugid = " . $this->registry->clean($this->bug['bugid'], TYPE_UINT));
176 $this->roles['voter'] = preg_split('#,#', $voters['userids'], 0, PREG_SPLIT_NO_EMPTY);
177
178 $commenters = $this->registry->db->query("SELECT userid FROM " . TABLE_PREFIX . "comment WHERE bugid = " . $this->registry->clean($this->bug['bugid'], TYPE_UINT));
179 while ($comment = $this->registry->db->fetch_array($commenters))
180 {
181 $this->roles['commenter']["$comment[userid]"] = $comment['userid'];
182 }
183
184 $masterids = array_merge($this->roles['-notapplicable-'], $this->roles['reporter'], $this->roles['assignee'], $this->roles['favorite'], $this->roles['voter'], $this->roles['commenter']);
185 $masterids = $this->registry->funct->array_strip_empty(array_unique($masterids));
186
187 if (is_array($masterids) AND sizeof($masterids) > 0)
188 {
189 $userinfo = $this->registry->db->query("
190 SELECT user.*, useremail.*
191 FROM " . TABLE_PREFIX . "useremail AS useremail
192 LEFT JOIN " . TABLE_PREFIX . "user AS user
193 ON (user.userid = useremail.userid)
194 WHERE useremail.userid IN (" . implode(',', $masterids) . ")
195 ");
196 while ($user = $this->registry->db->fetch_array($userinfo))
197 {
198 if (!is_array($this->users["$user[userid]"]))
199 {
200 $this->users["$user[userid]"] = $user;
201 unset($this->users["$user[userid]"]['mask'], $this->users["$user[userid]"]['relation']);
202 }
203 $this->users["$user[userid]"]['options']["$user[relation]"] = $user['mask'];
204 }
205 }
206 }
207
208 // ###################################################################
209 /**
210 * Sends the appropriate emails for changes to bugs. This function
211 * works a lot like the Logging class by taking BugAPI->objdata and
212 * BugAPI->values and then comparing the two arries and sending emails
213 * with the differences.
214 *
215 * @access public
216 */
217 function send_bug_changes_notice()
218 {
219 if (!isset($this->modified['bugid']))
220 {
221 return;
222 }
223
224 // fields with custom mask information
225 if ($this->original['assignedto'] != $this->modified['assignedto'])
226 {
227 if ($this->original['assignedto'] != '')
228 {
229 $this->notice_no_longer_assigned($this->original['assignedto']);
230 }
231 if ($this->modified['assignedto'] != '')
232 {
233 $this->notice_now_assigned($this->modified['assignedto']);
234 }
235 }
236 if ($this->original['status'] != $this->modified['status'])
237 {
238 $this->notice_status_change($this->original['status'], $this->modified['status']);
239 }
240 if ($this->original['resolution'] != $this->modified['resolution'])
241 {
242 $this->notice_resolution_change($this->original['resolution'], $this->modified['resolution']);
243 }
244 if ($this->original['duplicates'] != $this->modified['duplicates'])
245 {
246 $this->notice_duplicates_change($this->original['duplicates'], $this->modified['duplicates']);
247 }
248
249 // other standard fields that don't have custom masks
250 if ($this->original['severity'] != $this->modified['severity'])
251 {
252 $this->notice_severity_change($this->original['severity'], $this->modified['severity']);
253 }
254 if ($this->original['priority'] != $this->modified['priority'])
255 {
256 $this->notice_priority_change($this->original['priority'], $this->modified['priority']);
257 }
258 if (($this->original['product'] != $this->modified['product']) OR ($this->original['component'] != $this->modified['component']) OR ($this->original['version'] != $this->modified['version']))
259 {
260 $this->notice_pcv_change(array($this->original['product'], $this->original['component'], $this->original['version']), array($this->modified['product'], $this->modified['component'], $this->modified['version']));
261 }
262
263 $dofields = array(
264 'summary' => -1,
265 'dependency' => -1,
266 'hidden' => -1
267 );
268 foreach ($dofields AS $field => $lookup)
269 {
270 if ($this->original["$field"] != $this->modified["$field"])
271 {
272 $this->notice_other_change($field, $this->original["$field"], $this->modified["$field"]);
273 }
274 }
275 }
276
277 // ###################################################################
278 /**
279 * Sends an email to the specified user ID that they are no longer the
280 * person assigned to the bug.
281 *
282 * @access private
283 *
284 * @param integer User ID to send to
285 */
286 function notice_no_longer_assigned($userid)
287 {
288 if ($this->users["$userid"]['options'][0] & $this->registry->emailoptions['notifications']['assignedto'] AND in_array($userid, $this->roles['-notapplicable-']))
289 {
290 $user = construct_user_display($this->registry->userinfo, false);
291 eval('$part = "' . $this->registry->template->fetch('email/notice_unassigned.part') . '";');
292 $this->notices["$userid"][] = $part;
293 }
294 }
295
296 // ###################################################################
297 /**
298 * Informs the user that they have been made the assignee of the bug.
299 *
300 * @access private
301 *
302 * @param integer User ID
303 */
304 function notice_now_assigned($userid)
305 {
306 if ($this->users["$userid"]['options'][0] & $this->registry->emailoptions['notifications']['assignedto'] AND in_array($userid, $this->roles['-notapplicable-']))
307 {
308 $user = construct_user_display($this->registry->userinfo, false);
309 eval('$email = "' . $this->registry->template->fetch('email/notice_assigned.part') . '";');
310 $this->notices["$userid"][] = $email;
311 }
312 }
313
314 // ###################################################################
315 /**
316 * Sends a message to inform users that the status has changed.
317 *
318 * @access private
319 *
320 * @param integer Old status
321 * @param integer New status
322 */
323 function notice_status_change($old, $new)
324 {
325 $userlist = $this->fetch_users_with_on_bit('statusresolve');
326 foreach ($userlist AS $userid => $user)
327 {
328 eval('$email = "' . $this->registry->template->fetch('email/notice_status.part') . '";');
329 $this->notices["$user[userid]"][] = $email;
330 }
331 }
332
333 // ###################################################################
334 /**
335 * Sends an email to inform users that the resolution has changed.
336 *
337 * @access private
338 *
339 * @param integer Old resolution
340 * @param integer New resolution
341 */
342 function notice_resolution_change($old, $new)
343 {
344 $userlist = $this->fetch_users_with_on_bit('statusresolve');
345 foreach ($userlist AS $userid => $user)
346 {
347 eval('$email = "' . $this->registry->template->fetch('email/notice_resolution.part') . '";');
348 $this->notices["$user[userid]"][] = $email;
349 }
350 }
351
352 // ###################################################################
353 /**
354 * Informs users that the duplicates list has changed.
355 *
356 * @access private
357 *
358 * @param string Old duplicates list
359 * @param string New duplicates list
360 */
361 function notice_duplicates_change($old, $new)
362 {
363 $userlist = $this->fetch_useres_with_on_bit('duplicates');
364 foreach ($userlist AS $userid => $user)
365 {
366 eval('$email = "' . $this->registry->template->fetch('email/notice_duplicates.part') . '";');
367 $this->notices["$user[userid]"][] = $email;
368 }
369 }
370
371 // ###################################################################
372 /**
373 * Sends an email to inform users that the severity has changed.
374 *
375 * @access private
376 *
377 * @param integer Old severity
378 * @param integer New severity
379 */
380 function notice_severity_change($old, $new)
381 {
382 $userlist = $this->fetch_users_with_on_bit('otherfield');
383 foreach ($userlist AS $userid => $user)
384 {
385 eval('$email = "' . $this->registry->template->fetch('email/notice_severity.part') . '";');
386 $this->notices["$user[userid]"][] = $email;
387 }
388 }
389
390 // ###################################################################
391 /**
392 * Informs users that the priority changed.
393 *
394 * @access private
395 *
396 * @param integer Old priority
397 * @param integer New priority
398 */
399 function notice_priority_change($old, $new)
400 {
401 $userlist = $this->fetch_users_with_on_bit('otherfield');
402 foreach ($userlist AS $userid => $user)
403 {
404 eval('$email = "' . $this->registry->template->fetch('email/notice_priority.part') . '";');
405 $this->notices["$user[userid]"][] = $email;
406 }
407 }
408
409 // ###################################################################
410 /**
411 * Sends an email telling users that the product, component, or version
412 * has changed. This is done all at once because you really need to see
413 * the whole thing in the notice.
414 *
415 * @access private
416 *
417 * @param array Original PCV
418 * @param array Modified PCV
419 */
420 function notice_pcv_change($old, $new)
421 {
422 $userlist = $this->fetch_users_with_on_bit('otherfield');
423
424 $old = $this->registry->datastore['product']["$old[0]"]['title'] . '/' . ($old[1] ? $this->registry->datastore['product']["$old[1]"]['title'] . '/' : '') . $this->registry->datastore['version']["$old[2]"]['version'];
425 $new = $this->registry->datastore['product']["$new[0]"]['title'] . '/' . ($new[1] ? $this->registry->datastore['product']["$new[1]"]['title'] . '/' : '') . $this->registry->datastore['version']["$new[2]"]['version'];
426
427 foreach ($userlist AS $userid => $user)
428 {
429 eval('$email = "' . $this->registry->template->fetch('email/notice_product.part') . '";');
430 $this->notices["$user[userid]"][] = $email;
431 }
432 }
433
434 // ###################################################################
435 /**
436 * Sends the appropriate users information about a new comment being
437 * posted to the bug report.
438 *
439 * @access public
440 *
441 * @param array CommentAPI->values array
442 */
443 function send_new_comment_notice($comment)
444 {
445 $userlist = $this->fetch_users_with_on_bit('newcomment');
446 foreach ($userlist AS $userid => $user)
447 {
448 $user = construct_user_display($this->registry->userinfo, false);
449 $date = $this->registry->modules['date']->format($this->registry->options['dateformat'], $comment['dateline']);
450
451 eval('$email = "' . $this->registry->template->fetch('email/notice_comment.part') . '";');
452 $this->notices["$userid"][] = $email;
453 }
454 }
455
456 // ###################################################################
457 /**
458 * A notice for an individual field changing.
459 *
460 * @access private
461 *
462 * @param string Field name
463 * @param mixed Original value
464 * @param mixed Modified value
465 */
466 function notice_other_change($name, $old, $new)
467 {
468 $userlist = $this->fetch_users_with_on_bit('otherfield');
469 foreach ($userlist AS $userid => $user)
470 {
471 eval('$email = "' . $this->registry->template->fetch('email/notice_other.part') . '";');
472 $this->notices["$user[userid]"][] = $email;
473 }
474 }
475
476 // ###################################################################
477 /**
478 * Sends appropriate users a notice when a new attachment has been
479 * added.
480 *
481 * @access public
482 *
483 * @param array AttachmentAPI->values array
484 * @param array List of all attachments made obsolete
485 * @param array Newly-inserted attachment ID
486 */
487 function send_new_attachment_notice($attachment, $obsolete, $id)
488 {
489 $userlist = $this->fetch_users_with_on_bit('newattachment');
490 foreach ($userlist AS $userid => $user)
491 {
492 $user = construct_user_display($this->registry->userinfo, false);
493 $obsoletes = implode(', ', (array)$obsolete);
494
495 eval('$email = "' . $this->registry->template->fetch('email/notice_attachment.part') . '";');
496 $this->notices["$userid"][] = $email;
497 }
498 }
499
500 // ###################################################################
501 /**
502 * Sends a new bug notification notice to all those who have the option
503 * turned no. This does not use fetch_users_with_on_bit() because a
504 * query is more effective.
505 *
506 * @access public
507 *
508 * @param array Bug values array
509 * @param array Comment values array
510 */
511 function send_new_bug_notice($bug, $comment)
512 {
513 $userinfo = $this->registry->db->query("
514 SELECT user.*, useremail.*
515 FROM " . TABLE_PREFIX . "useremail AS useremail
516 LEFT JOIN " . TABLE_PREFIX . "user AS user
517 ON (user.userid = useremail.userid)
518 WHERE useremail.relation = 0
519 AND useremail.mask & " . $this->registry->emailoptions['notifications']['newbug'] . "
520 ");
521 while ($userInfo = $this->registry->db->fetch_array($userinfo))
522 {
523 if (!is_array($this->users["$userInfo[userid]"]))
524 {
525 $user = construct_user_display($this->registry->userinfo, false);
526 $product = $this->registry->datastore['product']["$bug[product]"]['title'] . '/' . ($bug['component'] ? $this->registry->datastore['product']["$bug[component]"]['title'] . '/' : '') . $this->registry->datastore['version']["$bug[version]"]['version'];
527 eval('$email = "' . $this->registry->template->fetch('email/notice_new_bug.part') . '";');
528 $this->notices["$userInfo[userid]"][] = $email;
529 $this->users["$userInfo[userid]"] = $userInfo;
530 unset($this->users["$userInfo[userid]"]['mask'], $this->users["$userInfo[userid]"]['relation']);
531 }
532 $this->users["$userInfo[userid]"]['options']["$userInfo[relation]"] = $userInfo['mask'];
533 }
534 }
535
536 // ###################################################################
537 /**
538 * Generates an array of users who have a given email notification flag
539 * turned on in their bitfields.
540 *
541 * @access private
542 *
543 * @param string Notification bitfield name
544 *
545 * @return array Array of users and their data
546 */
547 function fetch_users_with_on_bit($bitname)
548 {
549 $idlist = array();
550
551 foreach ($this->users AS $user)
552 {
553 foreach ($this->registry->emailoptions['relations'] AS $name => $bit)
554 {
555 if (in_array($user['userid'], $this->roles["$name"]) AND $user['options']["$bit"] & $this->registry->emailoptions['notifications']["$bitname"])
556 {
557 $idlist[] = $user['userid'];
558 }
559 }
560 }
561
562 $masters = array_unique($idlist);
563
564 $return = array();
565 foreach ($masters AS $userid)
566 {
567 $return["$userid"] =& $this->users["$userid"];
568 }
569
570 return $return;
571 }
572
573 // ###################################################################
574 /**
575 * Compiles and sends the actual emails to users.
576 *
577 * @access public
578 */
579 function finalize()
580 {
581 // get the current bug for permissions checks
582 $bug = $this->registry->db->query_first("SELECT * FROM " . TABLE_PREFIX . "bug WHERE bugid = " . $this->bug['bugid']);
583 foreach ($this->notices AS $userid => $noticelist)
584 {
585 if ($userid == $this->registry->userinfo['userid'])
586 {
587 $this->registry->debug("skipping user $userid because they're the one doing the thing");
588 continue;
589 }
590
591 // we wouldn't want people who favorite bugs getting hidden notices
592 if (!check_bug_permissions($bug, $this->users["$userid"]))
593 {
594 $this->registry->debug("skipping user $userid ({$this->users[$userid]['email']}) because of permissions");
595 continue;
596 }
597
598 $parts = implode("\n\n", $noticelist);
599
600 eval('$email = "' . $this->registry->template->fetch('email/bugnotification.xml') . '";');
601 $email = $this->registry->xml->parse($email);
602 $this->registry->mail->setSubject($email['email']['subject']['value']);
603 $this->registry->mail->setBodyText($email['email']['bodyText']['value']);
604
605 if (!empty($this->users["$userid"]['email']))
606 {
607 $this->registry->mail->send($this->users["$userid"]['email'], $this->users["$userid"]['displayname']);
608 }
609 else
610 {
611 $this->registry->debug("not sending an email to " . $userid . " because they don't have one?");
612 }
613 }
614 }
615 }
616
617 /*=====================================================================*\
618 || ###################################################################
619 || # $HeadURL$
620 || # $Id$
621 || ###################################################################
622 \*=====================================================================*/
623 ?>