r972: Rebranding from Iris Studios to Blue Static
[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 'favourite' => 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
133 $this->original = $original;
134 $this->modified = $modified;
135
136 $this->roles['-notapplicable-'] = (sizeof($modified) > 0 ? array($original['assignedto'], $modified['assignedto']) : array($original['assignedto']));
137 $this->roles['reporter'] = array($original['userid']);
138 $this->roles['assignee'] = (sizeof($modified) > 0 ? array($modified['assignedto']) : array($original['assignedto']));
139
140 $this->fetch_user_cache();
141 }
142
143 // ###################################################################
144 /**
145 * Fetches all the users who could be related to the bug and sticks
146 * their information into an array.
147 *
148 * @access private
149 */
150 function fetch_user_cache()
151 {
152 $favourites = $this->registry->db->query("SELECT userid FROM " . TABLE_PREFIX . "favourite WHERE bugid = " . $this->registry->clean($this->bug['bugid'], TYPE_UINT));
153 while ($fav = $this->registry->db->fetch_array($favourites))
154 {
155 $this->roles['favourite']["$fav[userid]"] = $fav['userid'];
156 }
157
158 $voters = $this->registry->db->query_first("SELECT userids FROM " . TABLE_PREFIX . "vote WHERE bugid = " . $this->registry->clean($this->bug['bugid'], TYPE_UINT));
159 $this->roles['voter'] = preg_split('#,#', $voters['userids'], 0, PREG_SPLIT_NO_EMPTY);
160
161 $commenters = $this->registry->db->query("SELECT userid FROM " . TABLE_PREFIX . "comment WHERE bugid = " . $this->registry->clean($this->bug['bugid'], TYPE_UINT));
162 while ($comment = $this->registry->db->fetch_array($commenters))
163 {
164 $this->roles['commenter']["$comment[userid]"] = $comment['userid'];
165 }
166
167 $masterids = array_merge($this->roles['-notapplicable-'], $this->roles['reporter'], $this->roles['assignee'], $this->roles['favourite'], $this->roles['voter'], $this->roles['commenter']);
168 $masterids = array_unique($masterids);
169
170 $userinfo = $this->registry->db->query("
171 SELECT user.*, useremail.*
172 FROM " . TABLE_PREFIX . "useremail AS useremail
173 LEFT JOIN " . TABLE_PREFIX . "user AS user
174 ON (user.userid = useremail.userid)
175 WHERE useremail.userid IN (" . implode(',', $masterids) . ")
176 ");
177 while ($user = $this->registry->db->fetch_array($userinfo))
178 {
179 if (!is_array($this->users["$user[userid]"]))
180 {
181 $this->users["$user[userid]"] = $user;
182 unset($this->users["$user[userid]"]['mask'], $this->users["$user[userid]"]['relation']);
183 }
184 $this->users["$user[userid]"]['options']["$user[relation]"] = $user['mask'];
185 }
186 }
187
188 // ###################################################################
189 /**
190 * Sends the appropriate emails for changes to bugs. This function
191 * works a lot like the Logging class by taking BugAPI->objdata and
192 * BugAPI->values and then comparing the two arries and sending emails
193 * with the differences.
194 *
195 * @access public
196 *
197 * @param array Original custom fields data
198 * @param array Modified custom fields data
199 */
200 function send_bug_changes_notice($original, $modified)
201 {
202 if (!isset($this->modified['bugid']))
203 {
204 return;
205 }
206
207 if ($this->original['assignedto'] != $this->modified['assignedto'])
208 {
209 if ($this->original['assignedto'] != '')
210 {
211 $this->notice_no_longer_assigned($this->original['assignedto']);
212 }
213 if ($this->modified['assignedto'] != '')
214 {
215 $this->notice_now_assigned($this->modified['assignedto']);
216 }
217 }
218
219 if ($this->original['status'] != $this->modified['status'])
220 {
221 $this->notice_status_change($this->original['status'], $this->modified['status']);
222 }
223 if ($this->original['resolution'] != $this->modified['resoultion'])
224 {
225 $this->notice_resolution_change($this->original['resolution'], $this->modified['resolution']);
226 }
227
228 if ($this->original['duplicates'] != $this->modified['duplicates'])
229 {
230 $this->notice_duplicates_change($this->original['duplicates'], $this->modified['duplicates']);
231 }
232
233 $dofields = array(
234 'summary' => -1,
235 'severity' => 'severityid',
236 'dependency' => -1,
237 'productid' => -1,
238 'componentid' => -1,
239 'versionid' => -1,
240 'hidden' => -1,
241 'priority' => 'priorityid'
242 );
243 foreach ($dofields AS $field => $lookup)
244 {
245 if ($this->original["$field"] != $this->modified["$field"])
246 {
247 $this->notice_other_change($field, $this->original["$field"], $this->modified["$field"]);
248 }
249 }
250
251 foreach ($modified AS $field => $value)
252 {
253 if ($field == 'bugid')
254 {
255 continue;
256 }
257 if ($original["$field"] != $modified["$field"])
258 {
259 $this->notice_other_change($field, $original["$field"], $modified["$field"]);
260 }
261 }
262 }
263
264 // ###################################################################
265 /**
266 * Sends an email to the specified user ID that they are no longer the
267 * person assigned to the bug.
268 *
269 * @access private
270 *
271 * @param integer User ID to send to
272 */
273 function notice_no_longer_assigned($userid)
274 {
275 if ($this->users["$userid"]['options'][0] & $this->registry->emailoptions['notifications']['assignedto'] AND in_array($userid, $this->roles['-notapplicable-']))
276 {
277 $this->notices["$userid"][] = sprintf(
278 $this->registry->lang->string('You are no longer assigned to this bug, per %1$s\'s changes.'),
279
280 construct_user_display($this->registry->userinfo, false)
281 );
282 }
283 }
284
285 // ###################################################################
286 /**
287 * Informs the user that they have been made the assignee of the bug.
288 *
289 * @access private
290 *
291 * @param integer User ID
292 */
293 function notice_now_assigned($userid)
294 {
295 if ($this->users["$userid"]['options'][0] & $this->registry->emailoptions['notifications']['assignedto'] AND in_array($userid, $this->roles['-notapplicable-']))
296 {
297 $this->notices["$userid"][] = sprintf(
298 $this->registry->lang->string('You have been assigned to this bug by %1$s.'),
299
300 construct_user_display($this->registry->userinfo, false)
301 );
302 }
303 }
304
305 // ###################################################################
306 /**
307 * Sends a message to inform users that the status has changed.
308 *
309 * @access private
310 *
311 * @param integer Old status
312 * @param integer New status
313 */
314 function notice_status_change($old, $new)
315 {
316 $userlist = $this->fetch_users_with_on_bit('statusresolve');
317 foreach ($userlist AS $userid => $user)
318 {
319 $this->notices["$user[userid]"][] = sprintf(
320 $this->registry->lang->string('The status field has changed from "%1$s" to "%2$s".'),
321
322 $this->registry->datastore['status']["$old"]['status'],
323 $this->registry->datastore['status']["$new"]['status']
324 );
325 }
326 }
327
328 // ###################################################################
329 /**
330 * Sends an email to inform users that the resolution has changed.
331 *
332 * @access private
333 *
334 * @param integer Old resolution
335 * @param integer New resolution
336 */
337 function notice_resolution_change($old, $new)
338 {
339 $userlist = $this->fetch_users_with_on_bit('statusresolve');
340 foreach ($userlist AS $userid => $user)
341 {
342 $this->notices["$user[userid]"][] = sprintf(
343 $this->registry->lang->string('The resolution field has changed from "%1$s" to "%2$s".'),
344
345 $this->registry->datastore['resolution']["$old"]['resolution'],
346 $this->registry->datastore['resolution']["$new"]['resolution']
347 );
348 }
349 }
350
351 // ###################################################################
352 /**
353 * Informs users that the duplicates list has changed.
354 *
355 * @access private
356 *
357 * @param string Old duplicates list
358 * @param string New duplicates list
359 */
360 function notice_duplicates_change($old, $new)
361 {
362 $userlist = $this->fetch_useres_with_on_bit('duplicates');
363 foreach ($userlist AS $userid => $user)
364 {
365 $this->notices["$user[userid]"][] = sprintf(
366 $this->registry->lang->string('The duplicates list has changed from "%1$s" to %2$s".'),
367
368 $old,
369 $new
370 );
371 }
372 }
373
374 // ###################################################################
375 /**
376 * Sends the appropriate users information about a new comment being
377 * posted to the bug report.
378 *
379 * @access public
380 *
381 * @param array CommentAPI->values array
382 */
383 function send_new_comment_notice($comment)
384 {
385 $userlist = $this->fetch_users_with_on_bit('newcomment');
386 foreach ($userlist AS $userid => $user)
387 {
388 $this->notices["$user[userid]"][] = sprintf(
389 $this->registry->lang->string('The following comment was added by %1$s on %2$s:
390 ============================================
391 %3$s
392 ============================================'),
393
394 construct_user_display($this->registry->userinfo, false),
395 $this->registry->modules['date']->format($this->registry->options['dateformat'], $comment['dateline']),
396 $comment['comment']
397 );
398 }
399 }
400
401 // ###################################################################
402 /**
403 * A notice for an individual field changing.
404 *
405 * @access private
406 *
407 * @param string Field name
408 * @param mixed Original value
409 * @param mixed Modified value
410 */
411 function notice_other_change($name, $old, $new)
412 {
413 $userlist = $this->fetch_users_with_on_bit('otherfield');
414 foreach ($userlist AS $userid => $user)
415 {
416 $this->notices["$user[userid]"][] = sprintf(
417 $this->registry->lang->string('The %1$s field changed from "%2$s" to "%3$s".'),
418
419 $name,
420 $old,
421 $new
422 );
423 }
424 }
425
426 // ###################################################################
427 /**
428 * Sends appropriate users a notice when a new attachment has been
429 * added.
430 *
431 * @access public
432 *
433 * @param array AttachmentAPI->values array
434 * @param array List of all attachments made obsolete
435 * @param array Newly-inserted attachment ID
436 */
437 function send_new_attachment_notice($attachment, $obsolete, $id)
438 {
439 $userlist = $this->fetch_users_with_on_bit('newattachment');
440 foreach ($userlist AS $userid => $user)
441 {
442 $this->notices["$userid"][] = sprintf(
443 $this->registry->lang->string('%1$s has uploaded a new attachment:
444 ============================================
445 File name: %2$s
446 Description: %3$s
447 File size: %4$s Bytes
448 Makes obsolete: %5$s
449 View: %6$s
450 ============================================'),
451
452 construct_user_display($this->registry->userinfo, false),
453 $attachment['filename'],
454 $attachment['description'],
455 $attachment['filesize'],
456 implode(', ', (array)$obsolete),
457 $this->registry->options['trackerurl'] . '/viewattachment.php?attachmentid=' . $id
458 );
459 }
460 }
461
462 // ###################################################################
463 /**
464 * Sends a new bug notification notice to all those who have the option
465 * turned no. This does not use fetch_users_with_on_bit() because a
466 * query is more effective.
467 *
468 * @access public
469 *
470 * @param array Bug values array
471 * @param array Comment values array
472 */
473 function send_new_bug_notice($bug, $comment)
474 {
475 $userinfo = $this->registry->db->query("
476 SELECT user.*, useremail.*
477 FROM " . TABLE_PREFIX . "useremail AS useremail
478 LEFT JOIN " . TABLE_PREFIX . "user AS user
479 ON (user.userid = useremail.userid)
480 WHERE useremail.relation = 0
481 AND useremail.mask & " . $this->registry->emailoptions['notifications']['newbug'] . "
482 ");
483 while ($user = $this->registry->db->fetch_array($userinfo))
484 {
485 if (!is_array($this->users["$user[userid]"]))
486 {
487 $this->notices["$user[userid]"][] = sprintf(
488 $this->registry->lang->string('
489 This bug has been added to the database:
490 ============================================
491 Bug ID: %1$s
492 Summary: %2$s
493 Reporter: %3$s
494 Product/Component/Version: %4$s
495 Initial report:
496 --------------------------------------------
497 %5$s
498 --------------------------------------------
499 ============================================'),
500 $bug['bugid'],
501 $bug['summary'],
502 construct_user_display($this->registry->userinfo, false),
503 $this->registry->datastore['product']["$bug[productid]"]['title'] . '/' . (($bug['componentid']) ? $this->registry->datastore['product']["$bug[componentid]"]['title'] . '/' : '') . $this->registry->datastore['version']["$bug[versionid]"]['version'],
504 $comment['comment']
505 );
506 $this->users["$user[userid]"] = $user;
507 unset($this->users["$user[userid]"]['mask'], $this->users["$user[userid]"]['relation']);
508 }
509 $this->users["$user[userid]"]['options']["$user[relation]"] = $user['mask'];
510 }
511 }
512
513 // ###################################################################
514 /**
515 * Generates an array of users who have a given email notification flag
516 * turned on in their bitfields.
517 *
518 * @access private
519 *
520 * @param string Notification bitfield name
521 *
522 * @return array Array of users and their data
523 */
524 function fetch_users_with_on_bit($bitname)
525 {
526 $idlist = array();
527
528 foreach ($this->users AS $user)
529 {
530 foreach ($this->registry->emailoptions['relations'] AS $name => $bit)
531 {
532 if (in_array($user['userid'], $this->roles["$name"]) AND $user['options']["$bit"] & $this->registry->emailoptions['notifications']["$bitname"])
533 {
534 $idlist[] = $user['userid'];
535 }
536 }
537 }
538
539 $masters = array_unique($idlist);
540
541 $return = array();
542 foreach ($masters AS $userid)
543 {
544 $return["$userid"] =& $this->users["$userid"];
545 }
546
547 return $return;
548 }
549
550 // ###################################################################
551 /**
552 * Compiles and sends the actual emails to users.
553 *
554 * @access public
555 */
556 function finalize()
557 {
558 $this->registry->mail->set('subject', sprintf($this->registry->lang->string('%1$s Bug Notification'), $this->registry->options['trackertitle']));
559 foreach ($this->notices AS $userid => $noticelist)
560 {
561 if ($userid == $this->registry->userinfo['userid'])
562 {
563 continue;
564 }
565
566 $this->registry->mail->to_add($this->users["$userid"]['displayname'], $this->users["$userid"]['email']);
567 $this->registry->mail->set('bodytext', sprintf($this->registry->lang->string('Hi %1$s,
568
569 You are receiving this email because you have opted to get notifications for the %2$s bug tracker. Here are the notices:
570 ###################################################################
571
572 %3$s
573
574 ###################################################################
575 If you no longer want to receive email from us, please log into your account and click the "My Controls" tab at the top of the screen to change email preferences.
576
577 %4$s'),
578 $this->users["$userid"]['displayname'],
579 $this->registry->options['trackertitle'],
580 implode("\n\n", $noticelist),
581 $this->registry->options['trackerurl']
582 ));
583 $this->registry->mail->send(true);
584 }
585 }
586 }
587
588 /*=====================================================================*\
589 || ###################################################################
590 || # $HeadURL$
591 || # $Id$
592 || ###################################################################
593 \*=====================================================================*/
594 ?>