r49832 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r49831‎ | r49832 | r49833 >
Date:19:44, 24 April 2009
Author:greg
Status:deferred
Tags:
Comment:
Extension for interaction with the Request Tracker system.
Modified paths:
  • /trunk/extensions/RT (added) (history)
  • /trunk/extensions/RT/README (added) (history)
  • /trunk/extensions/RT/RT.i18n.php (added) (history)
  • /trunk/extensions/RT/RT.php (added) (history)

Diff [purge]

Index: trunk/extensions/RT/RT.i18n.php
@@ -0,0 +1,29 @@
 2+<?php
 3+/**
 4+ * Internationalisation file for the RT extension.
 5+ *
 6+ * @ingroup Extensions
 7+ */
 8+
 9+/**
 10+ * Get all extension messages
 11+ *
 12+ * @return array
 13+ */
 14+
 15+$messages = array();
 16+
 17+/** English
 18+ * Greg Sabino Mullane <greg@endpoint.com>
 19+ */
 20+$messages['en'] = array(
 21+ 'rt-desc' => 'Fancy interface to RT (Request Tracker)',
 22+ 'rt-inactive' => 'The RT extension is not active',
 23+ 'rt-badquery' => 'The RT extension encountered an error when talking to the RT database',
 24+ 'rt-badlimit' => 'Invalid LIMIT (l) arg: must be a number. You tried: <b>$1</b>',
 25+ 'rt-badorderby' => 'Invalid ORDER BY (ob) arg: must be a standard field (see documentation). You tried: <b>$1</b>',
 26+ 'rt-badstatus' => 'Invalid status (s) arg: must be a standard field (see documentation). You tried: <b>$1</b>',
 27+ 'rt-badqueue' => 'Invalid queue (q) arg: must be a simple word. You tried: <b>$1</b>',
 28+ 'rt-badowner' => 'Invalid owner (o) arg: must be a valud username. You tried: <b>$1</b>',
 29+ 'rt-nomatches' => 'No matching RT tickets were found',
 30+);
