r37623 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r37622‎ | r37623 | r37624 >
Date:17:30, 13 July 2008
Author:siebrand
Status:old
Tags:
Comment:
Import BugzillaReports by Ian Homer from http://blog.bemoko.com/wp-content/uploads/2008/07/BugzillaReports-0.8.tar.gz.
* mainly unchanged. Removed trailing whitespace and some indentation fixes
* added descriptionmsg

Other:
* License not known; assuming default: GPL 2 or higher (checking...)
* Looks like it still needs some work (lazy loading, i18n)
* Documented at http://www.mediawiki.org/wiki/Extension:Bugzilla_Reports
* will encourage author to request SVN access to keep maintaining this
Modified paths:
  • /trunk/extensions/BugzillaReports (added) (history)
  • /trunk/extensions/BugzillaReports/BMWExtension.php (added) (history)
  • /trunk/extensions/BugzillaReports/BSQLQuery.php (added) (history)
  • /trunk/extensions/BugzillaReports/BugzillaQuery.php (added) (history)
  • /trunk/extensions/BugzillaReports/BugzillaReports.i18n.php (added) (history)
  • /trunk/extensions/BugzillaReports/BugzillaReports.php (added) (history)
  • /trunk/extensions/BugzillaReports/README (added) (history)
  • /trunk/extensions/BugzillaReports/RELEASE-NOTES (added) (history)
  • /trunk/extensions/BugzillaReports/scripts (added) (history)
  • /trunk/extensions/BugzillaReports/scripts/jquery-1.2.6.min.js (added) (history)
  • /trunk/extensions/BugzillaReports/skins (added) (history)
  • /trunk/extensions/BugzillaReports/skins/bz_main.css (added) (history)

Diff [purge]

Index: trunk/extensions/BugzillaReports/BugzillaQuery.php
@@ -0,0 +1,872 @@
 2+<?php
 3+require_once("$IP/extensions/BugzillaReports/BSQLQuery.php");
 4+/**
 5+ * A bugzilla query
 6+ */
 7+class BugzillaQuery extends BSQLQuery {
 8+ var $supportedParameters=array (
 9+ 'alias' => 'field',
 10+ 'bar' => 'column',
 11+ 'blocks' => 'field-depends',
 12+ 'cc' => 'field',
 13+ 'columns' => 'columns',
 14+ 'component' => 'field',
 15+ 'deadline' => 'field-date',
 16+ 'depends' => 'field-depends',
 17+ 'detailsrow' => 'columns',
 18+ 'detailsrowprepend' => 'free',
 19+ 'flag' => 'field-special',
 20+ 'format' => 'value', # table (default) or list
 21+ 'from' => 'field',
 22+ 'group' => 'sort',
 23+ 'groupformat' => 'value',
 24+ 'heading' => 'free',
 25+ 'headers' => 'value',
 26+ 'id' => 'field',
 27+ 'lastcomment' => 'boolean',
 28+ 'maxrows' => 'value',
 29+ 'maxrowsbar' => 'value',
 30+ 'milestone' => 'field',
 31+ 'modified' => 'field-date',
 32+ 'nameformat' => 'value', # real (default),tla or login
 33+ 'order' => 'value',
 34+ 'priority' => 'field',
 35+ 'product' => 'field',
 36+ 'quickflag' => 'value',
 37+ 'noresultsmessage' => 'free',
 38+ 'search' => 'field-text',
 39+ 'severity' => 'field',
 40+ 'sort' => 'sort',
 41+ 'status' => 'field',
 42+ 'to' => 'field',
 43+ 'url' => 'field',
 44+ 'version' => 'field',
 45+ 'votes' => 'field-int',
 46+ );
 47+ var $defaultParameters=array (
 48+ 'columns' => 'id,priority,status,severity,version,product,summary,url',
 49+ 'noresultsmessage' => 'no bugzilla tickets were found',
 50+ 'order' => 'asc',
 51+ 'status' => '!CLOSED',
 52+ 'sort' => 'priority'
 53+ );
 54+ var $columnName=array (
 55+ 'alias' => 'Alias',
 56+ 'blocks' => 'Blocks',
 57+ 'component' => 'Component',
 58+ 'cc' => 'CC',
 59+ 'deadline' => 'Deadline',
 60+ 'depends' => 'Depends',
 61+ 'flag' => 'Flagged For',
 62+ 'flagdate' => 'Flag Date',
 63+ 'flagfrom' => 'Flagged By',
 64+ 'flagname' => 'Flag',
 65+ 'from' => 'Requester',
 66+ 'id' => 'ID',
 67+ 'severity' => 'Severity',
 68+ 'status' => 'Status',
 69+ 'milestone' => 'Milestone',
 70+ 'modified' => 'Modified',
 71+ 'product' => 'Product',
 72+ 'priority' => 'P',
 73+ 'summary' => 'Summary',
 74+ 'to' => 'Assignee',
 75+ 'url' => '&nbsp;',
 76+ 'version' => 'Version',
 77+ 'votes' => 'Votes',
 78+ );
 79+ # Fields and their mapping to the value in the results sets
 80+ var $fieldMapping=array (
 81+ 'cc' => 'cc',
 82+ 'from' => 'raisedby',
 83+ 'to' => 'assignedto',
 84+ );
 85+ var $fieldSQLColumn=array (
 86+ 'cc' => 'ccprofiles.login_name',
 87+ 'component' => 'components.name',
 88+ 'from' => 'reporterprofiles.login_name',
 89+ 'milestone' => 'target_milestone',
 90+ 'modified' => 'lastdiffed',
 91+ 'product' => 'products.name',
 92+ 'severity' => 'bug_severity',
 93+ 'status' => 'bug_status',
 94+ 'to' => 'profiles.login_name',
 95+ 'url' => 'bug_file_loc',
 96+ );
 97+ var $fieldDefaultSort=array (
 98+ 'modified' => 'desc',
 99+ 'votes' => 'desc'
 100+ );
 101+ var $formats=array (
 102+ 'alias' => 'id',
 103+ 'blocks' => 'id',
 104+ 'cc' => 'name',
 105+ 'deadline' => 'date',
 106+ 'depends' => 'id',
 107+ 'flagdate' => 'date',
 108+ 'flagfrom' => 'name',
 109+ 'from' => 'name',
 110+ 'id' => 'id',
 111+ 'modified' => 'date',
 112+ 'to' => 'name',
 113+ 'url' => 'url',
 114+ );
 115+ var $fieldValues=array (
 116+ 'priority' => 'P1,P2,P3,P4,P5',
 117+ 'status' => 'ASSIGNED,NEW,RESOLVED,CLOSED',
 118+ 'severity' => 'blocker,critical,major,normal,minor,trivial,enhancement'
 119+ );
 120+ var $sortMapping=array (
 121+ 'deadline' => "COALESCE(deadline, '2100-01-01')",
 122+ 'milestone' => "COALESCE(NULLIF(milestone,'---'),'XXXXX')",
 123+ 'id' => 'bugs.bug_id'
 124+ );
 125+ var $dependsRowColumns=array (
 126+ 'depends' => "block",
 127+ 'dependsto' => "title", # Output in the title
 128+ 'dependsstatus' => "title", # Output in the title
 129+ 'dependssummary' => "block",
 130+ );
 131+ var $blocksRowColumns=array (
 132+ 'blocks' => "block",
 133+ 'blocksto' => "title", # Output in the title
 134+ 'blocksstatus' => "title",
 135+ 'blockssummary' => "block"
 136+ );
 137+ #
 138+ # Title for a given value rendering
 139+ #
 140+ var $valueTitle=array (
 141+ 'alias' => "id,alias",
 142+ 'blocks' => "blocks,blocksalias",
 143+ 'depends' => "depends,dependsalias",
 144+ 'id' => "id,alias"
 145+ );
 146+
 147+ private $output;
 148+
 149+ # WHY IS THIS NEEDED?
 150+ #var $dbhost="localhost";
 151+
 152+ #
 153+ # Parse in a context object which implements the following
 154+ #
 155+ # Public Variables
 156+ # - debug, bzserver, interwiki,
 157+ # - database, host, dbuser, password;
 158+ #
 159+ # Functions
 160+ # - debug
 161+ # - warn,
 162+ # - getErrorMessage
 163+ #
 164+ function BugzillaQuery( $context ) {
 165+ $this->setContext($context);
 166+ }
 167+
 168+ #
 169+ # Get rendering formats
 170+ #
 171+ function getFormats() {
 172+ return $this->formats;
 173+ }
 174+
 175+ #
 176+ # Get default priority
 177+ #
 178+ function getDefaultSort() {
 179+ return $this->defaultSort;
 180+ }
 181+
 182+
 183+ #
 184+ # Render the results
 185+ #
 186+ function render() {
 187+ $this->context->debug &&
 188+ $this->context->debug("Rendering BugzillaQuery");
 189+
 190+ $where="";
 191+ #
 192+ # If ID is set then don't do any other query since we're being pretty
 193+ # Specific
 194+ #
 195+
 196+ if ($this->get('id')) {
 197+ $where.=$this->getWhereClause($this->get('id'),"bug_id");
 198+ } else {
 199+ #
 200+ # Now process other fields and make sure we have SQL and implicit
 201+ # usage built up
 202+ #
 203+ foreach (array_keys($this->supportedParameters) as $column) {
 204+ if ($this->get($column)) {
 205+ switch ($this->supportedParameters[$column]) {
 206+ case "field" :
 207+ case "field-int" :
 208+ if (preg_match("/[,!+]/",$this->get($column))) {
 209+ $this->implictlyAddColumn($column);
 210+ } else {
 211+ $this->implictlyRemoveColumn($column);
 212+ }
 213+ case "field-depends" :
 214+ $sqlColumn=$column;
 215+ if (array_key_exists($column,
 216+ $this->fieldSQLColumn)) {
 217+ $sqlColumn=$this->fieldSQLColumn[$column];
 218+ }
 219+ switch ($this->supportedParameters[$column]) {
 220+ case "field" :
 221+ $where.=$this->
 222+ getWhereClause($this->get($column),
 223+ $sqlColumn);
 224+ break;
 225+ case "field-int" :
 226+ $where.=$this->
 227+ getIntWhereClause($this->get($column),
 228+ $sqlColumn);
 229+ break;
 230+ }
 231+ $this->requireField($column);
 232+ if (array_key_exists($column,
 233+ $this->fieldDefaultSort)) {
 234+ $this->setImplicit('sort',"$column");
 235+ $this->setImplicit('order',
 236+ $this->fieldDefaultSort[$column]);
 237+ }
 238+ break;
 239+ }
 240+ }
 241+ }
 242+ if ($this->get('format') == "list") {
 243+ $this->requireField("to");
 244+ $this->requireField("deadline");
 245+ }
 246+ if ($this->get('deadline')) {
 247+ $where.=$this->getDateWhereClause($this->get('deadline'),"deadline");
 248+ $this->requireField("deadline");
 249+ $this->setImplicit('sort',"deadline");
 250+ if (preg_match("/[,!+]/",$this->get("deadline"))) {
 251+ $this->implictlyAddColumn("deadline");
 252+ } else {
 253+ $this->implictlyRemoveColumn("deadline");
 254+ }
 255+
 256+ }
 257+ if ($this->get('flag')) {
 258+ $this->requireField("flag");
 259+ $this->implictlyAddColumn("flagfrom");
 260+ $this->implictlyAddColumn("flagname");
 261+ $this->implictlyAddColumn("flagdate");
 262+ }
 263+ if ($this->get('lastcomment')) {
 264+ $this->requireField("lastcomment");
 265+ }
 266+ if ($this->get('modified')) {
 267+ $where.=
 268+ $this->getDateWhereClause($this->get('modified'),"lastdiffed");
 269+ $this->requireField("modified");
 270+ $this->setImplicit('sort',"modified");
 271+ $this->setImplicit('order',"desc");
 272+ preg_match("/[+]/",$this->get('modified')) &&
 273+ $this->implictlyAddColumn("modified");
 274+ }
 275+ if ($this->get('search')) {
 276+ $where.=" and short_desc like '%".$this->search.="%'";
 277+ }
 278+ }
 279+
 280+ #
 281+ # Quick flag enabled by default
 282+ #
 283+ $this->requireField("quickflag");
 284+
 285+ #
 286+ # Alias enabled by default
 287+ #
 288+ $this->requireField("alias");
 289+
 290+ #
 291+ # Prepare the query;
 292+ #
 293+ $this->preSQLGenerate();
 294+
 295+ $this->context->debug &&
 296+ $this->context->debug("Columns required are "
 297+ .join(",",array_keys($this->fieldsRequired)));
 298+ $sql="SELECT *,bugs.bug_id as id";
 299+ if ($this->isRequired("blocks")) {
 300+ $sql.=", blockstab.blocks as blocks, blockstab.blocksalias as blocksalias, blockstab.blockssummary as blockssummary,blockstab.blocksstatus as blocksstatus, blockstab.blockspriority as blockspriority, blockstab.realname as blocksto";
 301+ }
 302+ if ($this->isRequired("cc")) {
 303+ if ($this->get('nameformat')=='login') {
 304+ $sql.=", ccprofiles.login_name as cc";
 305+ } else {
 306+ $sql.=", ccprofiles.realname as cc";
 307+ }
 308+ }
 309+ if ($this->isRequired("component")) {
 310+ $sql.=", components.name as component";
 311+ }
 312+ if ($this->isRequired("depends")) {
 313+ $sql.=", dependstab.depends as depends, dependstab.dependsalias as dependsalias, dependstab.dependssummary as dependssummary,dependstab.dependsstatus as dependsstatus, dependstab.dependspriority as dependspriority, dependstab.realname as dependsto";
 314+ }
 315+ if ($this->isRequired("flag")) {
 316+ if ($this->get('nameformat')=='login') {
 317+ $sql.=", flagprofiles.flagfrom_login as flagfrom";
 318+ $sql.=", flagprofiles.flag_login as flag";
 319+ } else {
 320+ $sql.=", flagprofiles.flagfrom_realname as flagfrom";
 321+ $sql.=", flagprofiles.flag_realname as flag";
 322+ }
 323+ $sql.=", flagprofiles.flagname as flagname";
 324+ $sql.=", flagprofiles.flagdate as flagdate";
 325+ } else if ($this->isRequired("quickflag")) {
 326+ $sql.=", quickflag.flagdate as flagdate";
 327+ }
 328+ if ($this->isRequired("from")) {
 329+ if ($this->get('nameformat')=='login') {
 330+ $sql.=", reporterprofiles.login_name as raisedby";
 331+ } else {
 332+ $sql.=", reporterprofiles.realname as raisedby";
 333+ }
 334+ }
 335+ if ($this->isRequired("milestone")) {
 336+ $sql.=", target_milestone as milestone";
 337+ }
 338+ if ($this->isRequired("modified")) {
 339+ $sql.=", lastdiffed as modified";
 340+ }
 341+ if ($this->isRequired("product")) {
 342+ $sql.=", products.name as product";
 343+ }
 344+ if ($this->isRequired("severity")) {
 345+ $sql.=", bug_severity as severity";
 346+ }
 347+ if ($this->isRequired("status")) {
 348+ $sql.=", bug_status as status";
 349+ }
 350+ if ($this->isRequired("summary")) {
 351+ $sql.=", short_desc as summary";
 352+ }
 353+ if ($this->isRequired("to")) {
 354+ if ($this->get('nameformat')=='login') {
 355+ $sql.=", profiles.login_name as assignedto";
 356+ } else {
 357+ $sql.=", profiles.realname as assignedto";
 358+ }
 359+ }
 360+ if ($this->isRequired("url")) {
 361+ $sql.=", bug_file_loc as url";
 362+ }
 363+ $sql.=" FROM ".$this->context->database.
 364+ ".bugs";
 365+ if ($this->isRequired("blocks")) {
 366+ $sql.=" LEFT JOIN (SELECT dependson,blocked as blocks, blockedbugs.alias as blocksalias, blockedbugs.short_desc as blockssummary, blockedbugs.bug_status as blocksstatus, blockedbugs.priority as blockspriority,login_name,realname from ".
 367+ $this->context->database.
 368+ ".dependencies"
 369+ ." INNER JOIN ".$this->context->database.".bugs as blockedbugs ON dependencies.blocked=blockedbugs.bug_id"
 370+ ." INNER JOIN ".
 371+ $this->context->database.
 372+ ".profiles ON blockedbugs.assigned_to=profiles.userid"
 373+ ." where blockedbugs.bug_status <> 'CLOSED' order by blockedbugs.priority) as blockstab ON blockstab.dependson=bugs.bug_id";
 374+ }
 375+ if ($this->isRequired("component")) {
 376+ $sql.=" LEFT JOIN ".
 377+ $this->context->database.
 378+ ".components on bugs.component_id=components.id";
 379+ }
 380+ if ($this->isRequired("cc")) {
 381+ $sql.=" INNER JOIN (SELECT bug_id,login_name,realname from ".
 382+ $this->context->database.
 383+ ".cc INNER JOIN ".
 384+ $this->context->database.
 385+ ".profiles ON cc.who=profiles.userid";
 386+ if ($this->get('cc')) {
 387+ $sql.=$this
 388+ ->getWhereClause($this->get('cc'),"profiles.login_name");
 389+ }
 390+ $sql.=") as ".
 391+ "ccprofiles on ccprofiles.bug_id=bugs.bug_id";
 392+ }
 393+ if ($this->isRequired("depends")) {
 394+ $sql.=" LEFT JOIN (SELECT blocked,dependson as depends, dependsonbugs.alias as dependsalias, dependsonbugs.short_desc as dependssummary, dependsonbugs.bug_status as dependsstatus, dependsonbugs.priority as dependspriority, login_name, realname from ".
 395+ $this->context->database.
 396+ ".dependencies"
 397+ ." INNER JOIN ".$this->context->database.".bugs as dependsonbugs ON dependencies.dependson=dependsonbugs.bug_id"
 398+ ." INNER JOIN ".
 399+ $this->context->database.
 400+ ".profiles ON dependsonbugs.assigned_to=profiles.userid"
 401+ ." where dependsonbugs.bug_status <> 'CLOSED' order by dependsonbugs.priority) as dependstab ON dependstab.blocked=bugs.bug_id";
 402+ }
 403+ if ($this->isRequired("flag")) {
 404+ $sql.=" INNER JOIN (SELECT bug_id,creation_date as flagdate,flagsto.login_name as flag_login,flagsto.realname as flag_realname,flagsfrom.login_name as flagfrom_login, flagsfrom.realname as flagfrom_realname,flagtypes.name as flagname from ".
 405+ $this->context->database.
 406+ ".flags INNER JOIN ".
 407+ $this->context->database.
 408+ ".flagtypes ON flags.type_id=flagtypes.id INNER JOIN ".
 409+ $this->context->database.
 410+ ".profiles as flagsto ON flags.requestee_id=flagsto.userid INNER JOIN ".
 411+ $this->context->database.
 412+ ".profiles as flagsfrom ON flags.setter_id=flagsfrom.userid where status='?'";
 413+ if ($this->get('flag')) {
 414+ $sql.=$this
 415+ ->getWhereClause($this->get('flag'),
 416+ "flagsto.login_name");
 417+ }
 418+ $sql.=") as ".
 419+ "flagprofiles on flagprofiles.bug_id=bugs.bug_id";
 420+ } else if ($this->isRequired("quickflag")) {
 421+ $sql.=" LEFT JOIN (SELECT bug_id as quickflagbugid, MAX(creation_date) as flagdate from ".
 422+ $this->context->database.
 423+ ".flags where status='?' group by quickflagbugid) as ".
 424+ "quickflag on quickflag.quickflagbugid=bugs.bug_id";
 425+ }
 426+ if ($this->isRequired("from")) {
 427+ $sql.=" LEFT JOIN ".
 428+ $this->context->database.
 429+ ".profiles as reporterprofiles on bugs.reporter=reporterprofiles.userid";
 430+ }
 431+ if ($this->isRequired("lastcomment")) {
 432+ $sql.=" LEFT JOIN (SELECT MAX(longdescs.comment_id) as sub_comment_id, ".
 433+ "longdescs.bug_id as sub_bug_id FROM ".$this->context->database.
 434+ ".longdescs GROUP BY longdescs.bug_id) ".
 435+ "descs ON bugs.bug_id=descs.sub_bug_id LEFT JOIN longdescs ON ".
 436+ "longdescs.comment_id=sub_comment_id";
 437+ }
 438+ if ($this->isRequired("product")) {
 439+ $sql.=" LEFT JOIN ".
 440+ $this->context->database.
 441+ ".products on bugs.product_id=products.id";
 442+ }
 443+ if ($this->isRequired("to")) {
 444+ $sql.=" LEFT JOIN ".
 445+ $this->context->database.
 446+ ".profiles on bugs.assigned_to=profiles.userid";
 447+ }
 448+ $sql.=" where 1=1 ".$where." order by ".
 449+ $this->getMappedSort()." ".$this->getOrder();
 450+ $sql.=";";
 451+ $this->context->debug && $this->context->debug("SQL : ".$sql);
 452+
 453+ $link = mysql_connect($this->context->host,
 454+ $this->context->dbuser, $this->context->password);
 455+
 456+ if (!$link)
 457+ return ""; $this->context->getErrorMessage('bReport_noconnection',
 458+ $this->context->dbuser,$this->context->host,mysql_error());
 459+
 460+ if (!mysql_select_db($this->context->database, $link)) {
 461+ mysql_close($link);
 462+ return $this->context->getErrorMessage('bReport_nodb');
 463+ }
 464+ $result = mysql_query($sql, $link);
 465+
 466+ #
 467+ # Check that the record set is open
 468+ #
 469+ if ($result) {
 470+ if (mysql_num_rows($result) > 0) {
 471+ $output=$this->renderHTML($result);
 472+ } else {
 473+ $output=$this->renderNoResultsHTML();
 474+ }
 475+
 476+ mysql_free_result($result);
 477+ } else {
 478+ return $this->context->getErrorMessage('bReport_sqlerror',$sql);
 479+ }
 480+
 481+ mysql_close($link);
 482+
 483+ if ($this->context->debug) {
 484+ $output.="<div>SQL = ".$sql."</div>";
 485+ }
 486+ return $output;
 487+ }
 488+
 489+ /**
 490+ * Extract options from a blob of text
 491+ *
 492+ * @param recordset
 493+ * @return rendered markup
 494+ */
 495+ public function renderHTML($result) {
 496+ $this->output = "";
 497+ $nRows=mysql_num_rows($result);
 498+
 499+ if ($this->get('heading')) {
 500+ $this->output.="<h1>".$this->get('heading')."</h1>";
 501+ }
 502+ # Table start
 503+ $this->output .= "<table class=\"bugzilla";
 504+ if ($this->get('bar')) {
 505+ $this->output.=" bz_bar";
 506+ }
 507+ $this->output.="\">";
 508+
 509+ # Initialise details row logic
 510+ $detailsRowColumns=array();
 511+ $arrayOfDetailRowColumns=explode(",",$this->get('detailsrow'));
 512+ foreach ($arrayOfDetailRowColumns as $detailRowColumn) {
 513+ $detailsRowColumns[$detailRowColumn]=1;
 514+ }
 515+ $this->numberOfMainRowColumns=0;
 516+ if ($this->get('bar')) {
 517+ $this->numberOfMainRowColumns=2;
 518+ } else {
 519+ foreach ($this->getColumns() as $column) {
 520+ if (!array_key_exists($column,$detailsRowColumns)) {
 521+ $this->numberOfMainRowColumns++;
 522+ }
 523+ }
 524+ }
 525+
 526+ # Display table header
 527+ if (!$this->get('bar') &&
 528+ $this->get('format')!='list' &&
 529+ $this->get('headers')!="hide") {
 530+ $this->output .= "<tr>";
 531+ foreach ($this->getColumns() as $column) {
 532+ $name=trim($column);
 533+ if (array_key_exists($column,$this->columnName)) {
 534+ $name=$this->columnName[$column];
 535+ }
 536+ if (($column=="summary") && ($nRows > 1)) {
 537+ $name.=" ($nRows tasks)";
 538+ }
 539+ $this->output .= "<th><b>$name</b></th>";
 540+ }
 541+ $this->output .= "</tr>";
 542+ }
 543+
 544+ # Create Table Data Rows
 545+ $even=true;
 546+ $count=0;
 547+ $localMaxRows=$this->getMaxRows();
 548+ $localMaxRowsForBarChart=$this->getMaxRowsForBarChart();
 549+ $groupValue="";
 550+ $groupTotal=0;
 551+ $groups;
 552+ $doGrouping=0;
 553+ if ($this->getGroup()) {
 554+ $doGrouping=1;
 555+ $groups=split(",",$this->getGroup());
 556+ }
 557+ $barArray=$this->getBarArray();
 558+ $currentId=0;
 559+ $currentBlocksId=0;
 560+ $currentDependsId=0;
 561+ while ($line = mysql_fetch_array($result, MYSQL_ASSOC)) {
 562+ #
 563+ # Add group heading
 564+ #
 565+ if ($doGrouping) {
 566+ $groupTotal++;
 567+ $newGroupValue=$this->formatForHeading(
 568+ $line[$this->mapField(
 569+ $groups[0])],$groups[0]);
 570+ if ($newGroupValue != $groupValue) {
 571+ $colspan=$this->numberOfMainRowColumns;
 572+ if ($this->get('bar')) {
 573+ if ($groupValue) {
 574+ $this->renderBar($barArray);
 575+ foreach (array_keys($barArray) as $key) {
 576+ $barArray[$key]=0;
 577+ }
 578+ }
 579+ $colspan-=1;
 580+ }
 581+ $groupValue=$newGroupValue;
 582+ $this->output.="<tr class=\"bz_group\"><td colspan=\""
 583+ .$colspan.
 584+ "\">".$groupValue.
 585+ "</td>";
 586+ if ($this->get('bar')) {
 587+ $this->output.="<td class=\"total\">&nbsp;</td>";
 588+ }
 589+ $this->output.="</tr>";
 590+ $groupTotal=0;
 591+ }
 592+ }
 593+ ++$count;
 594+ # Bar counter
 595+ if ($this->get('bar')) {
 596+ if ($count > $localMaxRowsForBarChart) {
 597+ $this->context->warn("Bar count greater than max allowed ".
 598+ "$count > $localMaxRowsForBarChart");
 599+ break;
 600+ }
 601+ $barArray[$line[$this->mapField($this->get('bar'))]]+=1;
 602+ continue;
 603+ }
 604+ # Safety check break out of loop if there's too much
 605+ if ($count > $localMaxRows) {
 606+ $this->context->warn("Report truncated - count greater than max allowed ".
 607+ "$count > $localMaxRows");
 608+ break;
 609+ }
 610+ #
 611+ # Only render the row if the ID has changed from previous row
 612+ # to support LEFT JOINS
 613+ #
 614+ if ($line["id"] != $currentId) {
 615+ $currentId=$line["id"];
 616+ $firstcolumn=true;
 617+ $even=!$even;
 618+ $class="bz_bug ".$line["priority"]." ".$line["bug_status"]." ";
 619+ if ($even) {
 620+ $class.="bz_row_even";
 621+ } else {
 622+ $class.="bz_row_odd";
 623+ }
 624+ $this->output .= "<tr class=\"".$class."\">";
 625+ if ($this->get('format') == "list") {
 626+ $this->output.="<td class=\"bz_list\" colspan=\"".
 627+ $this->numberOfMainRowColumns."\">";
 628+ $this->output.=
 629+ "[".$this->format($line[$this->mapField('to')],
 630+ 'to')."] ";
 631+ $deadline=$line[$this->mapField('deadline')];
 632+ if ($deadline) {
 633+ $this->output.=
 634+ "(".$this->format($deadline,"deadline").") ";
 635+ }
 636+ $this->output.=
 637+ $this->format($line[$this->mapField('summary')],
 638+ 'summary')." (#".
 639+ $this->format($line[$this->mapField('id')],
 640+ 'id').")";
 641+ $this->output.="</td>";
 642+ } else {
 643+ foreach ($this->getColumns() as $column) {
 644+ $dbColumn=$this->mapField($column);
 645+ if (!$this->get('detailsrow')
 646+ or !array_key_exists($dbColumn,
 647+ $detailsRowColumns)) {
 648+ $title=$this->getValueTitle($line,$column);
 649+ $value=$this->format($line[$dbColumn],$column,$title);
 650+ $this->output.="<td";
 651+ if ($title) {
 652+ $this->output.=" title=\"$title\"";
 653+ }
 654+ $this->output.=">$value";
 655+ if ($firstcolumn) {
 656+ $firstcolumn=false;
 657+ /**
 658+ * Start with a carriage return so that comments starting with
 659+ * list characters, e.g. *, # render in wiki style
 660+ */
 661+ if ($this->get('lastcomment')) {
 662+ $lastcomment=trim(htmlentities($line["thetext"], ENT_QUOTES, 'UTF-8'));
 663+ if (strlen($lastcomment) > 0) {
 664+ $this->output.="*<div class=\"bz_comment\"><span class=\"bug_id\">".
 665+ $line["bug_id"]."</span>\n".$lastcomment.
 666+ "</div>";
 667+ }
 668+ }
 669+ #
 670+ # Render quick flag
 671+ #
 672+ if ($this->isRequired('quickflag')) {
 673+ if ($line["flagdate"]) {
 674+ $this->output.="<span class=\"flag\" title=\"Flag : ".
 675+ $line["flagdate"]."\">?</span>";
 676+ }
 677+ }
 678+ }
 679+ $this->output.="</td>";
 680+ }
 681+ }
 682+ }
 683+ $this->output .= "</tr>";
 684+ }
 685+ if ($this->get('detailsrow')) {
 686+ $this->renderDetailsRow($detailsRowColumns,$line,
 687+ $this->get('detailsrowprepend'));
 688+ }
 689+ #
 690+ # We have LEFT JOIN so we need to ignore repeats
 691+ #
 692+ if ($this->get('blocks') &&
 693+ ($line["blocks"] != $currentBlocksId)) {
 694+ $currentBlocksId=$line["blocks"];
 695+ $this->renderDetailsRow(
 696+ $this->blocksRowColumns,$line,"&rArr; ");
 697+ }
 698+ #
 699+ # We have LEFT JOIN so we need to ignore repeats
 700+ #
 701+ if ($this->get('depends') &&
 702+ ($line["depends"] != $currentDependsId)) {
 703+ $currentDependsId=$line["depends"];
 704+ $this->renderDetailsRow(
 705+ $this->dependsRowColumns,$line,"&lArr; ");
 706+ }
 707+ }
 708+
 709+ #
 710+ # Display bar
 711+ #
 712+ if ($this->get('bar')) {
 713+ $this->renderBar($barArray);
 714+ $this->output.="<tr class=\"bz_bar_total\"><td colspan=\""
 715+ .($this->numberOfMainRowColumns-1).
 716+ "\">total</td><td class=\"total\">$nRows</td></tr>";
 717+ }
 718+
 719+ # Table end
 720+ $this->output .= "</table>";
 721+
 722+ return $this->output;
 723+ }
 724+
 725+ #
 726+ # Render a details row
 727+ #
 728+ private function renderDetailsRow($array,$line,$prepend) {
 729+ $details="";
 730+ $title="";
 731+ foreach (array_keys($array) as $column) {
 732+ $dbColumn=$this->mapField($column);
 733+ if ($line[$column]) {
 734+ $valueTitle=$this->getValueTitle($line,$column);
 735+ if ($valueTitle) {
 736+ $this->context->debug &&
 737+ $this->context
 738+ ->debug("Setting title for $column to $valueTitle");
 739+ }
 740+ switch ($array[$column]) {
 741+ case "title" :
 742+ $title.=$this
 743+ ->format($line[$dbColumn],
 744+ $column,$valueTitle)." ";
 745+ break;
 746+ default :
 747+ $details.=$this
 748+ ->format($line[$dbColumn],
 749+ $column,$valueTitle)." ";
 750+ }
 751+ }
 752+ }
 753+ if (trim($details)) {
 754+ $this->output.="<tr class=\"bz_details\"><td title=\"$title\" class=\"bz_details\" colspan=\"".
 755+ $this->numberOfMainRowColumns."\">";
 756+ $this->output.=$prepend.$details;
 757+ $this->ouput.="</td></tr>";
 758+ }
 759+ }
 760+
 761+ private function renderBar($barArray) {
 762+ $total=0;
 763+ $nonZeroKeyCount=0;
 764+ $arrayKeys=array_keys($barArray);
 765+ foreach ($arrayKeys as $key) {
 766+ $total+=$barArray[$key];
 767+ $barArray[$key]>0 && $nonZeroKeyCount++;
 768+ }
 769+ $classColourCode="few";
 770+ $keyCount=count($arrayKeys);
 771+ if ($keyCount > 6) {
 772+ $classColourCode="some";
 773+ } else if ($keyCount > 10) {
 774+ $classColourCode="many";
 775+ }
 776+ $this->output.="<tr class=\"bz_bar $classColourCode\"><td colspan=\""
 777+ .($this->numberOfMainRowColumns-1).
 778+ "\">";
 779+ $i=0;
 780+ $iNonZero=0;
 781+ sort($arrayKeys);
 782+ $rowTotal=0;
 783+ $rowWidthTotal=0;
 784+ foreach ($arrayKeys as $key) {
 785+ $count=$barArray[$key];
 786+ $i++;
 787+ if ($count > 0) {
 788+ $iNonZero++;
 789+ $widthString="";
 790+ $rowTotal+=$count;
 791+ if ($iNonZero==$nonZeroKeyCount) {
 792+ $class="C$i last";
 793+ # Make sure we don't get caught by rounding error
 794+ $width=100-$rowWidthTotal;
 795+ } else {
 796+ $width=number_format((100*$count)/$total,0);
 797+ $rowWidthTotal+=$width;
 798+ $class="C$i notlast";
 799+ }
 800+ $content;
 801+ if (strlen($key) > ($width /2) ) {
 802+ $content="&nbsp;";
 803+ } else {
 804+ $keyAndCount="$key ($count)";
 805+ if (strlen($keyAndCount) < ($width /2) ) {
 806+ $content=$keyAndCount;
 807+ } else {
 808+ $content=$key;
 809+ }
 810+ }
 811+ $widthString=" style=\"width:".$width."%\"";
 812+ $this->output.="<div title=\"$key = $count";
 813+ if ($this->context->debug) {
 814+ $this->output.="class=$class / width=$width / key count=$keyCount / nz key count=$nonZeroKeyCount / i=$i / inz=$iNonZero";
 815+ }
 816+ $this->output.=
 817+ "\"$widthString class=\"$class\"><div>".
 818+ "$content</div></div>";
 819+ }
 820+ }
 821+ $this->output.="</td><td class=\"total\">$rowTotal</td></tr>";
 822+ }
 823+
 824+ /**
 825+ * Render output for no results
 826+ */
 827+ public function renderNoResultsHTML() {
 828+ if ($this->get('noresultsmessage')) {
 829+ return "<div class=\"bz_noresults\">".
 830+ $this->get('noresultsmessage')."</div>";
 831+ } else {
 832+ return "";
 833+ }
 834+ }
 835+
 836+ public function getMatchExpression($match,$name,$negate) {
 837+ $timmedMatch=trim($match);
 838+ $pos=strpos($timmedMatch, "!");
 839+ if ($pos === false) {
 840+ if ($negate) {
 841+ return $name."<>'".$timmedMatch."'";
 842+ } else {
 843+ return $name."='".$timmedMatch."'";
 844+ }
 845+ } else if ($pos==0) {
 846+ if ($negate) {
 847+ return $name."='".substr($timmedMatch,1)."'";
 848+ } else {
 849+ return $name."<>'".substr($timmedMatch,1)."'";
 850+ }
 851+ } else {
 852+ $this->context->debug && $this->context->warn("The '!' operator must be the first character -> ignoring $name=$match");
 853+ }
 854+ }
 855+
 856+ #
 857+ # Initialise the bar array
 858+ #
 859+ private function getBarArray() {
 860+ $barArray=array();
 861+ if (array_key_exists($this->get('bar'),$this->fieldValues)) {
 862+ foreach (split(",",
 863+ $this->fieldValues[$this->get('bar')]) as $key) {
 864+ $barArray[$key]=0;
 865+ }
 866+ $this->context->debug &&
 867+ $this->context->debug("Initialised bar keys (".
 868+ $this->get('bar')."): ".
 869+ join(",",$arrayKeys));
 870+ }
 871+ return $barArray;
 872+ }
 873+}
