array(), 'reporter' => array(), 'assignee' => array(), 'favorite' => array(), 'voter' => array(), 'commenter' => array() ); /** * User cache list * @var array * @access private */ var $users = array(); /** * A list of notices per-user that are combined together in NotificationCenter::finalize() * @var array * @access private */ var $notices = array(); // ################################################################### /** * Constructor: set database objects * * @access public */ function __construct() { global $bugsys; $this->registry =& $bugsys; } // ################################################################### /** * (PHP 4) Constructor * * @access public */ function NotificationCenter() { $this->__construct(); } // ################################################################### /** * Sets the bug data so that all methods in this class have access to * it when sending emails. * * @access public * * @param array Original bug data * @param array Modified bug data */ function set_bug_data($original, $modified = array()) { if (sizeof($modified) > 0) { $this->bug = $modified; } else { $this->bug = $original; } $this->original = $original; $this->modified = $modified; $this->roles['-notapplicable-'] = (sizeof($modified) > 0 ? array($original['assignedto'], $modified['assignedto']) : array($original['assignedto'])); $this->roles['reporter'] = array($original['userid']); $this->roles['assignee'][] = (sizeof($modified) > 0 ? $modified['assignedto'] : $original['assignedto']); $this->fetch_user_cache(); } // ################################################################### /** * Fetches all the users who could be related to the bug and sticks * their information into an array. * * @access private */ function fetch_user_cache() { $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']); while ($newbug = $this->registry->db->fetch_array($newbuggers)) { $this->roles['-notapplicable-']["$newbug[userid]"] = $newbug['userid']; } $favorites = $this->registry->db->query("SELECT userid FROM " . TABLE_PREFIX . "favorite WHERE bugid = " . $this->registry->clean($this->bug['bugid'], TYPE_UINT)); while ($fav = $this->registry->db->fetch_array($favorites)) { $this->roles['favorite']["$fav[userid]"] = $fav['userid']; } $voters = $this->registry->db->query_first("SELECT userids FROM " . TABLE_PREFIX . "vote WHERE bugid = " . $this->registry->clean($this->bug['bugid'], TYPE_UINT)); $this->roles['voter'] = preg_split('#,#', $voters['userids'], 0, PREG_SPLIT_NO_EMPTY); $commenters = $this->registry->db->query("SELECT userid FROM " . TABLE_PREFIX . "comment WHERE bugid = " . $this->registry->clean($this->bug['bugid'], TYPE_UINT)); while ($comment = $this->registry->db->fetch_array($commenters)) { $this->roles['commenter']["$comment[userid]"] = $comment['userid']; } $masterids = array_merge($this->roles['-notapplicable-'], $this->roles['reporter'], $this->roles['assignee'], $this->roles['favorite'], $this->roles['voter'], $this->roles['commenter']); $masterids = $this->registry->funct->array_strip_empty(array_unique($masterids)); if (is_array($masterids) AND sizeof($masterids) > 0) { $userinfo = $this->registry->db->query(" SELECT user.*, useremail.* FROM " . TABLE_PREFIX . "useremail AS useremail LEFT JOIN " . TABLE_PREFIX . "user AS user ON (user.userid = useremail.userid) WHERE useremail.userid IN (" . implode(',', $masterids) . ") "); while ($user = $this->registry->db->fetch_array($userinfo)) { if (!is_array($this->users["$user[userid]"])) { $this->users["$user[userid]"] = $user; unset($this->users["$user[userid]"]['mask'], $this->users["$user[userid]"]['relation']); } $this->users["$user[userid]"]['options']["$user[relation]"] = $user['mask']; } } } // ################################################################### /** * Sends the appropriate emails for changes to bugs. This function * works a lot like the Logging class by taking BugAPI->objdata and * BugAPI->values and then comparing the two arries and sending emails * with the differences. * * @access public */ function send_bug_changes_notice() { if (!isset($this->modified['bugid'])) { return; } // fields with custom mask information if ($this->original['assignedto'] != $this->modified['assignedto']) { if ($this->original['assignedto'] != '') { $this->notice_no_longer_assigned($this->original['assignedto']); } if ($this->modified['assignedto'] != '') { $this->notice_now_assigned($this->modified['assignedto']); } } if ($this->original['status'] != $this->modified['status']) { $this->notice_status_change($this->original['status'], $this->modified['status']); } if ($this->original['resolution'] != $this->modified['resolution']) { $this->notice_resolution_change($this->original['resolution'], $this->modified['resolution']); } if ($this->original['duplicates'] != $this->modified['duplicates']) { $this->notice_duplicates_change($this->original['duplicates'], $this->modified['duplicates']); } // other standard fields that don't have custom masks if ($this->original['severity'] != $this->modified['severity']) { $this->notice_severity_change($this->original['severity'], $this->modified['severity']); } if ($this->original['priority'] != $this->modified['priority']) { $this->notice_priority_change($this->original['priority'], $this->modified['priority']); } if (($this->original['product'] != $this->modified['product']) OR ($this->original['component'] != $this->modified['component']) OR ($this->original['version'] != $this->modified['version'])) { $this->notice_pcv_change(array($this->original['product'], $this->original['component'], $this->original['version']), array($this->modified['product'], $this->modified['component'], $this->modified['version'])); } $dofields = array( 'summary' => -1, 'dependency' => -1, 'hidden' => -1 ); foreach ($dofields AS $field => $lookup) { if ($this->original["$field"] != $this->modified["$field"]) { $this->notice_other_change($field, $this->original["$field"], $this->modified["$field"]); } } } // ################################################################### /** * Sends an email to the specified user ID that they are no longer the * person assigned to the bug. * * @access private * * @param integer User ID to send to */ function notice_no_longer_assigned($userid) { if ($this->users["$userid"]['options'][0] & $this->registry->emailoptions['notifications']['assignedto'] AND in_array($userid, $this->roles['-notapplicable-'])) { $user = construct_user_display($this->registry->userinfo, false); eval('$part = "' . $this->registry->template->fetch(FetchEmailPath('notice_unassigned.part', $this->_localeFromUserId($userid))) . '";'); $this->notices["$userid"][] = $part; } } // ################################################################### /** * Informs the user that they have been made the assignee of the bug. * * @access private * * @param integer User ID */ function notice_now_assigned($userid) { if ($this->users["$userid"]['options'][0] & $this->registry->emailoptions['notifications']['assignedto'] AND in_array($userid, $this->roles['-notapplicable-'])) { $user = construct_user_display($this->registry->userinfo, false); eval('$email = "' . $this->registry->template->fetch(FetchEmailPath('notice_assigned.part', $this->_localeFromUserId($userid))) . '";'); $this->notices["$userid"][] = $email; } } // ################################################################### /** * Sends a message to inform users that the status has changed. * * @access private * * @param integer Old status * @param integer New status */ function notice_status_change($old, $new) { $userlist = $this->fetch_users_with_on_bit('statusresolve'); $old = bugdar::$datastore['status'][$old]['status']; $new = bugdar::$datastore['status'][$new]['status']; foreach ($userlist AS $userid => $user) { eval('$email = "' . $this->registry->template->fetch(FetchEmailPath('notice_status.part', $this->_localeFromUserId($userid))) . '";'); $this->notices["$user[userid]"][] = $email; } } // ################################################################### /** * Sends an email to inform users that the resolution has changed. * * @access private * * @param integer Old resolution * @param integer New resolution */ function notice_resolution_change($old, $new) { $userlist = $this->fetch_users_with_on_bit('statusresolve'); $old = bugdar::$datastore['resolution'][$old]['resolution']; $new = bugdar::$datastore['resolution'][$new]['resolution']; foreach ($userlist AS $userid => $user) { eval('$email = "' . $this->registry->template->fetch(FetchEmailPath('notice_resolution.part', $this->_localeFromUserId($userid))) . '";'); $this->notices["$user[userid]"][] = $email; } } // ################################################################### /** * Informs users that the duplicates list has changed. * * @access private * * @param string Old duplicates list * @param string New duplicates list */ function notice_duplicates_change($old, $new) { $userlist = $this->fetch_useres_with_on_bit('duplicates'); foreach ($userlist AS $userid => $user) { eval('$email = "' . $this->registry->template->fetch(FetchEmailPath('notice_duplicates.part', $this->_localeFromUserId($userid))) . '";'); $this->notices["$user[userid]"][] = $email; } } // ################################################################### /** * Sends an email to inform users that the severity has changed. * * @access private * * @param integer Old severity * @param integer New severity */ function notice_severity_change($old, $new) { $userlist = $this->fetch_users_with_on_bit('otherfield'); $old = bugdar::$datastore['severity'][$old]['severity']; $new = bugdar::$datastore['severity'][$new]['severity']; foreach ($userlist AS $userid => $user) { eval('$email = "' . $this->registry->template->fetch(FetchEmailPath('notice_severity.part', $this->_localeFromUserId($userid))) . '";'); $this->notices["$user[userid]"][] = $email; } } // ################################################################### /** * Informs users that the priority changed. * * @access private * * @param integer Old priority * @param integer New priority */ function notice_priority_change($old, $new) { $userlist = $this->fetch_users_with_on_bit('otherfield'); $old = bugdar::$datastore['priority'][$old]['priority']; $new = bugdar::$datastore['priority'][$new]['priority']; foreach ($userlist AS $userid => $user) { eval('$email = "' . $this->registry->template->fetch(FetchEmailPath('notice_priority.part', $this->_localeFromUserId($userid))) . '";'); $this->notices["$user[userid]"][] = $email; } } // ################################################################### /** * Sends an email telling users that the product, component, or version * has changed. This is done all at once because you really need to see * the whole thing in the notice. * * @access private * * @param array Original PCV * @param array Modified PCV */ function notice_pcv_change($old, $new) { $userlist = $this->fetch_users_with_on_bit('otherfield'); $old = bugdar::$datastore['product']["$old[0]"]['title'] . '/' . ($old[1] ? bugdar::$datastore['product']["$old[1]"]['title'] . '/' : '') . bugdar::$datastore['version']["$old[2]"]['version']; $new = bugdar::$datastore['product']["$new[0]"]['title'] . '/' . ($new[1] ? bugdar::$datastore['product']["$new[1]"]['title'] . '/' : '') . bugdar::$datastore['version']["$new[2]"]['version']; foreach ($userlist AS $userid => $user) { eval('$email = "' . $this->registry->template->fetch(FetchEmailPath('notice_product.part', $this->_localeFromUserId($userid))) . '";'); $this->notices["$user[userid]"][] = $email; } } // ################################################################### /** * Sends the appropriate users information about a new comment being * posted to the bug report. * * @access public * * @param array CommentAPI->values array */ function send_new_comment_notice($comment) { $userlist = $this->fetch_users_with_on_bit('newcomment'); foreach ($userlist AS $userid => $user) { $user = construct_user_display($this->registry->userinfo, false); $date = $this->registry->modules['date']->format($this->registry->options['dateformat'], $comment['dateline']); eval('$email = "' . $this->registry->template->fetch(FetchEmailPath('notice_comment.part', $this->_localeFromUserId($userid))) . '";'); $this->notices["$userid"][] = $email; } } // ################################################################### /** * A notice for an individual field changing. * * @access private * * @param string Field name * @param mixed Original value * @param mixed Modified value */ function notice_other_change($name, $old, $new) { $userlist = $this->fetch_users_with_on_bit('otherfield'); foreach ($userlist AS $userid => $user) { eval('$email = "' . $this->registry->template->fetch(FetchEmailPath('notice_other.part', $this->_localeFromUserId($userid))) . '";'); $this->notices["$user[userid]"][] = $email; } } // ################################################################### /** * Sends appropriate users a notice when a new attachment has been * added. * * @access public * * @param array AttachmentAPI->values array * @param array List of all attachments made obsolete * @param array Newly-inserted attachment ID */ function send_new_attachment_notice($attachment, $obsolete, $id) { $userlist = $this->fetch_users_with_on_bit('newattachment'); $url = bugdar::$options['trackerurl'] . "/viewattachment.php?attachmentid=$id"; foreach ($userlist AS $userid => $user) { $user = construct_user_display($this->registry->userinfo, false); $obsoletes = implode(', ', (array)$obsolete); eval('$email = "' . $this->registry->template->fetch(FetchEmailPath('notice_attachment.part', $this->_localeFromUserId($userid))) . '";'); $this->notices["$userid"][] = $email; } } // ################################################################### /** * Sends a new bug notification notice to all those who have the option * turned no. This does not use fetch_users_with_on_bit() because a * query is more effective. * * @access public * * @param array Bug values array * @param array Comment values array */ function send_new_bug_notice($bug, $comment) { $userinfo = $this->registry->db->query(" SELECT user.*, useremail.* FROM " . TABLE_PREFIX . "useremail AS useremail LEFT JOIN " . TABLE_PREFIX . "user AS user ON (user.userid = useremail.userid) WHERE useremail.relation = 0 AND useremail.mask & " . $this->registry->emailoptions['notifications']['newbug'] . " "); while ($userInfo = $this->registry->db->fetch_array($userinfo)) { if (!is_array($this->users["$userInfo[userid]"])) { $user = construct_user_display($this->registry->userinfo, false); $this->users["$userInfo[userid]"] = $userInfo; $product = bugdar::$datastore['product']["$bug[product]"]['title'] . '/' . ($bug['component'] ? bugdar::$datastore['product']["$bug[component]"]['title'] . '/' : '') . bugdar::$datastore['version']["$bug[version]"]['version']; eval('$email = "' . $this->registry->template->fetch(FetchEmailPath('notice_new_bug.part', $this->_localeFromUserId($userInfo['userid']))) . '";'); $this->notices["$userInfo[userid]"][] = $email; unset($this->users["$userInfo[userid]"]['mask'], $this->users["$userInfo[userid]"]['relation']); } $this->users["$userInfo[userid]"]['options']["$userInfo[relation]"] = $userInfo['mask']; } } // ################################################################### /** * Generates an array of users who have a given email notification flag * turned on in their bitfields. * * @access private * * @param string Notification bitfield name * * @return array Array of users and their data */ function fetch_users_with_on_bit($bitname) { $idlist = array(); foreach ($this->users AS $user) { foreach ($this->registry->emailoptions['relations'] AS $name => $bit) { if (in_array($user['userid'], $this->roles["$name"]) AND $user['options']["$bit"] & $this->registry->emailoptions['notifications']["$bitname"]) { $idlist[] = $user['userid']; } } } $masters = array_unique($idlist); $return = array(); foreach ($masters AS $userid) { $return["$userid"] =& $this->users["$userid"]; } return $return; } // ################################################################### /** * Compiles and sends the actual emails to users. * * @access public */ function finalize() { // get the current bug for permissions checks $bug = $this->registry->db->query_first("SELECT * FROM " . TABLE_PREFIX . "bug WHERE bugid = " . $this->bug['bugid']); foreach ($this->notices AS $userid => $noticelist) { if ($userid == $this->registry->userinfo['userid']) { $this->registry->debug("skipping user $userid because they're the one doing the thing"); continue; } // we wouldn't want people who favorite bugs getting hidden notices if (!check_bug_permissions($bug, $this->users["$userid"])) { $this->registry->debug("skipping user $userid ({$this->users[$userid]['email']}) because of permissions"); continue; } $parts = implode("\n\n", $noticelist); eval('$email = "' . $this->registry->template->fetch(FetchEmailPath('bugnotification.xml', $this->_localeFromUserId($userid))) . '";'); $email = $this->registry->xml->parse($email, true); $this->registry->mail->setSubject($email['email']['subject']['value']); $this->registry->mail->setBodyText($email['email']['bodyText']['value']); if (!empty($this->users["$userid"]['email'])) { $this->registry->mail->send($this->users["$userid"]['email'], $this->users["$userid"]['displayname']); } else { $this->registry->debug("not sending an email to " . $userid . " because they don't have one?"); } } } // ################################################################### /** * Returns the locale name from a given user ID * * @param integer User ID * * @return string Locale */ function _localeFromUserId($userid) { $langcode = bugdar::$datastore['language'][$this->users[$userid]['languageid']]['langcode']; if (!$langcode) { $langcode = bugdar::$datastore['language'][$this->registry->options['defaultlanguage']]['langcode']; } return $langcode; } }