2019-09-20 02:32:07 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace MediaWiki\Rest\Handler;
|
|
|
|
|
|
2020-11-11 00:59:31 +00:00
|
|
|
use ChangeTags;
|
2019-09-20 02:32:07 +00:00
|
|
|
use IDBAccessObject;
|
2021-05-04 20:45:30 +00:00
|
|
|
use MediaWiki\Page\ExistingPageRecord;
|
|
|
|
|
use MediaWiki\Page\PageLookup;
|
2021-05-26 05:12:16 +00:00
|
|
|
use MediaWiki\Permissions\GroupPermissionsLookup;
|
2019-09-20 02:32:07 +00:00
|
|
|
use MediaWiki\Rest\LocalizedHttpException;
|
|
|
|
|
use MediaWiki\Rest\Response;
|
2020-01-10 00:00:51 +00:00
|
|
|
use MediaWiki\Rest\SimpleHandler;
|
2019-09-20 02:32:07 +00:00
|
|
|
use MediaWiki\Revision\RevisionRecord;
|
|
|
|
|
use MediaWiki\Revision\RevisionStore;
|
|
|
|
|
use MediaWiki\Storage\NameTableAccessException;
|
|
|
|
|
use MediaWiki\Storage\NameTableStore;
|
|
|
|
|
use MediaWiki\Storage\NameTableStoreFactory;
|
2021-05-04 20:45:30 +00:00
|
|
|
use TitleFormatter;
|
2020-01-10 00:00:51 +00:00
|
|
|
use Wikimedia\Message\MessageValue;
|
2019-10-10 06:04:20 +00:00
|
|
|
use Wikimedia\Message\ParamType;
|
|
|
|
|
use Wikimedia\Message\ScalarParam;
|
2019-09-20 02:32:07 +00:00
|
|
|
use Wikimedia\ParamValidator\ParamValidator;
|
|
|
|
|
use Wikimedia\Rdbms\ILoadBalancer;
|
|
|
|
|
use Wikimedia\Rdbms\IResultWrapper;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handler class for Core REST API endpoints that perform operations on revisions
|
|
|
|
|
*/
|
|
|
|
|
class PageHistoryHandler extends SimpleHandler {
|
2022-12-07 14:48:58 +00:00
|
|
|
use PageRedirectHandlerTrait;
|
|
|
|
|
|
2020-05-15 21:36:51 +00:00
|
|
|
private const REVISIONS_RETURN_LIMIT = 20;
|
|
|
|
|
private const ALLOWED_FILTER_TYPES = [ 'anonymous', 'bot', 'reverted', 'minor' ];
|
2019-09-20 02:32:07 +00:00
|
|
|
|
|
|
|
|
/** @var RevisionStore */
|
|
|
|
|
private $revisionStore;
|
|
|
|
|
|
|
|
|
|
/** @var NameTableStore */
|
|
|
|
|
private $changeTagDefStore;
|
|
|
|
|
|
2021-05-26 05:12:16 +00:00
|
|
|
/** @var GroupPermissionsLookup */
|
|
|
|
|
private $groupPermissionsLookup;
|
2019-09-20 02:32:07 +00:00
|
|
|
|
|
|
|
|
/** @var ILoadBalancer */
|
|
|
|
|
private $loadBalancer;
|
|
|
|
|
|
2021-05-04 20:45:30 +00:00
|
|
|
/** @var PageLookup */
|
|
|
|
|
private $pageLookup;
|
|
|
|
|
|
|
|
|
|
/** @var TitleFormatter */
|
|
|
|
|
private $titleFormatter;
|
|
|
|
|
|
2019-09-20 02:32:07 +00:00
|
|
|
/**
|
2021-05-04 20:45:30 +00:00
|
|
|
* @var ExistingPageRecord|false|null
|
2020-03-16 20:52:39 +00:00
|
|
|
*/
|
2021-05-04 20:45:30 +00:00
|
|
|
private $page = false;
|
2020-03-16 20:52:39 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* RevisionStore $revisionStore
|
|
|
|
|
*
|
2019-09-20 02:32:07 +00:00
|
|
|
* @param RevisionStore $revisionStore
|
|
|
|
|
* @param NameTableStoreFactory $nameTableStoreFactory
|
2021-05-26 05:12:16 +00:00
|
|
|
* @param GroupPermissionsLookup $groupPermissionsLookup
|
2019-09-20 02:32:07 +00:00
|
|
|
* @param ILoadBalancer $loadBalancer
|
2021-05-04 20:45:30 +00:00
|
|
|
* @param PageLookup $pageLookup
|
|
|
|
|
* @param TitleFormatter $titleFormatter
|
2019-09-20 02:32:07 +00:00
|
|
|
*/
|
|
|
|
|
public function __construct(
|
|
|
|
|
RevisionStore $revisionStore,
|
|
|
|
|
NameTableStoreFactory $nameTableStoreFactory,
|
2021-05-26 05:12:16 +00:00
|
|
|
GroupPermissionsLookup $groupPermissionsLookup,
|
2021-05-04 20:45:30 +00:00
|
|
|
ILoadBalancer $loadBalancer,
|
|
|
|
|
PageLookup $pageLookup,
|
|
|
|
|
TitleFormatter $titleFormatter
|
2019-09-20 02:32:07 +00:00
|
|
|
) {
|
|
|
|
|
$this->revisionStore = $revisionStore;
|
|
|
|
|
$this->changeTagDefStore = $nameTableStoreFactory->getChangeTagDef();
|
2021-05-26 05:12:16 +00:00
|
|
|
$this->groupPermissionsLookup = $groupPermissionsLookup;
|
2019-09-20 02:32:07 +00:00
|
|
|
$this->loadBalancer = $loadBalancer;
|
2021-05-04 20:45:30 +00:00
|
|
|
$this->pageLookup = $pageLookup;
|
|
|
|
|
$this->titleFormatter = $titleFormatter;
|
2019-09-20 02:32:07 +00:00
|
|
|
}
|
|
|
|
|
|
2020-03-16 20:52:39 +00:00
|
|
|
/**
|
2021-05-04 20:45:30 +00:00
|
|
|
* @return ExistingPageRecord|null
|
2020-03-16 20:52:39 +00:00
|
|
|
*/
|
2021-05-04 20:45:30 +00:00
|
|
|
private function getPage(): ?ExistingPageRecord {
|
|
|
|
|
if ( $this->page === false ) {
|
|
|
|
|
$this->page = $this->pageLookup->getExistingPageByText(
|
|
|
|
|
$this->getValidatedParams()['title']
|
|
|
|
|
);
|
2020-03-16 20:52:39 +00:00
|
|
|
}
|
2021-05-04 20:45:30 +00:00
|
|
|
return $this->page;
|
2020-03-16 20:52:39 +00:00
|
|
|
}
|
|
|
|
|
|
2019-09-20 02:32:07 +00:00
|
|
|
/**
|
|
|
|
|
* At most one of older_than and newer_than may be specified. Keep in mind that revision ids
|
|
|
|
|
* are not monotonically increasing, so a revision may be older than another but have a
|
|
|
|
|
* higher revision id.
|
|
|
|
|
*
|
|
|
|
|
* @param string $title
|
|
|
|
|
* @return Response
|
|
|
|
|
* @throws LocalizedHttpException
|
|
|
|
|
*/
|
|
|
|
|
public function run( $title ) {
|
|
|
|
|
$params = $this->getValidatedParams();
|
|
|
|
|
if ( $params['older_than'] !== null && $params['newer_than'] !== null ) {
|
|
|
|
|
throw new LocalizedHttpException(
|
|
|
|
|
new MessageValue( 'rest-pagehistory-incompatible-params' ), 400 );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( ( $params['older_than'] !== null && $params['older_than'] < 1 ) ||
|
|
|
|
|
( $params['newer_than'] !== null && $params['newer_than'] < 1 )
|
|
|
|
|
) {
|
|
|
|
|
throw new LocalizedHttpException(
|
|
|
|
|
new MessageValue( 'rest-pagehistory-param-range-error' ), 400 );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$tagIds = [];
|
|
|
|
|
if ( $params['filter'] === 'reverted' ) {
|
2020-11-11 00:59:31 +00:00
|
|
|
foreach ( ChangeTags::REVERT_TAGS as $tagName ) {
|
2019-09-20 02:32:07 +00:00
|
|
|
try {
|
|
|
|
|
$tagIds[] = $this->changeTagDefStore->getId( $tagName );
|
|
|
|
|
} catch ( NameTableAccessException $exception ) {
|
|
|
|
|
// If no revisions are tagged with a name, no tag id will be present
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-04 20:45:30 +00:00
|
|
|
$page = $this->getPage();
|
2022-12-07 14:48:58 +00:00
|
|
|
|
2021-05-04 20:45:30 +00:00
|
|
|
if ( !$page ) {
|
2019-09-20 02:32:07 +00:00
|
|
|
throw new LocalizedHttpException(
|
2019-11-07 20:36:28 +00:00
|
|
|
new MessageValue( 'rest-nonexistent-title',
|
2019-10-10 06:04:20 +00:00
|
|
|
[ new ScalarParam( ParamType::PLAINTEXT, $title ) ]
|
2019-09-20 02:32:07 +00:00
|
|
|
),
|
|
|
|
|
404
|
|
|
|
|
);
|
|
|
|
|
}
|
2021-05-04 20:45:30 +00:00
|
|
|
if ( !$this->getAuthority()->authorizeRead( 'read', $page ) ) {
|
2019-10-11 15:24:42 +00:00
|
|
|
throw new LocalizedHttpException(
|
2019-11-07 20:36:28 +00:00
|
|
|
new MessageValue( 'rest-permission-denied-title',
|
2019-10-11 15:24:42 +00:00
|
|
|
[ new ScalarParam( ParamType::PLAINTEXT, $title ) ] ),
|
|
|
|
|
403
|
|
|
|
|
);
|
|
|
|
|
}
|
2019-09-20 02:32:07 +00:00
|
|
|
|
2022-12-07 14:48:58 +00:00
|
|
|
'@phan-var \MediaWiki\Page\ExistingPageRecord $page';
|
|
|
|
|
$redirectResponse = $this->createNormalizationRedirectResponseIfNeeded(
|
|
|
|
|
$page,
|
|
|
|
|
$params['title'] ?? null,
|
|
|
|
|
$this->titleFormatter
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( $redirectResponse !== null ) {
|
|
|
|
|
return $redirectResponse;
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-20 02:32:07 +00:00
|
|
|
$relativeRevId = $params['older_than'] ?? $params['newer_than'] ?? 0;
|
|
|
|
|
if ( $relativeRevId ) {
|
|
|
|
|
// Confirm the relative revision exists for this page. If so, get its timestamp.
|
|
|
|
|
$rev = $this->revisionStore->getRevisionByPageId(
|
2021-05-04 20:45:30 +00:00
|
|
|
$page->getId(),
|
2019-09-20 02:32:07 +00:00
|
|
|
$relativeRevId
|
|
|
|
|
);
|
|
|
|
|
if ( !$rev ) {
|
|
|
|
|
throw new LocalizedHttpException(
|
2019-11-07 20:36:28 +00:00
|
|
|
new MessageValue( 'rest-nonexistent-title-revision',
|
2019-10-10 06:04:20 +00:00
|
|
|
[ $relativeRevId, new ScalarParam( ParamType::PLAINTEXT, $title ) ]
|
2019-09-20 02:32:07 +00:00
|
|
|
),
|
|
|
|
|
404
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
$ts = $rev->getTimestamp();
|
|
|
|
|
if ( $ts === null ) {
|
|
|
|
|
throw new LocalizedHttpException(
|
|
|
|
|
new MessageValue( 'rest-pagehistory-timestamp-error',
|
|
|
|
|
[ $relativeRevId ]
|
|
|
|
|
),
|
|
|
|
|
500
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
$ts = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-04 20:45:30 +00:00
|
|
|
$res = $this->getDbResults( $page, $params, $relativeRevId, $ts, $tagIds );
|
|
|
|
|
$response = $this->processDbResults( $res, $page, $params );
|
2019-09-20 02:32:07 +00:00
|
|
|
return $this->getResponseFactory()->createJson( $response );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-05-04 20:45:30 +00:00
|
|
|
* @param ExistingPageRecord $page object identifying the page to load history for
|
2019-09-20 02:32:07 +00:00
|
|
|
* @param array $params request parameters
|
|
|
|
|
* @param int $relativeRevId relative revision id for paging, or zero if none
|
|
|
|
|
* @param int $ts timestamp for paging, or zero if none
|
|
|
|
|
* @param array $tagIds validated tags ids, or empty array if not needed for this query
|
|
|
|
|
* @return IResultWrapper|bool the results, or false if no query was executed
|
|
|
|
|
*/
|
2021-05-04 20:45:30 +00:00
|
|
|
private function getDbResults( ExistingPageRecord $page, array $params, $relativeRevId, $ts, $tagIds ) {
|
2019-09-20 02:32:07 +00:00
|
|
|
$dbr = $this->loadBalancer->getConnectionRef( DB_REPLICA );
|
|
|
|
|
$revQuery = $this->revisionStore->getQueryInfo();
|
|
|
|
|
$cond = [
|
2021-05-04 20:45:30 +00:00
|
|
|
'rev_page' => $page->getId()
|
2019-09-20 02:32:07 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
if ( $params['filter'] ) {
|
|
|
|
|
// The validator ensures this value, if present, is one of the expected values
|
|
|
|
|
switch ( $params['filter'] ) {
|
|
|
|
|
case 'bot':
|
2019-10-10 06:04:20 +00:00
|
|
|
$cond[] = 'EXISTS(' . $dbr->selectSQLText(
|
|
|
|
|
'user_groups',
|
2019-12-30 21:45:04 +00:00
|
|
|
'1',
|
2019-10-10 06:04:20 +00:00
|
|
|
[
|
|
|
|
|
'actor_rev_user.actor_user = ug_user',
|
2021-05-26 05:12:16 +00:00
|
|
|
'ug_group' => $this->groupPermissionsLookup->getGroupsWithPermission( 'bot' ),
|
2019-10-10 06:04:20 +00:00
|
|
|
'ug_expiry IS NULL OR ug_expiry >= ' . $dbr->addQuotes( $dbr->timestamp() )
|
|
|
|
|
],
|
|
|
|
|
__METHOD__
|
|
|
|
|
) . ')';
|
|
|
|
|
$bitmask = $this->getBitmask();
|
|
|
|
|
if ( $bitmask ) {
|
|
|
|
|
$cond[] = $dbr->bitAnd( 'rev_deleted', $bitmask ) . " != $bitmask";
|
|
|
|
|
}
|
2019-09-20 02:32:07 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'anonymous':
|
|
|
|
|
$cond[] = "actor_user IS NULL";
|
2019-10-10 06:04:20 +00:00
|
|
|
$bitmask = $this->getBitmask();
|
|
|
|
|
if ( $bitmask ) {
|
|
|
|
|
$cond[] = $dbr->bitAnd( 'rev_deleted', $bitmask ) . " != $bitmask";
|
|
|
|
|
}
|
2019-09-20 02:32:07 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'reverted':
|
|
|
|
|
if ( !$tagIds ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
$cond[] = 'EXISTS(' . $dbr->selectSQLText(
|
|
|
|
|
'change_tag',
|
2019-12-30 21:45:04 +00:00
|
|
|
'1',
|
2019-09-20 02:32:07 +00:00
|
|
|
[ 'ct_rev_id = rev_id', 'ct_tag_id' => $tagIds ],
|
|
|
|
|
__METHOD__
|
|
|
|
|
) . ')';
|
|
|
|
|
break;
|
2019-10-23 16:15:34 +00:00
|
|
|
|
|
|
|
|
case 'minor':
|
|
|
|
|
$cond[] = 'rev_minor_edit != 0';
|
|
|
|
|
break;
|
2019-09-20 02:32:07 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $relativeRevId ) {
|
|
|
|
|
$op = $params['older_than'] ? '<' : '>';
|
|
|
|
|
$sort = $params['older_than'] ? 'DESC' : 'ASC';
|
2022-09-16 12:44:18 +00:00
|
|
|
$cond[] = $dbr->buildComparison( $op, [
|
|
|
|
|
'rev_timestamp' => $dbr->timestamp( $ts ),
|
|
|
|
|
'rev_id' => $relativeRevId,
|
|
|
|
|
] );
|
2019-09-20 02:32:07 +00:00
|
|
|
$orderBy = "rev_timestamp $sort, rev_id $sort";
|
|
|
|
|
} else {
|
|
|
|
|
$orderBy = "rev_timestamp DESC, rev_id DESC";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Select one more than the return limit, to learn if there are additional revisions.
|
|
|
|
|
$limit = self::REVISIONS_RETURN_LIMIT + 1;
|
|
|
|
|
|
|
|
|
|
$res = $dbr->select(
|
|
|
|
|
$revQuery['tables'],
|
|
|
|
|
$revQuery['fields'],
|
|
|
|
|
$cond,
|
|
|
|
|
__METHOD__,
|
|
|
|
|
[
|
|
|
|
|
'ORDER BY' => $orderBy,
|
|
|
|
|
'LIMIT' => $limit,
|
|
|
|
|
],
|
|
|
|
|
$revQuery['joins']
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return $res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2019-10-10 06:04:20 +00:00
|
|
|
* Helper function for rev_deleted/user rights query conditions
|
|
|
|
|
*
|
|
|
|
|
* @todo Factor out rev_deleted logic per T233222
|
|
|
|
|
*
|
|
|
|
|
* @return int
|
|
|
|
|
*/
|
|
|
|
|
private function getBitmask() {
|
2021-01-06 18:12:43 +00:00
|
|
|
if ( !$this->getAuthority()->isAllowed( 'deletedhistory' ) ) {
|
2019-10-10 06:04:20 +00:00
|
|
|
$bitmask = RevisionRecord::DELETED_USER;
|
2021-01-06 18:12:43 +00:00
|
|
|
} elseif ( !$this->getAuthority()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
|
2019-10-10 06:04:20 +00:00
|
|
|
$bitmask = RevisionRecord::DELETED_USER | RevisionRecord::DELETED_RESTRICTED;
|
|
|
|
|
} else {
|
|
|
|
|
$bitmask = 0;
|
|
|
|
|
}
|
|
|
|
|
return $bitmask;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param IResultWrapper|bool $res database results, or false if no query was executed
|
2021-05-04 20:45:30 +00:00
|
|
|
* @param ExistingPageRecord $page object identifying the page to load history for
|
2019-09-20 02:32:07 +00:00
|
|
|
* @param array $params request parameters
|
|
|
|
|
* @return array response data
|
|
|
|
|
*/
|
2021-05-04 20:45:30 +00:00
|
|
|
private function processDbResults( $res, $page, $params ) {
|
2019-09-20 02:32:07 +00:00
|
|
|
$revisions = [];
|
|
|
|
|
|
2019-10-10 06:04:20 +00:00
|
|
|
if ( $res ) {
|
|
|
|
|
$sizes = [];
|
|
|
|
|
foreach ( $res as $row ) {
|
|
|
|
|
$rev = $this->revisionStore->newRevisionFromRow(
|
|
|
|
|
$row,
|
|
|
|
|
IDBAccessObject::READ_NORMAL,
|
2021-05-04 20:45:30 +00:00
|
|
|
$page
|
2019-10-10 06:04:20 +00:00
|
|
|
);
|
|
|
|
|
if ( !$revisions ) {
|
|
|
|
|
$firstRevId = $row->rev_id;
|
|
|
|
|
}
|
|
|
|
|
$lastRevId = $row->rev_id;
|
2019-09-20 02:32:07 +00:00
|
|
|
|
2019-10-10 06:04:20 +00:00
|
|
|
$revision = [
|
|
|
|
|
'id' => $rev->getId(),
|
|
|
|
|
'timestamp' => wfTimestamp( TS_ISO_8601, $rev->getTimestamp() ),
|
2019-11-07 18:37:07 +00:00
|
|
|
'minor' => $rev->isMinor(),
|
|
|
|
|
'size' => $rev->getSize()
|
2019-09-20 02:32:07 +00:00
|
|
|
];
|
|
|
|
|
|
2019-10-10 06:04:20 +00:00
|
|
|
// Remember revision sizes and parent ids for calculating deltas. If a revision's
|
|
|
|
|
// parent id is unknown, we will be unable to supply the delta for that revision.
|
|
|
|
|
$sizes[$rev->getId()] = $rev->getSize();
|
|
|
|
|
$parentId = $rev->getParentId();
|
|
|
|
|
if ( $parentId ) {
|
|
|
|
|
$revision['parent_id'] = $parentId;
|
|
|
|
|
}
|
2019-09-20 02:32:07 +00:00
|
|
|
|
2021-01-06 18:12:43 +00:00
|
|
|
$comment = $rev->getComment( RevisionRecord::FOR_THIS_USER, $this->getAuthority() );
|
2019-10-11 15:24:42 +00:00
|
|
|
$revision['comment'] = $comment ? $comment->text : null;
|
2019-10-10 06:04:20 +00:00
|
|
|
|
2021-01-06 18:12:43 +00:00
|
|
|
$revUser = $rev->getUser( RevisionRecord::FOR_THIS_USER, $this->getAuthority() );
|
2019-10-10 06:04:20 +00:00
|
|
|
if ( $revUser ) {
|
|
|
|
|
$revision['user'] = [
|
2019-10-11 02:10:40 +00:00
|
|
|
'id' => $revUser->isRegistered() ? $revUser->getId() : null,
|
|
|
|
|
'name' => $revUser->getName()
|
2019-10-10 06:04:20 +00:00
|
|
|
];
|
2019-10-11 02:10:40 +00:00
|
|
|
} else {
|
|
|
|
|
$revision['user'] = null;
|
2019-10-10 06:04:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$revisions[] = $revision;
|
2019-09-20 02:32:07 +00:00
|
|
|
|
2019-10-10 06:04:20 +00:00
|
|
|
// Break manually at the return limit. We may have more results than we can return.
|
|
|
|
|
if ( count( $revisions ) == self::REVISIONS_RETURN_LIMIT ) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2019-09-20 02:32:07 +00:00
|
|
|
}
|
2019-10-10 06:04:20 +00:00
|
|
|
|
|
|
|
|
// Request any parent sizes that we do not already know, then calculate deltas
|
|
|
|
|
$unknownSizes = [];
|
|
|
|
|
foreach ( $revisions as $revision ) {
|
|
|
|
|
if ( isset( $revision['parent_id'] ) && !isset( $sizes[$revision['parent_id']] ) ) {
|
|
|
|
|
$unknownSizes[] = $revision['parent_id'];
|
2019-09-20 02:32:07 +00:00
|
|
|
}
|
2019-10-10 06:04:20 +00:00
|
|
|
}
|
|
|
|
|
if ( $unknownSizes ) {
|
|
|
|
|
$sizes += $this->revisionStore->getRevisionSizes( $unknownSizes );
|
|
|
|
|
}
|
|
|
|
|
foreach ( $revisions as &$revision ) {
|
2019-10-23 20:13:29 +00:00
|
|
|
$revision['delta'] = null;
|
2019-10-10 06:04:20 +00:00
|
|
|
if ( isset( $revision['parent_id'] ) ) {
|
|
|
|
|
if ( isset( $sizes[$revision['parent_id']] ) ) {
|
|
|
|
|
$revision['delta'] = $revision['size'] - $sizes[$revision['parent_id']];
|
|
|
|
|
}
|
2019-09-20 02:32:07 +00:00
|
|
|
|
2019-10-10 06:04:20 +00:00
|
|
|
// We only remembered this for delta calculations. We do not want to return it.
|
|
|
|
|
unset( $revision['parent_id'] );
|
|
|
|
|
}
|
2019-09-20 02:32:07 +00:00
|
|
|
}
|
|
|
|
|
|
2019-10-10 06:04:20 +00:00
|
|
|
if ( $revisions && $params['newer_than'] ) {
|
|
|
|
|
$revisions = array_reverse( $revisions );
|
2022-03-29 18:11:06 +00:00
|
|
|
// @phan-suppress-next-next-line PhanPossiblyUndeclaredVariable
|
|
|
|
|
// $lastRevId is declared because $res has one element
|
2019-10-10 06:04:20 +00:00
|
|
|
$temp = $lastRevId;
|
2022-03-29 18:11:06 +00:00
|
|
|
// @phan-suppress-next-next-line PhanPossiblyUndeclaredVariable
|
|
|
|
|
// $firstRevId is declared because $res has one element
|
2019-10-10 06:04:20 +00:00
|
|
|
$lastRevId = $firstRevId;
|
|
|
|
|
$firstRevId = $temp;
|
|
|
|
|
}
|
2019-09-20 02:32:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$response = [
|
|
|
|
|
'revisions' => $revisions
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// Omit newer/older if there are no additional corresponding revisions.
|
|
|
|
|
// This facilitates clients doing "paging" style api operations.
|
2019-10-10 06:04:20 +00:00
|
|
|
if ( $revisions ) {
|
|
|
|
|
if ( $params['newer_than'] || $res->numRows() > self::REVISIONS_RETURN_LIMIT ) {
|
2022-03-29 18:11:06 +00:00
|
|
|
// @phan-suppress-next-next-line PhanPossiblyUndeclaredVariable
|
|
|
|
|
// $lastRevId is declared because $res has one element
|
2019-10-10 06:04:20 +00:00
|
|
|
$older = $lastRevId;
|
|
|
|
|
}
|
|
|
|
|
if ( $params['older_than'] ||
|
|
|
|
|
( $params['newer_than'] && $res->numRows() > self::REVISIONS_RETURN_LIMIT )
|
|
|
|
|
) {
|
2022-03-29 18:11:06 +00:00
|
|
|
// @phan-suppress-next-next-line PhanPossiblyUndeclaredVariable
|
|
|
|
|
// $firstRevId is declared because $res has one element
|
2019-10-10 06:04:20 +00:00
|
|
|
$newer = $firstRevId;
|
|
|
|
|
}
|
2019-09-20 02:32:07 +00:00
|
|
|
}
|
|
|
|
|
|
2020-03-19 10:42:17 +00:00
|
|
|
$queryParts = [];
|
2019-09-20 02:32:07 +00:00
|
|
|
|
2020-03-19 10:42:17 +00:00
|
|
|
if ( isset( $params['filter'] ) ) {
|
|
|
|
|
$queryParts['filter'] = $params['filter'];
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-04 20:45:30 +00:00
|
|
|
$pathParams = [ 'title' => $this->titleFormatter->getPrefixedDBkey( $page ) ];
|
2020-05-25 18:12:33 +00:00
|
|
|
|
|
|
|
|
$response['latest'] = $this->getRouteUrl( $pathParams, $queryParts );
|
2020-03-19 10:42:17 +00:00
|
|
|
|
|
|
|
|
if ( isset( $older ) ) {
|
2020-05-25 18:12:33 +00:00
|
|
|
$response['older'] =
|
|
|
|
|
$this->getRouteUrl( $pathParams, $queryParts + [ 'older_than' => $older ] );
|
2020-03-19 10:42:17 +00:00
|
|
|
}
|
|
|
|
|
if ( isset( $newer ) ) {
|
2020-05-25 18:12:33 +00:00
|
|
|
$response['newer'] =
|
|
|
|
|
$this->getRouteUrl( $pathParams, $queryParts + [ 'newer_than' => $newer ] );
|
2019-09-20 02:32:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $response;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function needsWriteAccess() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getParamSettings() {
|
|
|
|
|
return [
|
|
|
|
|
'title' => [
|
|
|
|
|
self::PARAM_SOURCE => 'path',
|
|
|
|
|
ParamValidator::PARAM_TYPE => 'string',
|
|
|
|
|
ParamValidator::PARAM_REQUIRED => true,
|
|
|
|
|
],
|
|
|
|
|
'older_than' => [
|
|
|
|
|
self::PARAM_SOURCE => 'query',
|
|
|
|
|
ParamValidator::PARAM_TYPE => 'integer',
|
|
|
|
|
ParamValidator::PARAM_REQUIRED => false,
|
|
|
|
|
],
|
|
|
|
|
'newer_than' => [
|
|
|
|
|
self::PARAM_SOURCE => 'query',
|
|
|
|
|
ParamValidator::PARAM_TYPE => 'integer',
|
|
|
|
|
ParamValidator::PARAM_REQUIRED => false,
|
|
|
|
|
],
|
|
|
|
|
'filter' => [
|
|
|
|
|
self::PARAM_SOURCE => 'query',
|
|
|
|
|
ParamValidator::PARAM_TYPE => self::ALLOWED_FILTER_TYPES,
|
|
|
|
|
ParamValidator::PARAM_REQUIRED => false,
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
}
|
2020-03-16 20:52:39 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns an ETag representing a page's latest revision.
|
|
|
|
|
*
|
|
|
|
|
* @return string|null
|
|
|
|
|
*/
|
|
|
|
|
protected function getETag(): ?string {
|
2021-05-04 20:45:30 +00:00
|
|
|
$page = $this->getPage();
|
|
|
|
|
if ( !$page ) {
|
2020-03-16 20:52:39 +00:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-04 20:45:30 +00:00
|
|
|
return '"' . $page->getLatest() . '"';
|
2020-03-16 20:52:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the time of the last change to the page.
|
|
|
|
|
*
|
|
|
|
|
* @return string|null
|
|
|
|
|
*/
|
|
|
|
|
protected function getLastModified(): ?string {
|
2021-05-04 20:45:30 +00:00
|
|
|
$page = $this->getPage();
|
|
|
|
|
if ( !$page ) {
|
2020-03-16 20:52:39 +00:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-04 20:45:30 +00:00
|
|
|
$rev = $this->revisionStore->getKnownCurrentRevision( $page );
|
2020-03-16 20:52:39 +00:00
|
|
|
return $rev->getTimestamp();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
protected function hasRepresentation() {
|
2021-05-04 20:45:30 +00:00
|
|
|
return (bool)$this->getPage();
|
2020-03-16 20:52:39 +00:00
|
|
|
}
|
2019-09-20 02:32:07 +00:00
|
|
|
}
|