[ Index ] |
PHP Cross Reference of Eventum |
[Summary view] [Print] [Text view]
1 <?php 2 /* vim: set expandtab tabstop=4 shiftwidth=4 encoding=utf-8: */ 3 // +----------------------------------------------------------------------+ 4 // | Eventum - Issue Tracking System | 5 // +----------------------------------------------------------------------+ 6 // | Copyright (c) 2003, 2004, 2005, 2006, 2007 MySQL AB | 7 // | | 8 // | This program is free software; you can redistribute it and/or modify | 9 // | it under the terms of the GNU General Public License as published by | 10 // | the Free Software Foundation; either version 2 of the License, or | 11 // | (at your option) any later version. | 12 // | | 13 // | This program is distributed in the hope that it will be useful, | 14 // | but WITHOUT ANY WARRANTY; without even the implied warranty of | 15 // | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 16 // | GNU General Public License for more details. | 17 // | | 18 // | You should have received a copy of the GNU General Public License | 19 // | along with this program; if not, write to: | 20 // | | 21 // | Free Software Foundation, Inc. | 22 // | 59 Temple Place - Suite 330 | 23 // | Boston, MA 02111-1307, USA. | 24 // +----------------------------------------------------------------------+ 25 // | Authors: João Prado Maia <jpm@mysql.com> | 26 // +----------------------------------------------------------------------+ 27 // 28 // @(#) $Id: bot.php 3266 2007-03-06 20:18:51Z glen $ 29 // 30 31 ini_set('memory_limit', '256M'); 32 33 require_once(dirname(__FILE__) . '/../../init.php'); 34 35 if (!file_exists(APP_CONFIG_PATH . 'irc_config.php')) { 36 echo "ERROR: No config specified. Please see setup/irc_config.php for config information.\n\n"; 37 exit; 38 } 39 40 require_once(APP_CONFIG_PATH . 'irc_config.php'); 41 require_once (APP_INC_PATH . 'db_access.php'); 42 require_once (APP_INC_PATH . 'class.auth.php'); 43 require_once (APP_INC_PATH . 'class.lock.php'); 44 require_once (APP_INC_PATH . 'class.issue.php'); 45 require_once (APP_INC_PATH . 'class.user.php'); 46 require_once(APP_PEAR_PATH . 'Net/SmartIRC.php'); 47 48 // if requested, clear the lock 49 if (in_array('--fix-lock', $argv)) { 50 Lock::release('irc_bot'); 51 echo "The lock file was removed successfully.\n"; 52 exit; 53 } 54 55 // acquire a lock to prevent multiple scripts from 56 // running at the same time 57 if (!Lock::acquire('irc_bot')) { 58 echo 'Error: Another instance of the script is still running. ', 59 "If this is not accurate, you may fix it by running this script with '--fix-lock' ", 60 "as the only parameter.\n"; 61 exit; 62 } 63 64 $auth = array(); 65 66 // map project_id => channel(s) 67 $channels = array(); 68 foreach ($irc_channels as $proj => $chan) { 69 $proj_id = Project::getID($proj); 70 $channels[$proj_id] = is_array($chan) ? $chan : array($chan); 71 } 72 73 class Eventum_Bot 74 { 75 function _isAuthenticated(&$irc, &$data) 76 { 77 global $auth; 78 79 if (in_array($data->nick, array_keys($auth))) { 80 return true; 81 } else { 82 $this->sendResponse($irc, $data->nick, 'Error: You need to be authenticated to run this command.'); 83 return false; 84 } 85 } 86 87 88 function _getEmailByNickname($nickname) 89 { 90 global $auth; 91 92 if (in_array($nickname, array_keys($auth))) { 93 return $auth[$nickname]; 94 } else { 95 return ''; 96 } 97 } 98 99 100 function clockUser(&$irc, &$data) 101 { 102 if (!$this->_isAuthenticated($irc, $data)) { 103 return; 104 } 105 $email = $this->_getEmailByNickname($data->nick); 106 107 $pieces = explode(' ', $data->message); 108 if ((count($pieces) == 2) && ($pieces[1] != 'in') && ($pieces[1] != 'out')) { 109 $this->sendResponse($irc, $data->nick, 'Error: wrong parameter count for "CLOCK" command. Format is "!clock [in|out]".'); 110 return; 111 } 112 if (@$pieces[1] == 'in') { 113 $res = User::clockIn(User::getUserIDByEmail($email)); 114 } elseif (@$pieces[1] == 'out') { 115 $res = User::clockOut(User::getUserIDByEmail($email)); 116 } else { 117 if (User::isClockedIn(User::getUserIDByEmail($email))) { 118 $msg = "clocked in"; 119 } else { 120 $msg = "clocked out"; 121 } 122 $this->sendResponse($irc, $data->nick, "You are currently $msg."); 123 return; 124 } 125 if ($res == 1) { 126 $this->sendResponse($irc, $data->nick, 'Thank you, you are now clocked ' . $pieces[1] . '.'); 127 } else { 128 $this->sendResponse($irc, $data->nick, 'Error clocking ' . $pieces[1] . '.'); 129 } 130 } 131 132 133 function listClockedInUsers(&$irc, &$data) 134 { 135 if (!$this->_isAuthenticated($irc, $data)) { 136 return; 137 } 138 139 $list = User::getClockedInList(); 140 if (count($list) == 0) { 141 $this->sendResponse($irc, $data->nick, 'There are no clocked-in users as of now.'); 142 } else { 143 $this->sendResponse($irc, $data->nick, 'The following is the list of clocked-in users:'); 144 foreach ($list as $name => $email) { 145 $this->sendResponse($irc, $data->nick, "$name: $email"); 146 } 147 } 148 } 149 150 151 function listQuarantinedIssues(&$irc, &$data) 152 { 153 if (!$this->_isAuthenticated($irc, $data)) { 154 return; 155 } 156 157 $list = Issue::getQuarantinedIssueList(); 158 if (count($list) == 0) { 159 $this->sendResponse($irc, $data->nick, 'There are no quarantined issues as of now.'); 160 } else { 161 $this->sendResponse($irc, $data->nick, 'The following are the details of the ' . count($list) . ' quarantined issue(s):'); 162 for ($i = 0; $i < count($list); $i++) { 163 $url = APP_BASE_URL . 'view.php?id=' . $list[$i]['iss_id']; 164 $msg = sprintf('Issue #%d: %s, Assignment: %s, %s', $list[$i]['iss_id'], $list[$i]['iss_summary'], $list[$i]['assigned_users'], $url); 165 $this->sendResponse($irc, $data->nick, $msg); 166 } 167 } 168 } 169 170 171 function listAvailableCommands(&$irc, &$data) 172 { 173 $commands = array( 174 'auth' => 'Format is "auth user@example.com password"', 175 'clock' => 'Format is "clock [in|out]"', 176 'list-clocked-in' => 'Format is "list-clocked-in"', 177 'list-quarantined' => 'Format is "list-quarantined"' 178 ); 179 $this->sendResponse($irc, $data->nick, "This is the list of available commands:"); 180 foreach ($commands as $command => $description) { 181 $this->sendResponse($irc, $data->nick, "$command: $description"); 182 } 183 } 184 185 186 function _updateAuthenticatedUser(&$irc, &$data) 187 { 188 global $auth; 189 190 $old_nick = $data->nick; 191 $new_nick = $data->message; 192 if (in_array($data->nick, array_keys($auth))) { 193 $auth[$new_nick] = $auth[$old_nick]; 194 unset($auth[$old_nick]); 195 } 196 } 197 198 199 function _removeAuthenticatedUser(&$irc, &$data) 200 { 201 global $auth; 202 203 if (in_array($data->nick, array_keys($auth))) { 204 unset($auth[$data->nick]); 205 } 206 } 207 208 209 function listAuthenticatedUsers(&$irc, &$data) 210 { 211 global $auth; 212 213 foreach ($auth as $nickname => $email) { 214 $this->sendResponse($irc, $data->nick, "$nickname => $email"); 215 } 216 } 217 218 219 function authenticate(&$irc, &$data) 220 { 221 global $auth; 222 223 $pieces = explode(' ', $data->message); 224 if (count($pieces) != 3) { 225 $this->sendResponse($irc, $data->nick, 'Error: wrong parameter count for "AUTH" command. Format is "!auth user@example.com password".'); 226 return; 227 } 228 $email = $pieces[1]; 229 $password = $pieces[2]; 230 // check if the email exists 231 if (!Auth::userExists($email)) { 232 $this->sendResponse($irc, $data->nick, 'Error: could not find a user account for the given email address "$email".'); 233 return; 234 } 235 // check if the given password is correct 236 if (!Auth::isCorrectPassword($email, $password)) { 237 $this->sendResponse($irc, $data->nick, 'Error: The email address / password combination could not be found in the system.'); 238 return; 239 } 240 // check if the user account is activated 241 if (!Auth::isActiveUser($email)) { 242 $this->sendResponse($irc, $data->nick, 'Error: Your user status is currently set as inactive. Please contact your local system administrator for further information.'); 243 return; 244 } else { 245 $auth[$data->nick] = $email; 246 $this->sendResponse($irc, $data->nick, 'Thank you, you have been successfully authenticated.'); 247 return; 248 } 249 } 250 251 252 /** 253 * Helper method to get the list of channels that should be used in the 254 * notifications 255 * 256 * @access private 257 * @param integer $prj_id The project ID 258 * @return array The list of channels 259 */ 260 function _getChannels($prj_id) 261 { 262 global $channels; 263 return $channels[$prj_id]; 264 } 265 266 267 /** 268 * Helper method to the projects a channel displays messages for. 269 * 270 * @access private 271 * @param string $channel The name of the channel 272 * @return array The projects displayed in the channel 273 */ 274 function _getProjectsForChannel($channel) 275 { 276 global $channels; 277 $projects = array(); 278 foreach ($channels as $prj_id => $prj_channels) { 279 foreach ($prj_channels as $prj_channel) { 280 if ($prj_channel == $channel) { 281 $projects[] = $prj_id; 282 } 283 } 284 } 285 return $projects; 286 } 287 288 289 /** 290 * Method used as a callback to send notification events to the proper 291 * recipients. 292 * 293 * @access public 294 * @param resource $irc The IRC connection handle 295 * @return void 296 */ 297 function notifyEvents(&$irc) 298 { 299 // check the message table 300 $stmt = "SELECT 301 ino_id, 302 ino_iss_id, 303 ino_prj_id, 304 ino_message 305 FROM 306 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "irc_notice 307 LEFT JOIN 308 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue 309 ON 310 iss_id=ino_iss_id 311 WHERE 312 ino_status='pending'"; 313 $res = $GLOBALS["db_api"]->dbh->getAll($stmt, DB_FETCHMODE_ASSOC); 314 for ($i = 0; $i < count($res); $i++) { 315 $channels = $this->_getChannels($res[$i]['ino_prj_id']); 316 if (count($channels) > 0) { 317 foreach ($channels as $channel) { 318 if ($res[$i]['ino_iss_id'] > 0) { 319 $res[$i]['ino_message'] .= ' - ' . APP_BASE_URL . 'view.php?id=' . $res[$i]['ino_iss_id']; 320 } elseif (substr($res[$i]['ino_message'], 0, strlen('New Pending Email')) == 'New Pending Email') { 321 $res[$i]['ino_message'] .= ' - ' . APP_BASE_URL . 'emails.php'; 322 } 323 if (count($this->_getProjectsForChannel($channel)) > 1) { 324 // if multiple projects display in the same channel, display project in message 325 $res[$i]['ino_message'] = "[" . Project::getName($res[$i]['ino_prj_id']) . "] " . $res[$i]['ino_message']; 326 } 327 $this->sendResponse($irc, $channel, $res[$i]['ino_message']); 328 } 329 // mark message as sent 330 $stmt = "UPDATE 331 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "irc_notice 332 SET 333 ino_status='sent' 334 WHERE 335 ino_id=" . $res[$i]['ino_id']; 336 $GLOBALS["db_api"]->dbh->query($stmt); 337 } 338 } 339 } 340 341 342 /** 343 * Method used to send a message to the given target. 344 * 345 * @access public 346 * @param resource $irc The IRC connection handle 347 * @param string $target The target for this message 348 * @param string $response The message to send 349 * @return void 350 */ 351 function sendResponse(&$irc, $target, $response) 352 { 353 // XXX: need way to handle messages with length bigger than 255 chars 354 if (!is_array($response)) { 355 $response = array($response); 356 } 357 foreach ($response as $line) { 358 if (substr($target, 0, 1) != '#') { 359 $type = SMARTIRC_TYPE_QUERY; 360 } else { 361 $type = SMARTIRC_TYPE_CHANNEL; 362 } 363 $irc->message($type, $target, $line, SMARTIRC_CRITICAL); 364 sleep(1); 365 } 366 } 367 368 369 function _joinChannels(&$irc) 370 { 371 foreach ($GLOBALS['channels'] as $prj_id => $channel_list) { 372 $irc->join($channel_list); 373 } 374 } 375 } 376 377 $bot = &new Eventum_Bot(); 378 $irc = &new Net_SmartIRC(); 379 $irc->setDebug(SMARTIRC_DEBUG_ALL); 380 $irc->setLogdestination(SMARTIRC_FILE); 381 $irc->setLogfile(APP_IRC_LOG); 382 $irc->setUseSockets(TRUE); 383 $irc->setAutoReconnect(TRUE); 384 $irc->setAutoRetry(TRUE); 385 $irc->setReceiveTimeout(600); 386 $irc->setTransmitTimeout(600); 387 388 $irc->registerTimehandler(3000, $bot, 'notifyEvents'); 389 390 // methods that keep track of who is authenticated 391 $irc->registerActionhandler(SMARTIRC_TYPE_QUERY, '^!?list-auth', $bot, 'listAuthenticatedUsers'); 392 $irc->registerActionhandler(SMARTIRC_TYPE_NICKCHANGE, '.*', $bot, '_updateAuthenticatedUser'); 393 $irc->registerActionhandler(SMARTIRC_TYPE_KICK|SMARTIRC_TYPE_QUIT|SMARTIRC_TYPE_PART, '.*', $bot, '_removeAuthenticatedUser'); 394 $irc->registerActionhandler(SMARTIRC_TYPE_LOGIN, '.*', $bot, '_joinChannels'); 395 396 // real bot commands 397 $irc->registerActionhandler(SMARTIRC_TYPE_QUERY, '^!?help', $bot, 'listAvailableCommands'); 398 $irc->registerActionhandler(SMARTIRC_TYPE_QUERY, '^!?auth ', $bot, 'authenticate'); 399 $irc->registerActionhandler(SMARTIRC_TYPE_QUERY, '^!?clock', $bot, 'clockUser'); 400 $irc->registerActionhandler(SMARTIRC_TYPE_QUERY, '^!?list-clocked-in', $bot, 'listClockedInUsers'); 401 $irc->registerActionhandler(SMARTIRC_TYPE_QUERY, '^!?list-quarantined', $bot, 'listQuarantinedIssues'); 402 403 $irc->connect($irc_server_hostname, $irc_server_port); 404 if (empty($username)) { 405 $irc->login($nickname, $realname); 406 } elseif (empty($password)) { 407 $irc->login($nickname, $realname, 0, $username); 408 } else { 409 $irc->login($nickname, $realname, 0, $username, $password); 410 } 411 $irc->listen(); 412 $irc->disconnect(); 413 414 // release the lock 415 Lock::release('irc_bot');
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Dec 19 21:21:33 2007 | Cross-referenced by PHPXref 0.7 |