* All API modules now support an assert parameter. See the new features section
for more details.
* Added prop=contributors to fetch the list of contributors to the page.
+* The following API modules will now return entries where fields have been
+ revision-deleted: list=deletedrevs, list=filearchive, list=recentchanges,
+ list=watchlist. "hidden" indicators will be included, in the same style as is
+ already done for prop=revisions.
+* The following API modules will now return the content of revision-deleted
+ fields, in addition to the "hidden" indicators, if the querying user has the
+ necessary rights: list=logevents, list=usercontribs, prop=imageinfo,
+ prop=revisions.
+* The above modules, where applicable, will now return entries filtered by
+ revision-deleted fields if the querying user has the necessary rights. For
+ example, prop=revisions with rvuser or rvexcludeuser will no longer skip
+ revisions where the user was revision-deleted if the current user has the
+ deletedhistory right.
+* The 'hideuser' right, used when blocking, is no longer necessary or
+ sufficient for seeing contributions with revision-deleted in
+ list=usercontribs.
+* list=watchlist now uses the querying user's rights rather than the wlowner's
+ rights when checking whether wlprop=patrol is allowed.
=== Languages updated in 1.23 ===
return $errors;
}
+
+ /**
+ * Check whether the current user has permission to view revision-deleted
+ * fields.
+ * @return bool
+ */
+ public function userCanSeeRevDel() {
+ return $this->getUser()->isAllowedAny( 'deletedhistory', 'deletedtext', 'suppressrevision' );
+ }
}
/**
$fld_token = false;
}
+ // If user can't undelete, no tokens
+ if ( !$user->isAllowed( 'undelete' ) ) {
+ $fld_token = false;
+ }
+
$result = $this->getResult();
$pageSet = $this->getPageSet();
$titles = $pageSet->getTitles();
}
$this->addTables( 'archive' );
- $this->addWhere( 'ar_deleted = 0' );
- $this->addFields( array( 'ar_title', 'ar_namespace', 'ar_timestamp' ) );
+ $this->addFields( array( 'ar_title', 'ar_namespace', 'ar_timestamp', 'ar_deleted' ) );
$this->addFieldsIf( 'ar_parent_id', $fld_parentid );
$this->addFieldsIf( 'ar_rev_id', $fld_revid );
if ( $fld_content ) {
$this->addTables( 'text' );
+ $this->addJoinConds(
+ array( 'text' => array( 'INNER JOIN', array( 'ar_text_id=old_id' ) ) )
+ );
$this->addFields( array( 'ar_text', 'ar_text_id', 'old_text', 'old_flags' ) );
- $this->addWhere( 'ar_text_id = old_id' );
// This also means stricter restrictions
- if ( !$user->isAllowed( 'undelete' ) ) {
+ if ( !$user->isAllowedAny( 'undelete', 'deletedtext' ) ) {
$this->dieUsage(
'You don\'t have permission to view deleted revision content',
'permissiondenied'
$db->addQuotes( $params['excludeuser'] ) );
}
+ if ( !is_null( $params['user'] ) || !is_null( $params['excludeuser'] ) ) {
+ // Paranoia: avoid brute force searches (bug 17342)
+ // (shouldn't be able to get here without 'deletedhistory', but
+ // check it again just in case)
+ if ( !$user->isAllowed( 'deletedhistory' ) ) {
+ $bitmask = Revision::DELETED_USER;
+ } elseif ( !$user->isAllowed( 'suppressrevision' ) ) {
+ $bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED;
+ } else {
+ $bitmask = 0;
+ }
+ if ( $bitmask ) {
+ $this->addWhere( $db->bitAnd( 'ar_deleted', $bitmask ) . " != $bitmask" );
+ }
+ }
+
if ( !is_null( $params['continue'] ) && ( $mode == 'all' || $mode == 'revs' ) ) {
$cont = explode( '|', $params['continue'] );
$this->dieContinueUsageIf( count( $cont ) != 3 );
}
$rev = array();
+ $anyHidden = false;
+
$rev['timestamp'] = wfTimestamp( TS_ISO_8601, $row->ar_timestamp );
if ( $fld_revid ) {
$rev['revid'] = intval( $row->ar_rev_id );
if ( $fld_parentid && !is_null( $row->ar_parent_id ) ) {
$rev['parentid'] = intval( $row->ar_parent_id );
}
- if ( $fld_user ) {
- $rev['user'] = $row->ar_user_text;
- }
- if ( $fld_userid ) {
- $rev['userid'] = $row->ar_user;
- }
- if ( $fld_comment ) {
- $rev['comment'] = $row->ar_comment;
+ if ( $fld_user || $fld_userid ) {
+ if ( $row->ar_deleted & Revision::DELETED_USER ) {
+ $rev['userhidden'] = '';
+ $anyHidden = true;
+ }
+ if ( Revision::userCanBitfield( $row->ar_deleted, Revision::DELETED_USER, $user ) ) {
+ if ( $fld_user ) {
+ $rev['user'] = $row->ar_user_text;
+ }
+ if ( $fld_userid ) {
+ $rev['userid'] = $row->ar_user;
+ }
+ }
}
- $title = Title::makeTitle( $row->ar_namespace, $row->ar_title );
-
- if ( $fld_parsedcomment ) {
- $rev['parsedcomment'] = Linker::formatComment( $row->ar_comment, $title );
+ if ( $fld_comment || $fld_parsedcomment ) {
+ if ( $row->ar_deleted & Revision::DELETED_COMMENT ) {
+ $rev['commenthidden'] = '';
+ $anyHidden = true;
+ }
+ if ( Revision::userCanBitfield( $row->ar_deleted, Revision::DELETED_COMMENT, $user ) ) {
+ if ( $fld_comment ) {
+ $rev['comment'] = $row->ar_comment;
+ }
+ if ( $fld_parsedcomment ) {
+ $title = Title::makeTitle( $row->ar_namespace, $row->ar_title );
+ $rev['parsedcomment'] = Linker::formatComment( $row->ar_comment, $title );
+ }
+ }
}
+
if ( $fld_minor && $row->ar_minor_edit == 1 ) {
$rev['minor'] = '';
}
$rev['len'] = $row->ar_len;
}
if ( $fld_sha1 ) {
- if ( $row->ar_sha1 != '' ) {
- $rev['sha1'] = wfBaseConvert( $row->ar_sha1, 36, 16, 40 );
- } else {
- $rev['sha1'] = '';
+ if ( $row->ar_deleted & Revision::DELETED_TEXT ) {
+ $rev['sha1hidden'] = '';
+ $anyHidden = true;
+ }
+ if ( Revision::userCanBitfield( $row->ar_deleted, Revision::DELETED_TEXT, $user ) ) {
+ if ( $row->ar_sha1 != '' ) {
+ $rev['sha1'] = wfBaseConvert( $row->ar_sha1, 36, 16, 40 );
+ } else {
+ $rev['sha1'] = '';
+ }
}
}
if ( $fld_content ) {
- ApiResult::setContent( $rev, Revision::getRevisionText( $row ) );
+ if ( $row->ar_deleted & Revision::DELETED_TEXT ) {
+ $rev['texthidden'] = '';
+ $anyHidden = true;
+ }
+ if ( Revision::userCanBitfield( $row->ar_deleted, Revision::DELETED_TEXT, $user ) ) {
+ ApiResult::setContent( $rev, Revision::getRevisionText( $row ) );
+ }
}
if ( $fld_tags ) {
}
}
+ if ( $anyHidden && ( $row->ar_deleted & Revision::DELETED_RESTRICTED ) ) {
+ $rev['suppressed'] = '';
+ }
+
if ( !isset( $pageMap[$row->ar_namespace][$row->ar_title] ) ) {
$pageID = $newPageID++;
$pageMap[$row->ar_namespace][$row->ar_title] = $pageID;
$a['revisions'] = array( $rev );
$result->setIndexedTagName( $a['revisions'], 'rev' );
+ $title = Title::makeTitle( $row->ar_namespace, $row->ar_title );
ApiQueryBase::addTitleInfo( $a, $title );
if ( $fld_token ) {
$a['token'] = $token;
$this->addTables( 'filearchive' );
+ $this->addFields( ArchivedFile::selectFields() );
$this->addFields( array( 'fa_name', 'fa_deleted' ) );
$this->addFieldsIf( 'fa_sha1', $fld_sha1 );
$this->addFieldsIf( 'fa_timestamp', $fld_timestamp );
}
}
- if ( !$user->isAllowed( 'suppressrevision' ) ) {
- // Filter out revisions that the user is not allowed to see. There
- // is no way to indicate that we have skipped stuff because the
- // continuation parameter is fa_name
-
- // Note that this field is unindexed. This should however not be
- // a big problem as files with fa_deleted are rare
- $this->addWhereFld( 'fa_deleted', 0 );
+ // Exclude files this user can't view.
+ if ( !$user->isAllowed( 'deletedtext' ) ) {
+ $bitmask = File::DELETED_FILE;
+ } elseif ( !$user->isAllowed( 'suppressrevision' ) ) {
+ $bitmask = File::DELETED_FILE | File::DELETED_RESTRICTED;
+ } else {
+ $bitmask = 0;
+ }
+ if ( $bitmask ) {
+ $this->addWhere( $this->getDB()->bitAnd( 'fa_deleted', $bitmask ) . " != $bitmask" );
}
$limit = $params['limit'];
$title = Title::makeTitle( NS_FILE, $row->fa_name );
self::addTitleInfo( $file, $title );
+ if ( $fld_description &&
+ Revision::userCanBitfield( $row->fa_deleted, File::DELETED_COMMENT, $user )
+ ) {
+ $file['description'] = $row->fa_description;
+ if ( isset( $prop['parseddescription'] ) ) {
+ $file['parseddescription'] = Linker::formatComment(
+ $row->fa_description, $title );
+ }
+ }
+ if ( $fld_user &&
+ Revision::userCanBitfield( $row->fa_deleted, File::DELETED_USER, $user )
+ ) {
+ $file['userid'] = $row->fa_user;
+ $file['user'] = $row->fa_user_text;
+ }
if ( $fld_sha1 ) {
$file['sha1'] = wfBaseConvert( $row->fa_sha1, 36, 16, 40 );
}
if ( $fld_timestamp ) {
$file['timestamp'] = wfTimestamp( TS_ISO_8601, $row->fa_timestamp );
}
- if ( $fld_user ) {
- $file['userid'] = $row->fa_user;
- $file['user'] = $row->fa_user_text;
- }
if ( $fld_size || $fld_dimensions ) {
$file['size'] = $row->fa_size;
$file['height'] = $row->fa_height;
$file['width'] = $row->fa_width;
}
- if ( $fld_description ) {
- $file['description'] = $row->fa_description;
- if ( isset( $prop['parseddescription'] ) ) {
- $file['parseddescription'] = Linker::formatComment(
- $row->fa_description, $title );
- }
- }
if ( $fld_mediatype ) {
$file['mediatype'] = $row->fa_media_type;
}
$scale = $this->getScale( $params );
- $metadataOpts = array(
+ $opts = array(
'version' => $params['metadataversion'],
'language' => $params['extmetadatalanguage'],
'multilang' => $params['extmetadatamultilang'],
'extmetadatafilter' => $params['extmetadatafilter'],
+ 'revdelUser' => $this->getUser(),
);
$pageIds = $this->getPageSet()->getAllTitlesByNamespace();
}
}
- $result = $this->getResult();
- //search only inside the local repo
+ $user = $this->getUser();
+ $findTitles = array_map( function ( $title ) use ( $user ) {
+ return array(
+ 'title' => $title,
+ 'private' => $user,
+ );
+ }, $titles );
+
if ( $params['localonly'] ) {
- $images = RepoGroup::singleton()->getLocalRepo()->findFiles( $titles );
+ $images = RepoGroup::singleton()->getLocalRepo()->findFiles( $findTitles );
} else {
- $images = RepoGroup::singleton()->findFiles( $titles );
+ $images = RepoGroup::singleton()->findFiles( $findTitles );
}
+
+ $result = $this->getResult();
foreach ( $titles as $title ) {
$pageId = $pageIds[NS_FILE][$title];
$start = $title === $fromTitle ? $fromTimestamp : $params['start'];
$fit = $this->addPageSubItem( $pageId,
self::getInfo( $img, $prop, $result,
- $finalThumbParams, $metadataOpts
+ $finalThumbParams, $opts
)
);
if ( !$fit ) {
$fit = self::getTransformCount() < self::TRANSFORM_LIMIT &&
$this->addPageSubItem( $pageId,
self::getInfo( $oldie, $prop, $result,
- $finalThumbParams, $metadataOpts
+ $finalThumbParams, $opts
)
);
if ( !$fit ) {
* @param array $prop of properties to get (in the keys)
* @param $result ApiResult object
* @param array $thumbParams containing 'width' and 'height' items, or null
- * @param array|bool|string $metadataOpts Options for metadata fetching.
+ * @param array|bool|string $opts Options for data fetching.
* This is an array consisting of the keys:
* 'version': The metadata version for the metadata option
* 'language': The language for extmetadata property
* 'multilang': Return all translations in extmetadata property
+ * 'revdelUser': User to use when checking whether to show revision-deleted fields.
* @return Array: result array
*/
- static function getInfo( $file, $prop, $result, $thumbParams = null, $metadataOpts = false ) {
+ static function getInfo( $file, $prop, $result, $thumbParams = null, $opts = false ) {
global $wgContLang;
- if ( !$metadataOpts || is_string( $metadataOpts ) ) {
- $metadataOpts = array(
- 'version' => $metadataOpts ?: 'latest',
+ $anyHidden = false;
+
+ if ( !$opts || is_string( $opts ) ) {
+ $opts = array(
+ 'version' => $opts ?: 'latest',
'language' => $wgContLang,
'multilang' => false,
'extmetadatafilter' => array(),
+ 'revdelUser' => null,
);
}
- $version = $metadataOpts['version'];
+ $version = $opts['version'];
$vals = array();
// Timestamp is shown even if the file is revdelete'd in interface
// so do same here.
$vals['timestamp'] = wfTimestamp( TS_ISO_8601, $file->getTimestamp() );
}
+ // Handle external callers who don't pass revdelUser
+ if ( isset( $opts['revdelUser'] ) && $opts['revdelUser'] ) {
+ $revdelUser = $opts['revdelUser'];
+ $canShowField = function ( $field ) use ( $file, $revdelUser ) {
+ return $file->userCan( $field, $revdelUser );
+ };
+ } else {
+ $canShowField = function ( $field ) use ( $file ) {
+ return !$file->isDeleted( $field );
+ };
+ }
+
$user = isset( $prop['user'] );
$userid = isset( $prop['userid'] );
if ( $user || $userid ) {
if ( $file->isDeleted( File::DELETED_USER ) ) {
$vals['userhidden'] = '';
- } else {
+ $anyHidden = true;
+ }
+ if ( $canShowField( File::DELETED_USER ) ) {
if ( $user ) {
$vals['user'] = $file->getUser();
}
if ( $pcomment || $comment ) {
if ( $file->isDeleted( File::DELETED_COMMENT ) ) {
$vals['commenthidden'] = '';
- } else {
+ $anyHidden = true;
+ }
+ if ( $canShowField( File::DELETED_COMMENT ) ) {
if ( $pcomment ) {
$vals['parsedcomment'] = Linker::formatComment(
- $file->getDescription(), $file->getTitle() );
+ $file->getDescription( File::RAW ), $file->getTitle() );
}
if ( $comment ) {
- $vals['comment'] = $file->getDescription();
+ $vals['comment'] = $file->getDescription( File::RAW );
}
}
}
$bitdepth = isset( $prop['bitdepth'] );
$uploadwarning = isset( $prop['uploadwarning'] );
- if ( ( $canonicaltitle || $url || $sha1 || $meta || $mime || $mediatype || $archive || $bitdepth )
- && $file->isDeleted( File::DELETED_FILE )
- ) {
+ if ( $uploadwarning ) {
+ $vals['html'] = SpecialUpload::getExistsWarning( UploadBase::getExistsWarning( $file ) );
+ }
+
+ if ( $file->isDeleted( File::DELETED_FILE ) ) {
$vals['filehidden'] = '';
+ $anyHidden = true;
+ }
+
+ if ( $anyHidden && $file->isDeleted( File::DELETED_RESTRICTED ) ) {
+ $vals['suppressed'] = true;
+ }
+ if ( !$canShowField( File::DELETED_FILE ) ) {
//Early return, tidier than indenting all following things one level
return $vals;
}
// start with a letter, and all the values are strings.
// Thus there should be no issue with format=xml.
$format = new FormatMetadata;
- $format->setSingleLanguage( !$metadataOpts['multilang'] );
- $format->getContext()->setLanguage( $metadataOpts['language'] );
+ $format->setSingleLanguage( !$opts['multilang'] );
+ $format->getContext()->setLanguage( $opts['language'] );
$extmetaArray = $format->fetchExtendedMetadata( $file );
- if ( $metadataOpts['extmetadatafilter'] ) {
+ if ( $opts['extmetadatafilter'] ) {
$extmetaArray = array_intersect_key(
- $extmetaArray, array_flip( $metadataOpts['extmetadatafilter'] )
+ $extmetaArray, array_flip( $opts['extmetadatafilter'] )
);
}
$vals['extmetadata'] = $extmetaArray;
$vals['bitdepth'] = $file->getBitDepth();
}
- if ( $uploadwarning ) {
- $vals['html'] = SpecialUpload::getExistsWarning( UploadBase::getExistsWarning( $file ) );
- }
-
return $vals;
}
}
public function getCacheMode( $params ) {
+ if ( $this->userCanSeeRevDel() ) {
+ return 'private';
+ }
+
return 'public';
}
}
// Paranoia: avoid brute force searches (bug 17342)
- if ( !is_null( $title ) ) {
- $this->addWhere( $db->bitAnd( 'log_deleted', LogPage::DELETED_ACTION ) . ' = 0' );
- }
- if ( !is_null( $user ) ) {
- $this->addWhere( $db->bitAnd( 'log_deleted', LogPage::DELETED_USER ) . ' = 0' );
+ if ( !is_null( $title ) || !is_null( $user ) ) {
+ if ( !$this->getUser()->isAllowed( 'deletedhistory' ) ) {
+ $titleBits = LogPage::DELETED_ACTION;
+ $userBits = LogPage::DELETED_USER;
+ } elseif ( !$this->getUser()->isAllowed( 'suppressrevision' ) ) {
+ $titleBits = LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED;
+ $userBits = LogPage::DELETED_USER | LogPage::DELETED_RESTRICTED;
+ } else {
+ $titleBits = 0;
+ $userBits = 0;
+ }
+ if ( !is_null( $title ) && $titleBits ) {
+ $this->addWhere( $db->bitAnd( 'log_deleted', $titleBits ) . " != $titleBits" );
+ }
+ if ( !is_null( $user ) && $userBits ) {
+ $this->addWhere( $db->bitAnd( 'log_deleted', $userBits ) . " != $userBits" );
+ }
}
$count = 0;
private function extractRowInfo( $row ) {
$logEntry = DatabaseLogEntry::newFromRow( $row );
$vals = array();
+ $anyHidden = false;
+ $user = $this->getUser();
if ( $this->fld_ids ) {
$vals['logid'] = intval( $row->log_id );
$title = Title::makeTitle( $row->log_namespace, $row->log_title );
}
- if ( $this->fld_title || $this->fld_ids ) {
+ if ( $this->fld_title || $this->fld_ids || $this->fld_details && $row->log_params !== '' ) {
if ( LogEventsList::isDeleted( $row, LogPage::DELETED_ACTION ) ) {
$vals['actionhidden'] = '';
- } else {
+ $anyHidden = true;
+ }
+ if ( LogEventsList::userCan( $row, LogPage::DELETED_ACTION, $user ) ) {
if ( $this->fld_title ) {
ApiQueryBase::addTitleInfo( $vals, $title );
}
if ( $this->fld_ids ) {
$vals['pageid'] = intval( $row->page_id );
}
+ if ( $this->fld_details && $row->log_params !== '' ) {
+ self::addLogParams(
+ $this->getResult(),
+ $vals,
+ $logEntry->getParameters(),
+ $logEntry->getType(),
+ $logEntry->getSubtype(),
+ $logEntry->getTimestamp(),
+ $logEntry->isLegacy()
+ );
+ }
}
}
$vals['action'] = $row->log_action;
}
- if ( $this->fld_details && $row->log_params !== '' ) {
- if ( LogEventsList::isDeleted( $row, LogPage::DELETED_ACTION ) ) {
- $vals['actionhidden'] = '';
- } else {
- self::addLogParams(
- $this->getResult(),
- $vals,
- $logEntry->getParameters(),
- $logEntry->getType(),
- $logEntry->getSubtype(),
- $logEntry->getTimestamp(),
- $logEntry->isLegacy()
- );
- }
- }
-
if ( $this->fld_user || $this->fld_userid ) {
if ( LogEventsList::isDeleted( $row, LogPage::DELETED_USER ) ) {
$vals['userhidden'] = '';
- } else {
+ $anyHidden = true;
+ }
+ if ( LogEventsList::userCan( $row, LogPage::DELETED_USER, $user ) ) {
if ( $this->fld_user ) {
$vals['user'] = $row->user_name === null ? $row->log_user_text : $row->user_name;
}
if ( ( $this->fld_comment || $this->fld_parsedcomment ) && isset( $row->log_comment ) ) {
if ( LogEventsList::isDeleted( $row, LogPage::DELETED_COMMENT ) ) {
$vals['commenthidden'] = '';
- } else {
+ $anyHidden = true;
+ }
+ if ( LogEventsList::userCan( $row, LogPage::DELETED_COMMENT, $user ) ) {
if ( $this->fld_comment ) {
$vals['comment'] = $row->log_comment;
}
}
}
+ if ( $anyHidden && LogEventsList::isDeleted( $row, LogPage::DELETED_RESTRICTED ) ) {
+ $vals['suppressed'] = '';
+ }
+
return $vals;
}
public function getCacheMode( $params ) {
+ if ( $this->userCanSeeRevDel() ) {
+ return 'private';
+ }
if ( !is_null( $params['prop'] ) && in_array( 'parsedcomment', $params['prop'] ) ) {
// formatComment() calls wfMessage() among other things
return 'anon-public-user-private';
/* Build our basic query. Namely, something along the lines of:
* SELECT * FROM recentchanges WHERE rc_timestamp > $start
* AND rc_timestamp < $end AND rc_namespace = $namespace
- * AND rc_deleted = 0
*/
$this->addTables( 'recentchanges' );
$index = array( 'recentchanges' => 'rc_timestamp' ); // May change
) );
$this->addWhereFld( 'rc_namespace', $params['namespace'] );
- $this->addWhereFld( 'rc_deleted', 0 );
if ( !is_null( $params['type'] ) ) {
$this->addWhereFld( 'rc_type', $this->parseRCType( $params['type'] ) );
$this->addWhereFld( 'ct_tag', $params['tag'] );
}
+ // Paranoia: avoid brute force searches (bug 17342)
+ if ( !is_null( $params['user'] ) || !is_null( $params['excludeuser'] ) ) {
+ if ( !$user->isAllowed( 'deletedhistory' ) ) {
+ $bitmask = Revision::DELETED_USER;
+ } elseif ( !$user->isAllowed( 'suppressrevision' ) ) {
+ $bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED;
+ } else {
+ $bitmask = 0;
+ }
+ if ( $bitmask ) {
+ $this->addWhere( $this->getDB()->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask" );
+ }
+ }
+ if ( $this->getRequest()->getCheck( 'namespace' ) ) {
+ // LogPage::DELETED_ACTION hides the affected page, too.
+ if ( !$user->isAllowed( 'deletedhistory' ) ) {
+ $bitmask = LogPage::DELETED_ACTION;
+ } elseif ( !$user->isAllowed( 'suppressrevision' ) ) {
+ $bitmask = LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED;
+ } else {
+ $bitmask = 0;
+ }
+ if ( $bitmask ) {
+ $this->addWhere( $this->getDB()->makeList( array(
+ 'rc_type != ' . RC_LOG,
+ $this->getDB()->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask",
+ ), LIST_OR ) );
+ }
+ }
+
$this->token = $params['token'];
$this->addOption( 'LIMIT', $params['limit'] + 1 );
$this->addOption( 'USE INDEX', $index );
public function extractRowInfo( $row ) {
/* Determine the title of the page that has been changed. */
$title = Title::makeTitle( $row->rc_namespace, $row->rc_title );
+ $user = $this->getUser();
/* Our output data. */
$vals = array();
$vals['type'] = $type;
}
+ $anyHidden = false;
+
/* Create a new entry in the result for the title. */
- if ( $this->fld_title ) {
- ApiQueryBase::addTitleInfo( $vals, $title );
+ if ( $this->fld_title || $this->fld_ids ) {
+ if ( $type === RC_LOG && ( $row->rc_deleted & LogPage::DELETED_ACTION ) ) {
+ $vals['actionhidden'] = '';
+ $anyHidden = true;
+ }
+ if ( $type !== RC_LOG ||
+ LogEventsList::userCanBitfield( $row->rc_deleted, LogPage::DELETED_ACTION, $user )
+ ) {
+ if ( $this->fld_title ) {
+ ApiQueryBase::addTitleInfo( $vals, $title );
+ }
+ if ( $this->fld_ids ) {
+ $vals['pageid'] = intval( $row->rc_cur_id );
+ $vals['revid'] = intval( $row->rc_this_oldid );
+ $vals['old_revid'] = intval( $row->rc_last_oldid );
+ }
+ }
}
- /* Add ids, such as rcid, pageid, revid, and oldid to the change's info. */
if ( $this->fld_ids ) {
$vals['rcid'] = intval( $row->rc_id );
- $vals['pageid'] = intval( $row->rc_cur_id );
- $vals['revid'] = intval( $row->rc_this_oldid );
- $vals['old_revid'] = intval( $row->rc_last_oldid );
}
- /* Add user data and 'anon' flag, if use is anonymous. */
+ /* Add user data and 'anon' flag, if user is anonymous. */
if ( $this->fld_user || $this->fld_userid ) {
-
- if ( $this->fld_user ) {
- $vals['user'] = $row->rc_user_text;
+ if ( $row->rc_deleted & Revision::DELETED_USER ) {
+ $vals['userhidden'] = '';
+ $anyHidden = true;
}
+ if ( Revision::userCanBitfield( $row->rc_deleted, Revision::DELETED_USER, $user ) ) {
+ if ( $this->fld_user ) {
+ $vals['user'] = $row->rc_user_text;
+ }
- if ( $this->fld_userid ) {
- $vals['userid'] = $row->rc_user;
- }
+ if ( $this->fld_userid ) {
+ $vals['userid'] = $row->rc_user;
+ }
- if ( !$row->rc_user ) {
- $vals['anon'] = '';
+ if ( !$row->rc_user ) {
+ $vals['anon'] = '';
+ }
}
}
}
/* Add edit summary / log summary. */
- if ( $this->fld_comment && isset( $row->rc_comment ) ) {
- $vals['comment'] = $row->rc_comment;
- }
+ if ( $this->fld_comment || $this->fld_parsedcomment ) {
+ if ( $row->rc_deleted & Revision::DELETED_COMMENT ) {
+ $vals['commenthidden'] = '';
+ $anyHidden = true;
+ }
+ if ( Revision::userCanBitfield( $row->rc_deleted, Revision::DELETED_COMMENT, $user ) ) {
+ if ( $this->fld_comment && isset( $row->rc_comment ) ) {
+ $vals['comment'] = $row->rc_comment;
+ }
- if ( $this->fld_parsedcomment && isset( $row->rc_comment ) ) {
- $vals['parsedcomment'] = Linker::formatComment( $row->rc_comment, $title );
+ if ( $this->fld_parsedcomment && isset( $row->rc_comment ) ) {
+ $vals['parsedcomment'] = Linker::formatComment( $row->rc_comment, $title );
+ }
+ }
}
if ( $this->fld_redirect ) {
$vals['patrolled'] = '';
}
- if ( $this->fld_patrolled && ChangesList::isUnpatrolled( $row, $this->getUser() ) ) {
+ if ( $this->fld_patrolled && ChangesList::isUnpatrolled( $row, $user ) ) {
$vals['unpatrolled'] = '';
}
if ( $this->fld_loginfo && $row->rc_type == RC_LOG ) {
- $vals['logid'] = intval( $row->rc_logid );
- $vals['logtype'] = $row->rc_log_type;
- $vals['logaction'] = $row->rc_log_action;
- $logEntry = DatabaseLogEntry::newFromRow( (array)$row );
- ApiQueryLogEvents::addLogParams(
- $this->getResult(),
- $vals,
- $logEntry->getParameters(),
- $logEntry->getType(),
- $logEntry->getSubtype(),
- $logEntry->getTimestamp()
- );
+ if ( $row->rc_deleted & LogPage::DELETED_ACTION ) {
+ $vals['actionhidden'] = '';
+ $anyHidden = true;
+ }
+ if ( LogEventsList::userCanBitfield( $row->rc_deleted, LogPage::DELETED_ACTION, $user ) ) {
+ $vals['logid'] = intval( $row->rc_logid );
+ $vals['logtype'] = $row->rc_log_type;
+ $vals['logaction'] = $row->rc_log_action;
+ $logEntry = DatabaseLogEntry::newFromRow( (array)$row );
+ ApiQueryLogEvents::addLogParams(
+ $this->getResult(),
+ $vals,
+ $logEntry->getParameters(),
+ $logEntry->getType(),
+ $logEntry->getSubtype(),
+ $logEntry->getTimestamp()
+ );
+ }
}
if ( $this->fld_tags ) {
}
if ( $this->fld_sha1 && $row->rev_sha1 !== null ) {
- // The RevDel check should currently never pass due to the
- // rc_deleted = 0 condition in the WHERE clause, but in case that
- // ever changes we check it here too.
if ( $row->rev_deleted & Revision::DELETED_TEXT ) {
$vals['sha1hidden'] = '';
- } elseif ( $row->rev_sha1 !== '' ) {
- $vals['sha1'] = wfBaseConvert( $row->rev_sha1, 36, 16, 40 );
- } else {
- $vals['sha1'] = '';
+ $anyHidden = true;
+ }
+ if ( Revision::userCanBitfield( $row->rev_deleted, Revision::DELETED_TEXT, $user ) ) {
+ if ( $row->rev_sha1 !== '' ) {
+ $vals['sha1'] = wfBaseConvert( $row->rev_sha1, 36, 16, 40 );
+ } else {
+ $vals['sha1'] = '';
+ }
}
}
}
}
+ if ( $anyHidden && ( $row->rc_deleted & Revision::DELETED_RESTRICTED ) ) {
+ $vals['suppressed'] = '';
+ }
+
return $vals;
}
if ( isset( $params['token'] ) ) {
return 'private';
}
+ if ( $this->userCanSeeRevDel() ) {
+ return 'private';
+ }
if ( !is_null( $params['prop'] ) && in_array( 'parsedcomment', $params['prop'] ) ) {
// formatComment() calls wfMessage() among other things
return 'anon-public-user-private';
if ( !$difftoRev ) {
$this->dieUsageMsg( array( 'nosuchrevid', $params['diffto'] ) );
}
- if ( $difftoRev->isDeleted( Revision::DELETED_TEXT ) ) {
+ if ( !$diffToRev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
$this->setWarning( "Couldn't diff to r{$difftoRev->getID()}: content is hidden" );
$params['diffto'] = null;
}
}
if ( !is_null( $params['user'] ) || !is_null( $params['excludeuser'] ) ) {
// Paranoia: avoid brute force searches (bug 17342)
- $this->addWhere( $db->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' );
+ if ( !$this->getUser()->isAllowed( 'deletedhistory' ) ) {
+ $bitmask = Revision::DELETED_USER;
+ } elseif ( !$this->getUser()->isAllowed( 'suppressrevision' ) ) {
+ $bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED;
+ } else {
+ $bitmask = 0;
+ }
+ if ( $bitmask ) {
+ $this->addWhere( $db->bitAnd( 'rev_deleted', $bitmask ) . " != $bitmask" );
+ }
}
} elseif ( $revCount > 0 ) {
$max = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
private function extractRowInfo( $row ) {
$revision = new Revision( $row );
$title = $revision->getTitle();
+ $user = $this->getUser();
$vals = array();
+ $anyHidden = false;
if ( $this->fld_ids ) {
$vals['revid'] = intval( $revision->getId() );
if ( $this->fld_user || $this->fld_userid ) {
if ( $revision->isDeleted( Revision::DELETED_USER ) ) {
$vals['userhidden'] = '';
- } else {
+ $anyHidden = true;
+ }
+ if ( $revision->userCan( Revision::DELETED_USER, $user ) ) {
if ( $this->fld_user ) {
- $vals['user'] = $revision->getUserText();
+ $vals['user'] = $revision->getRawUserText();
}
- $userid = $revision->getUser();
+ $userid = $revision->getRawUser();
if ( !$userid ) {
$vals['anon'] = '';
}
}
}
- if ( $this->fld_sha1 && !$revision->isDeleted( Revision::DELETED_TEXT ) ) {
- if ( $revision->getSha1() != '' ) {
- $vals['sha1'] = wfBaseConvert( $revision->getSha1(), 36, 16, 40 );
- } else {
- $vals['sha1'] = '';
+ if ( $this->fld_sha1 ) {
+ if ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
+ $vals['sha1hidden'] = '';
+ $anyHidden = true;
+ }
+ if ( $revision->userCan( Revision::DELETED_TEXT, $user ) ) {
+ if ( $revision->getSha1() != '' ) {
+ $vals['sha1'] = wfBaseConvert( $revision->getSha1(), 36, 16, 40 );
+ } else {
+ $vals['sha1'] = '';
+ }
}
- } elseif ( $this->fld_sha1 ) {
- $vals['sha1hidden'] = '';
}
if ( $this->fld_contentmodel ) {
if ( $this->fld_comment || $this->fld_parsedcomment ) {
if ( $revision->isDeleted( Revision::DELETED_COMMENT ) ) {
$vals['commenthidden'] = '';
- } else {
- $comment = $revision->getComment();
+ $anyHidden = true;
+ }
+ if ( $revision->userCan( Revision::DELETED_COMMENT, $user ) ) {
+ $comment = $revision->getRawComment();
if ( $this->fld_comment ) {
$vals['comment'] = $comment;
$content = null;
global $wgParser;
if ( $this->fld_content || !is_null( $this->diffto ) || !is_null( $this->difftotext ) ) {
- $content = $revision->getContent();
+ $content = $revision->getContent( Revision::FOR_THIS_USER, $this->getUser() );
// Expand templates after getting section content because
// template-added sections don't count and Parser::preprocess()
// will have less input
);
}
}
+ if ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
+ $vals['texthidden'] = '';
+ $anyHidden = true;
+ } elseif ( !$content ) {
+ $vals['textmissing'] = '';
+ }
}
- if ( $this->fld_content && $content && !$revision->isDeleted( Revision::DELETED_TEXT ) ) {
+ if ( $this->fld_content && $content ) {
$text = null;
if ( $this->generateXML ) {
if ( $text !== false ) {
ApiResult::setContent( $vals, $text );
}
- } elseif ( $this->fld_content ) {
- if ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
- $vals['texthidden'] = '';
- } else {
- $vals['textmissing'] = '';
- }
}
- if ( !is_null( $this->diffto ) || !is_null( $this->difftotext ) ) {
+ if ( $content && ( !is_null( $this->diffto ) || !is_null( $this->difftotext ) ) ) {
global $wgAPIMaxUncachedDiffs;
static $n = 0; // Number of uncached diffs we've had
- if ( is_null( $content ) ) {
- $vals['textmissing'] = '';
- } elseif ( $n < $wgAPIMaxUncachedDiffs ) {
+ if ( $n < $wgAPIMaxUncachedDiffs ) {
$vals['diff'] = array();
$context = new DerivativeContext( $this->getContext() );
$context->setTitle( $title );
}
}
+ if ( $anyHidden && $revision->isDeleted( Revision::DELETED_RESTRICTED ) ) {
+ $vals['suppressed'] = '';
+ }
+
return $vals;
}
if ( isset( $params['token'] ) ) {
return 'private';
}
+ if ( $this->userCanSeeRevDel() ) {
+ return 'private';
+ }
if ( !is_null( $params['prop'] ) && in_array( 'parsedcomment', $params['prop'] ) ) {
// formatComment() calls wfMessage() among other things
return 'anon-public-user-private';
);
}
- if ( !$user->isAllowed( 'hideuser' ) ) {
- $this->addWhere( $this->getDB()->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' );
+ // Don't include any revisions where we're not supposed to be able to
+ // see the username.
+ if ( !$user->isAllowed( 'deletedhistory' ) ) {
+ $bitmask = Revision::DELETED_USER;
+ } elseif ( !$user->isAllowed( 'suppressrevision' ) ) {
+ $bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED;
+ } else {
+ $bitmask = 0;
+ }
+ if ( $bitmask ) {
+ $this->addWhere( $this->getDB()->bitAnd( 'rev_deleted', $bitmask ) . " != $bitmask" );
}
+
// We only want pages by the specified users.
if ( $this->prefixMode ) {
$this->addWhere( 'rev_user_text' .
*/
private function extractRowInfo( $row ) {
$vals = array();
+ $anyHidden = false;
+
+ if ( $row->rev_deleted & Revision::DELETED_TEXT ) {
+ $vals['texthidden'] = '';
+ $anyHidden = true;
+ }
+ // Any rows where we can't view the user were filtered out in the query.
$vals['userid'] = $row->rev_user;
$vals['user'] = $row->rev_user_text;
if ( $row->rev_deleted & Revision::DELETED_USER ) {
$vals['userhidden'] = '';
+ $anyHidden = true;
}
if ( $this->fld_ids ) {
$vals['pageid'] = intval( $row->rev_page );
if ( ( $this->fld_comment || $this->fld_parsedcomment ) && isset( $row->rev_comment ) ) {
if ( $row->rev_deleted & Revision::DELETED_COMMENT ) {
$vals['commenthidden'] = '';
- } else {
+ $anyHidden = true;
+ }
+ if ( Revision::userCanBitfield( $row->rev_deleted, Revision::DELETED_COMMENT, $this->getUser() ) ) {
if ( $this->fld_comment ) {
$vals['comment'] = $row->rev_comment;
}
}
}
+ if ( $anyHidden && $row->rev_deleted & Revision::DELETED_RESTRICTED ) {
+ $vals['suppressed'] = '';
+ }
+
return $vals;
}
$params = $this->extractRequestParams();
- $user = $this->getWatchlistUser( $params );
+ $user = $this->getUser();
+ $wlowner = $this->getWatchlistUser( $params );
if ( !is_null( $params['prop'] ) && is_null( $resultPageSet ) ) {
$prop = array_flip( $params['prop'] );
'rc_title',
'rc_timestamp',
'rc_type',
+ 'rc_deleted',
) );
if ( is_null( $resultPageSet ) ) {
'watchlist',
) );
- $userId = $user->getId();
+ $userId = $wlowner->getId();
$this->addJoinConds( array( 'watchlist' => array( 'INNER JOIN',
array(
'wl_user' => $userId,
)
) ) );
- $this->addWhere( array(
- 'rc_deleted' => 0,
- ) );
-
$db = $this->getDB();
$this->addTimestampWhereRange( 'rc_timestamp', $params['dir'],
// Check permissions.
if ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ) {
- $user = $this->getUser();
if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
$this->dieUsage(
'You need the patrol right to request the patrolled flag',
!isset( $params['start'] ) && !isset( $params['end'] ) && $db->getType() == 'mysql'
);
+ // Paranoia: avoid brute force searches (bug 17342)
+ if ( !is_null( $params['user'] ) || !is_null( $params['excludeuser'] ) ) {
+ if ( !$user->isAllowed( 'deletedhistory' ) ) {
+ $bitmask = Revision::DELETED_USER;
+ } elseif ( !$user->isAllowed( 'suppressrevision' ) ) {
+ $bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED;
+ } else {
+ $bitmask = 0;
+ }
+ if ( $bitmask ) {
+ $this->addWhere( $this->getDB()->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask" );
+ }
+ }
+
+ // LogPage::DELETED_ACTION hides the affected page, too. So hide those
+ // entirely from the watchlist, or someone could guess the title.
+ if ( !$user->isAllowed( 'deletedhistory' ) ) {
+ $bitmask = LogPage::DELETED_ACTION;
+ } elseif ( !$user->isAllowed( 'suppressrevision' ) ) {
+ $bitmask = LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED;
+ } else {
+ $bitmask = 0;
+ }
+ if ( $bitmask ) {
+ $this->addWhere( $this->getDB()->makeList( array(
+ 'rc_type != ' . RC_LOG,
+ $this->getDB()->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask",
+ ), LIST_OR ) );
+ }
+
$this->addOption( 'LIMIT', $params['limit'] + 1 );
$ids = array();
}
private function extractRowInfo( $row ) {
+ /* Determine the title of the page that has been changed. */
+ $title = Title::makeTitle( $row->rc_namespace, $row->rc_title );
+ $user = $this->getUser();
+
+ /* Our output data. */
$vals = array();
$type = intval( $row->rc_type );
$vals['type'] = $type;
}
- if ( $this->fld_ids ) {
- $vals['pageid'] = intval( $row->rc_cur_id );
- $vals['revid'] = intval( $row->rc_this_oldid );
- $vals['old_revid'] = intval( $row->rc_last_oldid );
- }
+ $anyHidden = false;
- $title = Title::makeTitle( $row->rc_namespace, $row->rc_title );
-
- if ( $this->fld_title ) {
- ApiQueryBase::addTitleInfo( $vals, $title );
+ /* Create a new entry in the result for the title. */
+ if ( $this->fld_title || $this->fld_ids ) {
+ // These should already have been filtered out of the query, but just in case.
+ if ( $type === RC_LOG && ( $row->rc_deleted & LogPage::DELETED_ACTION ) ) {
+ $vals['actionhidden'] = '';
+ $anyHidden = true;
+ }
+ if ( $type !== RC_LOG ||
+ LogEventsList::userCanBitfield( $row->rc_deleted, LogPage::DELETED_ACTION, $user )
+ ) {
+ if ( $this->fld_title ) {
+ ApiQueryBase::addTitleInfo( $vals, $title );
+ }
+ if ( $this->fld_ids ) {
+ $vals['pageid'] = intval( $row->rc_cur_id );
+ $vals['revid'] = intval( $row->rc_this_oldid );
+ $vals['old_revid'] = intval( $row->rc_last_oldid );
+ }
+ }
}
+ /* Add user data and 'anon' flag, if user is anonymous. */
if ( $this->fld_user || $this->fld_userid ) {
-
- if ( $this->fld_userid ) {
- $vals['userid'] = $row->rc_user;
- // for backwards compatibility
- $vals['user'] = $row->rc_user;
+ if ( $row->rc_deleted & Revision::DELETED_USER ) {
+ $vals['userhidden'] = '';
+ $anyHidden = true;
}
+ if ( Revision::userCanBitfield( $row->rc_deleted, Revision::DELETED_USER, $user ) ) {
+ if ( $this->fld_userid ) {
+ $vals['userid'] = $row->rc_user;
+ // for backwards compatibility
+ $vals['user'] = $row->rc_user;
+ }
- if ( $this->fld_user ) {
- $vals['user'] = $row->rc_user_text;
- }
+ if ( $this->fld_user ) {
+ $vals['user'] = $row->rc_user_text;
+ }
- if ( !$row->rc_user ) {
- $vals['anon'] = '';
+ if ( !$row->rc_user ) {
+ $vals['anon'] = '';
+ }
}
}
+ /* Add flags, such as new, minor, bot. */
if ( $this->fld_flags ) {
+ if ( $row->rc_bot ) {
+ $vals['bot'] = '';
+ }
if ( $row->rc_type == RC_NEW ) {
$vals['new'] = '';
}
if ( $row->rc_minor ) {
$vals['minor'] = '';
}
- if ( $row->rc_bot ) {
- $vals['bot'] = '';
- }
}
- if ( $this->fld_patrol && isset( $row->rc_patrolled ) ) {
- $vals['patrolled'] = '';
+ /* Add sizes of each revision. (Only available on 1.10+) */
+ if ( $this->fld_sizes ) {
+ $vals['oldlen'] = intval( $row->rc_old_len );
+ $vals['newlen'] = intval( $row->rc_new_len );
}
+ /* Add the timestamp. */
if ( $this->fld_timestamp ) {
$vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->rc_timestamp );
}
- if ( $this->fld_sizes ) {
- $vals['oldlen'] = intval( $row->rc_old_len );
- $vals['newlen'] = intval( $row->rc_new_len );
- }
-
if ( $this->fld_notificationtimestamp ) {
$vals['notificationtimestamp'] = ( $row->wl_notificationtimestamp == null )
? ''
: wfTimestamp( TS_ISO_8601, $row->wl_notificationtimestamp );
}
- if ( $this->fld_comment && isset( $row->rc_comment ) ) {
- $vals['comment'] = $row->rc_comment;
+ /* Add edit summary / log summary. */
+ if ( $this->fld_comment || $this->fld_parsedcomment ) {
+ if ( $row->rc_deleted & Revision::DELETED_COMMENT ) {
+ $vals['commenthidden'] = '';
+ $anyHidden = true;
+ }
+ if ( Revision::userCanBitfield( $row->rc_deleted, Revision::DELETED_COMMENT, $user ) ) {
+ if ( $this->fld_comment && isset( $row->rc_comment ) ) {
+ $vals['comment'] = $row->rc_comment;
+ }
+
+ if ( $this->fld_parsedcomment && isset( $row->rc_comment ) ) {
+ $vals['parsedcomment'] = Linker::formatComment( $row->rc_comment, $title );
+ }
+ }
+ }
+
+ /* Add the patrolled flag */
+ if ( $this->fld_patrol && $row->rc_patrolled == 1 ) {
+ $vals['patrolled'] = '';
}
- if ( $this->fld_parsedcomment && isset( $row->rc_comment ) ) {
- $vals['parsedcomment'] = Linker::formatComment( $row->rc_comment, $title );
+ if ( $this->fld_patrol && ChangesList::isUnpatrolled( $row, $user ) ) {
+ $vals['unpatrolled'] = '';
}
if ( $this->fld_loginfo && $row->rc_type == RC_LOG ) {
- $vals['logid'] = intval( $row->rc_logid );
- $vals['logtype'] = $row->rc_log_type;
- $vals['logaction'] = $row->rc_log_action;
- $logEntry = DatabaseLogEntry::newFromRow( (array)$row );
- ApiQueryLogEvents::addLogParams(
- $this->getResult(),
- $vals,
- $logEntry->getParameters(),
- $logEntry->getType(),
- $logEntry->getSubtype(),
- $logEntry->getTimestamp()
- );
+ if ( $row->rc_deleted & LogPage::DELETED_ACTION ) {
+ $vals['actionhidden'] = '';
+ $anyHidden = true;
+ }
+ if ( LogEventsList::userCanBitfield( $row->rc_deleted, LogPage::DELETED_ACTION, $user ) ) {
+ $vals['logid'] = intval( $row->rc_logid );
+ $vals['logtype'] = $row->rc_log_type;
+ $vals['logaction'] = $row->rc_log_action;
+ $logEntry = DatabaseLogEntry::newFromRow( (array)$row );
+ ApiQueryLogEvents::addLogParams(
+ $this->getResult(),
+ $vals,
+ $logEntry->getParameters(),
+ $logEntry->getType(),
+ $logEntry->getSubtype(),
+ $logEntry->getTimestamp()
+ );
+ }
+ }
+
+ if ( $anyHidden && ( $row->rc_deleted & Revision::DELETED_RESTRICTED ) ) {
+ $vals['suppressed'] = '';
}
return $vals;
),
'token' => array(
ApiBase::PARAM_TYPE => 'string'
- )
+ ),
);
}
),
'owner' => 'The name of the user whose watchlist you\'d like to access',
'token' => 'Give a security token (settable in preferences) to ' .
- 'allow access to another user\'s watchlist'
+ 'allow access to another user\'s watchlist',
);
}