1422 lines
42 KiB
PHP
1422 lines
42 KiB
PHP
<?php
|
|
|
|
use MediaWiki\Linker\LinkTarget;
|
|
use MediaWiki\MediaWikiServices;
|
|
use MediaWiki\Page\ProperPageIdentity;
|
|
use MediaWiki\Permissions\Authority;
|
|
use MediaWiki\Revision\RenderedRevision;
|
|
use MediaWiki\Revision\RevisionRecord;
|
|
use MediaWiki\Storage\EditResult;
|
|
use MediaWiki\User\UserIdentity;
|
|
|
|
/**
|
|
* Functions for the Approved Revs extension called by hooks in the MediaWiki
|
|
* code.
|
|
*
|
|
* @file
|
|
* @ingroup Extensions
|
|
*
|
|
* @author Yaron Koren
|
|
* @author Jeroen De Dauw
|
|
*/
|
|
class ApprovedRevsHooks {
|
|
|
|
private static $mApprovalMagicWords = [
|
|
'MAG_APPROVALYEAR',
|
|
'MAG_APPROVALMONTH',
|
|
'MAG_APPROVALDAY',
|
|
'MAG_APPROVALTIMESTAMP',
|
|
'MAG_APPROVALUSER'
|
|
];
|
|
|
|
public static function userRevsApprovedAutomatically( User $user, Title $title ) {
|
|
global $egApprovedRevsAutomaticApprovals, $egApprovedRevsFileAutomaticApprovals;
|
|
if ( $title->getNamespace() == NS_FILE ) {
|
|
$automaticApproval = $egApprovedRevsFileAutomaticApprovals;
|
|
} else {
|
|
$automaticApproval = $egApprovedRevsAutomaticApprovals;
|
|
}
|
|
return ( ApprovedRevs::userCanApprove( $user, $title ) && $automaticApproval );
|
|
}
|
|
|
|
/**
|
|
* "noindex" and "nofollow" meta-tags are added to every revision page,
|
|
* so that search engines won't index them - remove those if this is
|
|
* the approved revision.
|
|
* There doesn't seem to be an ideal MediaWiki hook to use for this
|
|
* function - it currently uses 'ParserBeforeInternalParse', which works.
|
|
*/
|
|
public static function removeRobotsTag( Parser &$parser, &$text, &$strip_state ) {
|
|
global $wgRequest;
|
|
|
|
if ( !ApprovedRevs::isDefaultPageRequest( $wgRequest ) ) {
|
|
return;
|
|
}
|
|
|
|
$revisionID = ApprovedRevs::getApprovedRevID( $parser->getTitle() );
|
|
if ( !empty( $revisionID ) ) {
|
|
global $wgOut;
|
|
$wgOut->getMetadata()->setIndexPolicy( 'index' );
|
|
$wgOut->setFollowPolicy( 'follow' );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called by updateLinksAfterEdit().
|
|
*/
|
|
public static function getUpdateForTitle( Title $title ) {
|
|
if ( !ApprovedRevs::pageIsApprovable( $title ) ) {
|
|
return null;
|
|
}
|
|
|
|
$wikiPage = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $title );
|
|
// If this user's revisions get approved automatically, exit
|
|
// now, because this will be the approved revision anyway.
|
|
$userID = $wikiPage->getUser();
|
|
$user = User::newFromId( $userID );
|
|
if ( self::userRevsApprovedAutomatically( $user, $title ) ) {
|
|
return null;
|
|
}
|
|
|
|
$content = ApprovedRevs::getApprovedContent( $title );
|
|
|
|
// If there's no approved revision, and 'blank if
|
|
// unapproved' is set to true, set the content to blank.
|
|
if ( $content == null ) {
|
|
global $egApprovedRevsBlankIfUnapproved;
|
|
if ( $egApprovedRevsBlankIfUnapproved ) {
|
|
$content = $wikiPage->getContentHandler()->makeEmptyContent();
|
|
} else {
|
|
// If it's an unapproved page and there's no
|
|
// page blanking, exit here.
|
|
return null;
|
|
}
|
|
}
|
|
|
|
$editInfo = $wikiPage->prepareContentForEdit( $content, null, $user );
|
|
$luClass = class_exists( 'LinksUpdate' ) ? LinksUpdate::class :
|
|
MediaWiki\Deferred\LinksUpdate\LinksUpdate::class;
|
|
return new $luClass( $title, $editInfo->output );
|
|
}
|
|
|
|
/**
|
|
* Hook: RevisionDataUpdates
|
|
*
|
|
* Call LinksUpdate on the text of this page's approved revision,
|
|
* if there is one.
|
|
*/
|
|
public static function updateLinksAfterEdit(
|
|
Title $title,
|
|
RenderedRevision $renderedRevision,
|
|
array &$updates
|
|
) {
|
|
$update = self::getUpdateForTitle( $title );
|
|
if ( $update !== null ) {
|
|
// Wipe out any existing updates.
|
|
$updates = [ $update ];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Hook: PageSaveComplete
|
|
*
|
|
* If the user saving this page has approval power, and automatic
|
|
* approvals are enabled, and the page is approvable, and either
|
|
* (a) this page already has an approved revision, or (b) unapproved
|
|
* pages are shown as blank on this wiki, automatically set this
|
|
* latest revision to be the approved one - don't bother logging
|
|
* the approval, though; the log is reserved for manual approvals.
|
|
*/
|
|
public static function setLatestAsApproved(
|
|
WikiPage $wikiPage,
|
|
UserIdentity $user,
|
|
string $summary,
|
|
int $flags,
|
|
RevisionRecord $revisionRecord,
|
|
EditResult $editResult
|
|
) {
|
|
if ( $revisionRecord === null ) {
|
|
return;
|
|
}
|
|
|
|
$title = $wikiPage->getTitle();
|
|
if ( !self::userRevsApprovedAutomatically( $user, $title ) ) {
|
|
return;
|
|
}
|
|
|
|
if ( $title->getNamespace() == NS_FILE ) {
|
|
self::setOriginalFileRevAsApproved( $user, $title );
|
|
return;
|
|
}
|
|
|
|
if ( !ApprovedRevs::pageIsApprovable( $title ) ) {
|
|
return;
|
|
}
|
|
|
|
global $egApprovedRevsBlankIfUnapproved;
|
|
if ( !$egApprovedRevsBlankIfUnapproved ) {
|
|
$approvedRevID = ApprovedRevs::getApprovedRevID( $title );
|
|
if ( empty( $approvedRevID ) ) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Save approval without logging.
|
|
ApprovedRevs::saveApprovedRevIDInDB( $title, $revisionRecord->getID(), $user, true );
|
|
}
|
|
|
|
/**
|
|
* Hook: PageMoveComplete
|
|
*
|
|
* Automatically approve a page move (which, like an edit, also counts
|
|
* as a revision) if the user has auto-approve rights, and the revision
|
|
* right before this one was approved - we do this so that page moves
|
|
* don't lead to unnecessary approval work for anyone.
|
|
*/
|
|
public static function setPageMoveAsApproved(
|
|
LinkTarget $old,
|
|
LinkTarget $new,
|
|
UserIdentity $user,
|
|
int $pageid,
|
|
int $redirid,
|
|
string $reason,
|
|
RevisionRecord $revision
|
|
) {
|
|
if ( $revision === null ) {
|
|
return;
|
|
}
|
|
|
|
$approvedRevID = ApprovedRevs::getApprovedRevID( $new );
|
|
if ( empty( $approvedRevID ) ) {
|
|
return;
|
|
}
|
|
|
|
$revID = $revision->getID();
|
|
|
|
// We need to know if the revision before this current move
|
|
// was approved - unfortunately, there's no convenient function
|
|
// to determine that, so we do a DB query.
|
|
$dbr = ApprovedRevs::getReadDB();
|
|
$lastPreMoveRevID = $dbr->selectField(
|
|
[ 'page', 'revision' ],
|
|
'MAX(rev_id)',
|
|
"rev_page = $pageid AND rev_id < $revID",
|
|
__METHOD__
|
|
);
|
|
|
|
if ( $approvedRevID !== $lastPreMoveRevID ) {
|
|
return;
|
|
}
|
|
|
|
// Save approval without logging.
|
|
ApprovedRevs::saveApprovedRevIDInDB( $new, $revID, $user, true );
|
|
}
|
|
|
|
/**
|
|
* This method will actually work no matter how many revisions a file has,
|
|
* but in practice it's only called when the first revision is created.
|
|
*/
|
|
public static function setOriginalFileRevAsApproved( $user, $title ) {
|
|
global $wgLocalFileRepo;
|
|
|
|
$fileRepo = new LocalRepo( $wgLocalFileRepo );
|
|
$file = LocalFile::newFromTitle( $title, $fileRepo );
|
|
if ( $file->getTimestamp() == '' ) {
|
|
// Probably a page in the "File:" namespace was created,
|
|
// with no corresponding file upload.
|
|
return;
|
|
}
|
|
|
|
if ( defined( 'MW_QUIBBLE_CI' ) ) {
|
|
// FIXME: The actual save doesn't work in CI...
|
|
return;
|
|
}
|
|
|
|
ApprovedRevs::setApprovedFileInDB(
|
|
$title, $file->getTimestamp(), $file->getSha1(), $user
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Hook: UploadComplete
|
|
*
|
|
* Automatically approve a revision of a file, other than the first
|
|
* one, if the permissions for it are set.
|
|
*/
|
|
public static function setLatestFileRevAsApproved( UploadBase $image ) {
|
|
$user = RequestContext::getMain()->getUser();
|
|
$file = $image->getLocalFile();
|
|
$title = $file->getTitle();
|
|
|
|
// A newly-uploaded file will still have an article ID of 0 -
|
|
// ignore it. (It will get handled by the PageSaveComplete,
|
|
// or PageContentSaveComplete, hook anyway.)
|
|
if ( $title->getArticleID() == 0 ) {
|
|
return;
|
|
}
|
|
|
|
if ( !self::userRevsApprovedAutomatically( $user, $title ) ) {
|
|
return;
|
|
}
|
|
ApprovedRevs::setApprovedFileInDB(
|
|
$title, $file->getTimestamp(), $file->getSha1(), $user
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Hook: PageSaveComplete
|
|
*
|
|
* Set the text that's stored for the page for standard searches.
|
|
*/
|
|
public static function setSearchText(
|
|
WikiPage $wikiPage,
|
|
UserIdentity $user,
|
|
string $summary,
|
|
int $flags,
|
|
RevisionRecord $revisionRecord,
|
|
EditResult $editResult
|
|
) {
|
|
if ( $revisionRecord === null ) {
|
|
return;
|
|
}
|
|
|
|
$title = $wikiPage->getTitle();
|
|
if ( !ApprovedRevs::pageIsApprovable( $title ) ) {
|
|
return;
|
|
}
|
|
|
|
$revisionID = ApprovedRevs::getApprovedRevID( $title );
|
|
if ( $revisionID === null ) {
|
|
return;
|
|
}
|
|
|
|
// We only need to modify the search text if the approved
|
|
// revision is not the latest one.
|
|
if ( $revisionID != $wikiPage->getLatest() ) {
|
|
$approvedPage = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $title );
|
|
$approvedContent = $approvedPage->getContent();
|
|
ApprovedRevs::setPageSearchText( $title, $approvedContent );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Hook: SearchResultInitFromTitle
|
|
*
|
|
* Sets the correct page revision to display the "text snippet" for
|
|
* a search result.
|
|
*/
|
|
public static function setSearchRevisionID( $title, &$id ) {
|
|
$revisionID = ApprovedRevs::getApprovedRevID( $title );
|
|
if ( $revisionID !== null ) {
|
|
$id = $revisionID;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Hook: ArticleFromTitle
|
|
*
|
|
* Return the approved revision of the page, if there is one, and if
|
|
* the page is simply being viewed, and if no specific revision has
|
|
* been requested.
|
|
*/
|
|
public static function showApprovedRevision( &$title, &$article, $context ) {
|
|
$request = $context->getRequest();
|
|
|
|
if ( !ApprovedRevs::isDefaultPageRequest( $request ) ) {
|
|
return;
|
|
}
|
|
|
|
if ( !ApprovedRevs::pageIsApprovable( $title ) ) {
|
|
return;
|
|
}
|
|
|
|
global $egApprovedRevsBlankIfUnapproved;
|
|
$revisionID = ApprovedRevs::getApprovedRevID( $title );
|
|
|
|
// Blanking of unapproved pages seems to not work unless code
|
|
// like this is called - possibly because the cache needs to be
|
|
// disabled. There may be a better way to accomplish that than
|
|
// this, but this works, and it doesn't seem to have a
|
|
// noticeable negative impact, so we'll go with it for now, at
|
|
// least.
|
|
if ( !empty( $revisionID ) || $egApprovedRevsBlankIfUnapproved ) {
|
|
$article = new Article( $title, $revisionID );
|
|
// This call is necessary because it
|
|
// causes $article->mRevision to get initialized,
|
|
// which in turn allows "edit section" links to show
|
|
// up if the approved revision is also the latest.
|
|
$article->fetchRevisionRecord();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Hook: ArticleRevisionViewCustom
|
|
*
|
|
* @param RevisionStoreRecord $revisionStoreRecord
|
|
* @param Title $title
|
|
* @param int $oldid
|
|
* @param OutputPage $output
|
|
*/
|
|
public static function showBlankIfUnapproved( $revisionStoreRecord, $title, $oldid, $output ) {
|
|
global $egApprovedRevsBlankIfUnapproved;
|
|
if ( !$egApprovedRevsBlankIfUnapproved ) {
|
|
return true;
|
|
}
|
|
|
|
if ( !ApprovedRevs::pageIsApprovable( $title ) ) {
|
|
return true;
|
|
}
|
|
|
|
$revisionID = ApprovedRevs::getApprovedRevID( $title );
|
|
if ( !empty( $revisionID ) ) {
|
|
return true;
|
|
}
|
|
|
|
// If the user is looking at a specified revision of the page,
|
|
// always show it.
|
|
if ( $oldid > 0 ) {
|
|
return true;
|
|
}
|
|
|
|
// We need to do this just to check if we're in the 'view'
|
|
// action, not 'edit', 'history' etc.
|
|
$article = new Article( $title, $oldid );
|
|
$context = $article->getContext();
|
|
$request = $context->getRequest();
|
|
if ( !ApprovedRevs::isDefaultPageRequest( $request ) ) {
|
|
return true;
|
|
}
|
|
|
|
// We're still here - the page should be blank.
|
|
ApprovedRevs::addCSS();
|
|
|
|
// Returning false sets the content to blank.
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Sets the subtitle when viewing old revisions of a page.
|
|
* This function's code is mostly copied from Article::setOldSubtitle(),
|
|
* and it is meant to serve as a replacement for that function, using
|
|
* the 'DisplayOldSubtitle' hook.
|
|
* This display has the following differences from the standard one:
|
|
* - It includes a link to the approved revision, which goes to the
|
|
* default page.
|
|
* - It includes a "diff" link alongside it.
|
|
* - The "Latest revision" link points to the correct revision ID,
|
|
* instead of to the default page (unless the latest revision is also
|
|
* the approved one).
|
|
*
|
|
* @author Eli Handel
|
|
*/
|
|
public static function setOldSubtitle( $article, $revisionID ) {
|
|
$title = $article->getTitle();
|
|
$context = $article->getContext();
|
|
|
|
$unhide = $context->getRequest()->getInt( 'unhide' ) == 1;
|
|
|
|
// Cascade unhide param in links for easy deletion browsing.
|
|
$extraParams = [];
|
|
if ( $unhide ) {
|
|
$extraParams['unhide'] = 1;
|
|
}
|
|
|
|
$revisionLookup = MediaWikiServices::getInstance()->getRevisionLookup();
|
|
$rev = $revisionLookup->getRevisionById( $revisionID );
|
|
$timestamp = $rev->getTimestamp();
|
|
|
|
$wikiPage = $article->getPage();
|
|
$latestID = $wikiPage->getLatest();
|
|
$current = ( $revisionID == $latestID );
|
|
$approvedID = ApprovedRevs::getApprovedRevID( $title );
|
|
$language = $context->getLanguage();
|
|
$user = $context->getUser();
|
|
|
|
$td = $language->userTimeAndDate( $timestamp, $user );
|
|
$tddate = $language->userDate( $timestamp, $user );
|
|
$tdtime = $language->userTime( $timestamp, $user );
|
|
|
|
// Show the user links if they're allowed to see them.
|
|
// If hidden, then show them only if requested.
|
|
$userlinks = Linker::revUserTools( $rev, !$unhide );
|
|
|
|
$infomsg = $current && !wfMessage( 'revision-info-current' )->isDisabled()
|
|
? 'revision-info-current'
|
|
: 'revision-info';
|
|
|
|
$outputPage = $context->getOutput();
|
|
$revUser = $rev->getUser();
|
|
$userText = $revUser ? $revUser->getName() : '';
|
|
$comment = MediaWikiServices::getInstance()->getCommentFormatter()
|
|
->formatRevision( $rev, $user, true, true );
|
|
$revisionInfo = "<div id=\"mw-{$infomsg}\">" .
|
|
$context->msg( $infomsg, $td )
|
|
->rawParams( $userlinks )
|
|
->params( $revisionID, $tddate, $tdtime, $userText )
|
|
->rawParams( $comment )
|
|
->parse() .
|
|
"</div>";
|
|
$outputPage->addSubtitle( $revisionInfo );
|
|
|
|
// Created for Approved Revs
|
|
$latestLinkParams = [];
|
|
if ( $latestID != $approvedID ) {
|
|
$latestLinkParams['oldid'] = $latestID;
|
|
}
|
|
$linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
|
|
$lnk = $current
|
|
? wfMessage( 'currentrevisionlink' )->escaped()
|
|
: $linkRenderer->makeLink(
|
|
$title,
|
|
wfMessage( 'currentrevisionlink' )->escaped(),
|
|
[],
|
|
$latestLinkParams + $extraParams
|
|
);
|
|
$curdiff = $current
|
|
? wfMessage( 'diff' )->escaped()
|
|
: $linkRenderer->makeLink(
|
|
$title,
|
|
wfMessage( 'diff' )->escaped(),
|
|
[],
|
|
[
|
|
'diff' => 'cur',
|
|
'oldid' => $revisionID
|
|
] + $extraParams
|
|
);
|
|
$prev = $revisionLookup->getPreviousRevision( $rev );
|
|
$prevlink = $prev
|
|
? $linkRenderer->makeLink(
|
|
$title,
|
|
wfMessage( 'previousrevision' )->escaped(),
|
|
[],
|
|
[
|
|
'direction' => 'prev',
|
|
'oldid' => $revisionID
|
|
] + $extraParams
|
|
)
|
|
: wfMessage( 'previousrevision' )->escaped();
|
|
$prevdiff = $prev
|
|
? $linkRenderer->makeLink(
|
|
$title,
|
|
wfMessage( 'diff' )->escaped(),
|
|
[],
|
|
[
|
|
'diff' => 'prev',
|
|
'oldid' => $revisionID
|
|
] + $extraParams
|
|
)
|
|
: wfMessage( 'diff' )->escaped();
|
|
$nextlink = $current
|
|
? wfMessage( 'nextrevision' )->escaped()
|
|
: $linkRenderer->makeLink(
|
|
$title,
|
|
wfMessage( 'nextrevision' )->escaped(),
|
|
[],
|
|
[
|
|
'direction' => 'next',
|
|
'oldid' => $revisionID
|
|
] + $extraParams
|
|
);
|
|
$nextdiff = $current
|
|
? wfMessage( 'diff' )->escaped()
|
|
: $linkRenderer->makeLink(
|
|
$title,
|
|
wfMessage( 'diff' )->escaped(),
|
|
[],
|
|
[
|
|
'diff' => 'next',
|
|
'oldid' => $revisionID
|
|
] + $extraParams
|
|
);
|
|
|
|
// Added for Approved Revs
|
|
$approved = ( $approvedID != null && $revisionID == $approvedID );
|
|
$approvedlink = $approved
|
|
? wfMessage( 'approvedrevs-approvedrevision' )->escaped()
|
|
: $linkRenderer->makeLink(
|
|
$title,
|
|
wfMessage( 'approvedrevs-approvedrevision' )->escaped(),
|
|
[],
|
|
$extraParams
|
|
);
|
|
$approveddiff = $approved
|
|
? wfMessage( 'diff' )->escaped()
|
|
: $linkRenderer->makeLink(
|
|
$title,
|
|
wfMessage( 'diff' )->escaped(),
|
|
[],
|
|
[
|
|
'diff' => $approvedID,
|
|
'oldid' => $revisionID
|
|
] + $extraParams
|
|
);
|
|
|
|
$cdel = Linker::getRevDeleteLink( $user, $rev, $title );
|
|
if ( $cdel !== '' ) {
|
|
$cdel .= ' ';
|
|
}
|
|
|
|
// Modified for Approved Revs
|
|
$outputPage->addSubtitle( "<div id=\"mw-revision-nav\">" . $cdel .
|
|
wfMessage( 'approvedrevs-revision-nav' )->rawParams(
|
|
$prevdiff, $prevlink, $approvedlink, $approveddiff, $lnk, $curdiff, $nextlink, $nextdiff
|
|
)->escaped() . "</div>" );
|
|
}
|
|
|
|
/**
|
|
* If user is viewing the page via its main URL, and what they're
|
|
* seeing is the approved revision of the page, remove the standard
|
|
* subtitle shown for all non-latest revisions, and replace it with
|
|
* either nothing or a message explaining the situation, depending
|
|
* on the user's rights.
|
|
*/
|
|
public static function setSubtitle( &$article, &$revisionID ) {
|
|
$title = $article->getTitle();
|
|
if ( !ApprovedRevs::hasApprovedRevision( $title ) ) {
|
|
return true;
|
|
}
|
|
|
|
$context = $article->getContext();
|
|
$request = $context->getRequest();
|
|
if ( $request->getCheck( 'oldid' ) ) {
|
|
// If the user is looking at the latest revision,
|
|
// disable caching, to avoid the wiki getting the
|
|
// contents from the cache, and thus getting the
|
|
// approved contents instead.
|
|
if ( $revisionID == $article->getPage()->getLatest() ) {
|
|
global $wgEnableParserCache;
|
|
$wgEnableParserCache = false;
|
|
}
|
|
self::setOldSubtitle( $article, $revisionID );
|
|
// Don't show default Article::setOldSubtitle().
|
|
return false;
|
|
}
|
|
|
|
$text = "";
|
|
ApprovedRevs::addCSS();
|
|
|
|
$user = $context->getUser();
|
|
if ( ApprovedRevs::checkPermission( $user, $title, "viewlinktolatest" ) ) {
|
|
if ( $revisionID == $article->getPage()->getLatest() ) {
|
|
$text .= Xml::element(
|
|
'span',
|
|
[ 'class' => 'approvedAndLatestMsg' ],
|
|
wfMessage( 'approvedrevs-approvedandlatest' )->text()
|
|
);
|
|
} else {
|
|
$text .= wfMessage( 'approvedrevs-notlatest' )->parse();
|
|
|
|
$linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
|
|
$text .= ' ' . $linkRenderer->makeLink(
|
|
$title,
|
|
wfMessage( 'approvedrevs-viewlatestrev' )->parse(),
|
|
[],
|
|
[ 'oldid' => $article->getPage()->getLatest() ]
|
|
);
|
|
|
|
$text = Xml::tags(
|
|
'span',
|
|
[ 'class' => 'notLatestMsg' ],
|
|
$text
|
|
);
|
|
}
|
|
}
|
|
|
|
if ( ApprovedRevs::checkPermission( $user, $title, "viewapprover" ) ) {
|
|
$revisionUser = ApprovedRevs::getRevApprover( $title );
|
|
if ( $revisionUser ) {
|
|
$text .= Html::rawElement(
|
|
'span',
|
|
[ 'class' => 'approvingUser' ],
|
|
wfMessage( 'approvedrevs-approver' )->rawParams(
|
|
Linker::userLink( $revisionUser->getId(), $revisionUser->getName() )
|
|
)->escaped()
|
|
);
|
|
}
|
|
}
|
|
|
|
if ( $text !== "" ) {
|
|
$out = $context->getOutput();
|
|
if ( $out->getSubtitle() != '' ) {
|
|
$out->addSubtitle( '<br />' . $text );
|
|
} else {
|
|
$out->setSubtitle( $text );
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Hook: TitleGetEditNotices
|
|
*
|
|
* Add an edit notice if the approved revision
|
|
* is not the same as the latest one, so that users don't
|
|
* get confused, since they'll be seeing the latest one.
|
|
* Also add an edit notice if the user can't automatically
|
|
* approve.
|
|
*
|
|
* @param Title $title
|
|
* @param int $oldid
|
|
* @param array &$notices
|
|
*/
|
|
public static function addEditNotice( Title $title, $oldid, array &$notices ) {
|
|
$editPage = new EditPage( new Article( $title, $oldid ) );
|
|
$editPage->oldid = $oldid;
|
|
$article = $editPage->getArticle();
|
|
$context = $article->getContext();
|
|
$request = $context->getRequest();
|
|
$user = $context->getUser();
|
|
$approvedRevID = ApprovedRevs::getApprovedRevID( $title );
|
|
$latestRevID = $title->getLatestRevID();
|
|
|
|
if ( empty( $approvedRevID ) ) {
|
|
return true;
|
|
}
|
|
|
|
if ( $approvedRevID != $latestRevID && !$request->getCheck( 'oldid' ) ) {
|
|
// MediaWiki 1.40+
|
|
if ( class_exists( 'MediaWiki\Html\Html' ) ) {
|
|
$notices['approvedrevs-editwarning'] = MediaWiki\Html\Html::warningBox(
|
|
$context->msg( 'approvedrevs-editwarning',
|
|
$title->getFullURL( [
|
|
'diff' => $latestRevID,
|
|
'oldid' => $approvedRevID,
|
|
] )
|
|
)->parse() );
|
|
} else {
|
|
$notices['approvedrevs-editwarning'] = Html::warningBox(
|
|
$context->msg( 'approvedrevs-editwarning',
|
|
$title->getFullURL( [
|
|
'diff' => $latestRevID,
|
|
'oldid' => $approvedRevID,
|
|
] )
|
|
)->parse() );
|
|
}
|
|
}
|
|
|
|
if ( !self::userRevsApprovedAutomatically( $user, $title ) ) {
|
|
if ( class_exists( 'MediaWiki\Html\Html' ) ) {
|
|
$notices['approvedrevs-editwarning-pending'] =
|
|
MediaWiki\Html\Html::warningBox( $context->msg( 'approvedrevs-editwarning-pending' )->escaped() );
|
|
} else {
|
|
$notices['approvedrevs-editwarning-pending'] =
|
|
Html::warningBox( $context->msg( 'approvedrevs-editwarning-pending' )->escaped() );
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Hook: PageForms::HTMLBeforeForm
|
|
*
|
|
* Same as addWarningToEditPage(), but for the Page Forms
|
|
* 'edit with form' tab.
|
|
*/
|
|
public static function addWarningToPFForm( &$title, &$preFormHTML ) {
|
|
if ( $title == null || !$title->exists() ) {
|
|
return true;
|
|
}
|
|
|
|
$approvedRevID = ApprovedRevs::getApprovedRevID( $title );
|
|
$latestRevID = $title->getLatestRevID();
|
|
if ( !empty( $approvedRevID ) && $approvedRevID != $latestRevID ) {
|
|
ApprovedRevs::addCSS();
|
|
$preFormHTML .= Xml::element( 'p',
|
|
[ 'class' => 'approvedRevsEditWarning' ],
|
|
wfMessage( 'approvedrevs-editwarning' )->text() ) . "\n";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Hook: SkinTemplateNavigation::Universal
|
|
*
|
|
* If user is looking at a revision through a main 'view' URL (no
|
|
* revision specified), have the 'edit' tab link to the basic
|
|
* 'action=edit' URL (i.e., the latest revision), no matter which
|
|
* revision they're actually on.
|
|
*/
|
|
public static function changeEditLink( SkinTemplate &$skinTemplate, &$links ) {
|
|
$context = $skinTemplate->getContext();
|
|
$request = $context->getRequest();
|
|
|
|
if ( $request->getCheck( 'oldid' ) ) {
|
|
return;
|
|
}
|
|
|
|
$title = $skinTemplate->getTitle();
|
|
if ( ApprovedRevs::hasApprovedRevision( $title ) ) {
|
|
$contentActions = &$links['views'];
|
|
// The URL is the same regardless of whether the tab
|
|
// is 'edit' or 'view source', but the "action" is
|
|
// different.
|
|
if ( array_key_exists( 'edit', $contentActions ) ) {
|
|
$contentActions['edit']['href'] = $title->getLocalUrl( [ 'action' => 'edit' ] );
|
|
}
|
|
if ( array_key_exists( 've-edit', $contentActions ) ) {
|
|
$contentActions['ve-edit']['href'] = $title->getLocalUrl( [ 'veaction' => 'edit' ] );
|
|
}
|
|
if ( array_key_exists( 'viewsource', $contentActions ) ) {
|
|
$contentActions['viewsource']['href'] = $title->getLocalUrl( [ 'action' => 'edit' ] );
|
|
$skinTemplate->getOutput()->addJsConfigVars( 'wgEditLatestRevision', true );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Hook: PageHistoryLineEnding
|
|
*
|
|
* If the user is allowed to make revision approvals, add either an
|
|
* 'approve' or 'unapprove' link to the end of this row in the page
|
|
* history, depending on whether or not this is already the approved
|
|
* revision. If it's the approved revision also add on a "star"
|
|
* icon, regardless of the user.
|
|
*/
|
|
public static function addApprovalLink( $historyPage, &$row, &$s, &$classes ) {
|
|
$title = $historyPage->getTitle();
|
|
if ( !ApprovedRevs::pageIsApprovable( $title ) ) {
|
|
return;
|
|
}
|
|
|
|
$context = $historyPage->getContext();
|
|
$user = $context->getUser();
|
|
|
|
$approvedRevID = ApprovedRevs::getApprovedRevID( $title );
|
|
if ( $row->rev_id == $approvedRevID ) {
|
|
$s .= ' ★';
|
|
|
|
// add class to this line to highlight the approved rev with CSS
|
|
if ( is_array( $classes ) ) {
|
|
$classes[] = "approved-revision";
|
|
} else {
|
|
$classes = [ "approved-revision" ];
|
|
}
|
|
|
|
// We also need to add the CSS.
|
|
ApprovedRevs::addCSS();
|
|
}
|
|
if ( ApprovedRevs::userCanApprove( $user, $title ) ) {
|
|
if ( $row->rev_id == $approvedRevID ) {
|
|
$url = $title->getLocalUrl(
|
|
[ 'action' => 'unapprove' ]
|
|
);
|
|
$msg = wfMessage( 'approvedrevs-unapprove' )->text();
|
|
} else {
|
|
$url = $title->getLocalUrl(
|
|
[ 'action' => 'approve', 'oldid' => $row->rev_id ]
|
|
);
|
|
$msg = wfMessage( 'approvedrevs-approve' )->text();
|
|
}
|
|
$s .= ' (' . Xml::element(
|
|
'a',
|
|
[ 'href' => $url ],
|
|
$msg
|
|
) . ')';
|
|
}
|
|
}
|
|
|
|
public static function addApprovalDiffLink(
|
|
RevisionRecord $newRevision,
|
|
array &$links,
|
|
?RevisionRecord $prevRevision,
|
|
UserIdentity $userIdentity
|
|
) {
|
|
$title = Title::castFromLinkTarget( $newRevision->getPageAsLinkTarget() );
|
|
|
|
if ( $prevRevision === null || $title === null || !ApprovedRevs::pageIsApprovable( $title ) ) {
|
|
return;
|
|
}
|
|
|
|
$approvedRevID = ApprovedRevs::getApprovedRevID( $title );
|
|
|
|
if ( ApprovedRevs::userCanApprove( $userIdentity, $title ) && $prevRevision->getID() == $approvedRevID ) {
|
|
// array key is class applied to <span> wrapping around link
|
|
// default if blank is mw-diff-tool; add that along with extension-specific class
|
|
$links['mw-diff-tool ext-approved-revs-approval-span'] = HTML::element(
|
|
'a',
|
|
[
|
|
'href' => $title->getLocalUrl( [
|
|
'action' => 'approve',
|
|
'oldid' => $newRevision->getId()
|
|
] ),
|
|
'class' => 'ext-approved-revs-approval-link',
|
|
'title' => wfMessage( 'approvedrevs-approvethisrev' )->text()
|
|
],
|
|
wfMessage( 'approvedrevs-approve' )->text()
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @see https://www.mediawiki.org/wiki/Manual:Hooks/BeforeParserFetchTemplateRevisionRecord
|
|
*
|
|
* Use the approved revision, if it exists, for templates and other
|
|
* transcluded pages.
|
|
*/
|
|
public static function setTranscludedPageRev(
|
|
?LinkTarget $contextTitle,
|
|
LinkTarget $titleTarget,
|
|
bool &$skip,
|
|
?RevisionRecord &$revRecord
|
|
) {
|
|
$title = Title::castFromLinkTarget( $titleTarget );
|
|
$revisionID = ApprovedRevs::getApprovedRevID( $title );
|
|
if ( !empty( $revisionID ) ) {
|
|
$revLookup = MediaWikiServices::getInstance()->getRevisionLookup();
|
|
$revRecord = $revLookup->getRevisionById( $revisionID );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Hook: PageDeleteComplete
|
|
*
|
|
* Delete the approval record in the database if the page itself is
|
|
* deleted.
|
|
*/
|
|
public static function deleteRevisionApproval(
|
|
ProperPageIdentity $page, Authority $deleter, string $reason, int $pageID,
|
|
RevisionRecord $deletedRev, ManualLogEntry $logEntry, int $archivedRevisionCount
|
|
) {
|
|
ApprovedRevs::unsetApprovalInDB( $page->getTitle() );
|
|
}
|
|
|
|
/**
|
|
* @see https://www.mediawiki.org/wiki/Manual:Hooks/GetMagicVariableIDs
|
|
*
|
|
* Register magic-word variable IDs
|
|
*/
|
|
public static function addMagicWordVariableIDs( &$magicWordVariableIDs ) {
|
|
$magicWordVariableIDs[] = 'MAG_APPROVEDREVS';
|
|
$magicWordVariableIDs = array_merge( $magicWordVariableIDs, self::$mApprovalMagicWords );
|
|
}
|
|
|
|
/**
|
|
* Hook: ParserAfterTidy
|
|
*
|
|
* Set values in the page_props table based on the presence of the
|
|
* 'APPROVEDREVS' magic word in a page
|
|
*/
|
|
public static function handleMagicWords( &$parser, &$text ) {
|
|
$factory = MediaWikiServices::getInstance()->getMagicWordFactory();
|
|
$mw_hide = $factory->get( 'MAG_APPROVEDREVS' );
|
|
if ( $mw_hide->matchAndRemove( $text ) ) {
|
|
$parserOutput = $parser->getOutput();
|
|
$parserOutput->setPageProperty( 'approvedrevs', 'y' );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Hook: ParserGetVariableValueSwitch
|
|
*
|
|
* Assign a value to our variable.
|
|
*
|
|
* @param Parser $parser
|
|
* @param array &$variableCache
|
|
* @param string $magicWordId
|
|
* @param string &$ret
|
|
*/
|
|
public static function assignAValue( $parser, &$variableCache, $magicWordId, &$ret ) {
|
|
if ( !in_array( $magicWordId, self::$mApprovalMagicWords ) ) {
|
|
return;
|
|
}
|
|
|
|
$approvalInfo = ARParserFunctions::getApprovalInfo( $parser->getTitle() );
|
|
if ( $approvalInfo == null ) {
|
|
$variableCache[$magicWordId] = '';
|
|
$ret = '';
|
|
return;
|
|
}
|
|
switch ( $magicWordId ) {
|
|
case 'MAG_APPROVALYEAR':
|
|
$ret = date( 'Y', $approvalInfo[0] );
|
|
break;
|
|
case 'MAG_APPROVALMONTH':
|
|
$ret = date( 'm', $approvalInfo[0] );
|
|
break;
|
|
case 'MAG_APPROVALDAY':
|
|
$ret = date( 'd', $approvalInfo[0] );
|
|
break;
|
|
case 'MAG_APPROVALTIMESTAMP':
|
|
$ret = date( 'YmdHis', $approvalInfo[0] );
|
|
break;
|
|
case 'MAG_APPROVALUSER':
|
|
$userID = $approvalInfo[1];
|
|
$user = User::newFromID( $userID );
|
|
$ret = $user->getName();
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
$variableCache[$magicWordId] = $ret;
|
|
}
|
|
|
|
/**
|
|
* Hook: ParserFirstCallInit
|
|
*
|
|
* Register parser function(s).
|
|
*
|
|
* @param Parser &$parser
|
|
* @since 1.0
|
|
*/
|
|
public static function registerFunctions( &$parser ) {
|
|
$parser->setFunctionHook(
|
|
'approvable_by',
|
|
'ARParserFunctions::renderApprovableBy',
|
|
Parser::SFH_OBJECT_ARGS
|
|
);
|
|
$parser->setFunctionHook(
|
|
'MAG_APPROVALYEAR',
|
|
'ARParserFunctions::renderApprovalYear',
|
|
Parser::SFH_NO_HASH
|
|
);
|
|
$parser->setFunctionHook(
|
|
'MAG_APPROVALMONTH',
|
|
'ARParserFunctions::renderApprovalMonth',
|
|
Parser::SFH_NO_HASH
|
|
);
|
|
$parser->setFunctionHook(
|
|
'MAG_APPROVALDAY',
|
|
'ARParserFunctions::renderApprovalDay',
|
|
Parser::SFH_NO_HASH
|
|
);
|
|
$parser->setFunctionHook(
|
|
'MAG_APPROVALTIMESTAMP',
|
|
'ARParserFunctions::renderApprovalTimestamp',
|
|
Parser::SFH_NO_HASH
|
|
);
|
|
$parser->setFunctionHook(
|
|
'MAG_APPROVALUSER',
|
|
'ARParserFunctions::renderApprovalUser',
|
|
Parser::SFH_NO_HASH
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Hook: AdminLinks
|
|
*
|
|
* Add a link to Special:ApprovedPages to the page
|
|
* Special:AdminLinks, defined by the Admin Links extension.
|
|
*/
|
|
public static function addToAdminLinks( &$admin_links_tree ) {
|
|
$general_section = $admin_links_tree->getSection( wfMessage( 'adminlinks_general' )->text() );
|
|
$extensions_row = $general_section->getRow( 'extensions' );
|
|
if ( $extensions_row === null ) {
|
|
$extensions_row = new ALRow( 'extensions' );
|
|
$general_section->addRow( $extensions_row );
|
|
}
|
|
$extensions_row->addItem( ALItem::newFromSpecialPage( 'ApprovedRevs' ) );
|
|
}
|
|
|
|
public static function describeDBSchema( DatabaseUpdater $updater ) {
|
|
$dir = __DIR__;
|
|
|
|
// DB updates
|
|
// For now, there's just a single SQL file for all DB types.
|
|
//if ( $updater->getDB()->getType() == 'mysql' ) {
|
|
$updater->addExtensionUpdate( [
|
|
'addTable', 'approved_revs', "$dir/../sql/ApprovedRevs.sql", true
|
|
] );
|
|
$updater->addExtensionUpdate( [
|
|
'addField', 'approved_revs', 'approver_id', "$dir/../sql/patch-approver_id.sql", true
|
|
] );
|
|
$updater->addExtensionUpdate( [
|
|
'addField', 'approved_revs', 'approval_date', "$dir/../sql/patch-approval_date.sql", true
|
|
] );
|
|
$updater->addExtensionUpdate( [
|
|
'addTable', 'approved_revs_files', "$dir/../sql/ApprovedFiles.sql", true
|
|
] );
|
|
// }
|
|
}
|
|
|
|
/**
|
|
* Hook: ArticleViewHeader
|
|
*
|
|
* Display a message to the user if (a) "blank if unapproved" is set,
|
|
* (b) the page is approvable, (c) the user has 'viewlinktolatest'
|
|
* permission, and (d) either the page has no approved revision, or
|
|
* the user is looking at a revision that's not the latest - the
|
|
* displayed message depends on which of those cases it is.
|
|
* @todo - this should probably get split up into two methods.
|
|
*
|
|
* @since 0.5.6
|
|
*
|
|
* @param Article $article
|
|
* @param bool &$outputDone
|
|
* @param bool &$useParserCache
|
|
*/
|
|
public static function setArticleHeader( Article $article, &$outputDone, &$useParserCache ) {
|
|
global $egApprovedRevsBlankIfUnapproved;
|
|
|
|
// For now, we only set the header if "blank if unapproved"
|
|
// is set.
|
|
if ( !$egApprovedRevsBlankIfUnapproved ) {
|
|
return;
|
|
}
|
|
|
|
$title = $article->getTitle();
|
|
$context = $article->getContext();
|
|
$user = $context->getUser();
|
|
$out = $context->getOutput();
|
|
$request = $context->getRequest();
|
|
|
|
if ( !ApprovedRevs::pageIsApprovable( $title ) ) {
|
|
return;
|
|
}
|
|
|
|
// If the user isn't supposed to see these kinds of
|
|
// messages, exit.
|
|
if ( !ApprovedRevs::checkPermission( $user, $title, "viewlinktolatest" ) ) {
|
|
return;
|
|
}
|
|
|
|
// If there's an approved revision for this page, and the
|
|
// user is looking at it - either by simply going to the page,
|
|
// or by looking at the revision that happens to be approved -
|
|
// don't display anything.
|
|
$approvedRevID = ApprovedRevs::getApprovedRevID( $title );
|
|
if ( !empty( $approvedRevID ) &&
|
|
( !$request->getCheck( 'oldid' ) ||
|
|
$request->getInt( 'oldid' ) == $approvedRevID ) ) {
|
|
return;
|
|
}
|
|
|
|
// Disable caching, so that if it's a specific ID being shown
|
|
// that happens to be the latest, it doesn't show a blank page.
|
|
$useParserCache = false;
|
|
$out->addHTML( '<span style="margin-left: 10.75px">' );
|
|
|
|
// If the user is looking at a specific revision, show an
|
|
// "approve this revision" message - otherwise, it means
|
|
// there's no approved revision (we would have exited out if
|
|
// there were), so show a message explaining why the page is
|
|
// blank, with a link to the latest revision.
|
|
if ( $request->getCheck( 'oldid' ) ) {
|
|
if ( ApprovedRevs::userCanApprove( $user, $title ) ) {
|
|
// @TODO - why is this message being shown
|
|
// at all? Aren't the "approve this revision"
|
|
// links in the history page always good
|
|
// enough?
|
|
$out->addHTML( Xml::tags( 'span', [ 'id' => 'contentSub2' ],
|
|
Xml::element( 'a',
|
|
[ 'href' => $title->getLocalUrl(
|
|
[
|
|
'action' => 'approve',
|
|
'oldid' => $request->getInt( 'oldid' )
|
|
]
|
|
) ],
|
|
wfMessage( 'approvedrevs-approvethisrev' )->text()
|
|
) ) );
|
|
}
|
|
} else {
|
|
$out->addSubtitle(
|
|
htmlspecialchars( wfMessage( 'approvedrevs-blankpageshown' )->text() ) . ' ' .
|
|
Xml::element( 'a',
|
|
[ 'href' => $title->getLocalUrl(
|
|
[
|
|
'oldid' => $article->getRevIdFetched()
|
|
]
|
|
) ],
|
|
wfMessage( 'approvedrevs-viewlatestrev' )->text()
|
|
)
|
|
);
|
|
}
|
|
|
|
$out->addHTML( '</span>' );
|
|
}
|
|
|
|
/**
|
|
* Hook: ArticleViewHeader
|
|
*
|
|
* If this page is approvable, but has no approved revision, display
|
|
* a header message stating that, if the setting to display this
|
|
* message is activated.
|
|
*/
|
|
public static function displayNotApprovedHeader( Article $article, &$outputDone, &$useParserCache ) {
|
|
global $egApprovedRevsShowNotApprovedMessage;
|
|
if ( !$egApprovedRevsShowNotApprovedMessage ) {
|
|
return;
|
|
}
|
|
|
|
// If we're looking at an old revision of the page, no need to
|
|
// display anything.
|
|
if ( $article->mOldId !== 0 ) {
|
|
return;
|
|
}
|
|
|
|
$title = $article->getTitle();
|
|
if ( !ApprovedRevs::pageIsApprovable( $title ) ) {
|
|
return;
|
|
}
|
|
if ( ApprovedRevs::hasApprovedRevision( $title ) ) {
|
|
return;
|
|
}
|
|
|
|
$text = wfMessage( 'approvedrevs-noapprovedrevision' )->escaped();
|
|
|
|
$context = $article->getContext();
|
|
$out = $context->getOutput();
|
|
if ( $out->getSubtitle() != '' ) {
|
|
$out->addSubtitle( '<br />' . $text );
|
|
} else {
|
|
$out->setSubtitle( $text );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add a class to the <body> tag indicating the approval status
|
|
* of this page, so it can be styled accordingly.
|
|
*/
|
|
public static function addBodyClass( $out, $skin, &$bodyAttrs ) {
|
|
$title = $skin->getTitle();
|
|
$context = $skin->getContext();
|
|
$request = $context->getRequest();
|
|
|
|
if ( !ApprovedRevs::hasApprovedRevision( $title ) ) {
|
|
// This page has no approved rev.
|
|
$bodyAttrs['class'] .= " approvedRevs-noapprovedrev";
|
|
} else {
|
|
// The page has an approved rev - see if this is it.
|
|
$approvedRevID = ApprovedRevs::getApprovedRevID( $title );
|
|
if ( !empty( $approvedRevID ) &&
|
|
( !$request->getCheck( 'oldid' ) ||
|
|
$request->getInt( 'oldid' ) == $approvedRevID ) ) {
|
|
// This is the approved rev.
|
|
$bodyAttrs['class'] .= " approvedRevs-approved";
|
|
} else {
|
|
// This is not the approved rev.
|
|
$bodyAttrs['class'] .= " approvedRevs-notapproved";
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Hook: ImagePageFileHistoryLine
|
|
*
|
|
* On image pages (pages in NS_FILE), modify each line in the file history
|
|
* (file history, not history of wikitext on file page). Add
|
|
* "approved-revision" class to the appropriate row. For users with
|
|
* approve permissions on this page add "approve" and "unapprove" links as
|
|
* required.
|
|
*/
|
|
public static function onImagePageFileHistoryLine( $hist, $file, &$s, &$rowClass ) {
|
|
$fileTitle = $file->getTitle();
|
|
|
|
if ( !ApprovedRevs::fileIsApprovable( $fileTitle ) ) {
|
|
return;
|
|
}
|
|
|
|
$rowTimestamp = $file->getTimestamp();
|
|
$rowSha1 = $file->getSha1();
|
|
|
|
[ $approvedRevTimestamp, $approvedRevSha1 ] =
|
|
ApprovedRevs::getApprovedFileInfo( $file->getTitle() );
|
|
|
|
ApprovedRevs::addCSS();
|
|
|
|
// Apply class to row of approved revision
|
|
// Note: both here and below in the "userCanApprove" section, if the
|
|
// timestamp condition is removed then all rows with the same sha1 as
|
|
// the approved rev will be given the class "approved-revision", and
|
|
// highlighted. Only the actual approved rev will be given the
|
|
// star icon and approved revision label, though.
|
|
if ( $rowSha1 == $approvedRevSha1 && $rowTimestamp == $approvedRevTimestamp ) {
|
|
if ( $rowClass ) {
|
|
$rowClass .= ' ';
|
|
}
|
|
$rowClass .= "approved-revision";
|
|
|
|
$pattern = "/<td[^>]+filehistory-selected+[^>]+>/";
|
|
$replace = "$0 ★ " . wfMessage( 'approvedrevs-approvedrevision' )->escaped() . "<br />";
|
|
$s = preg_replace( $pattern, $replace, $s );
|
|
}
|
|
|
|
$user = $hist->getContext()->getUser();
|
|
if ( ApprovedRevs::userCanApprove( $user, $fileTitle ) ) {
|
|
if ( $rowSha1 == $approvedRevSha1 && $rowTimestamp == $approvedRevTimestamp ) {
|
|
$url = $fileTitle->getLocalUrl(
|
|
[ 'action' => 'unapprovefile' ]
|
|
);
|
|
$msg = wfMessage( 'approvedrevs-unapprove' )->text();
|
|
} else {
|
|
$url = $fileTitle->getLocalUrl(
|
|
[
|
|
'action' => 'approvefile',
|
|
'ts' => $rowTimestamp,
|
|
'sha1' => $rowSha1
|
|
]
|
|
);
|
|
$msg = wfMessage( 'approvedrevs-approve' )->text();
|
|
}
|
|
$s .= '<td>' . Xml::element(
|
|
'a',
|
|
[ 'href' => $url ],
|
|
$msg
|
|
) . '</td>';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Hook: BeforeParserFetchFileAndTitle
|
|
*
|
|
* Changes links and thumbnails of files to point to the approved
|
|
* revision in all cases except the primary file on file pages (e.g.
|
|
* the big image in the top left on File:My File.png). To modify that
|
|
* image, see self::onImagePageFindFile().
|
|
*/
|
|
public static function modifyFileLinks( $parser, Title $fileTitle, &$options, &$query ) {
|
|
if ( $fileTitle->getNamespace() == NS_MEDIA ) {
|
|
$fileTitle = Title::makeTitle( NS_FILE, $fileTitle->getDBkey() );
|
|
// avoid extra queries
|
|
$fileTitle->resetArticleId( $fileTitle->getArticleID() );
|
|
|
|
// Media link redirects don't get caught by the normal
|
|
// redirect check, so this extra check is required
|
|
$fileWikiPage = MediaWikiServices::getInstance()->getWikiPageFactory()
|
|
->newFromID( $fileTitle->getArticleID() );
|
|
if ( $fileWikiPage && $fileWikiPage->getRedirectTarget() ) {
|
|
$fileTitle = $fileWikiPage->getTitle();
|
|
}
|
|
}
|
|
|
|
if ( $fileTitle->isRedirect() ) {
|
|
$page = MediaWikiServices::getInstance()->getWikiPageFactory()
|
|
->newFromID( $fileTitle->getArticleID() );
|
|
$fileTitle = $page->getRedirectTarget();
|
|
// avoid extra queries
|
|
$fileTitle->resetArticleId( $fileTitle->getArticleID() );
|
|
}
|
|
|
|
# Tell Parser what file version to use
|
|
[ $approvedRevTimestamp, $approvedRevSha1 ] =
|
|
ApprovedRevs::getApprovedFileInfo( $fileTitle );
|
|
|
|
// No approved version of this file - just exit here, and
|
|
// possibly make it look like the file doesn't exist.
|
|
if ( ( !$approvedRevTimestamp ) || ( !$approvedRevSha1 ) ) {
|
|
global $egApprovedRevsBlankFileIfUnapproved;
|
|
if ( $egApprovedRevsBlankFileIfUnapproved ) {
|
|
$options['broken'] = true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
$options['time'] = wfTimestampOrNull( TS_MW, $approvedRevTimestamp );
|
|
$options['sha1'] = $approvedRevSha1;
|
|
|
|
# Stabilize the file link
|
|
if ( $query != '' ) {
|
|
$query .= '&';
|
|
}
|
|
$query .= "filetimestamp=" . urlencode(
|
|
wfTimestamp( TS_MW, $approvedRevTimestamp )
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Hook: ImagePageFindFile
|
|
*
|
|
* Applicable on image pages only, this changes the primary image
|
|
* on the page from the most recent to the approved revision.
|
|
*/
|
|
public static function onImagePageFindFile( $imagePage, &$normalFile, &$displayFile ) {
|
|
[ $approvedRevTimestamp, $approvedRevSha1 ] =
|
|
ApprovedRevs::getApprovedFileInfo( $imagePage->getFile()->getTitle() );
|
|
|
|
if ( ( !$approvedRevTimestamp ) || ( !$approvedRevSha1 ) ) {
|
|
return;
|
|
}
|
|
|
|
$repo = MediaWikiServices::getInstance()->getRepoGroup();
|
|
$title = $imagePage->getTitle();
|
|
$displayFile = $repo->findFile(
|
|
$title, [ 'time' => $approvedRevTimestamp ]
|
|
);
|
|
|
|
# If none found, try current
|
|
if ( !$displayFile ) {
|
|
wfDebug( __METHOD__ . ": {$title->getPrefixedDBkey()}: " .
|
|
"$approvedRevTimestamp not found, using current\n" );
|
|
$displayFile = $repo->findFile( $title );
|
|
// If still none found, use a valid local placeholder.
|
|
if ( !$displayFile ) {
|
|
// fallback to current
|
|
$displayFile = $repo->getLocalRepo()->newFile( $title );
|
|
}
|
|
$normalFile = $displayFile;
|
|
# If found, set $normalFile
|
|
} else {
|
|
wfDebug( __METHOD__ . ": {$title->getPrefixedDBkey()}: " .
|
|
"using timestamp $approvedRevTimestamp\n" );
|
|
$normalFile = $repo->findFile( $title );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Hook: FileDeleteComplete
|
|
*
|
|
* If a file is deleted, check if the sha1 (and timestamp?) exist in the
|
|
* approved_revs_files table, and delete that row accordingly. A deleted
|
|
* version of a file should not be the approved version!
|
|
*/
|
|
public static function onFileDeleteComplete( File $file, $oldimage, $article, $user, $reason ) {
|
|
$dbr = ApprovedRevs::getReadDB();
|
|
// check if this file has an approved revision
|
|
$approvedFile = $dbr->selectRow(
|
|
'approved_revs_files',
|
|
[ 'approved_timestamp', 'approved_sha1' ],
|
|
[ 'file_title' => $file->getTitle()->getDBkey() ],
|
|
__METHOD__
|
|
);
|
|
|
|
// If an approved revision exists, loop through all files in
|
|
// history. Since this hook happens AFTER deletion (there is
|
|
// no hook before deletion), check to see if the sha1 of the
|
|
// approved revision is NOT in the history. If it is not in
|
|
// the history, then it has no business being in the
|
|
// approved_revs_files table, and should be deleted.
|
|
if ( $approvedFile ) {
|
|
|
|
$revs = [];
|
|
$approvedExists = false;
|
|
|
|
$hist = $file->getHistory();
|
|
foreach ( $hist as $OldLocalFile ) {
|
|
// need to check both sha1 and timestamp, since
|
|
// reverted files can have the same sha1, but
|
|
// different timestamps
|
|
if (
|
|
$OldLocalFile->getTimestamp() == $approvedFile->approved_timestamp
|
|
&& $OldLocalFile->getSha1() == $approvedFile->approved_sha1 ) {
|
|
$approvedExists = true;
|
|
}
|
|
|
|
}
|
|
|
|
if ( !$approvedExists ) {
|
|
ApprovedRevs::unsetApprovedFileInDB( $file->getTitle(), $user );
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Hook: wgQueryPages
|
|
*
|
|
* @param array &$qp
|
|
*/
|
|
public static function onwgQueryPages( &$qp ) {
|
|
$qp['SpecialApprovedRevsPage'] = 'ApprovedRevs';
|
|
}
|
|
|
|
public static function onMpdfGetArticle( $title, &$article ) {
|
|
$revisionID = ApprovedRevs::getApprovedRevID( $title );
|
|
if ( $revisionID === null ) {
|
|
return;
|
|
}
|
|
$article = new Article( $title, $revisionID );
|
|
}
|
|
|
|
}
|