00001 <?php
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026 if ( !defined( 'MEDIAWIKI' ) ) {
00027
00028 require_once ( 'ApiQueryBase.php' );
00029 }
00030
00038 class ApiQueryRevisions extends ApiQueryBase {
00039
00040 public function __construct( $query, $moduleName ) {
00041 parent :: __construct( $query, $moduleName, 'rv' );
00042 }
00043
00044 private $fld_ids = false, $fld_flags = false, $fld_timestamp = false, $fld_size = false,
00045 $fld_comment = false, $fld_parsedcomment = false, $fld_user = false, $fld_content = false, $fld_tags = false;
00046
00047 protected function getTokenFunctions() {
00048
00049
00050
00051
00052
00053 if ( isset( $this->tokenFunctions ) )
00054 return $this->tokenFunctions;
00055
00056
00057 if ( !is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) )
00058 return array();
00059
00060 $this->tokenFunctions = array(
00061 'rollback' => array( 'ApiQueryRevisions', 'getRollbackToken' )
00062 );
00063 wfRunHooks( 'APIQueryRevisionsTokens', array( &$this->tokenFunctions ) );
00064 return $this->tokenFunctions;
00065 }
00066
00067 public static function getRollbackToken( $pageid, $title, $rev )
00068 {
00069 global $wgUser;
00070 if ( !$wgUser->isAllowed( 'rollback' ) )
00071 return false;
00072 return $wgUser->editToken( array( $title->getPrefixedText(),
00073 $rev->getUserText() ) );
00074 }
00075
00076 public function execute() {
00077 $params = $this->extractRequestParams( false );
00078
00079
00080
00081
00082
00083 $enumRevMode = ( !is_null( $params['user'] ) || !is_null( $params['excludeuser'] ) ||
00084 !is_null( $params['limit'] ) || !is_null( $params['startid'] ) ||
00085 !is_null( $params['endid'] ) || $params['dir'] === 'newer' ||
00086 !is_null( $params['start'] ) || !is_null( $params['end'] ) );
00087
00088
00089 $pageSet = $this->getPageSet();
00090 $pageCount = $pageSet->getGoodTitleCount();
00091 $revCount = $pageSet->getRevisionCount();
00092
00093
00094 if ( $revCount === 0 && $pageCount === 0 )
00095 return;
00096
00097 if ( $revCount > 0 && $enumRevMode )
00098 $this->dieUsage( 'The revids= parameter may not be used with the list options (limit, startid, endid, dirNewer, start, end).', 'revids' );
00099
00100 if ( $pageCount > 1 && $enumRevMode )
00101 $this->dieUsage( 'titles, pageids or a generator was used to supply multiple pages, but the limit, startid, endid, dirNewer, user, excludeuser, start and end parameters may only be used on a single page.', 'multpages' );
00102
00103 $this->diffto = $this->difftotext = null;
00104 if ( !is_null( $params['difftotext'] ) ) {
00105 $this->difftotext = $params['difftotext'];
00106 } else if ( !is_null( $params['diffto'] ) ) {
00107 if ( $params['diffto'] == 'cur' )
00108 $params['diffto'] = 0;
00109 if ( ( !ctype_digit( $params['diffto'] ) || $params['diffto'] < 0 )
00110 && $params['diffto'] != 'prev' && $params['diffto'] != 'next' )
00111 $this->dieUsage( 'rvdiffto must be set to a non-negative number, "prev", "next" or "cur"', 'diffto' );
00112
00113
00114
00115 if ( $params['diffto'] != 0 ) {
00116 $difftoRev = Revision::newFromID( $params['diffto'] );
00117 if ( !$difftoRev )
00118 $this->dieUsageMsg( array( 'nosuchrevid', $params['diffto'] ) );
00119 if ( !$difftoRev->userCan( Revision::DELETED_TEXT ) ) {
00120 $this->setWarning( "Couldn't diff to r{$difftoRev->getID()}: content is hidden" );
00121 $params['diffto'] = null;
00122 }
00123 }
00124 $this->diffto = $params['diffto'];
00125 }
00126
00127 $db = $this->getDB();
00128 $this->addTables( 'page' );
00129 $this->addFields( Revision::selectFields() );
00130 $this->addWhere( 'page_id = rev_page' );
00131
00132 $prop = array_flip( $params['prop'] );
00133
00134
00135 $this->fld_ids = isset ( $prop['ids'] );
00136
00137 $this->fld_flags = isset ( $prop['flags'] );
00138 $this->fld_timestamp = isset ( $prop['timestamp'] );
00139 $this->fld_comment = isset ( $prop['comment'] );
00140 $this->fld_parsedcomment = isset ( $prop['parsedcomment'] );
00141 $this->fld_size = isset ( $prop['size'] );
00142 $this->fld_user = isset ( $prop['user'] );
00143 $this->token = $params['token'];
00144
00145
00146 $index = array();
00147
00148 if ( !is_null( $this->token ) || $pageCount > 0 ) {
00149 $this->addFields( Revision::selectPageFields() );
00150 }
00151
00152 if ( isset ( $prop['tags'] ) ) {
00153 $this->fld_tags = true;
00154 $this->addTables( 'tag_summary' );
00155 $this->addJoinConds( array( 'tag_summary' => array( 'LEFT JOIN', array( 'rev_id=ts_rev_id' ) ) ) );
00156 $this->addFields( 'ts_tags' );
00157 }
00158
00159 if ( !is_null( $params['tag'] ) ) {
00160 $this->addTables( 'change_tag' );
00161 $this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN', array( 'rev_id=ct_rev_id' ) ) ) );
00162 $this->addWhereFld( 'ct_tag' , $params['tag'] );
00163 global $wgOldChangeTagsIndex;
00164 $index['change_tag'] = $wgOldChangeTagsIndex ? 'ct_tag' : 'change_tag_tag_id';
00165 }
00166
00167 if ( isset( $prop['content'] ) || !is_null( $this->difftotext ) ) {
00168
00169
00170 foreach ( $pageSet->getGoodTitles() as $title ) {
00171 if ( !$title->userCanRead() )
00172 $this->dieUsage(
00173 'The current user is not allowed to read ' . $title->getPrefixedText(),
00174 'accessdenied' );
00175 }
00176
00177 $this->addTables( 'text' );
00178 $this->addWhere( 'rev_text_id=old_id' );
00179 $this->addFields( 'old_id' );
00180 $this->addFields( Revision::selectTextFields() );
00181
00182 $this->fld_content = isset( $prop['content'] );
00183
00184 $this->expandTemplates = $params['expandtemplates'];
00185 $this->generateXML = $params['generatexml'];
00186 if ( isset( $params['section'] ) )
00187 $this->section = $params['section'];
00188 else
00189 $this->section = false;
00190 }
00191
00192
00193 $this->addTables( 'revision' );
00194
00195 $userMax = ( $this->fld_content ? ApiBase::LIMIT_SML1 : ApiBase::LIMIT_BIG1 );
00196 $botMax = ( $this->fld_content ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_BIG2 );
00197 $limit = $params['limit'];
00198 if ( $limit == 'max' ) {
00199 $limit = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
00200 $this->getResult()->addValue( 'limits', $this->getModuleName(), $limit );
00201 }
00202
00203 if ( $enumRevMode ) {
00204
00205
00206 if ( !is_null( $params['startid'] ) && !is_null( $params['start'] ) )
00207 $this->dieUsage( 'start and startid cannot be used together', 'badparams' );
00208
00209 if ( !is_null( $params['endid'] ) && !is_null( $params['end'] ) )
00210 $this->dieUsage( 'end and endid cannot be used together', 'badparams' );
00211
00212 if ( !is_null( $params['user'] ) && !is_null( $params['excludeuser'] ) )
00213 $this->dieUsage( 'user and excludeuser cannot be used together', 'badparams' );
00214
00215
00216
00217
00218
00219
00220
00221
00222 if ( is_null( $params['startid'] ) && is_null( $params['endid'] ) )
00223 $this->addWhereRange( 'rev_timestamp', $params['dir'],
00224 $params['start'], $params['end'] );
00225 else {
00226 $this->addWhereRange( 'rev_id', $params['dir'],
00227 $params['startid'], $params['endid'] );
00228
00229
00230 $this->addWhereRange( 'rev_timestamp', $params['dir'],
00231 $params['start'], $params['end'], false );
00232 }
00233
00234
00235 if ( is_null( $limit ) )
00236 $limit = 10;
00237 $this->validateLimit( 'limit', $limit, 1, $userMax, $botMax );
00238
00239
00240 $ids = array_keys( $pageSet->getGoodTitles() );
00241 $this->addWhereFld( 'rev_page', reset( $ids ) );
00242
00243 if ( !is_null( $params['user'] ) ) {
00244 $this->addWhereFld( 'rev_user_text', $params['user'] );
00245 } elseif ( !is_null( $params['excludeuser'] ) ) {
00246 $this->addWhere( 'rev_user_text != ' .
00247 $db->addQuotes( $params['excludeuser'] ) );
00248 }
00249 if ( !is_null( $params['user'] ) || !is_null( $params['excludeuser'] ) ) {
00250
00251 $this->addWhere( $db->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' );
00252 }
00253 }
00254 elseif ( $revCount > 0 ) {
00255 $max = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
00256 $revs = $pageSet->getRevisionIDs();
00257 if ( self::truncateArray( $revs, $max ) )
00258 $this->setWarning( "Too many values supplied for parameter 'revids': the limit is $max" );
00259
00260
00261 $this->addWhereFld( 'rev_id', array_keys( $revs ) );
00262
00263 if ( !is_null( $params['continue'] ) )
00264 $this->addWhere( "rev_id >= '" . intval( $params['continue'] ) . "'" );
00265 $this->addOption( 'ORDER BY', 'rev_id' );
00266
00267
00268 $limit = $revCount;
00269 }
00270 elseif ( $pageCount > 0 ) {
00271 $max = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
00272 $titles = $pageSet->getGoodTitles();
00273 if ( self::truncateArray( $titles, $max ) )
00274 $this->setWarning( "Too many values supplied for parameter 'titles': the limit is $max" );
00275
00276
00277
00278 $this->addWhere( 'page_id=rev_page' );
00279 $this->addWhere( 'page_latest=rev_id' );
00280
00281
00282 $this->addWhereFld( 'page_id', array_keys( $titles ) );
00283
00284 $this->addWhereFld( 'rev_page', array_keys( $titles ) );
00285
00286 if ( !is_null( $params['continue'] ) )
00287 {
00288 $cont = explode( '|', $params['continue'] );
00289 if ( count( $cont ) != 2 )
00290 $this->dieUsage( "Invalid continue param. You should pass the original " .
00291 "value returned by the previous query", "_badcontinue" );
00292 $pageid = intval( $cont[0] );
00293 $revid = intval( $cont[1] );
00294 $this->addWhere( "rev_page > '$pageid' OR " .
00295 "(rev_page = '$pageid' AND " .
00296 "rev_id >= '$revid')" );
00297 }
00298 $this->addOption( 'ORDER BY', 'rev_page, rev_id' );
00299
00300
00301 $limit = $pageCount;
00302 } else
00303 ApiBase :: dieDebug( __METHOD__, 'param validation?' );
00304
00305 $this->addOption( 'LIMIT', $limit + 1 );
00306 $this->addOption( 'USE INDEX', $index );
00307
00308 $data = array ();
00309 $count = 0;
00310 $res = $this->select( __METHOD__ );
00311
00312 while ( $row = $db->fetchObject( $res ) ) {
00313
00314 if ( ++ $count > $limit ) {
00315
00316 if ( !$enumRevMode )
00317 ApiBase :: dieDebug( __METHOD__, 'Got more rows then expected' );
00318 $this->setContinueEnumParameter( 'startid', intval( $row->rev_id ) );
00319 break;
00320 }
00321
00322
00323 $fit = $this->addPageSubItem( $row->rev_page, $this->extractRowInfo( $row ), 'rev' );
00324 if ( !$fit )
00325 {
00326 if ( $enumRevMode )
00327 $this->setContinueEnumParameter( 'startid', intval( $row->rev_id ) );
00328 else if ( $revCount > 0 )
00329 $this->setContinueEnumParameter( 'continue', intval( $row->rev_id ) );
00330 else
00331 $this->setContinueEnumParameter( 'continue', intval( $row->rev_page ) .
00332 '|' . intval( $row->rev_id ) );
00333 break;
00334 }
00335 }
00336 $db->freeResult( $res );
00337 }
00338
00339 private function extractRowInfo( $row ) {
00340 $revision = new Revision( $row );
00341 $title = $revision->getTitle();
00342 $vals = array ();
00343
00344 if ( $this->fld_ids ) {
00345 $vals['revid'] = intval( $revision->getId() );
00346
00347 if ( !is_null( $revision->getParentId() ) )
00348 $vals['parentid'] = intval( $revision->getParentId() );
00349 }
00350
00351 if ( $this->fld_flags && $revision->isMinor() )
00352 $vals['minor'] = '';
00353
00354 if ( $this->fld_user ) {
00355 if ( $revision->isDeleted( Revision::DELETED_USER ) ) {
00356 $vals['userhidden'] = '';
00357 } else {
00358 $vals['user'] = $revision->getUserText();
00359 if ( !$revision->getUser() )
00360 $vals['anon'] = '';
00361 }
00362 }
00363
00364 if ( $this->fld_timestamp ) {
00365 $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $revision->getTimestamp() );
00366 }
00367
00368 if ( $this->fld_size && !is_null( $revision->getSize() ) ) {
00369 $vals['size'] = intval( $revision->getSize() );
00370 }
00371
00372 if ( $this->fld_comment || $this->fld_parsedcomment ) {
00373 if ( $revision->isDeleted( Revision::DELETED_COMMENT ) ) {
00374 $vals['commenthidden'] = '';
00375 } else {
00376 $comment = $revision->getComment();
00377 if ( strval( $comment ) !== '' )
00378 {
00379 if ( $this->fld_comment )
00380 $vals['comment'] = $comment;
00381
00382 if ( $this->fld_parsedcomment ) {
00383 global $wgUser;
00384 $vals['parsedcomment'] = $wgUser->getSkin()->formatComment( $comment, $title );
00385 }
00386 }
00387 }
00388 }
00389
00390 if ( $this->fld_tags ) {
00391 if ( $row->ts_tags ) {
00392 $tags = explode( ',', $row->ts_tags );
00393 $this->getResult()->setIndexedTagName( $tags, 'tag' );
00394 $vals['tags'] = $tags;
00395 } else {
00396 $vals['tags'] = array();
00397 }
00398 }
00399
00400 if ( !is_null( $this->token ) )
00401 {
00402 $tokenFunctions = $this->getTokenFunctions();
00403 foreach ( $this->token as $t )
00404 {
00405 $val = call_user_func( $tokenFunctions[$t], $title->getArticleID(), $title, $revision );
00406 if ( $val === false )
00407 $this->setWarning( "Action '$t' is not allowed for the current user" );
00408 else
00409 $vals[$t . 'token'] = $val;
00410 }
00411 }
00412
00413 $text = null;
00414 if ( $this->fld_content || !is_null( $this->difftotext ) ) {
00415 global $wgParser;
00416 $text = $revision->getText();
00417
00418
00419
00420 if ( $this->section !== false ) {
00421 $text = $wgParser->getSection( $text, $this->section, false );
00422 if ( $text === false )
00423 $this->dieUsage( "There is no section {$this->section} in r" . $revision->getId(), 'nosuchsection' );
00424 }
00425 }
00426 if ( $this->fld_content && !$revision->isDeleted( Revision::DELETED_TEXT ) ) {
00427 if ( $this->generateXML ) {
00428 $wgParser->startExternalParse( $title, new ParserOptions(), OT_PREPROCESS );
00429 $dom = $wgParser->preprocessToDom( $text );
00430 if ( is_callable( array( $dom, 'saveXML' ) ) ) {
00431 $xml = $dom->saveXML();
00432 } else {
00433 $xml = $dom->__toString();
00434 }
00435 $vals['parsetree'] = $xml;
00436
00437 }
00438 if ( $this->expandTemplates ) {
00439 $text = $wgParser->preprocess( $text, $title, new ParserOptions() );
00440 }
00441 ApiResult :: setContent( $vals, $text );
00442 } else if ( $this->fld_content ) {
00443 $vals['texthidden'] = '';
00444 }
00445
00446 if ( !is_null( $this->diffto ) || !is_null( $this->difftotext ) ) {
00447 global $wgAPIMaxUncachedDiffs;
00448 static $n = 0;
00449 if ( $n < $wgAPIMaxUncachedDiffs ) {
00450 $vals['diff'] = array();
00451 if ( !is_null( $this->difftotext ) ) {
00452 $engine = new DifferenceEngine( $title );
00453 $engine->setText( $text, $this->difftotext );
00454 } else {
00455 $engine = new DifferenceEngine( $title, $revision->getID(), $this->diffto );
00456 $vals['diff']['from'] = $engine->getOldid();
00457 $vals['diff']['to'] = $engine->getNewid();
00458 }
00459 $difftext = $engine->getDiffBody();
00460 ApiResult::setContent( $vals['diff'], $difftext );
00461 if ( !$engine->wasCacheHit() )
00462 $n++;
00463 } else {
00464 $vals['diff']['notcached'] = '';
00465 }
00466 }
00467 return $vals;
00468 }
00469
00470 public function getCacheMode( $params ) {
00471 if ( isset( $params['token'] ) ) {
00472 return 'private';
00473 }
00474 if ( !is_null( $params['prop'] ) && in_array( 'parsedcomment', $params['prop'] ) ) {
00475
00476 return 'anon-public-user-private';
00477 }
00478 return 'public';
00479 }
00480
00481 public function getAllowedParams() {
00482 return array (
00483 'prop' => array (
00484 ApiBase :: PARAM_ISMULTI => true,
00485 ApiBase :: PARAM_DFLT => 'ids|timestamp|flags|comment|user',
00486 ApiBase :: PARAM_TYPE => array (
00487 'ids',
00488 'flags',
00489 'timestamp',
00490 'user',
00491 'size',
00492 'comment',
00493 'parsedcomment',
00494 'content',
00495 'tags'
00496 )
00497 ),
00498 'limit' => array (
00499 ApiBase :: PARAM_TYPE => 'limit',
00500 ApiBase :: PARAM_MIN => 1,
00501 ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1,
00502 ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
00503 ),
00504 'startid' => array (
00505 ApiBase :: PARAM_TYPE => 'integer'
00506 ),
00507 'endid' => array (
00508 ApiBase :: PARAM_TYPE => 'integer'
00509 ),
00510 'start' => array (
00511 ApiBase :: PARAM_TYPE => 'timestamp'
00512 ),
00513 'end' => array (
00514 ApiBase :: PARAM_TYPE => 'timestamp'
00515 ),
00516 'dir' => array (
00517 ApiBase :: PARAM_DFLT => 'older',
00518 ApiBase :: PARAM_TYPE => array (
00519 'newer',
00520 'older'
00521 )
00522 ),
00523 'user' => array(
00524 ApiBase :: PARAM_TYPE => 'user'
00525 ),
00526 'excludeuser' => array(
00527 ApiBase :: PARAM_TYPE => 'user'
00528 ),
00529 'tag' => null,
00530 'expandtemplates' => false,
00531 'generatexml' => false,
00532 'section' => null,
00533 'token' => array(
00534 ApiBase :: PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
00535 ApiBase :: PARAM_ISMULTI => true
00536 ),
00537 'continue' => null,
00538 'diffto' => null,
00539 'difftotext' => null,
00540 );
00541 }
00542
00543 public function getParamDescription() {
00544 return array (
00545 'prop' => 'Which properties to get for each revision.',
00546 'limit' => 'Limit how many revisions will be returned (enum)',
00547 'startid' => 'From which revision id to start enumeration (enum)',
00548 'endid' => 'Stop revision enumeration on this revid (enum)',
00549 'start' => 'From which revision timestamp to start enumeration (enum)',
00550 'end' => 'Enumerate up to this timestamp (enum)',
00551 'dir' => 'Direction of enumeration - towards "newer" or "older" revisions (enum)',
00552 'user' => 'Only include revisions made by user',
00553 'excludeuser' => 'Exclude revisions made by user',
00554 'expandtemplates' => 'Expand templates in revision content',
00555 'generatexml' => 'Generate XML parse tree for revision content',
00556 'section' => 'Only retrieve the content of this section',
00557 'token' => 'Which tokens to obtain for each revision',
00558 'continue' => 'When more results are available, use this to continue',
00559 'diffto' => array( 'Revision ID to diff each revision to.',
00560 'Use "prev", "next" and "cur" for the previous, next and current revision respectively.' ),
00561 'difftotext' => array( 'Text to diff each revision to. Only diffs a limited number of revisions.',
00562 'Overrides diffto. If rvsection is set, only that section will be diffed against this text.' ),
00563 'tag' => 'Only list revisions tagged with this tag',
00564 );
00565 }
00566
00567 public function getDescription() {
00568 return array (
00569 'Get revision information.',
00570 'This module may be used in several ways:',
00571 ' 1) Get data about a set of pages (last revision), by setting titles or pageids parameter.',
00572 ' 2) Get revisions for one given page, by using titles/pageids with start/end/limit params.',
00573 ' 3) Get data about a set of revisions by setting their IDs with revids parameter.',
00574 'All parameters marked as (enum) may only be used with a single page (#2).'
00575 );
00576 }
00577
00578 public function getPossibleErrors() {
00579 return array_merge( parent::getPossibleErrors(), array(
00580 array( 'nosuchrevid', 'diffto' ),
00581 array( 'code' => 'revids', 'info' => 'The revids= parameter may not be used with the list options (limit, startid, endid, dirNewer, start, end).' ),
00582 array( 'code' => 'multpages', 'info' => 'titles, pageids or a generator was used to supply multiple pages, but the limit, startid, endid, dirNewer, user, excludeuser, start and end parameters may only be used on a single page.' ),
00583 array( 'code' => 'diffto', 'info' => 'rvdiffto must be set to a non-negative number, "prev", "next" or "cur"' ),
00584 array( 'code' => 'badparams', 'info' => 'start and startid cannot be used together' ),
00585 array( 'code' => 'badparams', 'info' => 'end and endid cannot be used together' ),
00586 array( 'code' => 'badparams', 'info' => 'user and excludeuser cannot be used together' ),
00587 array( 'code' => 'nosuchsection', 'info' => 'There is no section section in rID' ),
00588 ) );
00589 }
00590
00591 protected function getExamples() {
00592 return array (
00593 'Get data with content for the last revision of titles "API" and "Main Page":',
00594 ' api.php?action=query&prop=revisions&titles=API|Main%20Page&rvprop=timestamp|user|comment|content',
00595 'Get last 5 revisions of the "Main Page":',
00596 ' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment',
00597 'Get first 5 revisions of the "Main Page":',
00598 ' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment&rvdir=newer',
00599 'Get first 5 revisions of the "Main Page" made after 2006-05-01:',
00600 ' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment&rvdir=newer&rvstart=20060501000000',
00601 'Get first 5 revisions of the "Main Page" that were not made made by anonymous user "127.0.0.1"',
00602 ' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment&rvexcludeuser=127.0.0.1',
00603 'Get first 5 revisions of the "Main Page" that were made by the user "MediaWiki default"',
00604 ' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment&rvuser=MediaWiki%20default',
00605 );
00606 }
00607
00608 public function getVersion() {
00609 return __CLASS__ . ': $Id: ApiQueryRevisions.php 72117 2010-09-01 16:50:07Z reedy $';
00610 }
00611 }