Property changes on: trunk/extensions/RT/RT.i18n.php
___________________________________________________________________
Added: svn:eol-style
131 + native
Index: trunk/extensions/RT/RT.php
@@ -0,0 +1,487 @@
 2+<?php
 3+/**
 4+ * RT (Request Tracker) extension for MediaWiki
 5+ *
 6+ * @file
 7+ * @ingroup Extensions
 8+ *
 9+ * Usage: Add the following three lines to LocalSettings.php:
 10+ * require_once( "$IP/extensions/RT/RT.php" );
 11+ * $wgRequestTracker_URL = 'https://rt.example.com/Ticket/Display.html?id';
 12+ * $wgRequestTracker_DBconn = 'user=rt dbname=rt';
 13+ *
 14+ * For other options, please see the complete documentation
 15+ *
 16+ * @author Greg Sabino Mullane <greg@endpoint.com>
 17+ * @license MIT <http://www.opensource.org/licenses/mit-license.php>
 18+ * @version 1.8
 19+ * @link http://www.mediawiki.org/wiki/Extension:RT
 20+ */
 21+
 22+$rt_uri = 'http://www.mediawiki.org/wiki/Extension:RT';
 23+
 24+## Default values: Override in LocalSettings.php, not here!
 25+$wgRequestTracker_URL = 'http://rt.example.com/Ticket/Display.html?id';
 26+$wgRequestTracker_DBconn = 'user=rt dbname=rt';
 27+$wgRequestTracker_Formats = array();
 28+$wgRequestTracker_Cachepage = 0;
 29+$wgRequestTracker_Useballoons = 1;
 30+$wgRequestTracker_Active = 1;
 31+
 32+## Time formatting
 33+## Example formats:
 34+## FMHH:MI AM FMMon DD, YYYY => 2:42 PM Jan 23, 2009
 35+## HH:MI FMMonth DD, YYYY => 14:42 January 23, 2009
 36+## YYYY/MM/DD => 2009/01/23
 37+## For a more complete list of possibilities, please visit:
 38+## http://www.postgresql.org/docs/current/interactive/functions-formatting.html
 39+$wgRequestTracker_TIMEFORMAT_LASTUPDATED = 'FMHH:MI AM FMMonth DD, YYYY';
 40+$wgRequestTracker_TIMEFORMAT_LASTUPDATED2 = 'FMMonth DD, YYYY';
 41+$wgRequestTracker_TIMEFORMAT_CREATED = 'FMHH:MI AM FMMonth DD, YYYY';
 42+$wgRequestTracker_TIMEFORMAT_CREATED2 = 'FMMonth DD, YYYY';
 43+$wgRequestTracker_TIMEFORMAT_RESOLVED = 'FMHH:MI AM FMMonth DD, YYYY';
 44+$wgRequestTracker_TIMEFORMAT_RESOLVED2 = 'FMMonth DD, YYYY';
 45+$wgRequestTracker_TIMEFORMAT_NOW = 'FMHH:MI AM FMMonth DD, YYYY';
 46+
 47+// Ensure nothing is done unless run via MediaWiki
 48+if ( !defined( 'MEDIAWIKI' ) ) {
 49+ echo( "This is an extension to the MediaWiki package and cannot be run standalone.\n" );
 50+ echo( "Please visit $rt_uri\n" );
 51+ die( -1 );
 52+}
 53+
 54+// Credits for Special:Version
 55+$wgExtensionCredits['parserhook'][] = array(
 56+ 'name' => 'RT',
 57+ 'version' => '1.8',
 58+ 'author' => array( 'Greg Sabino Mullane' ),
 59+ 'description' => 'Fancy interface to RT (Request Tracker)',
 60+ 'descriptionmsg' => 'rt-desc',
 61+ 'url' => $rt_uri,
 62+ );
 63+
 64+// Pull in the Internationalization file
 65+$wgExtensionMessagesFiles['RT'] = dirname( __FILE__ ) . '/RT.i18n.php';
 66+
 67+// Use a hook to enable control of parsing <rt>...</rt> content
 68+$wgExtensionFunctions[] = 'efRT_Setup';
 69+function efRT_Setup() {
 70+
 71+ global $wgParser, $wgUploadDirectory, $wgCommandLineMode;
 72+
 73+ wfLoadExtensionMessages( 'RT' );
 74+
 75+ if ( $wgCommandLineMode ) {
 76+ return true;
 77+ }
 78+
 79+ $wgParser->setHook( 'rt', 'rtRender' );
 80+
 81+ return true;
 82+}
 83+
 84+
 85+// This is called to process <rt>...</rt> within a page
 86+function rtRender( $input, $args=array(), $parser=null ) {
 87+
 88+ global $wgRequestTracker_Cachepage, $wgRequestTracker_Active, $wgRequestTracker_DBconn,
 89+ $wgRequestTracker_TIMEFORMAT_LASTUPDATED,
 90+ $wgRequestTracker_TIMEFORMAT_LASTUPDATED2,
 91+ $wgRequestTracker_TIMEFORMAT_CREATED,
 92+ $wgRequestTracker_TIMEFORMAT_CREATED2,
 93+ $wgRequestTracker_TIMEFORMAT_RESOLVED,
 94+ $wgRequestTracker_TIMEFORMAT_RESOLVED2,
 95+ $wgRequestTracker_TIMEFORMAT_NOW;
 96+
 97+ // Grab the number if one was given between the <tr> tags
 98+ $ticketnum = 0;
 99+ $matches = array();
 100+ if ( preg_match( '/^\s*(\d+)\s*$/', $input, $matches ) ) {
 101+ $ticketnum = $matches[0];
 102+ }
 103+
 104+ // Disable all caching unless told not to
 105+ if ( !$wgRequestTracker_Cachepage ) {
 106+ $parser->disableCache();
 107+ }
 108+
 109+ // Try and connect to the database if we are active
 110+ if ( $wgRequestTracker_Active ) {
 111+ global $wgUser;
 112+ $dbh = pg_connect( $wgRequestTracker_DBconn );
 113+ if ( $dbh == false ) {
 114+ wfDebug( "DB connection error\n" );
 115+ wfDebug( "Connection string: $wgRequestTracker_DBconn\n" );
 116+ $wgRequestTracker_Active = 0;
 117+ }
 118+ $tz = $wgUser->getOption( 'timecorrection' );
 119+ if ( $tz ) {
 120+ $found = array();
 121+ if ( preg_match ( '/((-?\d\d?):(\d\d))/', $tz, $found ) ) {
 122+ if ( $found[3] === '00' ) {
 123+ pg_query( "SET TIME ZONE $found[2]" );
 124+ }
 125+ else {
 126+ print( "SET TIME ZONE INTERVAL '$found[1]' HOUR TO MINUTE" );
 127+ }
 128+ }
 129+ }
 130+ }
 131+
 132+ // If we are not 'active', we leave right away, with minimal output
 133+ if ( !$wgRequestTracker_Active ) {
 134+ if ( $ticketnum ) {
 135+ return "<span class='rt-ticket-inactive'>RT #$ticketnum</span>";
 136+ }
 137+ $msg = wfMsg( 'rt-inactive' );
 138+ return "<table class='rt-table-inactive' border='1'><tr><td>$msg</td></tr></table>";
 139+ }
 140+
 141+ // Standard info we gather
 142+ $TZ = "AT TIME ZONE 'GMT'";
 143+ $ticketinfo = 't.id, t.subject, t.priority, INITCAP(t.status) AS status, q.name AS queue,'
 144+ . ' COALESCE(u.realname, u.name) AS owner,'
 145+ . ' u.name AS username,'
 146+ . ' COALESCE(u2.realname, u2.name) AS creator,'
 147+ . " TO_CHAR(t.lastupdated $TZ, '$wgRequestTracker_TIMEFORMAT_LASTUPDATED'::text) AS lastupdated,"
 148+ . " TO_CHAR(t.lastupdated $TZ, '$wgRequestTracker_TIMEFORMAT_LASTUPDATED2'::text) AS lastupdated2,"
 149+ . " TO_CHAR(now() $TZ, '$wgRequestTracker_TIMEFORMAT_NOW'::text) AS nowtime,"
 150+ . " TO_CHAR(t.created $TZ, '$wgRequestTracker_TIMEFORMAT_CREATED'::text) AS created,"
 151+ . " TO_CHAR(t.created $TZ, '$wgRequestTracker_TIMEFORMAT_CREATED2'::text) AS created2,"
 152+ . " TO_CHAR(t.resolved $TZ, '$wgRequestTracker_TIMEFORMAT_RESOLVED'::text) AS resolved,"
 153+ . " TO_CHAR(t.resolved $TZ, '$wgRequestTracker_TIMEFORMAT_RESOLVED2'::text) AS resolved2,"
 154+ . " CASE WHEN (now() $TZ - t.created) <= '1 second'::interval THEN '1 second' ELSE"
 155+ . " CASE WHEN (now() $TZ - t.created) <= '2 minute'::interval THEN EXTRACT(seconds FROM now() $TZ - t.created) || ' seconds' ELSE"
 156+ . " CASE WHEN (now() $TZ - t.created) <= '2 hour'::interval THEN EXTRACT(minutes FROM now() $TZ - t.created) || ' minutes' ELSE"
 157+ . " CASE WHEN (now() $TZ - t.created) <= '2 day'::interval THEN EXTRACT(hours FROM now() $TZ - t.created) || ' hours' ELSE"
 158+ . " EXTRACT(days FROM now() $TZ - t.created) || ' days' END END END END AS age";
 159+
 160+ // The standard query
 161+ $ticketquery = "SELECT $ticketinfo FROM tickets t"
 162+ . ' JOIN users u ON t.owner = u.id'
 163+ . ' JOIN users u2 ON t.creator = u2.id'
 164+ . ' JOIN queues q ON t.queue = q.id';
 165+
 166+ // If just a single number, treat it as <rt>#</rt>
 167+ if ( 1 === count( $args ) ) {
 168+ if ( preg_match( '/^\d+$/', key($args) ) ) {
 169+ $ticketnum = key($args);
 170+ }
 171+ }
 172+
 173+ // Look up a single ticket number
 174+ if ( $ticketnum ) {
 175+ $SQL = "$ticketquery AND t.id = $ticketnum";
 176+ $res = pg_query( $dbh, $SQL );
 177+ if ( !$res ) {
 178+ die ( wfMsg( 'rt-badquery' ) );
 179+ }
 180+ $info = pg_fetch_array( $res );
 181+ if ( !$info ) {
 182+ return "<span class='rt-nosuchticket'>RT #$ticketnum</span>";
 183+ }
 184+ return rtFancyLink( $info, $args, $parser, 0 );
 185+ }
 186+
 187+ // Add in a LIMIT clause if l=xx was used
 188+ $limit = '';
 189+ if ( array_key_exists( 'l', $args ) ) {
 190+ $limit = trim( $args['l'] );
 191+ if ( !preg_match( '/^ *\d+ *$/', $limit ) ) {
 192+ die ( wfMsg ( 'rt-badlimit', $limit ) );
 193+ }
 194+ $limit = " LIMIT $limit";
 195+ }
 196+
 197+ // Change the default ORDER BY clause if ob=xx was used
 198+ $orderby = 'ORDER BY t.lastupdated DESC, t.id';
 199+ $valid_orderby = array
 200+ (
 201+ 'id' => 't.id',
 202+ 'subject' => 't.subject',
 203+ 'priority' => 't.priority',
 204+ 'status' => 't.status',
 205+ 'queue' => 'q.name',
 206+ 'owner' => 'COALESCE(u.realname, u.name)',
 207+ 'creator' => 'COALESCE(u2.realname, u2.name)',
 208+ 'lastupdated' => 't.lastupdated',
 209+ 'created' => 't.created',
 210+ 'resolved' => 't.resolved',
 211+ );
 212+ if ( array_key_exists( 'ob', $args ) ) {
 213+ $orderby = 'ORDER BY';
 214+ $orderbyargs = trim( strtolower( $args['ob'] ) );
 215+ foreach ( preg_split( '/\s*,\s*/', $orderbyargs ) as $word ) {
 216+ $oldlen = strlen( $word );
 217+ $word = ltrim( $word, '!' );
 218+ $mod = $oldlen !== strlen( $word ) ? ' DESC' : '';
 219+ if ( !preg_match( '/^\w+$/', $word ) ) {
 220+ die ( wfMsg ( 'rt-badorderby', $word ) );
 221+ }
 222+ if ( array_key_exists( $word, $valid_orderby ) ) {
 223+ $word = $valid_orderby[$word];
 224+ }
 225+ else if ( !preg_match ('/^\d+$/', $word ) ) {
 226+ die ( wfMsg ( 'rt-badorderby', $word ) );
 227+ }
 228+ $orderby .= " $word$mod,";
 229+ }
 230+ $orderby = rtrim( $orderby, ',' );
 231+ }
 232+
 233+ // Determine what status to use. Default is new and open:
 234+ $searchstatus = "AND t.status IN ('new','open')";
 235+ $valid_status = array( 'new', 'open', 'resolved', 'deleted', 'stalled', 'rejected' );
 236+ if ( array_key_exists( 's', $args ) ) {
 237+ $statusargs = trim( strtolower( $args['s'] ) );
 238+ if ( $statusargs === 'all' ) {
 239+ $searchstatus = '';
 240+ }
 241+ else {
 242+ $searchstatus = 'AND t.status IN (';
 243+ foreach ( preg_split( '/\s*,\s*/', $statusargs ) as $word ) {
 244+ if ( !in_array( $word, $valid_status ) ) {
 245+ die ( wfMsg ( 'rt-badstatus', $word ) );
 246+ }
 247+ $searchstatus .= "'$word',";
 248+ }
 249+ $searchstatus = preg_replace( '/.$/', ')', $searchstatus );
 250+ }
 251+ }
 252+
 253+ // See if we are limiting to one or more queues
 254+ $searchq = '';
 255+ if ( array_key_exists('q', $args ) ) {
 256+ $qargs = trim( strtolower( $args['q'] ) );
 257+ $searchq = 'AND LOWER(q.name) IN (';
 258+ foreach ( preg_split( '/\s*,\s*/', $qargs ) as $word ) {
 259+ $word = trim( $word );
 260+ if ( !preg_match( '/^[\w \.-]+$/', $word ) ) {
 261+ die ( wfMsg ( 'rt-badqueue', $word ) );
 262+ }
 263+ $searchq .= "'$word',";
 264+ }
 265+ $searchq = preg_replace( '/.$/', ')', $searchq );
 266+ }
 267+
 268+ // See if we are limiting to one or more owners
 269+ $searchowner = '';
 270+ if ( array_key_exists('o', $args ) ) {
 271+ $oargs = trim( strtolower( $args['o'] ) );
 272+ $searchowner = 'AND LOWER(u.name) IN (';
 273+ foreach ( preg_split( '/\s*,\s*/', $oargs ) as $word ) {
 274+ $word = trim( $word );
 275+ if ( !preg_match( '/^[\w\@\.\-\:\/]+$/', $word ) ) {
 276+ die ( wfMsg ( 'rt-badowner', $word ) );
 277+ }
 278+ $searchowner .= "'$word',";
 279+ }
 280+ $searchowner = preg_replace( '/.$/', ')', $searchowner );
 281+ }
 282+
 283+ // Build and run the final query
 284+ $SQL = "$ticketquery $searchq $searchowner $searchstatus $orderby $limit";
 285+ $res = pg_query( $dbh, $SQL );
 286+ if ( !$res ) {
 287+ die ( wfMsg( 'rt-badquery' ) );
 288+ }
 289+ $info = pg_fetch_all( $res );
 290+ if ( !$info ) {
 291+ $msg = wfMsg( 'rt-nomatches' );
 292+ return "<table class='rt-table-empty' border='1'><tr><th>$msg</th><tr></table>";
 293+ }
 294+
 295+ // Figure out what columns to show
 296+ // Anything specifically requested is shown
 297+ // Everything else is either on or off by default, but can be overidden
 298+ $output = '';
 299+
 300+ // The queue: show by default unless searching a single queue
 301+ $showqueue = 1;
 302+ if ( array_key_exists('noqueue', $args )
 303+ || ($searchq
 304+ && false === strpos( $searchq, ',' )
 305+ && !array_key_exists( 'queue', $args ) ) ) {
 306+ $showqueue = 0;
 307+ }
 308+
 309+ // The owner: show by default unless searching a single owner
 310+ $showowner = 1;
 311+ if ( array_key_exists( 'noowner', $args )
 312+ || ( $searchowner
 313+ && false === strpos( $searchowner, ',' )
 314+ && !array_key_exists( 'owner', $args ) ) ) {
 315+ $showowner = 0;
 316+ }
 317+
 318+ // The status: show by default unless searching a single status
 319+ $showstatus = 1;
 320+ if ( array_key_exists( 'nostatus', $args )
 321+ || ( false === strpos($searchstatus, ',' )
 322+ && !array_key_exists( 'status', $args ) ) ) {
 323+ $showstatus = 0;
 324+ }
 325+
 326+ // Things we always show unless told not to:
 327+ $showsubject = ! array_key_exists( 'nosubject', $args );
 328+ $showupdated = ! array_key_exists( 'noupdated', $args );
 329+ $showticket = ! array_key_exists( 'noticket', $args );
 330+
 331+ // Things we don't show unless asked to:
 332+ $showpriority = array_key_exists( 'priority', $args );
 333+ $showupdated2 = array_key_exists( 'updated2', $args );
 334+ $showcreated = array_key_exists( 'created', $args );
 335+ $showcreated2 = array_key_exists( 'created2', $args );
 336+ $showresolved = array_key_exists( 'resolved', $args );
 337+ $showresolved2 = array_key_exists( 'resolved2', $args );
 338+ $showage = array_key_exists( 'age', $args );
 339+
 340+ // Unless 'tablerows' has been set, output the table and header tags
 341+ if ( !array_key_exists( 'tablerows',$args ) ) {
 342+
 343+ $output = "<table class='rt-table' border='1'><tr>";
 344+
 345+ if ( $showticket ) { $output .= '<th>Ticket</th>'; }
 346+ if ( $showqueue ) { $output .= '<th>Queue</th>'; }
 347+ if ( $showsubject ) { $output .= '<th>Subject</th>'; }
 348+ if ( $showstatus ) { $output .= '<th>Status</th>'; }
 349+ if ( $showpriority ) { $output .= '<th>Priority</th>'; }
 350+ if ( $showowner ) { $output .= '<th>Owner</th>'; }
 351+ if ( $showupdated ) { $output .= '<th>Last updated</th>'; }
 352+ if ( $showupdated2 ) { $output .= '<th>Last updated</th>'; }
 353+ if ( $showcreated ) { $output .= '<th>Created</th>'; }
 354+ if ( $showcreated2 ) { $output .= '<th>Created</th>'; }
 355+ if ( $showresolved ) { $output .= '<th>Resolved</th>'; }
 356+ if ( $showresolved2 ) { $output .= '<th>Resolved</th>'; }
 357+ if ( $showage ) { $output .= '<th>Age</th>'; }
 358+
 359+ $output .= '</tr>';
 360+ }
 361+
 362+ foreach ( $info as $row ) {
 363+
 364+ if ( $showticket ) {
 365+ $id = rtFancyLink( $row, $args, $parser, 1 );
 366+ $output .= "<td style='white-space: nowrap'>$id</td>";
 367+ }
 368+ if ( $showqueue ) { $output .= '<td>' . htmlspecialchars( $row['queue'] ) . '</td>'; }
 369+ if ( $showsubject ) { $output .= '<td>' . htmlspecialchars( $row['subject'] ) . '</td>'; }
 370+ if ( $showstatus ) { $output .= '<td>' . htmlspecialchars( $row['status'] ) . '</td>'; }
 371+ if ( $showpriority ) { $output .= '<td>' . htmlspecialchars( $row['priority'] ). '</td>'; }
 372+ if ( $showowner ) { $output .= '<td>' . htmlspecialchars( $row['owner'] ) . '</td>'; }
 373+ if ( $showupdated ) { $output .= '<td>' . $row['lastupdated'] . '</td>'; }
 374+ if ( $showupdated2 ) { $output .= '<td>' . $row['lastupdated2'] . '</td>'; }
 375+ if ( $showcreated ) { $output .= '<td>' . $row['created'] . '</td>'; }
 376+ if ( $showcreated2 ) { $output .= '<td>' . $row['created2'] . '</td>'; }
 377+ if ( $showresolved ) { $output .= '<td>' . $row['resolved'] . '</td>'; }
 378+ if ( $showresolved2 ) { $output .= '<td>' . $row['resolved2'] . '</td>'; }
 379+ if ( $showage ) { $output .= '<td>' . $row['age'] . '</td>'; }
 380+ $output .= '<tr>';
 381+ }
 382+
 383+ if ( !array_key_exists( 'tablerows',$args ) ) {
 384+ $output .= '</table>';
 385+ }
 386+
 387+ return $output;
 388+}
 389+
 390+
 391+function rtFancyLink( $row, $args, $parser, $istable ) {
 392+
 393+ global $wgRequestTracker_URL, $wgRequestTracker_Formats, $wgRequestTracker_Useballoons;
 394+
 395+ $ticketnum = $row['id'];
 396+ $ret = "[$wgRequestTracker_URL=$ticketnum RT #$ticketnum]";
 397+
 398+ ## Check for any custom format args in the rt tag.
 399+ ## If any are found, use that and ignore any other args
 400+ $foundformat = 0;
 401+ foreach ( array_keys( $args ) as $val ) {
 402+ if ( array_key_exists( $val, $wgRequestTracker_Formats ) ) {
 403+ $format = $wgRequestTracker_Formats[$val];
 404+ foreach ( array_keys( $row ) as $rev ) {
 405+ $format = str_replace( "?$rev?", "$row[$rev]", $format );
 406+ }
 407+ $ret .= " $format";
 408+ $foundformat = 1;
 409+ break;
 410+ }
 411+ }
 412+
 413+ ## Process any column-based args to the rt tag
 414+ if ( !$foundformat and !$istable ) {
 415+ foreach ( array_keys( $args ) as $val ) {
 416+ if ( array_key_exists( $val, $row ) ) {
 417+ $format = $args[$val];
 418+ if ( false === strpos( $format, '?' ) ) {
 419+ $showname = $val === 'lastupdated' ? 'Last updated' : ucfirst( $val );
 420+ $ret .= " $showname: $row[$val]";
 421+ }
 422+ else {
 423+ $ret .= " " . str_replace( '?', $row[$val], $format );
 424+ }
 425+ }
 426+ }
 427+ }
 428+
 429+ $ret = $parser->recursiveTagParse( $ret );
 430+
 431+ // Not using balloons? Just return the current text
 432+ if ( !$wgRequestTracker_Useballoons || array_key_exists( 'noballoon', $args ) ) {
 433+ return "<span class='rt-ticket-noballoon'>$ret</span>";
 434+ }
 435+
 436+ $safesub = preg_replace( '/\"/', '\"', $row['subject'] );
 437+ $safesub = preg_replace( '/\'/', "\'", $safesub );
 438+ $safesub = htmlspecialchars( $safesub );
 439+
 440+ $safeowner = $row['owner'];
 441+ if ($row['owner'] !== $row['username']) {
 442+ $safeowner .= " ($row[username])";
 443+ }
 444+ $safeowner = preg_replace( '/\"/', '\"', $safeowner );
 445+ $safeowner = preg_replace( '/\'/', "\'", $safeowner );
 446+ $safeowner = htmlspecialchars( $safeowner );
 447+
 448+ $safeq = preg_replace( '/\"/', '\"', $row['queue'] );
 449+ $safeq = preg_replace( '/\'/', "\'", $safeq );
 450+ $safeq = htmlspecialchars( $safeq );
 451+
 452+ $text = "RT #<b>$ticketnum</b>";
 453+ $text .= "<br />Status: <b>$row[status]</b>";
 454+ $text .= "<br />Subject: <b>$safesub</b>";
 455+ $text .= "<br />Owner: <b>$safeowner</b>";
 456+ $text .= "<br />Queue: <b>$safeq</b>";
 457+ $text .= "<br />Created: <b>$row[created]</b>";
 458+ if ( $row['status'] === 'Resolved' ) {
 459+ $text .= "<br />Resolved: <b>$row[resolved]</b>";
 460+ }
 461+ else {
 462+ $text .= "<br />Last updated: <b>$row[lastupdated]</b>";
 463+ }
 464+
 465+ ## Prepare some balloon-tek
 466+ $link = isset( $args['link'] ) ? $args['link'] : '';
 467+ $target = isset( $args['target'] ) ? $args['target'] : '';
 468+ $sticky = isset( $args['sticky'] ) ? $args['sticky'] : '0';
 469+ $width = isset( $args['width'] ) ? $args['width'] : '0';
 470+
 471+ $event = isset( $args['click'] ) && $args['click'] && !$link ? 'onclick' : 'onmouseover';
 472+ $event2 = '';
 473+ $event = "$event=\"balloon.showTooltip(event,'${text}',${sticky},${width})\"";
 474+
 475+ if ( preg_match( '/onclick/',$event ) && $args['hover'] ) {
 476+ $event2 = " onmouseover=\"balloon.showTooltip(event,'" . $args['hover'] . "',0,${width})\"";
 477+ }
 478+
 479+ $has_style = isset( $args['style'] ) && $args['style'];
 480+ $style = "style=\"" . ($has_style ? $args['style'] . ";cursor:pointer\"" : "cursor:pointer\"");
 481+ $target = $target ? "target=${target}" : '';
 482+ $output = "<span class='rt-ticket' ${event} ${event2} ${style}>$ret</span>";
 483+
 484+ return $output;
 485+}
 486+
 487+$rtDate = gmdate( 'YmdHis', @filemtime( __FILE__ ) );
 488+$wgCacheEpoch = max( $wgCacheEpoch, $rtDate );