Property changes on: trunk/extensions/BugzillaReports/BugzillaQuery.php
___________________________________________________________________
Added: svn:keywords
1874 + Id
Added: svn:eol-style
2875 + native
Index: trunk/extensions/BugzillaReports/RELEASE-NOTES
@@ -0,0 +1,76 @@
 2+=Release Notes=
 3+
 4+==v0.8 ==
 5+
 6+* Support heading which includes the heading (as an H1) above the report (only if there is content)
 7+* Support for empty noresultsmessage, if it's set to nothing then no no-results message will be displayed
 8+* Support for milestone search query and column name
 9+* Refactoring to improve supportability and future extensions
 10+* Support for "allow_call_time_pass_reference = Off" in php.ini without warnings
 11+* Clear up all warnings with "error_reporting = E_ALL" set in php.ini
 12+* Display tasks which a task is dependent on with "depends=*" and anything which blocks with "blocks=*" - assignee and status displayed on roll over (in the td title)
 13+* By default the real name is display for cc,to, from , etc names. If you want the login name to be displayed set "nameformat=login"
 14+* Support for name format as a succinct three letter acronym, e.g. Joe Blogs would be JBL and Jane Doe would be JDO, set "nameformat=tla" to enable this
 15+* Provided a simple list format configured by setting "format=list" which generates a list of tasks handy for cutting and pasting into an email
 16+* Support for alias in column and query
 17+* A little faint "+" appears next to any value if there's extra info when you roll over the value, for example if an alias is set then you see a little "+" and see the alias name when you roll over
 18+
 19+==v0.7 - 4th July 2008 ==
 20+
 21+* Regression Warning : There were some inconsistencies and bugs relating to the naming fields in query field parameters, sort parameters and column parameters. This naming has been simplified and made consistent, however it does mean that the use of functions should be adjusted inline with this release. Naming is now as follows:
 22+*: status (instead of bug_status), severity (instead of bug_severity), id (instead of bug_id), summary (instead of short_desc)
 23+* Warnings now output to page to make troubleshooting easier
 24+* Implicitly add column to provide url associated with task. Column doesn't have a header so if no rows have urls then nothing shows up.
 25+* Provide support for last modified with the parameter, modified=+ or sort=modified|order=desc
 26+* Added group feature, e.g. group=product to group the report into different products, including groupformat=radar to render headers for dates as today, tomorrow, this week etc ...
 27+* Added experimental bar chart feature - e.g. bar=priority groups all rows into a bar chart of different priorities. I say experimental, since doesn't look great yet, but it's functional.
 28+
 29+==v0.6 - 1st July 2008 ==
 30+
 31+* Fix setting of implicit sorting when some query fields set
 32+* Fix bug relating to array_fill_keys mandating two parameters
 33+
 34+==v0.5 - 1st July 2008 ==
 35+
 36+* Optimise SQL to only do joins when needed
 37+* Support search on component name, component=mycomponent
 38+* Support for vote reporting, columns=+votes to include votes column and votes=+ to display only tickets with votes against them
 39+* Significant refactoring to make extension easier to maintain and to allow further DB integrations to be developed (including abstractions from mediawiki) - I expect to do be doing a vTiger MediaWiki extension in the coming months, and I've had feedback that people would like to run this functionality outside of mediawiki.
 40+* Implicit sort and ordering based on query parameters if no explit sort and ordering set, for example if you say you want to list all rows with deadline set, then sorting will be on deadline (unless explicitly set otherwise)
 41+* Support for "from","to" and "cc" meaning respectively who raised the ticket, who's it assigned to and who's cc'd
 42+** Renamed column and query field login_name to "to" (requires changes to any function calls which make reference to login_name)
 43+** Support for reporter query field and column, e.g. from=me@bemoko.com and columns=+from
 44+** Support for query field on cc, but note that comma separated and not operators are not supported for this field.
 45+* Implicity remove columns if query set for a single value, i.e. all values in the column would be the same
 46+* Implicity add columns if query set for not null, e.g. votes=+ or deadline=+ would implicitly add the deadline and votes column
 47+* Support query for flagged tasks, e.g. flag=+me@bemoko.com
 48+* Make sort on deadline put earliest date first and nulls at end
 49+
 50+==v0.4 - 30th June 2008==
 51+
 52+* Fix query for free text match - using '''search''' parameter
 53+* Changed default status to !CLOSED
 54+* Default maxrows changed to 100
 55+* Support maxrows as parameter into function call (as well as server config max) - note function call value can't be higher than server config value
 56+* Support spaces in comma separated query list
 57+* Default columns changed to "bug_id,priority,bug_status,bug_severity,version,name,short_desc" with login_name removed
 58+* Support adding and removing columns from the default by using "column=+deadline" or "column=-name", or "column=+deadline,-name"
 59+* Support sort, e.g "sort=deadline" and ordering "order=desc"
 60+* Support search for deadline set "deadline=+" and deadline not set "deadline=-"
 61+* Change "name" column to "product" to make it more obvious and consistent
 62+
 63+==v0.3 - 26th June 2008==
 64+
 65+* Improve UI rendering of lastcomment bubbles
 66+* Fix the css to get tables rendered better
 67+* Improved error handling of mysql_query
 68+
 69+==v0.2 - 26th June 2008==
 70+
 71+* Render last comment for a bug in jQuery bubble - use the '''lastcomment''' parameter
 72+* Support for negation of query fields
 73+* Installation download bundle provided
 74+
 75+==v0.1 - 25th June 2008==
 76+
 77+* Baseline reporting functionality