Property changes on: trunk/extensions/RT/RT.php
___________________________________________________________________
Added: svn:eol-style
1489 + native
Index: trunk/extensions/RT/README
@@ -0,0 +1,127 @@
 2+This is the RequestTracker (RT) extension for MediaWiki. It allows
 3+you to view information from RT tickets on your wiki in real time.
 4+
 5+Written by Greg Sabino Mullane, End Point Corporation <greg@endpoint.com>
 6+
 7+This is only a quick overview. Full documentation can be found at:
 8+
 9+http://www.mediawiki.org/wiki/Extension:RT
 10+
 11+
 12+PREREQUISITES
 13+=============
 14+
 15+This module requires the use of the balloons extension:
 16+
 17+http://www.mediawiki.org/wiki/Extension:Balloons
 18+
 19+
 20+INSTALLATION
 21+============
 22+
 23+To install the RT module, add this line to your LocalSettings.php file:
 24+
 25+require_once("$IP/extensions/RT/RT.php");
 26+
 27+You should also add the global configuration values directly below
 28+this line. Each is described below by example below:
 29+
 30+$wgRequestTrackerURL = 'https://rt.endpoint.com/Ticket/Display.html?id';
 31+
 32+This should be the URL to your RT instance, and is used for generating links.
 33+
 34+
 35+$wgRequestTrackerDBconn = 'user=rt dbname=rt';
 36+
 37+This is the connection string to get to your RT database. This is passed directly
 38+to pg_connect, so you can also use 'port' and 'host' as needed.
 39+
 40+$wgRequestTrackerFormats = array
 41+ (
 42+ 'normal' => "Subject: <b>?subject?</b> Status:?status?",
 43+ );
 44+
 45+This is a list of specific named formats you wish to use. Items to be
 46+replaced as placeholders should be surrounded by question marks.
 47+
 48+
 49+USAGE
 50+=====
 51+
 52+To use this extension, simply add <rt></rt> tags to the source of
 53+your wiki. To view specific tickets, simply put the number between
 54+the tags like so:
 55+
 56+<rt>1234</rt>
 57+
 58+This will create an external link to ticket 1234. If the ticket does
 59+not exist, the non-hyperlinked text "RT #1234" will appear.
 60+
 61+You can also control what information is displayed. The best way to do
 62+this is to create a global format by adding entries to the
 63+$wgRequestTrackerFormats array. Placeholders should be entered as words
 64+with question marks on both sides of them. Current information that
 65+can be displayed is:
 66+
 67+subject
 68+status
 69+lastupdated
 70+created
 71+owner
 72+queue
 73+priority
 74+resolved
 75+
 76+The RT links also have a balloon mouseover effect that gives all of the current
 77+information (as of the last page load) for the ticket. This allows you to save
 78+valuable screen real-estate.
 79+
 80+You can also generate a list of tickets, either by owner, or by queue. To view
 81+a table of all new and open tickets by a specific user, use this syntax:
 82+
 83+<rt o="Greg"></rt>
 84+
 85+By default, the table will show columns with the RT ticket id, the subject,
 86+the status, and the queue. Each of these can be turned off by adding noxxx
 87+to the inside of the rt tag. You can also add in the additional fields
 88+'update' and 'create'. For example, the following line will show a table with
 89+all new or open tickets owned by the user 'Greg', with no status, and
 90+an additional column showing the last update time:
 91+
 92+<rt o="greg" nostatus update></rt>
 93+
 94+The owner name is case-insensitive. Multiple owners can be separated by commas,
 95+in which case an additional column named "Owner" will be added. This can be turned
 96+off by adding "noowner", or forced on by using "owner".
 97+
 98+Rows are ordered with the most recently updated tickets at the top.
 99+
 100+In a similar way to the 'owner' query, you can search by the name of an RT queue.
 101+The following line will show all open or new tickets in the sales queue:
 102+
 103+<rt q="sales"></rt>
 104+
 105+The queue name is case-insensitive. Multiple queues can be separated by commas,
 106+in which case an additional column named "Queue" will be added. This can be turned
 107+off by adding "noqueue", or forced on by using "queue".
 108+
 109+For table outputs, the status criteria can be changed from the default of
 110+'new and open' by adding a "c" argument to tell it what criteria to use
 111+for the status. For example, to view all resolved RT ticket in the queue 'Postgres':
 112+
 113+<rt q="postgres" c="resolved"></rt>
 114+
 115+To add a limit to any table output, add a "l" argument as the maximum number of
 116+sorted rows to show. Remember that rows sort with the most recently updated tickets
 117+at the top.
 118+
 119+
 120+WARNING
 121+=======
 122+
 123+These tags will query the RT database on every page load, so don't put these on
 124+very highly trafficed page unless your database can handle the load.
 125+
 126+Use of these tags is akin to making your entire RT database available to anyone who
 127+can edit the wiki. Hopefully, those communities have a complete overlap.
 128+

Status & tagging log