\ No newline at end of file
Property changes on: trunk/extensions/BugzillaReports/RELEASE-NOTES
___________________________________________________________________
Added: svn:eol-style
178 + native
Index: trunk/extensions/BugzillaReports/scripts/jquery-1.2.6.min.js
@@ -0,0 +1,32 @@
 2+/*
 3+ * jQuery 1.2.6 - New Wave Javascript
 4+ *
 5+ * Copyright (c) 2008 John Resig (jquery.com)
 6+ * Dual licensed under the MIT (MIT-LICENSE.txt)
 7+ * and GPL (GPL-LICENSE.txt) licenses.
 8+ *
 9+ * $Date: 2008-05-24 14:22:17 -0400 (Sat, 24 May 2008) $
 10+ * $Rev: 5685 $
 11+ */
 12+(function(){var _jQuery=window.jQuery,_$=window.$;var jQuery=window.jQuery=window.$=function(selector,context){return new jQuery.fn.init(selector,context);};var quickExpr=/^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/,isSimple=/^.[^:#\[\.]*$/,undefined;jQuery.fn=jQuery.prototype={init:function(selector,context){selector=selector||document;if(selector.nodeType){this[0]=selector;this.length=1;return this;}if(typeof selector=="string"){var match=quickExpr.exec(selector);if(match&&(match[1]||!context)){if(match[1])selector=jQuery.clean([match[1]],context);else{var elem=document.getElementById(match[3]);if(elem){if(elem.id!=match[3])return jQuery().find(selector);return jQuery(elem);}selector=[];}}else
 13+return jQuery(context).find(selector);}else if(jQuery.isFunction(selector))return jQuery(document)[jQuery.fn.ready?"ready":"load"](selector);return this.setArray(jQuery.makeArray(selector));},jquery:"1.2.6",size:function(){return this.length;},length:0,get:function(num){return num==undefined?jQuery.makeArray(this):this[num];},pushStack:function(elems){var ret=jQuery(elems);ret.prevObject=this;return ret;},setArray:function(elems){this.length=0;Array.prototype.push.apply(this,elems);return this;},each:function(callback,args){return jQuery.each(this,callback,args);},index:function(elem){var ret=-1;return jQuery.inArray(elem&&elem.jquery?elem[0]:elem,this);},attr:function(name,value,type){var options=name;if(name.constructor==String)if(value===undefined)return this[0]&&jQuery[type||"attr"](this[0],name);else{options={};options[name]=value;}return this.each(function(i){for(name in options)jQuery.attr(type?this.style:this,name,jQuery.prop(this,options[name],type,i,name));});},css:function(key,value){if((key=='width'||key=='height')&&parseFloat(value)<0)value=undefined;return this.attr(key,value,"curCSS");},text:function(text){if(typeof text!="object"&&text!=null)return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(text));var ret="";jQuery.each(text||this,function(){jQuery.each(this.childNodes,function(){if(this.nodeType!=8)ret+=this.nodeType!=1?this.nodeValue:jQuery.fn.text([this]);});});return ret;},wrapAll:function(html){if(this[0])jQuery(html,this[0].ownerDocument).clone().insertBefore(this[0]).map(function(){var elem=this;while(elem.firstChild)elem=elem.firstChild;return elem;}).append(this);return this;},wrapInner:function(html){return this.each(function(){jQuery(this).contents().wrapAll(html);});},wrap:function(html){return this.each(function(){jQuery(this).wrapAll(html);});},append:function(){return this.domManip(arguments,true,false,function(elem){if(this.nodeType==1)this.appendChild(elem);});},prepend:function(){return this.domManip(arguments,true,true,function(elem){if(this.nodeType==1)this.insertBefore(elem,this.firstChild);});},before:function(){return this.domManip(arguments,false,false,function(elem){this.parentNode.insertBefore(elem,this);});},after:function(){return this.domManip(arguments,false,true,function(elem){this.parentNode.insertBefore(elem,this.nextSibling);});},end:function(){return this.prevObject||jQuery([]);},find:function(selector){var elems=jQuery.map(this,function(elem){return jQuery.find(selector,elem);});return this.pushStack(/[^+>] [^+>]/.test(selector)||selector.indexOf("..")>-1?jQuery.unique(elems):elems);},clone:function(events){var ret=this.map(function(){if(jQuery.browser.msie&&!jQuery.isXMLDoc(this)){var clone=this.cloneNode(true),container=document.createElement("div");container.appendChild(clone);return jQuery.clean([container.innerHTML])[0];}else
 14+return this.cloneNode(true);});var clone=ret.find("*").andSelf().each(function(){if(this[expando]!=undefined)this[expando]=null;});if(events===true)this.find("*").andSelf().each(function(i){if(this.nodeType==3)return;var events=jQuery.data(this,"events");for(var type in events)for(var handler in events[type])jQuery.event.add(clone[i],type,events[type][handler],events[type][handler].data);});return ret;},filter:function(selector){return this.pushStack(jQuery.isFunction(selector)&&jQuery.grep(this,function(elem,i){return selector.call(elem,i);})||jQuery.multiFilter(selector,this));},not:function(selector){if(selector.constructor==String)if(isSimple.test(selector))return this.pushStack(jQuery.multiFilter(selector,this,true));else
 15+selector=jQuery.multiFilter(selector,this);var isArrayLike=selector.length&&selector[selector.length-1]!==undefined&&!selector.nodeType;return this.filter(function(){return isArrayLike?jQuery.inArray(this,selector)<0:this!=selector;});},add:function(selector){return this.pushStack(jQuery.unique(jQuery.merge(this.get(),typeof selector=='string'?jQuery(selector):jQuery.makeArray(selector))));},is:function(selector){return!!selector&&jQuery.multiFilter(selector,this).length>0;},hasClass:function(selector){return this.is("."+selector);},val:function(value){if(value==undefined){if(this.length){var elem=this[0];if(jQuery.nodeName(elem,"select")){var index=elem.selectedIndex,values=[],options=elem.options,one=elem.type=="select-one";if(index<0)return null;for(var i=one?index:0,max=one?index+1:options.length;i<max;i++){var option=options[i];if(option.selected){value=jQuery.browser.msie&&!option.attributes.value.specified?option.text:option.value;if(one)return value;values.push(value);}}return values;}else
 16+return(this[0].value||"").replace(/\r/g,"");}return undefined;}if(value.constructor==Number)value+='';return this.each(function(){if(this.nodeType!=1)return;if(value.constructor==Array&&/radio|checkbox/.test(this.type))this.checked=(jQuery.inArray(this.value,value)>=0||jQuery.inArray(this.name,value)>=0);else if(jQuery.nodeName(this,"select")){var values=jQuery.makeArray(value);jQuery("option",this).each(function(){this.selected=(jQuery.inArray(this.value,values)>=0||jQuery.inArray(this.text,values)>=0);});if(!values.length)this.selectedIndex=-1;}else
 17+this.value=value;});},html:function(value){return value==undefined?(this[0]?this[0].innerHTML:null):this.empty().append(value);},replaceWith:function(value){return this.after(value).remove();},eq:function(i){return this.slice(i,i+1);},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments));},map:function(callback){return this.pushStack(jQuery.map(this,function(elem,i){return callback.call(elem,i,elem);}));},andSelf:function(){return this.add(this.prevObject);},data:function(key,value){var parts=key.split(".");parts[1]=parts[1]?"."+parts[1]:"";if(value===undefined){var data=this.triggerHandler("getData"+parts[1]+"!",[parts[0]]);if(data===undefined&&this.length)data=jQuery.data(this[0],key);return data===undefined&&parts[1]?this.data(parts[0]):data;}else
 18+return this.trigger("setData"+parts[1]+"!",[parts[0],value]).each(function(){jQuery.data(this,key,value);});},removeData:function(key){return this.each(function(){jQuery.removeData(this,key);});},domManip:function(args,table,reverse,callback){var clone=this.length>1,elems;return this.each(function(){if(!elems){elems=jQuery.clean(args,this.ownerDocument);if(reverse)elems.reverse();}var obj=this;if(table&&jQuery.nodeName(this,"table")&&jQuery.nodeName(elems[0],"tr"))obj=this.getElementsByTagName("tbody")[0]||this.appendChild(this.ownerDocument.createElement("tbody"));var scripts=jQuery([]);jQuery.each(elems,function(){var elem=clone?jQuery(this).clone(true)[0]:this;if(jQuery.nodeName(elem,"script"))scripts=scripts.add(elem);else{if(elem.nodeType==1)scripts=scripts.add(jQuery("script",elem).remove());callback.call(obj,elem);}});scripts.each(evalScript);});}};jQuery.fn.init.prototype=jQuery.fn;function evalScript(i,elem){if(elem.src)jQuery.ajax({url:elem.src,async:false,dataType:"script"});else
 19+jQuery.globalEval(elem.text||elem.textContent||elem.innerHTML||"");if(elem.parentNode)elem.parentNode.removeChild(elem);}function now(){return+new Date;}jQuery.extend=jQuery.fn.extend=function(){var target=arguments[0]||{},i=1,length=arguments.length,deep=false,options;if(target.constructor==Boolean){deep=target;target=arguments[1]||{};i=2;}if(typeof target!="object"&&typeof target!="function")target={};if(length==i){target=this;--i;}for(;i<length;i++)if((options=arguments[i])!=null)for(var name in options){var src=target[name],copy=options[name];if(target===copy)continue;if(deep&&copy&&typeof copy=="object"&&!copy.nodeType)target[name]=jQuery.extend(deep,src||(copy.length!=null?[]:{}),copy);else if(copy!==undefined)target[name]=copy;}return target;};var expando="jQuery"+now(),uuid=0,windowData={},exclude=/z-?index|font-?weight|opacity|zoom|line-?height/i,defaultView=document.defaultView||{};jQuery.extend({noConflict:function(deep){window.$=_$;if(deep)window.jQuery=_jQuery;return jQuery;},isFunction:function(fn){return!!fn&&typeof fn!="string"&&!fn.nodeName&&fn.constructor!=Array&&/^[\s[]?function/.test(fn+"");},isXMLDoc:function(elem){return elem.documentElement&&!elem.body||elem.tagName&&elem.ownerDocument&&!elem.ownerDocument.body;},globalEval:function(data){data=jQuery.trim(data);if(data){var head=document.getElementsByTagName("head")[0]||document.documentElement,script=document.createElement("script");script.type="text/javascript";if(jQuery.browser.msie)script.text=data;else
 20+script.appendChild(document.createTextNode(data));head.insertBefore(script,head.firstChild);head.removeChild(script);}},nodeName:function(elem,name){return elem.nodeName&&elem.nodeName.toUpperCase()==name.toUpperCase();},cache:{},data:function(elem,name,data){elem=elem==window?windowData:elem;var id=elem[expando];if(!id)id=elem[expando]=++uuid;if(name&&!jQuery.cache[id])jQuery.cache[id]={};if(data!==undefined)jQuery.cache[id][name]=data;return name?jQuery.cache[id][name]:id;},removeData:function(elem,name){elem=elem==window?windowData:elem;var id=elem[expando];if(name){if(jQuery.cache[id]){delete jQuery.cache[id][name];name="";for(name in jQuery.cache[id])break;if(!name)jQuery.removeData(elem);}}else{try{delete elem[expando];}catch(e){if(elem.removeAttribute)elem.removeAttribute(expando);}delete jQuery.cache[id];}},each:function(object,callback,args){var name,i=0,length=object.length;if(args){if(length==undefined){for(name in object)if(callback.apply(object[name],args)===false)break;}else
 21+for(;i<length;)if(callback.apply(object[i++],args)===false)break;}else{if(length==undefined){for(name in object)if(callback.call(object[name],name,object[name])===false)break;}else
 22+for(var value=object[0];i<length&&callback.call(value,i,value)!==false;value=object[++i]){}}return object;},prop:function(elem,value,type,i,name){if(jQuery.isFunction(value))value=value.call(elem,i);return value&&value.constructor==Number&&type=="curCSS"&&!exclude.test(name)?value+"px":value;},className:{add:function(elem,classNames){jQuery.each((classNames||"").split(/\s+/),function(i,className){if(elem.nodeType==1&&!jQuery.className.has(elem.className,className))elem.className+=(elem.className?" ":"")+className;});},remove:function(elem,classNames){if(elem.nodeType==1)elem.className=classNames!=undefined?jQuery.grep(elem.className.split(/\s+/),function(className){return!jQuery.className.has(classNames,className);}).join(" "):"";},has:function(elem,className){return jQuery.inArray(className,(elem.className||elem).toString().split(/\s+/))>-1;}},swap:function(elem,options,callback){var old={};for(var name in options){old[name]=elem.style[name];elem.style[name]=options[name];}callback.call(elem);for(var name in options)elem.style[name]=old[name];},css:function(elem,name,force){if(name=="width"||name=="height"){var val,props={position:"absolute",visibility:"hidden",display:"block"},which=name=="width"?["Left","Right"]:["Top","Bottom"];function getWH(){val=name=="width"?elem.offsetWidth:elem.offsetHeight;var padding=0,border=0;jQuery.each(which,function(){padding+=parseFloat(jQuery.curCSS(elem,"padding"+this,true))||0;border+=parseFloat(jQuery.curCSS(elem,"border"+this+"Width",true))||0;});val-=Math.round(padding+border);}if(jQuery(elem).is(":visible"))getWH();else
 23+jQuery.swap(elem,props,getWH);return Math.max(0,val);}return jQuery.curCSS(elem,name,force);},curCSS:function(elem,name,force){var ret,style=elem.style;function color(elem){if(!jQuery.browser.safari)return false;var ret=defaultView.getComputedStyle(elem,null);return!ret||ret.getPropertyValue("color")=="";}if(name=="opacity"&&jQuery.browser.msie){ret=jQuery.attr(style,"opacity");return ret==""?"1":ret;}if(jQuery.browser.opera&&name=="display"){var save=style.outline;style.outline="0 solid black";style.outline=save;}if(name.match(/float/i))name=styleFloat;if(!force&&style&&style[name])ret=style[name];else if(defaultView.getComputedStyle){if(name.match(/float/i))name="float";name=name.replace(/([A-Z])/g,"-$1").toLowerCase();var computedStyle=defaultView.getComputedStyle(elem,null);if(computedStyle&&!color(elem))ret=computedStyle.getPropertyValue(name);else{var swap=[],stack=[],a=elem,i=0;for(;a&&color(a);a=a.parentNode)stack.unshift(a);for(;i<stack.length;i++)if(color(stack[i])){swap[i]=stack[i].style.display;stack[i].style.display="block";}ret=name=="display"&&swap[stack.length-1]!=null?"none":(computedStyle&&computedStyle.getPropertyValue(name))||"";for(i=0;i<swap.length;i++)if(swap[i]!=null)stack[i].style.display=swap[i];}if(name=="opacity"&&ret=="")ret="1";}else if(elem.currentStyle){var camelCase=name.replace(/\-(\w)/g,function(all,letter){return letter.toUpperCase();});ret=elem.currentStyle[name]||elem.currentStyle[camelCase];if(!/^\d+(px)?$/i.test(ret)&&/^\d/.test(ret)){var left=style.left,rsLeft=elem.runtimeStyle.left;elem.runtimeStyle.left=elem.currentStyle.left;style.left=ret||0;ret=style.pixelLeft+"px";style.left=left;elem.runtimeStyle.left=rsLeft;}}return ret;},clean:function(elems,context){var ret=[];context=context||document;if(typeof context.createElement=='undefined')context=context.ownerDocument||context[0]&&context[0].ownerDocument||document;jQuery.each(elems,function(i,elem){if(!elem)return;if(elem.constructor==Number)elem+='';if(typeof elem=="string"){elem=elem.replace(/(<(\w+)[^>]*?)\/>/g,function(all,front,tag){return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?all:front+"></"+tag+">";});var tags=jQuery.trim(elem).toLowerCase(),div=context.createElement("div");var wrap=!tags.indexOf("<opt")&&[1,"<select multiple='multiple'>","</select>"]||!tags.indexOf("<leg")&&[1,"<fieldset>","</fieldset>"]||tags.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"<table>","</table>"]||!tags.indexOf("<tr")&&[2,"<table><tbody>","</tbody></table>"]||(!tags.indexOf("<td")||!tags.indexOf("<th"))&&[3,"<table><tbody><tr>","</tr></tbody></table>"]||!tags.indexOf("<col")&&[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"]||jQuery.browser.msie&&[1,"div<div>","</div>"]||[0,"",""];div.innerHTML=wrap[1]+elem+wrap[2];while(wrap[0]--)div=div.lastChild;if(jQuery.browser.msie){var tbody=!tags.indexOf("<table")&&tags.indexOf("<tbody")<0?div.firstChild&&div.firstChild.childNodes:wrap[1]=="<table>"&&tags.indexOf("<tbody")<0?div.childNodes:[];for(var j=tbody.length-1;j>=0;--j)if(jQuery.nodeName(tbody[j],"tbody")&&!tbody[j].childNodes.length)tbody[j].parentNode.removeChild(tbody[j]);if(/^\s/.test(elem))div.insertBefore(context.createTextNode(elem.match(/^\s*/)[0]),div.firstChild);}elem=jQuery.makeArray(div.childNodes);}if(elem.length===0&&(!jQuery.nodeName(elem,"form")&&!jQuery.nodeName(elem,"select")))return;if(elem[0]==undefined||jQuery.nodeName(elem,"form")||elem.options)ret.push(elem);else
 24+ret=jQuery.merge(ret,elem);});return ret;},attr:function(elem,name,value){if(!elem||elem.nodeType==3||elem.nodeType==8)return undefined;var notxml=!jQuery.isXMLDoc(elem),set=value!==undefined,msie=jQuery.browser.msie;name=notxml&&jQuery.props[name]||name;if(elem.tagName){var special=/href|src|style/.test(name);if(name=="selected"&&jQuery.browser.safari)elem.parentNode.selectedIndex;if(name in elem&&notxml&&!special){if(set){if(name=="type"&&jQuery.nodeName(elem,"input")&&elem.parentNode)throw"type property can't be changed";elem[name]=value;}if(jQuery.nodeName(elem,"form")&&elem.getAttributeNode(name))return elem.getAttributeNode(name).nodeValue;return elem[name];}if(msie&&notxml&&name=="style")return jQuery.attr(elem.style,"cssText",value);if(set)elem.setAttribute(name,""+value);var attr=msie&&notxml&&special?elem.getAttribute(name,2):elem.getAttribute(name);return attr===null?undefined:attr;}if(msie&&name=="opacity"){if(set){elem.zoom=1;elem.filter=(elem.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(value)+''=="NaN"?"":"alpha(opacity="+value*100+")");}return elem.filter&&elem.filter.indexOf("opacity=")>=0?(parseFloat(elem.filter.match(/opacity=([^)]*)/)[1])/100)+'':"";}name=name.replace(/-([a-z])/ig,function(all,letter){return letter.toUpperCase();});if(set)elem[name]=value;return elem[name];},trim:function(text){return(text||"").replace(/^\s+|\s+$/g,"");},makeArray:function(array){var ret=[];if(array!=null){var i=array.length;if(i==null||array.split||array.setInterval||array.call)ret[0]=array;else
 25+while(i)ret[--i]=array[i];}return ret;},inArray:function(elem,array){for(var i=0,length=array.length;i<length;i++)if(array[i]===elem)return i;return-1;},merge:function(first,second){var i=0,elem,pos=first.length;if(jQuery.browser.msie){while(elem=second[i++])if(elem.nodeType!=8)first[pos++]=elem;}else
 26+while(elem=second[i++])first[pos++]=elem;return first;},unique:function(array){var ret=[],done={};try{for(var i=0,length=array.length;i<length;i++){var id=jQuery.data(array[i]);if(!done[id]){done[id]=true;ret.push(array[i]);}}}catch(e){ret=array;}return ret;},grep:function(elems,callback,inv){var ret=[];for(var i=0,length=elems.length;i<length;i++)if(!inv!=!callback(elems[i],i))ret.push(elems[i]);return ret;},map:function(elems,callback){var ret=[];for(var i=0,length=elems.length;i<length;i++){var value=callback(elems[i],i);if(value!=null)ret[ret.length]=value;}return ret.concat.apply([],ret);}});var userAgent=navigator.userAgent.toLowerCase();jQuery.browser={version:(userAgent.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/)||[])[1],safari:/webkit/.test(userAgent),opera:/opera/.test(userAgent),msie:/msie/.test(userAgent)&&!/opera/.test(userAgent),mozilla:/mozilla/.test(userAgent)&&!/(compatible|webkit)/.test(userAgent)};var styleFloat=jQuery.browser.msie?"styleFloat":"cssFloat";jQuery.extend({boxModel:!jQuery.browser.msie||document.compatMode=="CSS1Compat",props:{"for":"htmlFor","class":"className","float":styleFloat,cssFloat:styleFloat,styleFloat:styleFloat,readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing"}});jQuery.each({parent:function(elem){return elem.parentNode;},parents:function(elem){return jQuery.dir(elem,"parentNode");},next:function(elem){return jQuery.nth(elem,2,"nextSibling");},prev:function(elem){return jQuery.nth(elem,2,"previousSibling");},nextAll:function(elem){return jQuery.dir(elem,"nextSibling");},prevAll:function(elem){return jQuery.dir(elem,"previousSibling");},siblings:function(elem){return jQuery.sibling(elem.parentNode.firstChild,elem);},children:function(elem){return jQuery.sibling(elem.firstChild);},contents:function(elem){return jQuery.nodeName(elem,"iframe")?elem.contentDocument||elem.contentWindow.document:jQuery.makeArray(elem.childNodes);}},function(name,fn){jQuery.fn[name]=function(selector){var ret=jQuery.map(this,fn);if(selector&&typeof selector=="string")ret=jQuery.multiFilter(selector,ret);return this.pushStack(jQuery.unique(ret));};});jQuery.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(name,original){jQuery.fn[name]=function(){var args=arguments;return this.each(function(){for(var i=0,length=args.length;i<length;i++)jQuery(args[i])[original](this);});};});jQuery.each({removeAttr:function(name){jQuery.attr(this,name,"");if(this.nodeType==1)this.removeAttribute(name);},addClass:function(classNames){jQuery.className.add(this,classNames);},removeClass:function(classNames){jQuery.className.remove(this,classNames);},toggleClass:function(classNames){jQuery.className[jQuery.className.has(this,classNames)?"remove":"add"](this,classNames);},remove:function(selector){if(!selector||jQuery.filter(selector,[this]).r.length){jQuery("*",this).add(this).each(function(){jQuery.event.remove(this);jQuery.removeData(this);});if(this.parentNode)this.parentNode.removeChild(this);}},empty:function(){jQuery(">*",this).remove();while(this.firstChild)this.removeChild(this.firstChild);}},function(name,fn){jQuery.fn[name]=function(){return this.each(fn,arguments);};});jQuery.each(["Height","Width"],function(i,name){var type=name.toLowerCase();jQuery.fn[type]=function(size){return this[0]==window?jQuery.browser.opera&&document.body["client"+name]||jQuery.browser.safari&&window["inner"+name]||document.compatMode=="CSS1Compat"&&document.documentElement["client"+name]||document.body["client"+name]:this[0]==document?Math.max(Math.max(document.body["scroll"+name],document.documentElement["scroll"+name]),Math.max(document.body["offset"+name],document.documentElement["offset"+name])):size==undefined?(this.length?jQuery.css(this[0],type):null):this.css(type,size.constructor==String?size:size+"px");};});function num(elem,prop){return elem[0]&&parseInt(jQuery.curCSS(elem[0],prop,true),10)||0;}var chars=jQuery.browser.safari&&parseInt(jQuery.browser.version)<417?"(?:[\\w*_-]|\\\\.)":"(?:[\\w\u0128-\uFFFF*_-]|\\\\.)",quickChild=new RegExp("^>\\s*("+chars+"+)"),quickID=new RegExp("^("+chars+"+)(#)("+chars+"+)"),quickClass=new RegExp("^([#.]?)("+chars+"*)");jQuery.extend({expr:{"":function(a,i,m){return m[2]=="*"||jQuery.nodeName(a,m[2]);},"#":function(a,i,m){return a.getAttribute("id")==m[2];},":":{lt:function(a,i,m){return i<m[3]-0;},gt:function(a,i,m){return i>m[3]-0;},nth:function(a,i,m){return m[3]-0==i;},eq:function(a,i,m){return m[3]-0==i;},first:function(a,i){return i==0;},last:function(a,i,m,r){return i==r.length-1;},even:function(a,i){return i%2==0;},odd:function(a,i){return i%2;},"first-child":function(a){return a.parentNode.getElementsByTagName("*")[0]==a;},"last-child":function(a){return jQuery.nth(a.parentNode.lastChild,1,"previousSibling")==a;},"only-child":function(a){return!jQuery.nth(a.parentNode.lastChild,2,"previousSibling");},parent:function(a){return a.firstChild;},empty:function(a){return!a.firstChild;},contains:function(a,i,m){return(a.textContent||a.innerText||jQuery(a).text()||"").indexOf(m[3])>=0;},visible:function(a){return"hidden"!=a.type&&jQuery.css(a,"display")!="none"&&jQuery.css(a,"visibility")!="hidden";},hidden:function(a){return"hidden"==a.type||jQuery.css(a,"display")=="none"||jQuery.css(a,"visibility")=="hidden";},enabled:function(a){return!a.disabled;},disabled:function(a){return a.disabled;},checked:function(a){return a.checked;},selected:function(a){return a.selected||jQuery.attr(a,"selected");},text:function(a){return"text"==a.type;},radio:function(a){return"radio"==a.type;},checkbox:function(a){return"checkbox"==a.type;},file:function(a){return"file"==a.type;},password:function(a){return"password"==a.type;},submit:function(a){return"submit"==a.type;},image:function(a){return"image"==a.type;},reset:function(a){return"reset"==a.type;},button:function(a){return"button"==a.type||jQuery.nodeName(a,"button");},input:function(a){return/input|select|textarea|button/i.test(a.nodeName);},has:function(a,i,m){return jQuery.find(m[3],a).length;},header:function(a){return/h\d/i.test(a.nodeName);},animated:function(a){return jQuery.grep(jQuery.timers,function(fn){return a==fn.elem;}).length;}}},parse:[/^(\[) *@?([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/,/^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/,new RegExp("^([:.#]*)("+chars+"+)")],multiFilter:function(expr,elems,not){var old,cur=[];while(expr&&expr!=old){old=expr;var f=jQuery.filter(expr,elems,not);expr=f.t.replace(/^\s*,\s*/,"");cur=not?elems=f.r:jQuery.merge(cur,f.r);}return cur;},find:function(t,context){if(typeof t!="string")return[t];if(context&&context.nodeType!=1&&context.nodeType!=9)return[];context=context||document;var ret=[context],done=[],last,nodeName;while(t&&last!=t){var r=[];last=t;t=jQuery.trim(t);var foundToken=false,re=quickChild,m=re.exec(t);if(m){nodeName=m[1].toUpperCase();for(var i=0;ret[i];i++)for(var c=ret[i].firstChild;c;c=c.nextSibling)if(c.nodeType==1&&(nodeName=="*"||c.nodeName.toUpperCase()==nodeName))r.push(c);ret=r;t=t.replace(re,"");if(t.indexOf(" ")==0)continue;foundToken=true;}else{re=/^([>+~])\s*(\w*)/i;if((m=re.exec(t))!=null){r=[];var merge={};nodeName=m[2].toUpperCase();m=m[1];for(var j=0,rl=ret.length;j<rl;j++){var n=m=="~"||m=="+"?ret[j].nextSibling:ret[j].firstChild;for(;n;n=n.nextSibling)if(n.nodeType==1){var id=jQuery.data(n);if(m=="~"&&merge[id])break;if(!nodeName||n.nodeName.toUpperCase()==nodeName){if(m=="~")merge[id]=true;r.push(n);}if(m=="+")break;}}ret=r;t=jQuery.trim(t.replace(re,""));foundToken=true;}}if(t&&!foundToken){if(!t.indexOf(",")){if(context==ret[0])ret.shift();done=jQuery.merge(done,ret);r=ret=[context];t=" "+t.substr(1,t.length);}else{var re2=quickID;var m=re2.exec(t);if(m){m=[0,m[2],m[3],m[1]];}else{re2=quickClass;m=re2.exec(t);}m[2]=m[2].replace(/\\/g,"");var elem=ret[ret.length-1];if(m[1]=="#"&&elem&&elem.getElementById&&!jQuery.isXMLDoc(elem)){var oid=elem.getElementById(m[2]);if((jQuery.browser.msie||jQuery.browser.opera)&&oid&&typeof oid.id=="string"&&oid.id!=m[2])oid=jQuery('[@id="'+m[2]+'"]',elem)[0];ret=r=oid&&(!m[3]||jQuery.nodeName(oid,m[3]))?[oid]:[];}else{for(var i=0;ret[i];i++){var tag=m[1]=="#"&&m[3]?m[3]:m[1]!=""||m[0]==""?"*":m[2];if(tag=="*"&&ret[i].nodeName.toLowerCase()=="object")tag="param";r=jQuery.merge(r,ret[i].getElementsByTagName(tag));}if(m[1]==".")r=jQuery.classFilter(r,m[2]);if(m[1]=="#"){var tmp=[];for(var i=0;r[i];i++)if(r[i].getAttribute("id")==m[2]){tmp=[r[i]];break;}r=tmp;}ret=r;}t=t.replace(re2,"");}}if(t){var val=jQuery.filter(t,r);ret=r=val.r;t=jQuery.trim(val.t);}}if(t)ret=[];if(ret&&context==ret[0])ret.shift();done=jQuery.merge(done,ret);return done;},classFilter:function(r,m,not){m=" "+m+" ";var tmp=[];for(var i=0;r[i];i++){var pass=(" "+r[i].className+" ").indexOf(m)>=0;if(!not&&pass||not&&!pass)tmp.push(r[i]);}return tmp;},filter:function(t,r,not){var last;while(t&&t!=last){last=t;var p=jQuery.parse,m;for(var i=0;p[i];i++){m=p[i].exec(t);if(m){t=t.substring(m[0].length);m[2]=m[2].replace(/\\/g,"");break;}}if(!m)break;if(m[1]==":"&&m[2]=="not")r=isSimple.test(m[3])?jQuery.filter(m[3],r,true).r:jQuery(r).not(m[3]);else if(m[1]==".")r=jQuery.classFilter(r,m[2],not);else if(m[1]=="["){var tmp=[],type=m[3];for(var i=0,rl=r.length;i<rl;i++){var a=r[i],z=a[jQuery.props[m[2]]||m[2]];if(z==null||/href|src|selected/.test(m[2]))z=jQuery.attr(a,m[2])||'';if((type==""&&!!z||type=="="&&z==m[5]||type=="!="&&z!=m[5]||type=="^="&&z&&!z.indexOf(m[5])||type=="$="&&z.substr(z.length-m[5].length)==m[5]||(type=="*="||type=="~=")&&z.indexOf(m[5])>=0)^not)tmp.push(a);}r=tmp;}else if(m[1]==":"&&m[2]=="nth-child"){var merge={},tmp=[],test=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(m[3]=="even"&&"2n"||m[3]=="odd"&&"2n+1"||!/\D/.test(m[3])&&"0n+"+m[3]||m[3]),first=(test[1]+(test[2]||1))-0,last=test[3]-0;for(var i=0,rl=r.length;i<rl;i++){var node=r[i],parentNode=node.parentNode,id=jQuery.data(parentNode);if(!merge[id]){var c=1;for(var n=parentNode.firstChild;n;n=n.nextSibling)if(n.nodeType==1)n.nodeIndex=c++;merge[id]=true;}var add=false;if(first==0){if(node.nodeIndex==last)add=true;}else if((node.nodeIndex-last)%first==0&&(node.nodeIndex-last)/first>=0)add=true;if(add^not)tmp.push(node);}r=tmp;}else{var fn=jQuery.expr[m[1]];if(typeof fn=="object")fn=fn[m[2]];if(typeof fn=="string")fn=eval("false||function(a,i){return "+fn+";}");r=jQuery.grep(r,function(elem,i){return fn(elem,i,m,r);},not);}}return{r:r,t:t};},dir:function(elem,dir){var matched=[],cur=elem[dir];while(cur&&cur!=document){if(cur.nodeType==1)matched.push(cur);cur=cur[dir];}return matched;},nth:function(cur,result,dir,elem){result=result||1;var num=0;for(;cur;cur=cur[dir])if(cur.nodeType==1&&++num==result)break;return cur;},sibling:function(n,elem){var r=[];for(;n;n=n.nextSibling){if(n.nodeType==1&&n!=elem)r.push(n);}return r;}});jQuery.event={add:function(elem,types,handler,data){if(elem.nodeType==3||elem.nodeType==8)return;if(jQuery.browser.msie&&elem.setInterval)elem=window;if(!handler.guid)handler.guid=this.guid++;if(data!=undefined){var fn=handler;handler=this.proxy(fn,function(){return fn.apply(this,arguments);});handler.data=data;}var events=jQuery.data(elem,"events")||jQuery.data(elem,"events",{}),handle=jQuery.data(elem,"handle")||jQuery.data(elem,"handle",function(){if(typeof jQuery!="undefined"&&!jQuery.event.triggered)return jQuery.event.handle.apply(arguments.callee.elem,arguments);});handle.elem=elem;jQuery.each(types.split(/\s+/),function(index,type){var parts=type.split(".");type=parts[0];handler.type=parts[1];var handlers=events[type];if(!handlers){handlers=events[type]={};if(!jQuery.event.special[type]||jQuery.event.special[type].setup.call(elem)===false){if(elem.addEventListener)elem.addEventListener(type,handle,false);else if(elem.attachEvent)elem.attachEvent("on"+type,handle);}}handlers[handler.guid]=handler;jQuery.event.global[type]=true;});elem=null;},guid:1,global:{},remove:function(elem,types,handler){if(elem.nodeType==3||elem.nodeType==8)return;var events=jQuery.data(elem,"events"),ret,index;if(events){if(types==undefined||(typeof types=="string"&&types.charAt(0)=="."))for(var type in events)this.remove(elem,type+(types||""));else{if(types.type){handler=types.handler;types=types.type;}jQuery.each(types.split(/\s+/),function(index,type){var parts=type.split(".");type=parts[0];if(events[type]){if(handler)delete events[type][handler.guid];else
 27+for(handler in events[type])if(!parts[1]||events[type][handler].type==parts[1])delete events[type][handler];for(ret in events[type])break;if(!ret){if(!jQuery.event.special[type]||jQuery.event.special[type].teardown.call(elem)===false){if(elem.removeEventListener)elem.removeEventListener(type,jQuery.data(elem,"handle"),false);else if(elem.detachEvent)elem.detachEvent("on"+type,jQuery.data(elem,"handle"));}ret=null;delete events[type];}}});}for(ret in events)break;if(!ret){var handle=jQuery.data(elem,"handle");if(handle)handle.elem=null;jQuery.removeData(elem,"events");jQuery.removeData(elem,"handle");}}},trigger:function(type,data,elem,donative,extra){data=jQuery.makeArray(data);if(type.indexOf("!")>=0){type=type.slice(0,-1);var exclusive=true;}if(!elem){if(this.global[type])jQuery("*").add([window,document]).trigger(type,data);}else{if(elem.nodeType==3||elem.nodeType==8)return undefined;var val,ret,fn=jQuery.isFunction(elem[type]||null),event=!data[0]||!data[0].preventDefault;if(event){data.unshift({type:type,target:elem,preventDefault:function(){},stopPropagation:function(){},timeStamp:now()});data[0][expando]=true;}data[0].type=type;if(exclusive)data[0].exclusive=true;var handle=jQuery.data(elem,"handle");if(handle)val=handle.apply(elem,data);if((!fn||(jQuery.nodeName(elem,'a')&&type=="click"))&&elem["on"+type]&&elem["on"+type].apply(elem,data)===false)val=false;if(event)data.shift();if(extra&&jQuery.isFunction(extra)){ret=extra.apply(elem,val==null?data:data.concat(val));if(ret!==undefined)val=ret;}if(fn&&donative!==false&&val!==false&&!(jQuery.nodeName(elem,'a')&&type=="click")){this.triggered=true;try{elem[type]();}catch(e){}}this.triggered=false;}return val;},handle:function(event){var val,ret,namespace,all,handlers;event=arguments[0]=jQuery.event.fix(event||window.event);namespace=event.type.split(".");event.type=namespace[0];namespace=namespace[1];all=!namespace&&!event.exclusive;handlers=(jQuery.data(this,"events")||{})[event.type];for(var j in handlers){var handler=handlers[j];if(all||handler.type==namespace){event.handler=handler;event.data=handler.data;ret=handler.apply(this,arguments);if(val!==false)val=ret;if(ret===false){event.preventDefault();event.stopPropagation();}}}return val;},fix:function(event){if(event[expando]==true)return event;var originalEvent=event;event={originalEvent:originalEvent};var props="altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target timeStamp toElement type view wheelDelta which".split(" ");for(var i=props.length;i;i--)event[props[i]]=originalEvent[props[i]];event[expando]=true;event.preventDefault=function(){if(originalEvent.preventDefault)originalEvent.preventDefault();originalEvent.returnValue=false;};event.stopPropagation=function(){if(originalEvent.stopPropagation)originalEvent.stopPropagation();originalEvent.cancelBubble=true;};event.timeStamp=event.timeStamp||now();if(!event.target)event.target=event.srcElement||document;if(event.target.nodeType==3)event.target=event.target.parentNode;if(!event.relatedTarget&&event.fromElement)event.relatedTarget=event.fromElement==event.target?event.toElement:event.fromElement;if(event.pageX==null&&event.clientX!=null){var doc=document.documentElement,body=document.body;event.pageX=event.clientX+(doc&&doc.scrollLeft||body&&body.scrollLeft||0)-(doc.clientLeft||0);event.pageY=event.clientY+(doc&&doc.scrollTop||body&&body.scrollTop||0)-(doc.clientTop||0);}if(!event.which&&((event.charCode||event.charCode===0)?event.charCode:event.keyCode))event.which=event.charCode||event.keyCode;if(!event.metaKey&&event.ctrlKey)event.metaKey=event.ctrlKey;if(!event.which&&event.button)event.which=(event.button&1?1:(event.button&2?3:(event.button&4?2:0)));return event;},proxy:function(fn,proxy){proxy.guid=fn.guid=fn.guid||proxy.guid||this.guid++;return proxy;},special:{ready:{setup:function(){bindReady();return;},teardown:function(){return;}},mouseenter:{setup:function(){if(jQuery.browser.msie)return false;jQuery(this).bind("mouseover",jQuery.event.special.mouseenter.handler);return true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery(this).unbind("mouseover",jQuery.event.special.mouseenter.handler);return true;},handler:function(event){if(withinElement(event,this))return true;event.type="mouseenter";return jQuery.event.handle.apply(this,arguments);}},mouseleave:{setup:function(){if(jQuery.browser.msie)return false;jQuery(this).bind("mouseout",jQuery.event.special.mouseleave.handler);return true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery(this).unbind("mouseout",jQuery.event.special.mouseleave.handler);return true;},handler:function(event){if(withinElement(event,this))return true;event.type="mouseleave";return jQuery.event.handle.apply(this,arguments);}}}};jQuery.fn.extend({bind:function(type,data,fn){return type=="unload"?this.one(type,data,fn):this.each(function(){jQuery.event.add(this,type,fn||data,fn&&data);});},one:function(type,data,fn){var one=jQuery.event.proxy(fn||data,function(event){jQuery(this).unbind(event,one);return(fn||data).apply(this,arguments);});return this.each(function(){jQuery.event.add(this,type,one,fn&&data);});},unbind:function(type,fn){return this.each(function(){jQuery.event.remove(this,type,fn);});},trigger:function(type,data,fn){return this.each(function(){jQuery.event.trigger(type,data,this,true,fn);});},triggerHandler:function(type,data,fn){return this[0]&&jQuery.event.trigger(type,data,this[0],false,fn);},toggle:function(fn){var args=arguments,i=1;while(i<args.length)jQuery.event.proxy(fn,args[i++]);return this.click(jQuery.event.proxy(fn,function(event){this.lastToggle=(this.lastToggle||0)%i;event.preventDefault();return args[this.lastToggle++].apply(this,arguments)||false;}));},hover:function(fnOver,fnOut){return this.bind('mouseenter',fnOver).bind('mouseleave',fnOut);},ready:function(fn){bindReady();if(jQuery.isReady)fn.call(document,jQuery);else
 28+jQuery.readyList.push(function(){return fn.call(this,jQuery);});return this;}});jQuery.extend({isReady:false,readyList:[],ready:function(){if(!jQuery.isReady){jQuery.isReady=true;if(jQuery.readyList){jQuery.each(jQuery.readyList,function(){this.call(document);});jQuery.readyList=null;}jQuery(document).triggerHandler("ready");}}});var readyBound=false;function bindReady(){if(readyBound)return;readyBound=true;if(document.addEventListener&&!jQuery.browser.opera)document.addEventListener("DOMContentLoaded",jQuery.ready,false);if(jQuery.browser.msie&&window==top)(function(){if(jQuery.isReady)return;try{document.documentElement.doScroll("left");}catch(error){setTimeout(arguments.callee,0);return;}jQuery.ready();})();if(jQuery.browser.opera)document.addEventListener("DOMContentLoaded",function(){if(jQuery.isReady)return;for(var i=0;i<document.styleSheets.length;i++)if(document.styleSheets[i].disabled){setTimeout(arguments.callee,0);return;}jQuery.ready();},false);if(jQuery.browser.safari){var numStyles;(function(){if(jQuery.isReady)return;if(document.readyState!="loaded"&&document.readyState!="complete"){setTimeout(arguments.callee,0);return;}if(numStyles===undefined)numStyles=jQuery("style, link[rel=stylesheet]").length;if(document.styleSheets.length!=numStyles){setTimeout(arguments.callee,0);return;}jQuery.ready();})();}jQuery.event.add(window,"load",jQuery.ready);}jQuery.each(("blur,focus,load,resize,scroll,unload,click,dblclick,"+"mousedown,mouseup,mousemove,mouseover,mouseout,change,select,"+"submit,keydown,keypress,keyup,error").split(","),function(i,name){jQuery.fn[name]=function(fn){return fn?this.bind(name,fn):this.trigger(name);};});var withinElement=function(event,elem){var parent=event.relatedTarget;while(parent&&parent!=elem)try{parent=parent.parentNode;}catch(error){parent=elem;}return parent==elem;};jQuery(window).bind("unload",function(){jQuery("*").add(document).unbind();});jQuery.fn.extend({_load:jQuery.fn.load,load:function(url,params,callback){if(typeof url!='string')return this._load(url);var off=url.indexOf(" ");if(off>=0){var selector=url.slice(off,url.length);url=url.slice(0,off);}callback=callback||function(){};var type="GET";if(params)if(jQuery.isFunction(params)){callback=params;params=null;}else{params=jQuery.param(params);type="POST";}var self=this;jQuery.ajax({url:url,type:type,dataType:"html",data:params,complete:function(res,status){if(status=="success"||status=="notmodified")self.html(selector?jQuery("<div/>").append(res.responseText.replace(/<script(.|\s)*?\/script>/g,"")).find(selector):res.responseText);self.each(callback,[res.responseText,status,res]);}});return this;},serialize:function(){return jQuery.param(this.serializeArray());},serializeArray:function(){return this.map(function(){return jQuery.nodeName(this,"form")?jQuery.makeArray(this.elements):this;}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password/i.test(this.type));}).map(function(i,elem){var val=jQuery(this).val();return val==null?null:val.constructor==Array?jQuery.map(val,function(val,i){return{name:elem.name,value:val};}):{name:elem.name,value:val};}).get();}});jQuery.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(i,o){jQuery.fn[o]=function(f){return this.bind(o,f);};});var jsc=now();jQuery.extend({get:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data=null;}return jQuery.ajax({type:"GET",url:url,data:data,success:callback,dataType:type});},getScript:function(url,callback){return jQuery.get(url,null,callback,"script");},getJSON:function(url,data,callback){return jQuery.get(url,data,callback,"json");},post:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data={};}return jQuery.ajax({type:"POST",url:url,data:data,success:callback,dataType:type});},ajaxSetup:function(settings){jQuery.extend(jQuery.ajaxSettings,settings);},ajaxSettings:{url:location.href,global:true,type:"GET",timeout:0,contentType:"application/x-www-form-urlencoded",processData:true,async:true,data:null,username:null,password:null,accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(s){s=jQuery.extend(true,s,jQuery.extend(true,{},jQuery.ajaxSettings,s));var jsonp,jsre=/=\?(&|$)/g,status,data,type=s.type.toUpperCase();if(s.data&&s.processData&&typeof s.data!="string")s.data=jQuery.param(s.data);if(s.dataType=="jsonp"){if(type=="GET"){if(!s.url.match(jsre))s.url+=(s.url.match(/\?/)?"&":"?")+(s.jsonp||"callback")+"=?";}else if(!s.data||!s.data.match(jsre))s.data=(s.data?s.data+"&":"")+(s.jsonp||"callback")+"=?";s.dataType="json";}if(s.dataType=="json"&&(s.data&&s.data.match(jsre)||s.url.match(jsre))){jsonp="jsonp"+jsc++;if(s.data)s.data=(s.data+"").replace(jsre,"="+jsonp+"$1");s.url=s.url.replace(jsre,"="+jsonp+"$1");s.dataType="script";window[jsonp]=function(tmp){data=tmp;success();complete();window[jsonp]=undefined;try{delete window[jsonp];}catch(e){}if(head)head.removeChild(script);};}if(s.dataType=="script"&&s.cache==null)s.cache=false;if(s.cache===false&&type=="GET"){var ts=now();var ret=s.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+ts+"$2");s.url=ret+((ret==s.url)?(s.url.match(/\?/)?"&":"?")+"_="+ts:"");}if(s.data&&type=="GET"){s.url+=(s.url.match(/\?/)?"&":"?")+s.data;s.data=null;}if(s.global&&!jQuery.active++)jQuery.event.trigger("ajaxStart");var remote=/^(?:\w+:)?\/\/([^\/?#]+)/;if(s.dataType=="script"&&type=="GET"&&remote.test(s.url)&&remote.exec(s.url)[1]!=location.host){var head=document.getElementsByTagName("head")[0];var script=document.createElement("script");script.src=s.url;if(s.scriptCharset)script.charset=s.scriptCharset;if(!jsonp){var done=false;script.onload=script.onreadystatechange=function(){if(!done&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){done=true;success();complete();head.removeChild(script);}};}head.appendChild(script);return undefined;}var requestDone=false;var xhr=window.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest();if(s.username)xhr.open(type,s.url,s.async,s.username,s.password);else
 29+xhr.open(type,s.url,s.async);try{if(s.data)xhr.setRequestHeader("Content-Type",s.contentType);if(s.ifModified)xhr.setRequestHeader("If-Modified-Since",jQuery.lastModified[s.url]||"Thu, 01 Jan 1970 00:00:00 GMT");xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Accept",s.dataType&&s.accepts[s.dataType]?s.accepts[s.dataType]+", */*":s.accepts._default);}catch(e){}if(s.beforeSend&&s.beforeSend(xhr,s)===false){s.global&&jQuery.active--;xhr.abort();return false;}if(s.global)jQuery.event.trigger("ajaxSend",[xhr,s]);var onreadystatechange=function(isTimeout){if(!requestDone&&xhr&&(xhr.readyState==4||isTimeout=="timeout")){requestDone=true;if(ival){clearInterval(ival);ival=null;}status=isTimeout=="timeout"&&"timeout"||!jQuery.httpSuccess(xhr)&&"error"||s.ifModified&&jQuery.httpNotModified(xhr,s.url)&&"notmodified"||"success";if(status=="success"){try{data=jQuery.httpData(xhr,s.dataType,s.dataFilter);}catch(e){status="parsererror";}}if(status=="success"){var modRes;try{modRes=xhr.getResponseHeader("Last-Modified");}catch(e){}if(s.ifModified&&modRes)jQuery.lastModified[s.url]=modRes;if(!jsonp)success();}else
 30+jQuery.handleError(s,xhr,status);complete();if(s.async)xhr=null;}};if(s.async){var ival=setInterval(onreadystatechange,13);if(s.timeout>0)setTimeout(function(){if(xhr){xhr.abort();if(!requestDone)onreadystatechange("timeout");}},s.timeout);}try{xhr.send(s.data);}catch(e){jQuery.handleError(s,xhr,null,e);}if(!s.async)onreadystatechange();function success(){if(s.success)s.success(data,status);if(s.global)jQuery.event.trigger("ajaxSuccess",[xhr,s]);}function complete(){if(s.complete)s.complete(xhr,status);if(s.global)jQuery.event.trigger("ajaxComplete",[xhr,s]);if(s.global&&!--jQuery.active)jQuery.event.trigger("ajaxStop");}return xhr;},handleError:function(s,xhr,status,e){if(s.error)s.error(xhr,status,e);if(s.global)jQuery.event.trigger("ajaxError",[xhr,s,e]);},active:0,httpSuccess:function(xhr){try{return!xhr.status&&location.protocol=="file:"||(xhr.status>=200&&xhr.status<300)||xhr.status==304||xhr.status==1223||jQuery.browser.safari&&xhr.status==undefined;}catch(e){}return false;},httpNotModified:function(xhr,url){try{var xhrRes=xhr.getResponseHeader("Last-Modified");return xhr.status==304||xhrRes==jQuery.lastModified[url]||jQuery.browser.safari&&xhr.status==undefined;}catch(e){}return false;},httpData:function(xhr,type,filter){var ct=xhr.getResponseHeader("content-type"),xml=type=="xml"||!type&&ct&&ct.indexOf("xml")>=0,data=xml?xhr.responseXML:xhr.responseText;if(xml&&data.documentElement.tagName=="parsererror")throw"parsererror";if(filter)data=filter(data,type);if(type=="script")jQuery.globalEval(data);if(type=="json")data=eval("("+data+")");return data;},param:function(a){var s=[];if(a.constructor==Array||a.jquery)jQuery.each(a,function(){s.push(encodeURIComponent(this.name)+"="+encodeURIComponent(this.value));});else
 31+for(var j in a)if(a[j]&&a[j].constructor==Array)jQuery.each(a[j],function(){s.push(encodeURIComponent(j)+"="+encodeURIComponent(this));});else
 32+s.push(encodeURIComponent(j)+"="+encodeURIComponent(jQuery.isFunction(a[j])?a[j]():a[j]));return s.join("&").replace(/%20/g,"+");}});jQuery.fn.extend({show:function(speed,callback){return speed?this.animate({height:"show",width:"show",opacity:"show"},speed,callback):this.filter(":hidden").each(function(){this.style.display=this.oldblock||"";if(jQuery.css(this,"display")=="none"){var elem=jQuery("<"+this.tagName+" />").appendTo("body");this.style.display=elem.css("display");if(this.style.display=="none")this.style.display="block";elem.remove();}}).end();},hide:function(speed,callback){return speed?this.animate({height:"hide",width:"hide",opacity:"hide"},speed,callback):this.filter(":visible").each(function(){this.oldblock=this.oldblock||jQuery.css(this,"display");this.style.display="none";}).end();},_toggle:jQuery.fn.toggle,toggle:function(fn,fn2){return jQuery.isFunction(fn)&&jQuery.isFunction(fn2)?this._toggle.apply(this,arguments):fn?this.animate({height:"toggle",width:"toggle",opacity:"toggle"},fn,fn2):this.each(function(){jQuery(this)[jQuery(this).is(":hidden")?"show":"hide"]();});},slideDown:function(speed,callback){return this.animate({height:"show"},speed,callback);},slideUp:function(speed,callback){return this.animate({height:"hide"},speed,callback);},slideToggle:function(speed,callback){return this.animate({height:"toggle"},speed,callback);},fadeIn:function(speed,callback){return this.animate({opacity:"show"},speed,callback);},fadeOut:function(speed,callback){return this.animate({opacity:"hide"},speed,callback);},fadeTo:function(speed,to,callback){return this.animate({opacity:to},speed,callback);},animate:function(prop,speed,easing,callback){var optall=jQuery.speed(speed,easing,callback);return this[optall.queue===false?"each":"queue"](function(){if(this.nodeType!=1)return false;var opt=jQuery.extend({},optall),p,hidden=jQuery(this).is(":hidden"),self=this;for(p in prop){if(prop[p]=="hide"&&hidden||prop[p]=="show"&&!hidden)return opt.complete.call(this);if(p=="height"||p=="width"){opt.display=jQuery.css(this,"display");opt.overflow=this.style.overflow;}}if(opt.overflow!=null)this.style.overflow="hidden";opt.curAnim=jQuery.extend({},prop);jQuery.each(prop,function(name,val){var e=new jQuery.fx(self,opt,name);if(/toggle|show|hide/.test(val))e[val=="toggle"?hidden?"show":"hide":val](prop);else{var parts=val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),start=e.cur(true)||0;if(parts){var end=parseFloat(parts[2]),unit=parts[3]||"px";if(unit!="px"){self.style[name]=(end||1)+unit;start=((end||1)/e.cur(true))*start;self.style[name]=start+unit;}if(parts[1])end=((parts[1]=="-="?-1:1)*end)+start;e.custom(start,end,unit);}else
 33+e.custom(start,val,"");}});return true;});},queue:function(type,fn){if(jQuery.isFunction(type)||(type&&type.constructor==Array)){fn=type;type="fx";}if(!type||(typeof type=="string"&&!fn))return queue(this[0],type);return this.each(function(){if(fn.constructor==Array)queue(this,type,fn);else{queue(this,type).push(fn);if(queue(this,type).length==1)fn.call(this);}});},stop:function(clearQueue,gotoEnd){var timers=jQuery.timers;if(clearQueue)this.queue([]);this.each(function(){for(var i=timers.length-1;i>=0;i--)if(timers[i].elem==this){if(gotoEnd)timers[i](true);timers.splice(i,1);}});if(!gotoEnd)this.dequeue();return this;}});var queue=function(elem,type,array){if(elem){type=type||"fx";var q=jQuery.data(elem,type+"queue");if(!q||array)q=jQuery.data(elem,type+"queue",jQuery.makeArray(array));}return q;};jQuery.fn.dequeue=function(type){type=type||"fx";return this.each(function(){var q=queue(this,type);q.shift();if(q.length)q[0].call(this);});};jQuery.extend({speed:function(speed,easing,fn){var opt=speed&&speed.constructor==Object?speed:{complete:fn||!fn&&easing||jQuery.isFunction(speed)&&speed,duration:speed,easing:fn&&easing||easing&&easing.constructor!=Function&&easing};opt.duration=(opt.duration&&opt.duration.constructor==Number?opt.duration:jQuery.fx.speeds[opt.duration])||jQuery.fx.speeds.def;opt.old=opt.complete;opt.complete=function(){if(opt.queue!==false)jQuery(this).dequeue();if(jQuery.isFunction(opt.old))opt.old.call(this);};return opt;},easing:{linear:function(p,n,firstNum,diff){return firstNum+diff*p;},swing:function(p,n,firstNum,diff){return((-Math.cos(p*Math.PI)/2)+0.5)*diff+firstNum;}},timers:[],timerId:null,fx:function(elem,options,prop){this.options=options;this.elem=elem;this.prop=prop;if(!options.orig)options.orig={};}});jQuery.fx.prototype={update:function(){if(this.options.step)this.options.step.call(this.elem,this.now,this);(jQuery.fx.step[this.prop]||jQuery.fx.step._default)(this);if(this.prop=="height"||this.prop=="width")this.elem.style.display="block";},cur:function(force){if(this.elem[this.prop]!=null&&this.elem.style[this.prop]==null)return this.elem[this.prop];var r=parseFloat(jQuery.css(this.elem,this.prop,force));return r&&r>-10000?r:parseFloat(jQuery.curCSS(this.elem,this.prop))||0;},custom:function(from,to,unit){this.startTime=now();this.start=from;this.end=to;this.unit=unit||this.unit||"px";this.now=this.start;this.pos=this.state=0;this.update();var self=this;function t(gotoEnd){return self.step(gotoEnd);}t.elem=this.elem;jQuery.timers.push(t);if(jQuery.timerId==null){jQuery.timerId=setInterval(function(){var timers=jQuery.timers;for(var i=0;i<timers.length;i++)if(!timers[i]())timers.splice(i--,1);if(!timers.length){clearInterval(jQuery.timerId);jQuery.timerId=null;}},13);}},show:function(){this.options.orig[this.prop]=jQuery.attr(this.elem.style,this.prop);this.options.show=true;this.custom(0,this.cur());if(this.prop=="width"||this.prop=="height")this.elem.style[this.prop]="1px";jQuery(this.elem).show();},hide:function(){this.options.orig[this.prop]=jQuery.attr(this.elem.style,this.prop);this.options.hide=true;this.custom(this.cur(),0);},step:function(gotoEnd){var t=now();if(gotoEnd||t>this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var done=true;for(var i in this.options.curAnim)if(this.options.curAnim[i]!==true)done=false;if(done){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(jQuery.css(this.elem,"display")=="none")this.elem.style.display="block";}if(this.options.hide)this.elem.style.display="none";if(this.options.hide||this.options.show)for(var p in this.options.curAnim)jQuery.attr(this.elem.style,p,this.options.orig[p]);}if(done)this.options.complete.call(this.elem);return false;}else{var n=t-this.startTime;this.state=n/this.options.duration;this.pos=jQuery.easing[this.options.easing||(jQuery.easing.swing?"swing":"linear")](this.state,n,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update();}return true;}};jQuery.extend(jQuery.fx,{speeds:{slow:600,fast:200,def:400},step:{scrollLeft:function(fx){fx.elem.scrollLeft=fx.now;},scrollTop:function(fx){fx.elem.scrollTop=fx.now;},opacity:function(fx){jQuery.attr(fx.elem.style,"opacity",fx.now);},_default:function(fx){fx.elem.style[fx.prop]=fx.now+fx.unit;}}});jQuery.fn.offset=function(){var left=0,top=0,elem=this[0],results;if(elem)with(jQuery.browser){var parent=elem.parentNode,offsetChild=elem,offsetParent=elem.offsetParent,doc=elem.ownerDocument,safari2=safari&&parseInt(version)<522&&!/adobeair/i.test(userAgent),css=jQuery.curCSS,fixed=css(elem,"position")=="fixed";if(elem.getBoundingClientRect){var box=elem.getBoundingClientRect();add(box.left+Math.max(doc.documentElement.scrollLeft,doc.body.scrollLeft),box.top+Math.max(doc.documentElement.scrollTop,doc.body.scrollTop));add(-doc.documentElement.clientLeft,-doc.documentElement.clientTop);}else{add(elem.offsetLeft,elem.offsetTop);while(offsetParent){add(offsetParent.offsetLeft,offsetParent.offsetTop);if(mozilla&&!/^t(able|d|h)$/i.test(offsetParent.tagName)||safari&&!safari2)border(offsetParent);if(!fixed&&css(offsetParent,"position")=="fixed")fixed=true;offsetChild=/^body$/i.test(offsetParent.tagName)?offsetChild:offsetParent;offsetParent=offsetParent.offsetParent;}while(parent&&parent.tagName&&!/^body|html$/i.test(parent.tagName)){if(!/^inline|table.*$/i.test(css(parent,"display")))add(-parent.scrollLeft,-parent.scrollTop);if(mozilla&&css(parent,"overflow")!="visible")border(parent);parent=parent.parentNode;}if((safari2&&(fixed||css(offsetChild,"position")=="absolute"))||(mozilla&&css(offsetChild,"position")!="absolute"))add(-doc.body.offsetLeft,-doc.body.offsetTop);if(fixed)add(Math.max(doc.documentElement.scrollLeft,doc.body.scrollLeft),Math.max(doc.documentElement.scrollTop,doc.body.scrollTop));}results={top:top,left:left};}function border(elem){add(jQuery.curCSS(elem,"borderLeftWidth",true),jQuery.curCSS(elem,"borderTopWidth",true));}function add(l,t){left+=parseInt(l,10)||0;top+=parseInt(t,10)||0;}return results;};jQuery.fn.extend({position:function(){var left=0,top=0,results;if(this[0]){var offsetParent=this.offsetParent(),offset=this.offset(),parentOffset=/^body|html$/i.test(offsetParent[0].tagName)?{top:0,left:0}:offsetParent.offset();offset.top-=num(this,'marginTop');offset.left-=num(this,'marginLeft');parentOffset.top+=num(offsetParent,'borderTopWidth');parentOffset.left+=num(offsetParent,'borderLeftWidth');results={top:offset.top-parentOffset.top,left:offset.left-parentOffset.left};}return results;},offsetParent:function(){var offsetParent=this[0].offsetParent;while(offsetParent&&(!/^body|html$/i.test(offsetParent.tagName)&&jQuery.css(offsetParent,'position')=='static'))offsetParent=offsetParent.offsetParent;return jQuery(offsetParent);}});jQuery.each(['Left','Top'],function(i,name){var method='scroll'+name;jQuery.fn[method]=function(val){if(!this[0])return;return val!=undefined?this.each(function(){this==window||this==document?window.scrollTo(!i?val:jQuery(window).scrollLeft(),i?val:jQuery(window).scrollTop()):this[method]=val;}):this[0]==window||this[0]==document?self[i?'pageYOffset':'pageXOffset']||jQuery.boxModel&&document.documentElement[method]||document.body[method]:this[0][method];};});jQuery.each(["Height","Width"],function(i,name){var tl=i?"Left":"Top",br=i?"Right":"Bottom";jQuery.fn["inner"+name]=function(){return this[name.toLowerCase()]()+num(this,"padding"+tl)+num(this,"padding"+br);};jQuery.fn["outer"+name]=function(margin){return this["inner"+name]()+num(this,"border"+tl+"Width")+num(this,"border"+br+"Width")+(margin?num(this,"margin"+tl)+num(this,"margin"+br):0);};});})();
\ No newline at end of file
Property changes on: trunk/extensions/BugzillaReports/scripts/jquery-1.2.6.min.js
___________________________________________________________________
Added: svn:keywords
134 + Id
Index: trunk/extensions/BugzillaReports/skins/bz_main.css
@@ -0,0 +1,179 @@
 2+table.bugzilla, div.bz_noresults {
 3+ border-radius:8px;
 4+ border: 1px solid #f0f0f0;
 5+ border-spacing:0px;
 6+ border-collapse: collapse;
 7+}
 8+span.flag {
 9+ font-size: x-small;
 10+ color: #999;
 11+}
 12+div.bz_noresults {
 13+ padding: 5px;
 14+ font-weight:bold;
 15+ color: #909090;
 16+ text-align:center;
 17+}
 18+div.warning {
 19+ padding: 5px;
 20+ font-weight:bold;
 21+ color: #f00;
 22+}
 23+div.bz_comment {
 24+ position: absolute;
 25+ -moz-border-radius:8px;
 26+ border: 1px solid #909090;
 27+ background-color: #fff;
 28+ padding:10px;
 29+ margin:5px 5px 5px 20px;
 30+ width:50%;
 31+}
 32+div.bz_comment span.bug_id {
 33+ -moz-border-radius:4px;
 34+ position: relative;
 35+ top:-5px;
 36+ left: -5px;
 37+ padding: 2px;
 38+ background-color:#e0e0e0;
 39+ font-size: x-small;
 40+}
 41+table.bugzilla td{
 42+ padding:2px 5px 2px 5px;
 43+}
 44+table.bugzilla td.bz_details{
 45+ padding:2px 5px 2px 30px;
 46+ color: #222;
 47+ font-style: italic;
 48+}
 49+table.bugzilla tr.bz_group {
 50+ background-color: #fff;
 51+}
 52+table.bugzilla tr.bz_group:hover {
 53+ background-color: #e0e0e0;
 54+}
 55+table.bugzilla tr.bz_group td {
 56+ text-align: right;
 57+ border-bottom:2px solid #555;
 58+ padding: 2px 5px 2px 2px;
 59+ color: #555;
 60+ font-weight: bold;
 61+}
 62+table.bugzilla tr.bz_group td.total {
 63+ border-bottom:0px solid #555;
 64+}
 65+table.bugzilla td.total {
 66+ border: 1px solid #f0f0f0;
 67+}
 68+table.bugzilla td.total:hover,table.bugzilla tr.bz_bar td.total:hover {
 69+ background-color: #dde;
 70+}
 71+table.bugzilla tr.bz_row_odd {
 72+ background-color: #f0f0f0;
 73+}
 74+table.bugzilla tr.P1 {
 75+ color: #b30000;
 76+ font-weight: bold;
 77+}
 78+table.bugzilla tr.P4 {
 79+ color: #57585b;
 80+}
 81+table.bugzilla tr.P5 {
 82+ color: #a7a8ab;
 83+}
 84+table.bugzilla tr.RESOLVED td {
 85+ font-style: italic;
 86+}
 87+table.bugzilla tr.CLOSED td {
 88+ text-decoration: line-through;
 89+}
 90+table.bugzilla tr {
 91+ vertical-align:top;
 92+}
 93+table.bugzilla tr:hover {
 94+ background-color: #e0e0e0;
 95+}
 96+table.bz_bar {
 97+ width:500px;
 98+}
 99+table.bugzilla tr.bz_bar {
 100+ background-color: #ddd;
 101+}
 102+table.bugzilla tr.bz_bar td {
 103+ margin: 0px;
 104+ padding: 0px;
 105+ border-right:2px solid #555;
 106+ border-bottom:2px solid #555;
 107+}
 108+table.bugzilla tr.bz_bar td.total {
 109+ border-bottom:0px;
 110+ border-right:0px;
 111+ text-align:right;
 112+ padding-right:2px;
 113+ background-color:#fff;
 114+}
 115+table.bugzilla tr.bz_bar_total td {
 116+ font-weight: bold;
 117+ border-right:0px;
 118+ text-align:right;
 119+ padding-right:2px;
 120+ background-color:#eee;
 121+}
 122+table.bugzilla tr.bz_bar td div {
 123+ text-align:right;
 124+ color:#000;
 125+}
 126+table.bugzilla tr.bz_bar td div div {
 127+ text-align:right;
 128+ color:#000;
 129+ font-size:small;
 130+ border-left:2px solid #555;
 131+ padding-right:4px;
 132+}
 133+table.bugzilla tr.bz_bar td div.C1{
 134+ background-color: #aae;
 135+}
 136+table.bugzilla tr.bz_bar td div.C2{
 137+ background-color: #bbe;
 138+}
 139+table.bugzilla tr.bz_bar td div.C3{
 140+ background-color: #cce;
 141+}
 142+table.bugzilla tr.bz_bar td div.C4{
 143+ background-color: #dde;
 144+}
 145+table.bugzilla tr.bz_bar td div.C5{
 146+ background-color: #eee;
 147+}
 148+table.bugzilla tr.bz_bar td div.notlast{
 149+ float: left;
 150+}
 151+table.bugzilla tr.bz_bar td div.last{
 152+ float: right;
 153+}
 154+table.bugzilla tr.some td div.C1{
 155+ background-color: #aae;
 156+}
 157+table.bugzilla tr.some td div.C2{
 158+ background-color: #abe;
 159+}
 160+table.bugzilla tr.some td div.C3{
 161+ background-color: #bbe;
 162+}
 163+table.bugzilla tr.some td div.C4{
 164+ background-color: #bce;
 165+}
 166+table.bugzilla tr.some td div.C5{
 167+ background-color: #cce;
 168+}
 169+table.bugzilla tr.some td div.C6{
 170+ background-color: #cde;
 171+}
 172+table.bugzilla tr.some td div.C7{
 173+ background-color: #dde;
 174+}
 175+table.bugzilla tr.some td div.C8{
 176+ background-color: #dee;
 177+}
 178+table.bugzilla tr.some td div.C9{
 179+ background-color: #eee;
 180+}
Property changes on: trunk/extensions/BugzillaReports/skins/bz_main.css
___________________________________________________________________
Added: svn:keywords
1181 + Id
Added: svn:eol-style
2182 + native
Index: trunk/extensions/BugzillaReports/BMWExtension.php
@@ -0,0 +1,74 @@
 2+<?php
 3+/*
 4+Bemoko MediaWiki Extension
 5+*/
 6+
 7+/**
 8+ * The bugzilla report objects
 9+ */
 10+abstract class BMWExtension {
 11+ public $debug;
 12+ var $warnings;
 13+
 14+ abstract protected function set($name,$value);
 15+
 16+ /**
 17+ * Extract options from a arguments
 18+ *
 19+ * @param string $args function arguments
 20+ */
 21+ public function extractOptions( $args ) {
 22+ $this->debug && $this->debug("Calling extractOptions");
 23+ foreach( $args as $line ) {
 24+ if( strpos( $line, '=' ) === false )
 25+ continue;
 26+ list( $name, $value ) = explode( '=', $line, 2 );
 27+ $value=trim($value);
 28+ $match=$this->getParameterRegex($name);
 29+ if (!$match) {
 30+ /**
 31+ * Safe parameter reading by default
 32+ * only allowing alphanumeric
 33+ */
 34+ $match="/^[\w]*$/";
 35+ }
 36+ if (preg_match($match, $value, $matches)) {
 37+ $this->set($name,$matches[0]);
 38+ $this->debug &&
 39+ $this->debug("Parameter [".$name."=".$matches[0]."]");
 40+ } else {
 41+ $this->warn("Parameter ".
 42+ $name."=".$value." is invalid using regex $match");
 43+ }
 44+ }
 45+ }
 46+
 47+ /**
 48+ * MediaWiki debug message
 49+ */
 50+ public function debug($message) {
 51+ if ($this->debug) {
 52+ if ($message) {
 53+ wfDebugLog("BMWExtension",$message);
 54+ } else {
 55+ $this->warn("Null message sent to debug statement");
 56+ }
 57+ }
 58+ }
 59+ /**
 60+ * MediaWiki warn message
 61+ */
 62+ public function warn($message) {
 63+ wfDebugLog("BMWExtension","WARN: *** ".$message);
 64+ $this->warnings.="<li>".$message."</li>";
 65+ }
 66+
 67+ public function getWarnings() {
 68+ if ($this->warnings) {
 69+ return "<div class=\"warning\"><b>Warnings were generated during the execution of function</b><ol>".$this->warnings."</ol></div>";
 70+ } else {
 71+ return NULL;
 72+ }
 73+ }
 74+
 75+}
Property changes on: trunk/extensions/BugzillaReports/BMWExtension.php
___________________________________________________________________
Added: svn:keywords
176 + Id
Added: svn:eol-style
277 + native
Index: trunk/extensions/BugzillaReports/BugzillaReports.i18n.php
@@ -0,0 +1,13 @@
 2+<?php
 3+/**
 4+ * Internationalisation file for extension BugzillaReport.
 5+ */
 6+
 7+$messages = array();
 8+
 9+$messages['en'] = array(
 10+ 'bReport-desc' => 'Provide bugzilla reports using the parser function #bugzilla',
 11+ 'bReport_noconnection' => 'Cannot get database connection ($1@$2) : $3',
 12+ 'bReport_nodb' => 'Database not found',
 13+ 'bReport_sqlerror' => 'Error running the sql : $1'
 14+);
Property changes on: trunk/extensions/BugzillaReports/BugzillaReports.i18n.php
___________________________________________________________________
Added: svn:keywords
115 + Id
Added: svn:eol-style
216 + native
Index: trunk/extensions/BugzillaReports/BSQLQuery.php
@@ -0,0 +1,829 @@
 2+<?php
 3+/**
 4+ * A general SQL query
 5+ */
 6+abstract class BSQLQuery {
 7+ protected $context;
 8+
 9+ #
 10+ # Parameter values
 11+ #
 12+ private $parameters=array();
 13+
 14+ #
 15+ # Arbitary cached values for private use
 16+ #
 17+ private $bsql_cache=array();
 18+
 19+ #
 20+ # Date long time in the future, useful for sorting purposes
 21+ # when null mapped to this
 22+ #
 23+ var $futureDate='2100-01-01';
 24+ #
 25+ # Work out what db data is required and record in
 26+ # $fieldsRequired array so we can optimise the SQL
 27+ # ... no point in wasting energy
 28+ #
 29+
 30+ var $fieldsRequired=array();
 31+
 32+ # Cached array of columns that we actually want to render
 33+ var $columnsToRender;
 34+
 35+ # Number of columns in a report
 36+ var $numberOfMainRowColumns;
 37+
 38+ # Columns implicitly removed or added, note that explict setting
 39+ # overrides this
 40+ private $implicityAddedColumns=array();
 41+ private $implicityRemovedColumns=array();
 42+
 43+ # Columns implicitly sort and order, note that explict setting
 44+ # overrides this
 45+ var $implicitParameters=array();
 46+
 47+ # Cached versions so we only calculate once
 48+ var $cache=array();
 49+
 50+ abstract protected function getFormats();
 51+ abstract protected function getDefaultSort();
 52+
 53+ #
 54+ # Override this to map sort value to appropriate SQL
 55+ #
 56+ protected function getSortMapping($column) {
 57+ if (array_key_exists($column,$this->sortMapping)) {
 58+ return $this->sortMapping[$column];
 59+ } else {
 60+ return $column;
 61+ }
 62+ }
 63+ protected function setSortMapping($column,$mapping) {
 64+ $this->sortMapping[$column]=$mapping;
 65+ }
 66+
 67+ public function setContext($context) {
 68+ $this->context=$context;
 69+ }
 70+
 71+ #
 72+ # Set parameter value
 73+ #
 74+ public function set($name,$value) {
 75+ if (array_key_exists($name,$this->supportedParameters)) {
 76+ switch ($this->supportedParameters[$name]) {
 77+ case 'field' :
 78+ $this->parameters[$name]=
 79+ $this->tidyCommaSeparated($value);
 80+ break;
 81+ case 'columns' :
 82+ $this->parameters[$name]=
 83+ $this->tidyCommaSeparated($value);
 84+ break;
 85+ default:
 86+ $this->parameters[$name]=$value;
 87+ }
 88+ $this->context->debug &&
 89+ $this->context->debug("BSQLQuery parameter set $name=".
 90+ $this->parameters[$name]);
 91+ } else {
 92+ $this->context->warn("Setting parameter $name is not supported");
 93+ }
 94+ }
 95+
 96+ #
 97+ # Tidy a field value, which essentially means removing the spaces next
 98+ # to the columns
 99+ #
 100+ private function tidyCommaSeparated($value) {
 101+ $newValue;
 102+ foreach (split(",",$value) as $singleValue) {
 103+ if (!isset($newValue)) {
 104+ $newValue=trim($singleValue);
 105+ } else {
 106+ $newValue.=",".trim($singleValue);
 107+ }
 108+ }
 109+ return $newValue;
 110+ }
 111+
 112+ #
 113+ # Return supported regex for each parameter
 114+ #
 115+ public function getParameterRegex($name) {
 116+ $regex;
 117+ switch ($this->supportedParameters[$name]) {
 118+ case 'column' :
 119+ $regex="/^[\w,+-]*$/";
 120+ break;
 121+ case 'columns' :
 122+ $regex="/^[\w,\s+-]*$/";
 123+ break;
 124+ case 'field-date' :
 125+ $regex="/^[\*+-]*$/";
 126+ break;
 127+ case 'free' :
 128+ $regex="/^.*$/";
 129+ break;
 130+ default :
 131+ if (substr($this->supportedParameters[$name],0,5)
 132+ == "field") {
 133+ $regex="/^[\w,@\.\s\*!()+-]*$/";
 134+ } else {
 135+ $regex="/^[\w]*$/";
 136+ }
 137+ }
 138+ return $regex;
 139+ }
 140+
 141+ #
 142+ # Set implicit parameter value
 143+ #
 144+ protected function setImplicit($name,$value) {
 145+ if (array_key_exists($name,$this->supportedParameters)) {
 146+ $this->implicitParameters[$name]=$value;
 147+ } else {
 148+ $this->context->warn("Setting parameter $name is not supported");
 149+ }
 150+ }
 151+
 152+ #
 153+ # Get parameter value
 154+ #
 155+ protected function get($name) {
 156+ if (array_key_exists($name,$this->supportedParameters)) {
 157+ if (array_key_exists($name,$this->parameters)) {
 158+ return $this->parameters[$name];
 159+ } else {
 160+ if (array_key_exists($name,$this->defaultParameters)) {
 161+ return $this->defaultParameters[$name];
 162+ } else {
 163+ return NULL;
 164+ }
 165+ }
 166+ } else {
 167+ $this->context->warn("Getting parameter $name is not supported");
 168+ return NULL;
 169+ }
 170+ }
 171+
 172+ #
 173+ # Get implicit parameter value
 174+ #
 175+ protected function getImplicit($name) {
 176+ if (array_key_exists($name,$this->supportedParameters)) {
 177+ if (array_key_exists($name,$this->implicitParameters)) {
 178+ return $this->implicitParameters[$name];
 179+ } else {
 180+ return NULL;
 181+ }
 182+ } else {
 183+ $this->context->warn(
 184+ "Getting implicit parameter $name is not supported");
 185+ return NULL;
 186+ }
 187+ }
 188+
 189+ #
 190+ # Get explicit parameter value
 191+ #
 192+ protected function getExplicit($name) {
 193+ if (array_key_exists($name,$this->supportedParameters)) {
 194+ if (array_key_exists($name,$this->parameters)) {
 195+ return $this->parameters[$name];
 196+ } else {
 197+ return NULL;
 198+ }
 199+ } else {
 200+ $this->context->warn(
 201+ "Getting explicit parameter $name is not supported");
 202+ return NULL;
 203+ }
 204+ }
 205+
 206+ #
 207+ # Get default parameter value
 208+ #
 209+ protected function getDefault($name) {
 210+ if (array_key_exists($name,$this->supportedParameters)) {
 211+ if (array_key_exists($name,$this->defaultParameters)) {
 212+ return $this->defaultParameters[$name];
 213+ } else {
 214+ return NULL;
 215+ }
 216+ } else {
 217+ $this->context->warn(
 218+ "Getting default parameter $name is not supported");
 219+ return NULL;
 220+ }
 221+ }
 222+
 223+ #
 224+ # Identify a field as required
 225+ #
 226+ public function requireField($column) {
 227+ $this->context->debug &&
 228+ $this->context
 229+ ->debug("Field required : ".$column);
 230+ $this->fieldsRequired[$column]=1;
 231+ }
 232+
 233+ #
 234+ # Determine whether a field is required
 235+ #
 236+ public function isRequired($column) {
 237+ if (array_key_exists($column,$this->fieldsRequired)) {
 238+ return TRUE;
 239+ } else {
 240+ return FALSE;
 241+ }
 242+ }
 243+
 244+ #
 245+ # Convert date to nice words
 246+ #
 247+ private function getRadarFormat($value) {
 248+ if (!$this->bsql_cache["today"]) {
 249+ $this->bsql_cache["yesterday"]=date("Y-m-d",strtotime("-1 day"));
 250+ $this->bsql_cache["today"]=date("Y-m-d");
 251+ $this->bsql_cache["tomorrow"]=date("Y-m-d",strtotime("+1 day"));
 252+ $this->bsql_cache["thisweek"]=date("Y-W");
 253+ $this->bsql_cache["nextweek"]=date("Y-W",strtotime("+1 week"));
 254+ $this->bsql_cache["thismonth"]=date("Y-m");
 255+ $this->bsql_cache["nextmonth"]=date("Y-m",strtotime("+1 month"));
 256+ $this->bsql_cache["thisyear"]=date("Y");
 257+ $this->bsql_cache["nextyear"]=date("Y",strtotime("+1 year"));
 258+ }
 259+ if (date("Y-m-d",$value) == $this->bsql_cache["yesterday"]) {
 260+ return "yesterday";
 261+ } else if ($value < (time()-86400)) {
 262+ return "overdue";
 263+ } else if (date("Y-m-d",$value) == $this->bsql_cache["today"]) {
 264+ return "today";
 265+ } else if (date("Y-m-d",$value) == $this->bsql_cache["tomorrow"]) {
 266+ return "tomorrow";
 267+ } else if (date("Y-W",$value) == $this->bsql_cache["thisweek"]) {
 268+ return "this week";
 269+ } else if (date("Y-W",$value) == $this->bsql_cache["nextweek"]) {
 270+ return "next week";
 271+ } else if (date("Y-m",$value) == $this->bsql_cache["thismonth"]) {
 272+ return "this month";
 273+ } else if (date("Y-m",$value) == $this->bsql_cache["nextmonth"]) {
 274+ return "next month";
 275+ } else if (date("Y",$value) == $this->bsql_cache["thisyear"]) {
 276+ return "this year";
 277+ } else if (date("Y",$value) == $this->bsql_cache["nextyear"]) {
 278+ return "next year";
 279+ } else {
 280+ return "years away";
 281+ }
 282+ }
 283+
 284+ private function formatForExplicitFormat($value,$format,$title) {
 285+ switch ($format) {
 286+ case "date" :
 287+ if ($value) {
 288+ $time=strtotime($value);
 289+ if ($time == strtotime($this->futureDate)) {
 290+ return "";
 291+ } else {
 292+ $formattedDate=date("Y-m-d",strtotime($value));
 293+ }
 294+ return $formattedDate;
 295+ } else {
 296+ return "";
 297+ }
 298+ case "radar" :
 299+ if ($value) {
 300+ $time=strtotime($value);
 301+ if ($time == strtotime($this->futureDate)) {
 302+ return "";
 303+ } else {
 304+ $formattedDate=
 305+ $this->getRadarFormat(strtotime($value));
 306+ }
 307+ return $formattedDate;
 308+ } else {
 309+ return "";
 310+ }
 311+ case "url" :
 312+ if ($value) {
 313+ return "[$value]";
 314+ } else {
 315+ return "&nbsp;";
 316+ }
 317+ case "id" :
 318+ # Render as interwiki or external link
 319+ if ($value) {
 320+ $text;
 321+ if ($title) {
 322+ $flag="";
 323+ if ($title!=$value) {
 324+ $flag="<span class=\"flag\">+</span>";
 325+ }
 326+ $text="<span title=\"$title\">$value$flag</span>";
 327+ } else {
 328+ $text=$value;
 329+ }
 330+ if ($this->context->interwiki) {
 331+ return "[[".$this->context->interwiki.
 332+ ":".$value.
 333+ "|".$text."]]";
 334+ } else {
 335+ return "[".$this->context->bzserver.
 336+ "/show_bug.cgi?id=".$value.
 337+ " ".$text."]";
 338+ }
 339+ } else {
 340+ return "&nbsp;";
 341+ }
 342+ case "name" :
 343+ if ($value) {
 344+ if ($this->get("nameformat") == "tla") {
 345+ return $this->convertNameToTla($value);
 346+ } else {
 347+ return $value;
 348+ }
 349+ } else {
 350+ return "&nbsp;";
 351+ }
 352+ default :
 353+ $this->context->warn("Format ".
 354+ $format.
 355+ " not recognised");
 356+ return $value;
 357+ }
 358+ }
 359+
 360+
 361+ /**
 362+ * Convert a name to a TLA
 363+ */
 364+
 365+ public function convertNameToTla($value) {
 366+ $names=split(" ",$value);
 367+ $tla;
 368+ if (sizeof($names) ==1) {
 369+ if (strlen($value) > 3) {
 370+ $tla=substring($value,0,3);
 371+ } else {
 372+ $tla=$value;
 373+ }
 374+ } else {
 375+ if (strlen($names[1]) > 1) {
 376+ $tla=substr($names[0],0,1).substr($names[1],0,2);
 377+ } else if (strlen($name[0] > 1)) {
 378+ $tla=substr($names[0],0,2).substr($names[1],0,1);
 379+ } else {
 380+ $tla=$names[0].$names[1]."A";
 381+ }
 382+ }
 383+ return strtoupper($tla);
 384+ }
 385+ /**
 386+ * Format a value
 387+ */
 388+ public function format($value,$column,$title) {
 389+ $formats=$this->getFormats();
 390+ if (array_key_exists($column,$formats)) {
 391+ return $this->formatForExplicitFormat($value,
 392+ $formats[$column],$title);
 393+ } else {
 394+ return $value;
 395+ }
 396+ }
 397+
 398+ /**
 399+ * Get a title for a given value
 400+ */
 401+ public function getValueTitle($line,$column) {
 402+ $title="";
 403+ if (array_key_exists($column,$this->valueTitle)) {
 404+ $columns=split(",",$this->valueTitle[$column]);
 405+ foreach ($columns as $column) {
 406+ $title.=$line[$column]." ";
 407+ }
 408+ $title=trim($title);
 409+ }
 410+ return $title;
 411+ }
 412+
 413+
 414+ #
 415+ # Formatting with null mapped to a string, useful for headings
 416+ #
 417+ public function formatForHeading($value,$column) {
 418+ if ($this->get('groupformat')) {
 419+ $this->context->debug &&
 420+ $this->context
 421+ ->debug("Group format set : ".$this->get('groupformat'));
 422+ $value=$this->formatForExplicitFormat($value,
 423+ $this->get('groupformat'));
 424+ } else {
 425+ $value=$this->format($value,$column);
 426+ }
 427+ if ($value) {
 428+ return $value;
 429+ } else {
 430+ return "not set";
 431+ }
 432+ }
 433+
 434+ /**
 435+ * Get a where clause from a named field matching a comma separated list
 436+ */
 437+ public function getWhereClause($match,$name) {
 438+ $this->context->debug &&
 439+ $this->context->debug("Generating where clause for $name=$match");
 440+ if (preg_match("/[\*+-]/",$match)) {
 441+ return $this->getWhereClauseSpecial($match,$name);
 442+ } else if (strstr($match,",")) {
 443+ $pos=strpos($match,"!(");
 444+ $where=" and (";
 445+ $operator;
 446+ $negate;
 447+ if ($pos===false) {
 448+ $operator="OR";
 449+ $negate=false;
 450+ } else {
 451+ $match=substr($match,2,-1);
 452+ $operator="AND";
 453+ $negate=true;
 454+ }
 455+ $first=true;
 456+ foreach (explode(",", $match) as $value) {
 457+ if ($first) {
 458+ $first=false;
 459+ } else {
 460+ $where.=" $operator ";
 461+ }
 462+ $where.=$this->getMatchExpression($value,$name,$negate);
 463+ }
 464+ $where.=") ";
 465+ return $where;
 466+ } else {
 467+ return " and ".$this->getMatchExpression($match,$name,false);
 468+ }
 469+ }
 470+
 471+ /**
 472+ * Get a int where clause
 473+ * ... very simple for now
 474+ */
 475+ public function getIntWhereClause($match,$name) {
 476+ if (preg_match("/[\*+-]/",$match)) {
 477+ switch ($match) {
 478+ case "+":
 479+ return " and $name > 0";
 480+ default:
 481+ $this->context->warn("Int match not recognised $name=$match");
 482+ }
 483+ } else {
 484+ $this->context->warn("Int match not recognised $name=$match");
 485+ return "";
 486+ }
 487+ }
 488+
 489+ /**
 490+ * Get a date where clause
 491+ * ... very simple for now
 492+ */
 493+ public function getDateWhereClause($match,$name) {
 494+ if (preg_match("/[\*+-]/",$match)) {
 495+ return $this->getWhereClauseSpecial($match,$name);
 496+ } else {
 497+ $this->context->warn("Date match not recognised $name=$match");
 498+ return "";
 499+ }
 500+ }
 501+
 502+ public function getWhereClauseSpecial($match,$name) {
 503+ switch ($match) {
 504+ case "+":
 505+ return " and $name IS NOT NULL";
 506+ case "-":
 507+ return " and $name IS NULL";
 508+ case "*":
 509+ return "";
 510+ default:
 511+ $this->context->warn("Special match not recognised $name=$match");
 512+ return "";
 513+ }
 514+ return " and $name IS NOT NULL";
 515+ }
 516+
 517+ /**
 518+ * Get maximum number of rows
 519+ */
 520+ public function getMaxRows() {
 521+ if ($this->get('maxrows')) {
 522+ if ($this->get('maxrows') > $this->context->maxrowsFromConfig) {
 523+ $this->context->warn("Max rows in function parameter greater than in config -> ignoring");
 524+ return $this->context->maxrowsFromConfig;
 525+ } else {
 526+ return $this->get('maxrows');
 527+ }
 528+ } else {
 529+ return $this->context->maxrowsFromConfig;
 530+ }
 531+ }
 532+
 533+ /**
 534+ * Get maximum number of rows
 535+ */
 536+ public function getMaxRowsForBarChart() {
 537+ if ($this->get('maxrowsbar')) {
 538+ if ($this->get('maxrowsbar') > $this->context->maxrowsForBarChartFromConfig) {
 539+ $this->context->warn("Max rows bar in function parameter greater than in config -> ignoring");
 540+ return $this->context->maxrowsForBarChartFromConfig;
 541+ } else {
 542+ return $this->get('maxrowsbar');
 543+ }
 544+ } else {
 545+ return $this->context->maxrowsForBarChartFromConfig;
 546+ }
 547+ }
 548+
 549+ /**
 550+ * Get sort
 551+ */
 552+ public function getSort() {
 553+ # Not explicit on function call or notcached
 554+ if (!array_key_exists('sort',$this->cache)) {
 555+ $sort;
 556+ if ($this->getExplicit('sort')) {
 557+ $sort=$this->getExplicit('sort');
 558+ } else if ($this->getImplicit('sort')) {
 559+ # Implicit on usage and other function call parameters
 560+ $sort=$this->getImplicit('sort');
 561+ } else {
 562+ # Default behaviour
 563+ $sort=$this->getDefault('sort');
 564+ }
 565+ #
 566+ # Prepend with group (if set)
 567+ #
 568+ if ($this->getGroup()) {
 569+ $sort=$this->getGroup().",".$sort;
 570+ }
 571+ $this->context->debug &&
 572+ $this->context->debug("Sort set to $sort");
 573+ $this->cache['sort']=$sort;
 574+ }
 575+ return $this->cache['sort'];
 576+ }
 577+
 578+ #
 579+ # Return sort with mapping
 580+ #
 581+ public function getMappedSort() {
 582+ $mappedSort=array();
 583+ foreach(explode(",",$this->getSort()) as $column) {
 584+ array_push($mappedSort,$this->getSortMapping($column));
 585+ }
 586+ return join(",",$mappedSort);
 587+ }
 588+ /**
 589+ * Get order
 590+ */
 591+ public function getOrder() {
 592+ if (!array_key_exists('order',$this->cache)) {
 593+ $order;
 594+ if ($this->getExplicit('order')) {
 595+ $order=$this->getExplicit('order');
 596+ } else if ($this->getImplicit('order')) {
 597+ $order=$this->getImplicit('order');
 598+ } else {
 599+ $order=$this->getDefault('order');
 600+ }
 601+ if ($order == "desc") {
 602+ $this->cache['order']="DESC";
 603+ } else {
 604+ $this->cache['order']="ASC";
 605+ }
 606+ $this->context->debug &&
 607+ $this->context->debug("Order set to ".$this->cache['order']);
 608+ }
 609+ return $this->cache['order'];
 610+ }
 611+
 612+ /**
 613+ * Get group
 614+ */
 615+ public function getGroup() {
 616+ if (!array_key_exists('group',$this->cache)) {
 617+ $group;
 618+ if ($this->getExplicit('group')) {
 619+ # Explicit on function call
 620+ $group=$this->getExplicit('group');
 621+ } else if ($this->getImplicit('group')) {
 622+ # Implicit on usage and other function call parameters
 623+ $group=$this->getImplicit('group');
 624+ } else {
 625+ # Default behaviour is nothing
 626+ $group=FALSE;
 627+ }
 628+ $this->cache['group']=$group;
 629+ }
 630+ return $this->cache['group'];
 631+ }
 632+
 633+ public function implictlyRemoveColumn($column) {
 634+ $this->implicityRemovedColumns[$column]=$column;
 635+ $this->context->debug("Registering column for implicit removal : ".
 636+ $column);
 637+ }
 638+
 639+ public function implictlyAddColumn($column) {
 640+ $this->implicityAddedColumns[$column]=$column;
 641+ $this->context->debug("Registering column for implicit addition : ".
 642+ $column);
 643+ }
 644+
 645+ /**
 646+ * Get columns
 647+ */
 648+ public function getColumns() {
 649+ if ($this->columnsToRender) {
 650+ return $this->columnsToRender;
 651+ }
 652+ if ($this->getExplicit('columns')) {
 653+ if (preg_match("/^([+-])(.*)$/",
 654+ $this->getExplicit('columns'), $array)) {
 655+ $this->context->debug &&
 656+ $this->context->
 657+ debug("Adjusting columns (comma separated) : ".
 658+ $array[1].":".$array[2]);
 659+ $newColumns=explode(",",$this->getDefault('columns'));
 660+ $deltaColumns=explode(",",$array[2]);
 661+ $defaultOperation=$array[1];
 662+ foreach ($deltaColumns as $deltaColumn) {
 663+ $newColumn=$deltaColumn;
 664+ $operation=$defaultOperation;
 665+ /**
 666+ * Support operations on subsequent columns (not just the first)
 667+ */
 668+ if (preg_match("/^([+-])(.*)$/",
 669+ $deltaColumn, $pregDeltaColumn)) {
 670+ $operation=$pregDeltaColumn[1];
 671+ $newColumn=$pregDeltaColumn[2];
 672+ }
 673+ $this->context->debug &&
 674+ $this->context->
 675+ debug("Adjusting columns (single string): ".
 676+ $operation.":".$newColumn);
 677+ /**
 678+ * Add or remove column
 679+ */
 680+ if ($operation == "+") {
 681+ $this->context->debug &&
 682+ $this->context->
 683+ debug("Adding column [$newColumn]");
 684+ array_push($newColumns,$newColumn);
 685+ if (array_key_exists($newColumn,
 686+ $this->implicityRemovedColumns)) {
 687+ $this->context->debug &&
 688+ $this->context->
 689+ debug("Removing implicit removal of column : $newColumn");
 690+ unset($this->implicityRemovedColumns[$newColumn]);
 691+ }
 692+ } else if ($operation == "-") {
 693+ $found=-1;
 694+ $i=0;
 695+ foreach ($newColumns as $search) {
 696+ if ($search==$newColumn) {
 697+ $found=$i;
 698+ break;
 699+ }
 700+ $i++;
 701+ }
 702+ if ($found > -1) {
 703+ $this->context->debug &&
 704+ $this->context->debug("Removing column [$newColumn,$found]");
 705+ unset($newColumns[$found]);
 706+ if (array_key_exists($newColumn,
 707+ $this->implicityAddedColumns)) {
 708+ unset($this
 709+ ->implicityAddedColumns[$newColumn]);
 710+ }
 711+ } else {
 712+ $this->context
 713+ ->warn("Can't remove column [$newColumn] it doesn't exists");
 714+ }
 715+ } else {
 716+ $this->context
 717+ ->warn("Operation not recognised in column "
 718+ .$operation);
 719+ }
 720+ }
 721+ /**
 722+ * We may have removed values from the columns so we need to
 723+ * recreate array by calling array_values function
 724+ */
 725+ $this->columnsToRender=$this->
 726+ applyImplicitColumns(array_values($newColumns));
 727+ $this->context->debug &&
 728+ $this->context->debug("Columns to display adjusted to "
 729+ .join(",",$this->columnsToRender));
 730+ } else {
 731+ $this->context->debug &&
 732+ $this->context->debug("Columns explicitly set to ".
 733+ $this->get('columns'));
 734+ # Explicit columns - so don't apply implicit rules
 735+ $this->columnsToRender=explode(",",
 736+ $this->getExplicit('columns'));
 737+ }
 738+ } else {
 739+ $this->columnsToRender=$this->
 740+ applyImplicitColumns(explode(",",
 741+ $this->getDefault('columns')));
 742+ $this->context->debug &&
 743+ $this->context->debug("Columns set to default "
 744+ .join(",",$this->columnsToRender));
 745+ }
 746+ return $this->columnsToRender;
 747+ }
 748+
 749+ #
 750+ # Apply implicit column rules
 751+ #
 752+ private function applyImplicitColumns($columns) {
 753+ $newColumns=$this->array_fill_keys(array_merge(
 754+ $columns,
 755+ array_keys($this->implicityAddedColumns)
 756+ ));
 757+ $this->context->debug &&
 758+ $this->context->debug("Implictly adding column "
 759+ .join(",",array_keys($this->implicityAddedColumns)));
 760+ foreach ($this->implicityRemovedColumns as $column) {
 761+ if (array_key_exists($column,$newColumns)) {
 762+ $this->context->debug &&
 763+ $this->context->
 764+ debug("Implictly removing column ".$column);
 765+ unset($newColumns[$column]);
 766+ }
 767+ }
 768+ return array_keys($newColumns);
 769+ }
 770+
 771+ #
 772+ # Local implementation of array_fill_keys since it's only supported
 773+ # from php 5.2
 774+ #
 775+ private function array_fill_keys($array,$value=NULL) {
 776+ $newArray=array();
 777+ foreach ($array as $key) {
 778+ if ($value) {
 779+ $newArray[$key]=$value;
 780+ } else {
 781+ $newArray[$key]=$key;
 782+ }
 783+ }
 784+ return $newArray;
 785+ }
 786+
 787+ #
 788+ # Initialisation prior to generating the SQL
 789+ #
 790+ protected function preSQLGenerate() {
 791+ #
 792+ # Process sort variable and add implicit columns
 793+ #
 794+ foreach (explode(",",$this->getSort()) as $column) {
 795+ $this->implictlyAddColumn($column);
 796+ }
 797+ #
 798+ # But remove group
 799+ #
 800+ foreach (explode(",",$this->getGroup()) as $column) {
 801+ $this->implictlyRemoveColumn($column);
 802+ # Although we still need the field in the SQL
 803+ $this->requireField($column);
 804+ }
 805+ #
 806+ # Require fields listed in columns
 807+ #
 808+ foreach ($this->getColumns() as $column) {
 809+ $this->requireField($column);
 810+ }
 811+
 812+ #
 813+ # Require the bar field
 814+ #
 815+ if ($this->get('bar')) {
 816+ $this->context->debug &&
 817+ $this->context->debug("Requiring bar field ".
 818+ $this->get('bar'));
 819+ $this->requireField($this->get('bar'));
 820+ }
 821+ }
 822+
 823+ public function mapField($column) {
 824+ if (array_key_exists($column,$this->fieldMapping)) {
 825+ return $this->fieldMapping[$column];
 826+ } else {
 827+ return $column;
 828+ }
 829+ }
 830+}
Property changes on: trunk/extensions/BugzillaReports/BSQLQuery.php
___________________________________________________________________
Added: svn:keywords
1831 + Id
Added: svn:eol-style
2832 + native
Index: trunk/extensions/BugzillaReports/README
@@ -0,0 +1,320 @@
 2+{ {extension
 3+|name=BugzillaReports
 4+|author=[[User:Ian Homer|Ian Homer]]
 5+|type=parser function
 6+|version=0.8
 7+|update=July 4, 2008
 8+|status=beta
 9+|mediawiki=1.12
 10+|description=Generate bugzilla reports
 11+|download=[http://blog.bemoko.com/bugzillareports/ BugzillaReports]
 12+}}
 13+'''BugzillaReports''' generates great looking reports from [[Bugzilla]] which you can include in your [[MediaWiki]] pages. Great to provide access to Bugzilla info, either from an access control point of view or providing reports easier for non-techies to read. Supports interactions with voting & flagging and helps you to see what needs to be done next. It's like your controlled window onto Bugzilla ...
 14+[[Category:Bugzilla]]
 15+
 16+=Installation=
 17+
 18+# Download the extension bundle from [http://blog.bemoko.com/bugzillareports/ BugzillaReports]
 19+# Unpack bundle into your '''extensions''' directory. This should give you
 20+#: BugzillaReports/BugzillaReports.i18n.php
 21+#: BugzillaReports/BugzillaReports.php
 22+#: BugzillaReports/skins/bz_main.css
 23+#: etc
 24+
 25+Add the following to your LocalSettings.php file:
 26+
 27+<source lang="php">
 28+require_once("$IP/extensions/BugzillaReports/BugzillaReports.php");
 29+$wgBugzillaReports = array(
 30+ 'database' => "bugzilla",
 31+ 'user' => "user",
 32+ 'password' => "(your password)",
 33+ 'bzserver' => "http://host"
 34+);
 35+</source>
 36+
 37+or if you want to use [[interwiki]] links
 38+
 39+<source lang="php">
 40+...
 41+ 'interwiki' => "bugzilla"
 42+...
 43+</source>
 44+
 45+where parameters are defined as follows:
 46+
 47+# '''host''' &rArr; defaults to localhost, but if Bugzilla not on same machine as your mediawiki install then set the hostname here
 48+# Use one of
 49+## '''bzserver''' &rArr; point to bugzilla server e.g. http://host". Note that the path "/show_bug.cgi?id=" is automatically added for you by the extension
 50+## '''interwiki''' &rArr; [[interwiki]] prefix linking to bugzilla instance
 51+##: For example set up interwiki prefix of '''bugzilla''' to point to URL http://host/show_bug.cgi?id=$1
 52+# '''maxrows''' &rArr; set maximum number of rows to display (defaults to 200)
 53+
 54+=Example Output=
 55+
 56+[[Image:bugzilla-reports-screenshot3.png|Bugzilla Report]]
 57+
 58+Note that the bubble appears as you roll over a bug that has a last comment set.
 59+
 60+=Usage=
 61+
 62+==By Example==
 63+Generate a report from Bugzilla based on search criteria, e.g.
 64+
 65+[[Image:bugzilla-reports-screenshot1.png|Bugzilla Report|right]]
 66+
 67+List all open bugs - note that status defaults to NEW,ASSIGNED,UNCONFIRMED,REOPENED and maximum of 200 rows (by default) are returned. Results are sorted by priority.
 68+
 69+<source lang="php">
 70+{{#bugzilla:}}
 71+</source>
 72+
 73+Report on P3 and higher bugs
 74+
 75+<source lang="php">
 76+{{#bugzilla:
 77+ |priority=P1,P2,P3
 78+}}
 79+</source>
 80+
 81+Report on a particular product
 82+
 83+<source lang="php">
 84+{{#bugzilla:
 85+ |product=myproduct
 86+ |version=1.3
 87+ |lastcomment=1
 88+}}
 89+</source>
 90+
 91+Report on specific bug ids
 92+<source lang="php">
 93+{{#bugzilla:id=30,56,78,93}}
 94+</source>
 95+
 96+[[Image:bugzilla-reports-screenshot2.png|Last Comment Bubbles|right]]
 97+Report on all enhancements with the text wiki in the summary with last comment bubbles
 98+
 99+<source lang="php">
 100+{{#bugzilla:
 101+ |search=wiki
 102+ |severity=enhancement
 103+ |header=hide
 104+ |lastcomment=1
 105+}}
 106+</source>
 107+
 108+Setting the '''lastcomment''' parameter will generate a report which renders the last comment in bundles in bubbles as you hover each row (see image left) This has been tested on Firefox 3 and Safari 3, but this option may have issues with other and older browser. I'd be happy to hear feedback on this feature.
 109+
 110+Report on all bugs that are not new
 111+
 112+<source lang="php">
 113+{{#bugzilla:status=!NEW}}
 114+</source>
 115+
 116+Everything except P1 and P2 bugs
 117+
 118+<source lang="php">
 119+{{#bugzilla:
 120+ |priority=!(P1,P2)
 121+ |status=*
 122+}}
 123+</source>
 124+
 125+Set the message when no results are returned
 126+
 127+<source lang="php">
 128+{{#bugzilla:
 129+ |product=doesntexist
 130+ |noresultsmessage=all tasks complete
 131+}}
 132+</source>
 133+
 134+Tasks with votes against them
 135+
 136+<source lang="php">
 137+{{#bugzilla:columns=+votes|votes=+}}
 138+</source>
 139+
 140+[[Image:bugzilla-reports-screenshot4.png]]
 141+
 142+<source lang="php">
 143+{{#bugzilla
 144+ |group=deadline
 145+ |groupformat=radar
 146+}}
 147+</source>
 148+
 149+[[Image:bugzilla-reports-screenshot5.png]]
 150+
 151+<source lang="php">
 152+{{#bugzilla
 153+ |group=version
 154+ |bar=status
 155+}}
 156+</source>
 157+
 158+All tasks created by me but not assigned to me
 159+
 160+<source lang="php">
 161+{{#bugzilla:columns=+to|from=me@bemoko.com|to=!me@bemoko.com}}
 162+</source>
 163+
 164+All tasks I'm cc'd on but not created by me and not assigned to me
 165+
 166+<source lang="php">
 167+{{#bugzilla:columns=+to,+from
 168+ |to=!me@bemoko.com
 169+ |from=!me@bemoko.com
 170+ |cc=me@bemoko.com}}
 171+</source>
 172+
 173+===Task Radar===
 174+
 175+<source lang="php">
 176+{{task radar|me@bemoko.com}}
 177+</source>
 178+
 179+where task radar is defined as:
 180+
 181+<source lang="php">
 182+=Flagged=
 183+{{#bugzilla:flag={{{1}}}|lastcomment=1|noresultsmessage=No tasks flagged}}
 184+=Up & Coming=
 185+{{#bugzilla:to={{{1}}}|columns=-version,+deadline|deadline=+|group=deadline|groupformat=radar|lastcomment=1|noresultsmessage=Nothing up & coming}}
 186+=My Product Priority Radar=
 187+{{#bugzilla:to={{{1}}}|lastcomment=1|product=myproduct|noresultsmessage=Nothing to do on myproduct}}
 188+=Voted Tasks=
 189+{{#bugzilla:to={{{1}}}|columns=+deadline|group=product|votes=+|noresultsmessage=None of your tasks have received votes}}
 190+=Raised=
 191+{{#bugzilla:from={{{1}}}|to=!{{{1}}}|noresultsmessage=You have not raised anything}}
 192+=CC=
 193+{{#bugzilla:to=!{{{1}}}|from=!{{{1}}}|cc={{{1}}}|noresultsmessage=No ccs}}
 194+=Other Priority Radar=
 195+{{#bugzilla:to={{{1}}}|product=!myproduct|group=product|noresultsmessage=Nothing else to do}}
 196+</source>
 197+
 198+==Parameter Usage==
 199+
 200+{| style="-moz-border-radius:8px;border: 1px solid #f0f0f0;border-spacing:0px;width: 80%;margin:auto"
 201+|- style="vertical-align:top; background-color: #c0c0c0;"
 202+! parameter
 203+! style="width:30%" |values
 204+! description
 205+|- style="vertical-align:top;"
 206+| '''bar'''
 207+| column name
 208+| field to summarise in a bar chart - note that this also works in conjunction with the group parameter to provide a set of bar charts.
 209+|- style="vertical-align:top; background-color: #f0f0f0;"
 210+| '''cc'''
 211+| single username
 212+| Query for any task cc'd to the specified username, only supports single value
 213+|- style="vertical-align:top;"
 214+| '''columns'''
 215+| id, cc, deadline, modified, priority, product, severity, status, summary, to, version
 216+| comma separated list of columns to display in the specified order
 217+|- style="vertical-align:top;background-color: #f0f0f0;"
 218+| '''debug'''
 219+| ''anything''
 220+| Set to enable debugging - outputs SQL at end of table
 221+|- style="vertical-align:top;"
 222+| '''component'''
 223+| component name
 224+| component query field
 225+|- style="vertical-align:top; background-color: #f0f0f0;"
 226+| '''detailsrow'''
 227+| ''same as values for columns''
 228+| comma separated list of columns to display in the specified order on a second row
 229+|- style="vertical-align:top;"
 230+| '''flag'''
 231+| username, e.g. me@bemoko.com
 232+| search for all tasks flag for the specified user
 233+|- style="vertical-align:top;"
 234+| '''from'''
 235+| username, e.g. me@bemoko.com
 236+| reported by query field
 237+|- style="vertical-align:top;background-color: #f0f0f0;"
 238+| '''group'''
 239+| any column name as described in the columns field
 240+| field to group report on
 241+|- style="vertical-align:top;"
 242+| '''groupformat'''
 243+| radar (headings set to relative date names, e.g. today, tomorrow, next week)
 244+| format to be applied to group headings
 245+|- style="vertical-align:top;background-color: #f0f0f0;"
 246+| '''header'''
 247+| hide
 248+| Set to '''"hide"''' to hide the header row
 249+|- style="vertical-align:top;"
 250+| '''id'''
 251+| ''bugzilla id''
 252+| ID query field
 253+|- style="vertical-align:top;background-color: #f0f0f0;"
 254+| '''lastcomments'''
 255+| 1
 256+| display the last comment for each bug
 257+|- style="vertical-align:top;"
 258+| '''maxrows'''
 259+| any integer less than that configure in the LocalSettings.php configuration
 260+| Maximum number of rows to return
 261+|- style="vertical-align:top;background-color: #f0f0f0;"
 262+| '''noresultsmessage'''
 263+| any text messag
 264+| Set the message when there are no results matching the query
 265+|- style="vertical-align:top;"
 266+| '''order'''
 267+| desc (or asc)
 268+| sort order, defaults to asc
 269+|- style="vertical-align:top;background-color: #f0f0f0;"
 270+| '''priority'''
 271+| P1,P2,P3,P4,P5
 272+| priority query field
 273+|- style="vertical-align:top;"
 274+| '''product'''
 275+| ''bugzilla product names''
 276+| product name query field
 277+|- style="vertical-align:top;background-color: #f0f0f0;"
 278+| '''search'''
 279+| ''anything''
 280+| free text search - only searches short_desc at the moment
 281+|- style="vertical-align:top;"
 282+| '''severity'''
 283+| blocker, critical, major, normal, minor, trivial, enhancement
 284+| severity query field
 285+|- style="vertical-align:top;background-color: #f0f0f0;"
 286+| '''sort'''
 287+| any column name (or comma separated list of column names) as described in the columns field
 288+| field to sort on
 289+|- style="vertical-align:top;"
 290+| '''status'''
 291+| NEW,ASSIGNED,UNCONFIRMED,REOPENED,RESOLVED,VERIFIED,CLOSED
 292+| status query field, note that this defaults to !CLOSED
 293+|- style="vertical-align:top;background-color: #f0f0f0;"
 294+| '''to'''
 295+| a valid username, e.g. me@bemoko.com
 296+| assigned to query field
 297+|- style="vertical-align:top;"
 298+| '''version'''
 299+| ''bugzilla version name/number''
 300+| version query field
 301+|}
 302+
 303+
 304+All query fields can take the following constructs
 305+
 306+* value - single value to match
 307+* value1,value2, value3 - comma separate list of value to match
 308+* !value - match on everything except value
 309+* !(value1, value2, value3) - match on everything except the listed values
 310+* "*" - match on everything (useful to override inbuilt default of status)
 311+* "+" - match on not null
 312+* "-" - match on not set
 313+
 314+==With Semantic MediaWiki==
 315+
 316+This extension works well with the [http://semantic-mediawiki.org/wiki/Semantic_MediaWiki Semantic MediaWiki] by
 317+
 318+# tagging your page with a property, e.g. <nowiki>[[task::52]]</nowiki>
 319+# and then passing the results of semantic search into the bugzilla reports function, e.g.
 320+
 321+<nowiki>{{#bugzilla:|headers=hide|id={{#ask: [[{{PAGENAME}}]] [[task::+]] | ? task | link=none | headers=hide}}}}</nowiki>
\ No newline at end of file
Property changes on: trunk/extensions/BugzillaReports/README
___________________________________________________________________
Added: svn:eol-style
1322 + native
Index: trunk/extensions/BugzillaReports/BugzillaReports.php
@@ -0,0 +1,166 @@
 2+<?php
 3+/*
 4+See README for installation and usage
 5+*/
 6+if ( !defined( 'MEDIAWIKI' ) ) {
 7+ die( 'This file is a MediaWiki extension, it is not a valid entry point' );
 8+}
 9+require_once("$IP/extensions/BugzillaReports/BMWExtension.php");
 10+require_once("$IP/extensions/BugzillaReports/BugzillaQuery.php");
 11+
 12+$wgExtensionCredits['parserhook'][] = array(
 13+ 'name' => 'BugzillaReports',
 14+ 'version' => '0.8-SNAPSHOT',
 15+ 'url' => 'http://www.mediawiki.org/wiki/Extension:Bugzilla_Reports',
 16+ 'author' => '[http://blog.bemoko.com Ian Homer]',
 17+ 'description' => 'Provide bugzilla reports'
 18+ 'descriptionmsg' => 'bReport-desc'
 19+);
 20+
 21+$wgExtensionFunctions[] = 'efBugzillaReportsSetup';
 22+$wgExtensionMessagesFiles['BugzillaReports'] = dirname(__FILE__) . '/BugzillaReports.i18n.php';
 23+
 24+$wgHooks['LanguageGetMagic'][] = 'efBugzillaReportsMagic';
 25+
 26+$bzScriptPath = $wgScriptPath . '/extensions/BugzillaReports';
 27+$bzHeadIncluded=false; // flag to record whether the head has been included already so that we only include it once
 28+
 29+/**
 30+ * Register the function hook
 31+ */
 32+function efBugzillaReportsSetup() {
 33+ global $wgParser;
 34+ $wgParser->setFunctionHook( 'bugzilla', 'efBugzillaReportsRender' );
 35+}
 36+
 37+/**
 38+ * Register the magic word
 39+ */
 40+function efBugzillaReportsMagic( &$magicWords, $langCode ) {
 41+ $magicWords['bugzilla'] = array( 0, 'bugzilla' );
 42+ return true;
 43+}
 44+
 45+/**
 46+ * Call to render the bugzilla report
 47+ */
 48+function efBugzillaReportsRender( &$parser) {
 49+ $bugzillaReport = new BugzillaReport( $parser );
 50+ $args = func_get_args();
 51+ array_shift( $args );
 52+ return $bugzillaReport->render($args);
 53+}
 54+
 55+/**
 56+ * The bugzilla report objects
 57+ */
 58+class BugzillaReport extends BMWExtension {
 59+
 60+ # The handle on the query object
 61+ var $query;
 62+
 63+ # Default max rows for a report
 64+ var $maxrowsFromConfig;
 65+ var $maxrowsFromConfigDefault=100;
 66+
 67+ # Default max rows which are used for aggregation of a bar chart report
 68+ var $maxrowsForBarChartFromConfig;
 69+ var $maxrowsForBarChartFromConfigDefault=500;
 70+
 71+ public $dbuser,$bzserver,$interwiki;
 72+ public $database,$host,$password;
 73+
 74+ function BugzillaReport( &$parser ) {
 75+ $this->parser =& $parser;
 76+
 77+ }
 78+
 79+ public function render($args) {
 80+ global $wgBugzillaReports;
 81+ global $bzScriptPath;
 82+ global $wgDBserver,$wgDBname,$wgDBuser,$wgDBpassword;
 83+ global $bzHeadIncluded;
 84+
 85+ # Initialise query
 86+ $this->query=new BugzillaQuery($this);
 87+ $this->extractOptions($args);
 88+
 89+ if (!$bzHeadIncluded) {
 90+ $bzHeadIncluded=true;
 91+ $this->parser->mOutput->addHeadItem('<link rel="stylesheet" type="text/css" media="screen, projection" href="' . $bzScriptPath . '/skins/bz_main.css" />');
 92+ $this->parser->mOutput->addHeadItem('<script type="text/javascript" src="' . $bzScriptPath . '/scripts/jquery-1.2.6.min.js" ></script>');
 93+ $script=<<< EOH
 94+<script type= "text/javascript">
 95+$(document).ready(function(){
 96+ $("div.bz_comment").hide();
 97+ $("tr.bz_bug").hover(
 98+ function () {
 99+ $(this).find("td div.bz_comment").show();
 100+ },
 101+ function () {
 102+ $(this).find("td div.bz_comment").hide();
 103+ }
 104+ )
 105+});
 106+</script>
 107+EOH;
 108+ $this->parser->mOutput->addHeadItem($script);
 109+ }
 110+
 111+ $this->dbuser=$this->getProperty("user",$wgDBuser);
 112+ $this->bzserver=$this->getProperty("bzserver","bzserver-property-not-set");
 113+ $this->interwiki=$this->getProperty("interwiki",null);
 114+ $this->database=$wgBugzillaReports['database'];
 115+ $this->host=$wgBugzillaReports['host'];
 116+ $this->password=$wgBugzillaReports['password'];
 117+ $this->maxrowsFromConfig=
 118+ $this->getProperty("maxrows",$this->maxrowsFromConfigDefault);
 119+ $this->maxrowsForBarChartFromConfig=
 120+ $this->getProperty("maxrowsbar",
 121+ $this->maxrowsForBarChartFromConfigDefault);
 122+
 123+ $this->debug && $this->debug("Rendering BugzillaReport");
 124+ return $this->query->render().$this->getWarnings();
 125+ }
 126+
 127+ #
 128+ # Set value - implementation of the abstract function from BMWExtension
 129+ #
 130+ protected function set($name,$value) {
 131+ # debug variable is store on this object
 132+ if ($name=="debug") {
 133+ $this->$name=$value;
 134+ } else {
 135+ $this->query->set($name,$value);
 136+ }
 137+ }
 138+
 139+ protected function getParameterRegex($name) {
 140+ if ($name=="debug") {
 141+ return "/^1$/";
 142+ } else {
 143+ return $this->query->getParameterRegex($name);
 144+ }
 145+ }
 146+
 147+ function getProperty($name,$default) {
 148+ global $wgBugzillaReports;
 149+ $value;
 150+ if (array_key_exists($name,$wgBugzillaReports)) {
 151+ $value=$wgBugzillaReports[$name];
 152+ } else {
 153+ $value=$default;
 154+ }
 155+ $this->debug &&
 156+ $this->debug("Env property $name=$value");
 157+ return $value;
 158+ }
 159+
 160+ public function getErrorMessage($key) {
 161+ $args = func_get_args();
 162+ array_shift( $args );
 163+ wfLoadExtensionMessages( 'BugzillaReports' );
 164+ return '<strong class="error">BugzillaReports : '.
 165+ wfMsgForContent($key,$args) . '</strong>';
 166+ }
 167+}
Property changes on: trunk/extensions/BugzillaReports/BugzillaReports.php
___________________________________________________________________
Added: svn:keywords
1168 + Id
Added: svn:eol-style
2169 + native

Status & tagging log