r85405 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r85404‎ | r85405 | r85406 >
Date:00:10, 5 April 2011
Author:aaron
Status:ok
Tags:
Comment:
* Renamed /forms to /business and pushed UI stuff into classes in /presentation
* Consolidated code duplication into single currentIncludeVersions() function
* More cleanup needed...
Modified paths:
  • /trunk/extensions/FlaggedRevs/FlaggedArticleView.php (modified) (history)
  • /trunk/extensions/FlaggedRevs/FlaggedRevs.php (modified) (history)
  • /trunk/extensions/FlaggedRevs/api/ApiReview.php (modified) (history)
  • /trunk/extensions/FlaggedRevs/business (added) (history)
  • /trunk/extensions/FlaggedRevs/business/PageStabilityForm.php (modified) (history)
  • /trunk/extensions/FlaggedRevs/business/RevisionReviewForm.php (replaced) (history)
  • /trunk/extensions/FlaggedRevs/forms (deleted) (history)
  • /trunk/extensions/FlaggedRevs/presentation (added) (history)
  • /trunk/extensions/FlaggedRevs/presentation/RejectConfirmationFormGUI.php (added) (history)
  • /trunk/extensions/FlaggedRevs/presentation/RevisionReviewFormGUI.php (added) (history)
  • /trunk/extensions/FlaggedRevs/specialpages/RevisionReview_body.php (modified) (history)

Diff [purge]

Index: trunk/extensions/FlaggedRevs/business/RevisionReviewForm.php
@@ -0,0 +1,638 @@
 2+<?php
 3+# (c) Aaron Schulz 2010 GPL
 4+if ( !defined( 'MEDIAWIKI' ) ) {
 5+ echo "FlaggedRevs extension\n";
 6+ exit( 1 );
 7+}
 8+/**
 9+ * Class containing revision review form business logic
 10+ * Note: edit tokens are the responsibility of caller
 11+ * Usage: (a) set ALL form params before doing anything else
 12+ * (b) call ready() when all params are set
 13+ * (c) call submit() as needed
 14+ */
 15+class RevisionReviewForm
 16+{
 17+ /* Form parameters which can be user given */
 18+ protected $page = null;
 19+ protected $approve = false;
 20+ protected $unapprove = false;
 21+ protected $reject = false;
 22+ protected $oldid = 0;
 23+ protected $refid = 0;
 24+ protected $templateParams = '';
 25+ protected $imageParams = '';
 26+ protected $fileVersion = '';
 27+ protected $validatedParams = '';
 28+ protected $comment = '';
 29+ protected $dims = array();
 30+ protected $lastChangeTime = null; # Conflict handling
 31+ protected $newLastChangeTime = null; # Conflict handling
 32+
 33+ protected $oflags = array();
 34+ protected $inputLock = 0; # Disallow bad submissions
 35+
 36+ protected $user = null;
 37+
 38+ public function __construct( User $user ) {
 39+ $this->user = $user;
 40+ foreach ( FlaggedRevs::getTags() as $tag ) {
 41+ $this->dims[$tag] = 0;
 42+ }
 43+ }
 44+
 45+ public function getUser() {
 46+ return $this->user;
 47+ }
 48+
 49+ public function getPage() {
 50+ return $this->page;
 51+ }
 52+
 53+ public function setPage( Title $value ) {
 54+ $this->trySet( $this->page, $value );
 55+ }
 56+
 57+ public function setApprove( $value ) {
 58+ $this->trySet( $this->approve, $value );
 59+ }
 60+
 61+ public function setUnapprove( $value ) {
 62+ $this->trySet( $this->unapprove, $value );
 63+ }
 64+
 65+ public function setReject( $value ) {
 66+ $this->trySet( $this->reject, $value );
 67+ }
 68+
 69+ public function setLastChangeTime( $value ) {
 70+ $this->trySet( $this->lastChangeTime, $value );
 71+ }
 72+
 73+ public function getNewLastChangeTime() {
 74+ return $this->newLastChangeTime;
 75+ }
 76+
 77+ public function getRefId() {
 78+ return $this->refid;
 79+ }
 80+
 81+ public function setRefId( $value ) {
 82+ $this->trySet( $this->refid, (int)$value );
 83+ }
 84+
 85+ public function getOldId() {
 86+ return $this->oldid;
 87+ }
 88+
 89+ public function setOldId( $value ) {
 90+ $this->trySet( $this->oldid, (int)$value );
 91+ }
 92+
 93+ public function getTemplateParams() {
 94+ return $this->templateParams;
 95+ }
 96+
 97+ public function setTemplateParams( $value ) {
 98+ $this->trySet( $this->templateParams, $value );
 99+ }
 100+
 101+ public function getFileParams() {
 102+ return $this->imageParams;
 103+ }
 104+
 105+ public function setFileParams( $value ) {
 106+ $this->trySet( $this->imageParams, $value );
 107+ }
 108+
 109+ public function getFileVersion() {
 110+ return $this->fileVersion;
 111+ }
 112+
 113+ public function setFileVersion( $value ) {
 114+ $this->trySet( $this->fileVersion, $value );
 115+ }
 116+
 117+ public function getValidatedParams() {
 118+ return $this->validatedParams;
 119+ }
 120+
 121+ public function setValidatedParams( $value ) {
 122+ $this->trySet( $this->validatedParams, $value );
 123+ }
 124+
 125+ public function getComment() {
 126+ return $this->comment;
 127+ }
 128+
 129+ public function setComment( $value ) {
 130+ $this->trySet( $this->comment, $value );
 131+ }
 132+
 133+ public function getDims() {
 134+ return $this->dims;
 135+ }
 136+
 137+ public function setDim( $tag, $value ) {
 138+ if ( !in_array( $tag, FlaggedRevs::getTags() ) ) {
 139+ throw new MWException( "FlaggedRevs tag $tag does not exist.\n" );
 140+ }
 141+ $this->trySet( $this->dims[$tag], (int)$value );
 142+ }
 143+
 144+ /**
 145+ * Set a member field to a value if the fields are unlocked
 146+ */
 147+ protected function trySet( &$field, $value ) {
 148+ if ( $this->inputLock ) {
 149+ throw new MWException( __CLASS__ . " fields cannot be set anymore.\n");
 150+ } else {
 151+ $field = $value; // still allowing input
 152+ }
 153+ }
 154+
 155+ /**
 156+ * Signal that inputs are starting
 157+ */
 158+ public function start() {
 159+ $this->inputLock = 0;
 160+ }
 161+
 162+ /**
 163+ * Signal that inputs are done and load old config
 164+ * @return mixed (true on success, error string on target failure)
 165+ */
 166+ public function ready() {
 167+ $this->inputLock = 1;
 168+ $status = $this->checkTarget();
 169+ if ( $status !== true ) {
 170+ return $status; // bad target
 171+ }
 172+ # Get the revision's current flags, if any
 173+ $this->oflags = FlaggedRevs::getRevisionTags( $this->page, $this->oldid, FR_MASTER );
 174+ # Set initial value for newLastChangeTime (if unchanged on submit)
 175+ $this->newLastChangeTime = $this->lastChangeTime;
 176+ return $status;
 177+ }
 178+
 179+ /*
 180+ * Check that the target page is valid
 181+ * @return mixed (true on success, error string on failure)
 182+ */
 183+ protected function checkTarget() {
 184+ if ( is_null( $this->page ) ) {
 185+ return 'review_page_invalid';
 186+ } elseif ( !$this->page->exists() ) {
 187+ return 'review_page_notexists';
 188+ }
 189+ $fa = FlaggedArticle::getTitleInstance( $this->page );
 190+ if ( !$fa->isReviewable() ) {
 191+ return 'review_page_unreviewable';
 192+ }
 193+ return true;
 194+ }
 195+
 196+ /*
 197+ * Verify and clean up parameters (e.g. from POST request).
 198+ * @return mixed (true on success, error string on failure)
 199+ */
 200+ protected function checkSettings() {
 201+ $status = $this->checkTarget();
 202+ if ( $status !== true ) {
 203+ return $status; // bad page target
 204+ } elseif ( !$this->oldid ) {
 205+ return 'review_no_oldid'; // bad revision target
 206+ }
 207+ # Check that an action is specified (approve, reject, de-approve)
 208+ if ( $this->getAction() === null ) {
 209+ return 'review_param_missing'; // user didn't say
 210+ }
 211+ # Fill in implicit tag data for binary flag case
 212+ $iDims = $this->implicitDims();
 213+ if ( $iDims ) {
 214+ $this->dims = $iDims; // binary flag case
 215+ }
 216+ if ( $this->getAction() === 'approve' ) {
 217+ # We must at least rate each category as 1, the minimum
 218+ if ( in_array( 0, $this->dims, true ) ) {
 219+ return 'review_too_low';
 220+ }
 221+ # Special token to discourage fiddling with template/files...
 222+ $k = self::validationKey(
 223+ $this->templateParams, $this->imageParams, $this->fileVersion, $this->oldid );
 224+ if ( $this->validatedParams !== $k ) {
 225+ return 'review_bad_key';
 226+ }
 227+ }
 228+ # Check permissions and validate
 229+ # FIXME: invalid vs denied
 230+ if ( !FlaggedRevs::userCanSetFlags( $this->user, $this->dims, $this->oflags ) ) {
 231+ return 'review_denied';
 232+ }
 233+ return true;
 234+ }
 235+
 236+ public function isAllowed() {
 237+ // Basic permission check
 238+ return ( $this->page
 239+ && $this->page->userCan( 'review' )
 240+ && $this->page->userCan( 'edit' )
 241+ );
 242+ }
 243+
 244+ // implicit dims for binary flag case
 245+ private function implicitDims() {
 246+ $tag = FlaggedRevs::binaryTagName();
 247+ if ( $tag ) {
 248+ if ( $this->approve ) {
 249+ return array( $tag => 1 );
 250+ } else if ( $this->unapprove ) {
 251+ return array( $tag => 0 );
 252+ }
 253+ }
 254+ return null;
 255+ }
 256+
 257+ /*
 258+ * What are we doing?
 259+ * @return string (approve,unapprove,reject)
 260+ */
 261+ public function getAction() {
 262+ if ( !$this->reject && !$this->unapprove && $this->approve ) {
 263+ return 'approve';
 264+ } elseif ( !$this->reject && $this->unapprove && !$this->approve ) {
 265+ return 'unapprove';
 266+ } elseif ( $this->reject && !$this->unapprove && !$this->approve ) {
 267+ return 'reject';
 268+ }
 269+ return null; // nothing valid asserted
 270+ }
 271+
 272+ /**
 273+ * Submit the form parameters for the page config to the DB.
 274+ *
 275+ * @return mixed (true on success, error string on failure)
 276+ */
 277+ public function submit() {
 278+ if ( !$this->inputLock ) {
 279+ throw new MWException( __CLASS__ . " input fields not set yet.\n");
 280+ }
 281+ $status = $this->checkSettings();
 282+ if ( $status !== true ) {
 283+ return $status; // cannot submit - broken params
 284+ }
 285+ # Double-check permissions
 286+ if ( !$this->isAllowed() ) {
 287+ return 'review_denied';
 288+ }
 289+ # We can only approve actual revisions...
 290+ if ( $this->getAction() === 'approve' ) {
 291+ $rev = Revision::newFromTitle( $this->page, $this->oldid );
 292+ # Check for archived/deleted revisions...
 293+ if ( !$rev || $rev->mDeleted ) {
 294+ return 'review_bad_oldid';
 295+ }
 296+ $oldFrev = FlaggedRevision::newFromTitle( $this->page, $this->oldid, FR_MASTER );
 297+ # Check for review conflicts...
 298+ if ( $this->lastChangeTime !== null ) { // API uses null
 299+ $lastChange = $oldFrev ? $oldFrev->getTimestamp() : '';
 300+ if ( $lastChange !== $this->lastChangeTime ) {
 301+ return 'review_conflict_oldid';
 302+ }
 303+ }
 304+ $status = $this->approveRevision( $rev, $oldFrev );
 305+ # We can only unapprove approved revisions...
 306+ } elseif ( $this->getAction() === 'unapprove' ) {
 307+ $oldFrev = FlaggedRevision::newFromTitle( $this->page, $this->oldid, FR_MASTER );
 308+ # Check for review conflicts...
 309+ if ( $this->lastChangeTime !== null ) { // API uses null
 310+ $lastChange = $oldFrev ? $oldFrev->getTimestamp() : '';
 311+ if ( $lastChange !== $this->lastChangeTime ) {
 312+ return 'review_conflict_oldid';
 313+ }
 314+ }
 315+ # Check if we can find this flagged rev...
 316+ if ( !$oldFrev ) {
 317+ return 'review_not_flagged';
 318+ }
 319+ $status = $this->unapproveRevision( $oldFrev );
 320+ } elseif ( $this->getAction() === 'reject' ) {
 321+ $newRev = Revision::newFromTitle( $this->page, $this->oldid );
 322+ $oldRev = Revision::newFromTitle( $this->page, $this->refid );
 323+ # Do not mess with archived/deleted revisions
 324+ if ( !$oldRev || $oldRev->isDeleted( Revision::DELETED_TEXT ) ) {
 325+ return 'review_bad_oldid';
 326+ } elseif ( !$newRev || $newRev->isDeleted( Revision::DELETED_TEXT ) ) {
 327+ return 'review_bad_oldid';
 328+ }
 329+ $srev = FlaggedRevision::newFromStable( $this->page, FR_MASTER );
 330+ if ( $srev && $srev->getRevId() > $oldRev->getId() ) {
 331+ return 'review_cannot_reject'; // not really a use case
 332+ }
 333+ $article = new Article( $this->page );
 334+ $new_text = $article->getUndoText( $newRev, $oldRev );
 335+ if ( $new_text === false ) {
 336+ return 'review_cannot_undo';
 337+ }
 338+ $baseRevId = $newRev->isCurrent() ? $oldRev->getId() : 0;
 339+ $article->doEdit( $new_text, $this->getComment(), 0, $baseRevId, $this->user );
 340+ }
 341+ # Watch page if set to do so
 342+ if ( $status === true ) {
 343+ if ( $this->user->getOption( 'flaggedrevswatch' )
 344+ && !$this->page->userIsWatching() )
 345+ {
 346+ $this->user->addWatch( $this->page );
 347+ }
 348+ }
 349+ return $status;
 350+ }
 351+
 352+ /**
 353+ * Adds or updates the flagged revision table for this page/id set
 354+ * @param Revision $rev The revision to be accepted
 355+ * @param FlaggedRevision $oldFrev Currently accepted version of $rev or null
 356+ * @returns true on success, array of errors on failure
 357+ */
 358+ private function approveRevision( Revision $rev, FlaggedRevision $oldFrev = null ) {
 359+ wfProfileIn( __METHOD__ );
 360+ # Revision rating flags
 361+ $flags = $this->dims;
 362+ $quality = 0; // quality tier from flags
 363+ if ( FlaggedRevs::isQuality( $flags ) ) {
 364+ $quality = FlaggedRevs::isPristine( $flags ) ? 2 : 1;
 365+ }
 366+ # Our template/file version pointers
 367+ list( $tmpVersions, $fileVersions ) = self::getIncludeVersions(
 368+ $this->templateParams, $this->imageParams
 369+ );
 370+ # If this is an image page, store corresponding file info
 371+ $fileData = array( 'name' => null, 'timestamp' => null, 'sha1' => null );
 372+ if ( $this->page->getNamespace() == NS_FILE && $this->fileVersion ) {
 373+ # Stable upload version for file pages...
 374+ $data = explode( '#', $this->fileVersion, 2 );
 375+ if ( count( $data ) == 2 ) {
 376+ $fileData['name'] = $this->page->getDBkey();
 377+ $fileData['timestamp'] = $data[0];
 378+ $fileData['sha1'] = $data[1];
 379+ }
 380+ }
 381+
 382+ # Get current stable version ID (for logging)
 383+ $oldSv = FlaggedRevision::newFromStable( $this->page, FR_MASTER );
 384+
 385+ # Is this a duplicate review?
 386+ if ( $oldFrev &&
 387+ $oldFrev->getTags() == $flags && // tags => quality
 388+ $oldFrev->getFileSha1() == $fileData['sha1'] &&
 389+ $oldFrev->getFileTimestamp() == $fileData['timestamp'] &&
 390+ $oldFrev->getTemplateVersions( FR_MASTER ) == $tmpVersions &&
 391+ $oldFrev->getFileVersions( FR_MASTER ) == $fileVersions )
 392+ {
 393+ wfProfileOut( __METHOD__ );
 394+ return true; // don't record if the same
 395+ }
 396+
 397+ # Insert the review entry...
 398+ $flaggedRevision = new FlaggedRevision( array(
 399+ 'rev_id' => $rev->getId(),
 400+ 'page_id' => $rev->getPage(),
 401+ 'user' => $this->user->getId(),
 402+ 'timestamp' => wfTimestampNow(),
 403+ 'quality' => $quality,
 404+ 'tags' => FlaggedRevision::flattenRevisionTags( $flags ),
 405+ 'img_name' => $fileData['name'],
 406+ 'img_timestamp' => $fileData['timestamp'],
 407+ 'img_sha1' => $fileData['sha1'],
 408+ 'templateVersions' => $tmpVersions,
 409+ 'fileVersions' => $fileVersions,
 410+ 'flags' => ''
 411+ ) );
 412+ $flaggedRevision->insertOn();
 413+ # Update recent changes...
 414+ $rcId = $rev->isUnpatrolled(); // int
 415+ self::updateRecentChanges( $this->page, $rev->getId(), $rcId, true );
 416+
 417+ # Update the article review log...
 418+ $oldSvId = $oldSv ? $oldSv->getRevId() : 0;
 419+ FlaggedRevsLogs::updateReviewLog( $this->page, $this->dims, $this->oflags,
 420+ $this->comment, $this->oldid, $oldSvId, true );
 421+
 422+ # Get the new stable version as of now
 423+ $sv = FlaggedRevision::determineStable( $this->page, FR_MASTER/*consistent*/ );
 424+ # Update page and tracking tables and clear cache
 425+ $changed = FlaggedRevs::stableVersionUpdates( $this->page, $sv, $oldSv );
 426+ if ( $changed ) {
 427+ FlaggedRevs::HTMLCacheUpdates( $this->page ); // purge pages that use this page
 428+ }
 429+
 430+ # Caller may want to get the change time
 431+ $this->newLastChangeTime = $flaggedRevision->getTimestamp();
 432+
 433+ wfProfileOut( __METHOD__ );
 434+ return true;
 435+ }
 436+
 437+ /**
 438+ * @param FlaggedRevision $frev
 439+ * Removes flagged revision data for this page/id set
 440+ */
 441+ private function unapproveRevision( FlaggedRevision $frev ) {
 442+ wfProfileIn( __METHOD__ );
 443+
 444+ # Get current stable version ID (for logging)
 445+ $oldSv = FlaggedRevision::newFromStable( $this->page, FR_MASTER );
 446+
 447+ $dbw = wfGetDB( DB_MASTER );
 448+ # Delete from flaggedrevs table
 449+ $dbw->delete( 'flaggedrevs',
 450+ array( 'fr_page_id' => $frev->getPage(), 'fr_rev_id' => $frev->getRevId() ) );
 451+ # Wipe versioning params
 452+ $dbw->delete( 'flaggedtemplates', array( 'ft_rev_id' => $frev->getRevId() ) );
 453+ $dbw->delete( 'flaggedimages', array( 'fi_rev_id' => $frev->getRevId() ) );
 454+ # Update recent changes
 455+ self::updateRecentChanges( $this->page, $frev->getRevId(), false, false );
 456+
 457+ # Update the article review log
 458+ $oldSvId = $oldSv ? $oldSv->getRevId() : 0;
 459+ FlaggedRevsLogs::updateReviewLog( $this->page, $this->dims, $this->oflags,
 460+ $this->comment, $this->oldid, $oldSvId, false );
 461+
 462+ # Get the new stable version as of now
 463+ $sv = FlaggedRevision::determineStable( $this->page, FR_MASTER/*consistent*/ );
 464+ # Update page and tracking tables and clear cache
 465+ $changed = FlaggedRevs::stableVersionUpdates( $this->page, $sv, $oldSv );
 466+ if ( $changed ) {
 467+ FlaggedRevs::HTMLCacheUpdates( $this->page ); // purge pages that use this page
 468+ }
 469+
 470+ # Caller may want to get the change time
 471+ $this->newLastChangeTime = '';
 472+
 473+ wfProfileOut( __METHOD__ );
 474+ return true;
 475+ }
 476+
 477+ /**
 478+ * Get a validation key from versioning metadata
 479+ * @param string $tmpP
 480+ * @param string $imgP
 481+ * @param string $imgV
 482+ * @param integer $rid rev ID
 483+ * @return string
 484+ */
 485+ public static function validationKey( $tmpP, $imgP, $imgV, $rid ) {
 486+ global $wgSecretKey, $wgProxyKey;
 487+ $key = $wgSecretKey ? $wgSecretKey : $wgProxyKey; // fall back to $wgProxyKey
 488+ $p = md5( $key . $imgP . $tmpP . $rid . $imgV );
 489+ return $p;
 490+ }
 491+
 492+ public static function updateRecentChanges(
 493+ Title $title, $revId, $rcId = false, $patrol = true
 494+ ) {
 495+ wfProfileIn( __METHOD__ );
 496+ $revId = intval( $revId );
 497+ $dbw = wfGetDB( DB_MASTER );
 498+ # Olders edits be marked as patrolled now...
 499+ $dbw->update( 'recentchanges',
 500+ array( 'rc_patrolled' => $patrol ? 1 : 0 ),
 501+ array( 'rc_cur_id' => $title->getArticleId(),
 502+ $patrol ? "rc_this_oldid <= $revId" : "rc_this_oldid = $revId" ),
 503+ __METHOD__,
 504+ // Performance
 505+ array( 'USE INDEX' => 'rc_cur_id', 'LIMIT' => 50 )
 506+ );
 507+ # New page patrol may be enabled. If so, the rc_id may be the first
 508+ # edit and not this one. If it is different, mark it too.
 509+ if ( $rcId && $rcId != $revId ) {
 510+ $dbw->update( 'recentchanges',
 511+ array( 'rc_patrolled' => 1 ),
 512+ array( 'rc_id' => $rcId,
 513+ 'rc_type' => RC_NEW ),
 514+ __METHOD__
 515+ );
 516+ }
 517+ wfProfileOut( __METHOD__ );
 518+ }
 519+
 520+ /**
 521+ * Get template and image parameters from parser output to use on forms.
 522+ * @param FlaggedArticle $article
 523+ * @param array $templateIDs (from ParserOutput/OutputPage->mTemplateIds)
 524+ * @param array $imageSHA1Keys (from ParserOutput/OutputPage->mImageTimeKeys)
 525+ * @returns array( templateParams, imageParams, fileVersion )
 526+ */
 527+ public static function getIncludeParams(
 528+ FlaggedArticle $article, array $templateIDs, array $imageSHA1Keys
 529+ ) {
 530+ $templateParams = $imageParams = $fileVersion = '';
 531+ # NS -> title -> rev ID mapping
 532+ foreach ( $templateIDs as $namespace => $t ) {
 533+ foreach ( $t as $dbKey => $revId ) {
 534+ $temptitle = Title::makeTitle( $namespace, $dbKey );
 535+ $templateParams .= $temptitle->getPrefixedDBKey() . "|" . $revId . "#";
 536+ }
 537+ }
 538+ # Image -> timestamp -> sha1 mapping
 539+ foreach ( $imageSHA1Keys as $dbKey => $timeAndSHA1 ) {
 540+ $imageParams .= $dbKey . "|" . $timeAndSHA1['time'] . "|" . $timeAndSHA1['sha1'] . "#";
 541+ }
 542+ # For image pages, note the displayed image version
 543+ if ( $article->getTitle()->getNamespace() == NS_FILE ) {
 544+ $file = $article->getDisplayedFile(); // File obj
 545+ if ( $file ) {
 546+ $fileVersion = $file->getTimestamp() . "#" . $file->getSha1();
 547+ }
 548+ }
 549+ return array( $templateParams, $imageParams, $fileVersion );
 550+ }
 551+
 552+ /**
 553+ * Get template and image versions from form value for parser output.
 554+ * @param string $templateParams
 555+ * @param string $imageParams
 556+ * @returns array( templateIds, fileSHA1Keys )
 557+ * templateIds like ParserOutput->mTemplateIds
 558+ * fileSHA1Keys like ParserOutput->mImageTimeKeys
 559+ */
 560+ public static function getIncludeVersions( $templateParams, $imageParams ) {
 561+ $templateIds = array();
 562+ $templateMap = explode( '#', trim( $templateParams ) );
 563+ foreach ( $templateMap as $template ) {
 564+ if ( !$template ) {
 565+ continue;
 566+ }
 567+ $m = explode( '|', $template, 2 );
 568+ if ( !isset( $m[0] ) || !isset( $m[1] ) || !$m[0] ) {
 569+ continue;
 570+ }
 571+ list( $prefixed_text, $rev_id ) = $m;
 572+ # Get the template title
 573+ $tmp_title = Title::newFromText( $prefixed_text ); // Normalize this to be sure...
 574+ if ( is_null( $tmp_title ) ) {
 575+ continue; // Page must be valid!
 576+ }
 577+ if ( !isset( $templateIds[$tmp_title->getNamespace()] ) ) {
 578+ $templateIds[$tmp_title->getNamespace()] = array();
 579+ }
 580+ $templateIds[$tmp_title->getNamespace()][$tmp_title->getDBkey()] = $rev_id;
 581+ }
 582+ # Our image version pointers
 583+ $fileSHA1Keys = array();
 584+ $imageMap = explode( '#', trim( $imageParams ) );
 585+ foreach ( $imageMap as $image ) {
 586+ if ( !$image ) {
 587+ continue;
 588+ }
 589+ $m = explode( '|', $image, 3 );
 590+ # Expand our parameters ... <name>#<timestamp>#<key>
 591+ if ( !isset( $m[0] ) || !isset( $m[1] ) || !isset( $m[2] ) || !$m[0] ) {
 592+ continue;
 593+ }
 594+ list( $dbkey, $time, $key ) = $m;
 595+ # Get the file title
 596+ $img_title = Title::makeTitle( NS_FILE, $dbkey ); // Normalize
 597+ if ( is_null( $img_title ) ) {
 598+ continue; // Page must be valid!
 599+ }
 600+ $fileSHA1Keys[$img_title->getDBkey()] = array();
 601+ $fileSHA1Keys[$img_title->getDBkey()]['time'] = $time ? $time : false;
 602+ $fileSHA1Keys[$img_title->getDBkey()]['sha1'] = strlen( $key ) ? $key : false;
 603+ }
 604+ return array( $templateIds, $fileSHA1Keys );
 605+ }
 606+
 607+ /**
 608+ * Get template and image versions from parsing a revision.
 609+ * @param Article $article
 610+ * @param Revision $rev
 611+ * @returns array( templateIds, fileSHA1Keys )
 612+ * templateIds like ParserOutput->mTemplateIds
 613+ * fileSHA1Keys like ParserOutput->mImageTimeKeys
 614+ */
 615+ public static function currentIncludeVersions( Article $article, Revision $rev ) {
 616+ global $wgParser, $wgOut, $wgEnableParserCache;
 617+ wfProfileIn( __METHOD__ );
 618+ $pOutput = false;
 619+ # Current version: try parser cache
 620+ if ( $rev->isCurrent() ) {
 621+ $parserCache = ParserCache::singleton();
 622+ $pOutput = $parserCache->get( $article, $wgOut->parserOptions() );
 623+ }
 624+ # Otherwise (or on cache miss), parse the rev text...
 625+ if ( !$pOutput ) {
 626+ $text = $rev->getText();
 627+ $title = $article->getTitle();
 628+ $options = FlaggedRevs::makeParserOptions();
 629+ $pOutput = $wgParser->parse(
 630+ $text, $title, $options, true, true, $rev->getId() );
 631+ # Might as well save the cache while we're at it
 632+ if ( $rev->isCurrent() && $wgEnableParserCache ) {
 633+ $parserCache->save( $pOutput, $article, $options );
 634+ }
 635+ }
 636+ wfProfileOut( __METHOD__ );
 637+ return array( $pOutput->getTemplateIds(), $pOutput->getImageTimeKeys() );
 638+ }
 639+}
Property changes on: trunk/extensions/FlaggedRevs/business/RevisionReviewForm.php
___________________________________________________________________
Added: svn:eol-style
1640 + native
Index: trunk/extensions/FlaggedRevs/business/PageStabilityForm.php
@@ -0,0 +1,639 @@
 2+<?php
 3+# (c) Aaron Schulz 2010 GPL
 4+if ( !defined( 'MEDIAWIKI' ) ) {
 5+ echo "FlaggedRevs extension\n";
 6+ exit( 1 );
 7+}
 8+/**
 9+ * Class containing stability settings form business logic
 10+ * Note: edit tokens are the responsibility of caller
 11+ * Usage: (a) set ALL form params before doing anything else
 12+ * (b) call ready() when all params are set
 13+ * (c) call preloadSettings() or submit() as needed
 14+ */
 15+abstract class PageStabilityForm
 16+{
 17+ /* Form parameters which can be user given */
 18+ protected $page = false; # Target page obj
 19+ protected $watchThis = null; # Watch checkbox
 20+ protected $reviewThis = null; # Auto-review option...
 21+ protected $reasonExtra = ''; # Custom/extra reason
 22+ protected $reasonSelection = ''; # Reason dropdown key
 23+ protected $expiryCustom = ''; # Custom expiry
 24+ protected $expirySelection = ''; # Expiry dropdown key
 25+ protected $override = -1; # Default version
 26+ protected $autoreview = ''; # Autoreview restrictions...
 27+
 28+ protected $oldConfig = array(); # Old page config
 29+ protected $inputLock = 0; # Disallow bad submissions
 30+
 31+ protected $user = null;
 32+
 33+ public function __construct( User $user ) {
 34+ $this->user = $user;
 35+ }
 36+
 37+ public function getPage() {
 38+ return $this->page;
 39+ }
 40+
 41+ public function setPage( Title $value ) {
 42+ $this->trySet( $this->page, $value );
 43+ }
 44+
 45+ public function getWatchThis() {
 46+ return $this->watchThis;
 47+ }
 48+
 49+ public function setWatchThis( $value ) {
 50+ $this->trySet( $this->watchThis, $value );
 51+ }
 52+
 53+ public function getReasonExtra() {
 54+ return $this->reasonExtra;
 55+ }
 56+
 57+ public function setReasonExtra( $value ) {
 58+ $this->trySet( $this->reasonExtra, $value );
 59+ }
 60+
 61+ public function getReasonSelection() {
 62+ return $this->reasonSelection;
 63+ }
 64+
 65+ public function setReasonSelection( $value ) {
 66+ $this->trySet( $this->reasonSelection, $value );
 67+ }
 68+
 69+ public function getExpiryCustom() {
 70+ return $this->expiryCustom;
 71+ }
 72+
 73+ public function setExpiryCustom( $value ) {
 74+ $this->trySet( $this->expiryCustom, $value );
 75+ }
 76+
 77+ public function getExpirySelection() {
 78+ return $this->expirySelection;
 79+ }
 80+
 81+ public function setExpirySelection( $value ) {
 82+ $this->trySet( $this->expirySelection, $value );
 83+ }
 84+
 85+ public function getAutoreview() {
 86+ return $this->autoreview;
 87+ }
 88+
 89+ public function setAutoreview( $value ) {
 90+ $this->trySet( $this->autoreview, $value );
 91+ }
 92+
 93+ /*
 94+ * Get the final expiry, all inputs considered
 95+ * Note: does not check if the expiration is less than wfTimestampNow()
 96+ * @return 14-char timestamp or "infinity", or false if the input was invalid
 97+ */
 98+ public function getExpiry() {
 99+ if ( $this->expirySelection == 'existing' ) {
 100+ return $this->oldConfig['expiry'];
 101+ } elseif ( $this->expirySelection == 'othertime' ) {
 102+ $value = $this->expiryCustom;
 103+ } else {
 104+ $value = $this->expirySelection;
 105+ }
 106+ if ( $value == 'infinite' || $value == 'indefinite' || $value == 'infinity' ) {
 107+ $time = Block::infinity();
 108+ } else {
 109+ $unix = strtotime( $value );
 110+ # On error returns -1 for PHP <5.1 and false for PHP >=5.1
 111+ if ( !$unix || $unix === -1 ) {
 112+ return false;
 113+ }
 114+ // FIXME: non-qualified absolute times are not in users
 115+ // specified timezone and there isn't notice about it in the ui
 116+ $time = wfTimestamp( TS_MW, $unix );
 117+ }
 118+ return $time;
 119+ }
 120+
 121+ /*
 122+ * Get the final reason, all inputs considered
 123+ * @return string
 124+ */
 125+ public function getReason() {
 126+ # Custom reason replaces dropdown
 127+ if ( $this->reasonSelection != 'other' ) {
 128+ $comment = $this->reasonSelection; // start with dropdown reason
 129+ if ( $this->reasonExtra != '' ) {
 130+ # Append custom reason
 131+ $comment .= wfMsgForContent( 'colon-separator' ) . $this->reasonExtra;
 132+ }
 133+ } else {
 134+ $comment = $this->reasonExtra; // just use custom reason
 135+ }
 136+ return $comment;
 137+ }
 138+
 139+ /**
 140+ * Set a member field to a value if the fields are unlocked
 141+ */
 142+ protected function trySet( &$field, $value ) {
 143+ if ( $this->inputLock ) {
 144+ throw new MWException( __CLASS__ . " fields cannot be set anymore.\n");
 145+ } else {
 146+ $field = $value; // still allowing input
 147+ }
 148+ }
 149+
 150+ /**
 151+ * Signal that inputs are starting
 152+ */
 153+ public function start() {
 154+ $this->inputLock = 0;
 155+ }
 156+
 157+ /**
 158+ * Signal that inputs are done and load old config
 159+ * @return mixed (true on success, error string on target failure)
 160+ */
 161+ public function ready() {
 162+ $this->inputLock = 1;
 163+ $status = $this->checkTarget();
 164+ if ( $status !== true ) {
 165+ return $status; // bad target
 166+ }
 167+ $this->loadOldConfig(); // current settings from DB
 168+ return $status;
 169+ }
 170+
 171+ /*
 172+ * Preload existing page settings (e.g. from GET request).
 173+ * @return mixed (true on success, error string on failure)
 174+ */
 175+ public function preloadSettings() {
 176+ if ( !$this->inputLock ) {
 177+ throw new MWException( __CLASS__ . " input fields not set yet.\n");
 178+ }
 179+ $status = $this->checkTarget();
 180+ if ( $status !== true ) {
 181+ return $status; // bad target
 182+ }
 183+ if ( $this->oldConfig['expiry'] == Block::infinity() ) {
 184+ $this->expirySelection = 'infinite'; // no settings set OR indefinite
 185+ } else {
 186+ $this->expirySelection = 'existing'; // settings set and NOT indefinite
 187+ }
 188+ return $this->reallyPreloadSettings(); // load the params...
 189+ }
 190+
 191+ /*
 192+ * @return mixed (true on success, error string on failure)
 193+ */
 194+ protected function reallyPreloadSettings() {
 195+ return true;
 196+ }
 197+
 198+ /*
 199+ * Verify and clean up parameters (e.g. from POST request).
 200+ * @return mixed (true on success, error string on failure)
 201+ */
 202+ protected function checkSettings() {
 203+ $status = $this->checkTarget();
 204+ if ( $status !== true ) {
 205+ return $status; // bad target
 206+ }
 207+ if ( $this->expiryCustom != '' ) {
 208+ // Custom expiry takes precedence
 209+ $this->expirySelection = 'othertime';
 210+ }
 211+ $status = $this->reallyCheckSettings(); // check other params...
 212+ return $status;
 213+ }
 214+
 215+ /*
 216+ * @return mixed (true on success, error string on failure)
 217+ */
 218+ protected function reallyCheckSettings() {
 219+ return true;
 220+ }
 221+
 222+ /*
 223+ * Check that the target page is valid
 224+ * @return mixed (true on success, error string on failure)
 225+ */
 226+ protected function checkTarget() {
 227+ if ( is_null( $this->page ) ) {
 228+ return 'stabilize_page_invalid';
 229+ } elseif ( !$this->page->exists() ) {
 230+ return 'stabilize_page_notexists';
 231+ } elseif ( !FlaggedRevs::inReviewNamespace( $this->page ) ) {
 232+ return 'stabilize_page_unreviewable';
 233+ }
 234+ return true;
 235+ }
 236+
 237+ protected function loadOldConfig() {
 238+ # Get the current page config
 239+ $this->oldConfig = FlaggedPageConfig::getPageStabilitySettings( $this->page, FR_MASTER );
 240+ }
 241+
 242+ /*
 243+ * Can the user change the settings for this page?
 244+ * Note: if the current autoreview restriction is too high for this user
 245+ * then this will return false. Useful for form selectors.
 246+ * @return bool
 247+ */
 248+ public function isAllowed() {
 249+ # Users who cannot edit or review the page cannot set this
 250+ return ( $this->page
 251+ && $this->page->userCan( 'stablesettings' )
 252+ && $this->page->userCan( 'edit' )
 253+ && $this->page->userCan( 'review' )
 254+ );
 255+ }
 256+
 257+ /**
 258+ * Submit the form parameters for the page config to the DB.
 259+ *
 260+ * @return mixed (true on success, error string on failure)
 261+ */
 262+ public function submit() {
 263+ if ( !$this->inputLock ) {
 264+ throw new MWException( __CLASS__ . " input fields not set yet.\n");
 265+ }
 266+ $status = $this->checkSettings();
 267+ if ( $status !== true ) {
 268+ return $status; // cannot submit - broken params
 269+ }
 270+ # Double-check permissions
 271+ if ( !$this->isAllowed() ) {
 272+ return 'stablize_denied';
 273+ }
 274+ # Are we are going back to site defaults?
 275+ $reset = $this->newConfigIsReset();
 276+ # Parse and cleanup the expiry time given...
 277+ $expiry = $this->getExpiry();
 278+ if ( $expiry === false ) {
 279+ return 'stabilize_expiry_invalid';
 280+ } elseif ( $expiry !== Block::infinity() && $expiry < wfTimestampNow() ) {
 281+ return 'stabilize_expiry_old';
 282+ }
 283+ # Update the DB row with the new config...
 284+ $changed = $this->updateConfigRow( $reset );
 285+ # Log if this actually changed anything...
 286+ if ( $changed ) {
 287+ # Update logs and make a null edit
 288+ $nullRev = $this->updateLogsAndHistory( $reset );
 289+ if ( $this->reviewThis ) {
 290+ # Null edit may have been auto-reviewed already
 291+ $frev = FlaggedRevision::newFromTitle(
 292+ $this->page, $nullRev->getId(), FR_MASTER );
 293+ # Check if this null edit is to be reviewed...
 294+ if ( !$frev ) {
 295+ $flags = null;
 296+ $article = new Article( $this->page );
 297+ # Review this revision of the page...
 298+ $ok = FlaggedRevs::autoReviewEdit(
 299+ $article, $this->user, $nullRev, $flags, true );
 300+ if ( $ok ) {
 301+ FlaggedRevs::markRevisionPatrolled( $nullRev ); // reviewed -> patrolled
 302+ }
 303+ }
 304+ }
 305+ # Update page and tracking tables and clear cache
 306+ FlaggedRevs::stableVersionUpdates( $this->page );
 307+ }
 308+ # Apply watchlist checkbox value (may be NULL)
 309+ $this->updateWatchlist();
 310+ # Take this opportunity to purge out expired configurations
 311+ FlaggedPageConfig::purgeExpiredConfigurations();
 312+ return true;
 313+ }
 314+
 315+ /*
 316+ * Do history & log updates:
 317+ * (a) Add a new stability log entry
 318+ * (b) Add a null edit like the log entry
 319+ * @return Revision
 320+ */
 321+ protected function updateLogsAndHistory( $reset ) {
 322+ global $wgContLang;
 323+ $article = new Article( $this->page );
 324+ $latest = $this->page->getLatestRevID( Title::GAID_FOR_UPDATE );
 325+ # Config may have changed to allow stable versions.
 326+ # Refresh tracking to account for any hidden reviewed versions...
 327+ $frev = FlaggedRevision::newFromStable( $this->page, FR_MASTER );
 328+ if ( $frev ) {
 329+ FlaggedRevs::updateStableVersion( $article, $frev, $latest );
 330+ } else {
 331+ FlaggedRevs::clearTrackingRows( $article->getId() );
 332+ }
 333+ $reason = $this->getReason();
 334+ # Insert stability log entry...
 335+ $log = new LogPage( 'stable' );
 336+ if ( $reset ) {
 337+ $log->addEntry( 'reset', $this->page, $reason );
 338+ $type = "stable-logentry-reset";
 339+ $settings = ''; // no level, expiry info
 340+ } else {
 341+ $params = $this->getLogParams();
 342+ $action = ( $this->oldConfig === FlaggedPageConfig::getDefaultVisibilitySettings() )
 343+ ? 'config' // set a custom configuration
 344+ : 'modify'; // modified an existing custom configuration
 345+ $log->addEntry( $action, $this->page, $reason,
 346+ FlaggedRevsLogs::collapseParams( $params ) );
 347+ $type = "stable-logentry-config";
 348+ // Settings message in text form (e.g. [x=a,y=b,z])
 349+ $settings = FlaggedRevsLogs::stabilitySettings( $params, true /*content*/ );
 350+ }
 351+ # Build null-edit comment...<action: reason [settings] (expiry)>
 352+ $comment = $wgContLang->ucfirst(
 353+ wfMsgForContent( $type, $this->page->getPrefixedText() ) ); // action
 354+ if ( $reason != '' ) {
 355+ $comment .= wfMsgForContent( 'colon-separator' ) . $reason; // add reason
 356+ }
 357+ if ( $settings != '' ) {
 358+ $comment .= " {$settings}"; // add settings
 359+ }
 360+ # Insert a null revision...
 361+ $dbw = wfGetDB( DB_MASTER );
 362+ $nullRev = Revision::newNullRevision( $dbw, $article->getId(), $comment, true );
 363+ $nullRev->insertOn( $dbw );
 364+ # Update page record and touch page
 365+ $article->updateRevisionOn( $dbw, $nullRev, $latest );
 366+ wfRunHooks( 'NewRevisionFromEditComplete', array( $article, $nullRev, $latest ) );
 367+ # Return null Revision object for autoreview check
 368+ return $nullRev;
 369+ }
 370+
 371+ /*
 372+ * Checks if new config is the same as the site default
 373+ * @return bool
 374+ */
 375+ protected function newConfigIsReset() {
 376+ return false;
 377+ }
 378+
 379+ /*
 380+ * Get assoc. array of log params
 381+ * @return array
 382+ */
 383+ protected function getLogParams() {
 384+ return array();
 385+ }
 386+
 387+ /*
 388+ * (a) Watch page if $watchThis is true
 389+ * (b) Unwatch if $watchThis is false
 390+ */
 391+ protected function updateWatchlist() {
 392+ # Apply watchlist checkbox value (may be NULL)
 393+ if ( $this->watchThis === true ) {
 394+ $this->user->addWatch( $this->page );
 395+ } elseif ( $this->watchThis === false ) {
 396+ $this->user->removeWatch( $this->page );
 397+ }
 398+ }
 399+
 400+ // Same JS used for expiry for either $wgFlaggedRevsProtection case
 401+ public static function addProtectionJS() {
 402+ global $wgOut;
 403+ $wgOut->addScript(
 404+ "<script type=\"text/javascript\">
 405+ function onFRChangeExpiryDropdown() {
 406+ document.getElementById('mwStabilizeExpiryOther').value = '';
 407+ }
 408+ function onFRChangeExpiryField() {
 409+ document.getElementById('mwStabilizeExpirySelection').value = 'othertime';
 410+ }
 411+ </script>"
 412+ );
 413+ }
 414+}
 415+
 416+// Assumes $wgFlaggedRevsProtection is off
 417+class PageStabilityGeneralForm extends PageStabilityForm {
 418+ public function getReviewThis() {
 419+ return $this->reviewThis;
 420+ }
 421+
 422+ public function setReviewThis( $value ) {
 423+ $this->trySet( $this->reviewThis, $value );
 424+ }
 425+
 426+ public function getOverride() {
 427+ return $this->override;
 428+ }
 429+
 430+ public function setOverride( $value ) {
 431+ $this->trySet( $this->override, $value );
 432+ }
 433+
 434+ protected function reallyPreloadSettings() {
 435+ $this->override = $this->oldConfig['override'];
 436+ $this->autoreview = $this->oldConfig['autoreview'];
 437+ $this->watchThis = $this->page->userIsWatching();
 438+ return true;
 439+ }
 440+
 441+ protected function reallyCheckSettings() {
 442+ $this->override = $this->override ? 1 : 0; // default version settings is 0 or 1
 443+ // Check autoreview restriction setting
 444+ if ( $this->autoreview != '' // restriction other than 'none'
 445+ && !in_array( $this->autoreview, FlaggedRevs::getRestrictionLevels() ) )
 446+ {
 447+ return 'stabilize_invalid_autoreview'; // invalid value
 448+ }
 449+ if ( !FlaggedRevs::userCanSetAutoreviewLevel( $this->user, $this->autoreview ) ) {
 450+ return 'stabilize_denied'; // invalid value
 451+ }
 452+ return true;
 453+ }
 454+
 455+ protected function getLogParams() {
 456+ return array(
 457+ 'override' => $this->override,
 458+ 'autoreview' => $this->autoreview,
 459+ 'expiry' => $this->getExpiry(), // TS_MW/infinity
 460+ 'precedence' => 1 // here for log hook b/c
 461+ );
 462+ }
 463+
 464+ // Return current config array
 465+ public function getOldConfig() {
 466+ if ( !$this->inputLock ) {
 467+ throw new MWException( __CLASS__ . " input fields not set yet.\n");
 468+ }
 469+ return $this->oldConfig;
 470+ }
 471+
 472+ // returns whether row changed
 473+ protected function updateConfigRow( $reset ) {
 474+ $changed = false;
 475+ $dbw = wfGetDB( DB_MASTER );
 476+ # If setting to site default values and there is a row then erase it
 477+ if ( $reset ) {
 478+ $dbw->delete( 'flaggedpage_config',
 479+ array( 'fpc_page_id' => $this->page->getArticleID() ),
 480+ __METHOD__
 481+ );
 482+ $changed = ( $dbw->affectedRows() != 0 ); // did this do anything?
 483+ # Otherwise, add/replace row if we are not just setting it to the site default
 484+ } elseif ( !$reset ) {
 485+ $dbExpiry = Block::encodeExpiry( $this->getExpiry(), $dbw );
 486+ # Get current config...
 487+ $oldRow = $dbw->selectRow( 'flaggedpage_config',
 488+ array( 'fpc_select', 'fpc_override', 'fpc_level', 'fpc_expiry' ),
 489+ array( 'fpc_page_id' => $this->page->getArticleID() ),
 490+ __METHOD__,
 491+ 'FOR UPDATE'
 492+ );
 493+ # Check if this is not the same config as the existing row (if any)
 494+ $changed = $this->configIsDifferent( $oldRow,
 495+ $this->select, $this->override, $this->autoreview, $dbExpiry );
 496+ # If the new config is different, replace the old row...
 497+ if ( $changed ) {
 498+ $dbw->replace( 'flaggedpage_config',
 499+ array( 'PRIMARY' ),
 500+ array(
 501+ 'fpc_page_id' => $this->page->getArticleID(),
 502+ 'fpc_select' => 1, // unused
 503+ 'fpc_override' => (int)$this->override,
 504+ 'fpc_level' => $this->autoreview,
 505+ 'fpc_expiry' => $dbExpiry
 506+ ),
 507+ __METHOD__
 508+ );
 509+ }
 510+ }
 511+ return $changed;
 512+ }
 513+
 514+ protected function newConfigIsReset() {
 515+ return ( $this->override == FlaggedRevs::isStableShownByDefault()
 516+ && $this->autoreview == '' );
 517+ }
 518+
 519+ // Checks if new config is different than the existing row
 520+ protected function configIsDifferent( $oldRow, $override, $autoreview, $dbExpiry ) {
 521+ if( !$oldRow ) {
 522+ return true; // no previous config
 523+ }
 524+ return ( $oldRow->fpc_override != $override // ...override changed, or...
 525+ || $oldRow->fpc_level != $autoreview // ...autoreview level changed, or...
 526+ || $oldRow->fpc_expiry != $dbExpiry // ...expiry changed
 527+ );
 528+ }
 529+}
 530+
 531+// Assumes $wgFlaggedRevsProtection is on
 532+class PageStabilityProtectForm extends PageStabilityForm {
 533+ protected function reallyPreloadSettings() {
 534+ $this->autoreview = $this->oldConfig['autoreview']; // protect level
 535+ $this->watchThis = $this->page->userIsWatching();
 536+ return true;
 537+ }
 538+
 539+ protected function reallyCheckSettings() {
 540+ # WMF temp hack...protection limit quota
 541+ global $wgFlaggedRevsProtectQuota;
 542+ if ( isset( $wgFlaggedRevsProtectQuota ) // quota exists
 543+ && $this->autoreview != '' // and we are protecting
 544+ && FlaggedPageConfig::getProtectionLevel( $this->oldConfig ) == 'none' ) // page unprotected
 545+ {
 546+ $dbw = wfGetDB( DB_MASTER );
 547+ $count = $dbw->selectField( 'flaggedpage_config', 'COUNT(*)', '', __METHOD__ );
 548+ if ( $count >= $wgFlaggedRevsProtectQuota ) {
 549+ return 'stabilize_protect_quota';
 550+ }
 551+ }
 552+ # Autoreview only when protecting currently unprotected pages
 553+ $this->reviewThis = ( FlaggedPageConfig::getProtectionLevel( $this->oldConfig ) == 'none' );
 554+ # Autoreview restriction => use stable
 555+ # No autoreview restriction => site default
 556+ $this->override = ( $this->autoreview != '' )
 557+ ? 1 // edits require review before being published
 558+ : (int)FlaggedRevs::isStableShownByDefault(); // site default
 559+ # Check that settings are a valid protection level...
 560+ $newConfig = array(
 561+ 'override' => $this->override,
 562+ 'autoreview' => $this->autoreview
 563+ );
 564+ if ( FlaggedPageConfig::getProtectionLevel( $newConfig ) == 'invalid' ) {
 565+ return 'stabilize_invalid_level'; // double-check configuration
 566+ }
 567+ # Check autoreview restriction setting
 568+ if ( !FlaggedRevs::userCanSetAutoreviewLevel( $this->user, $this->autoreview ) ) {
 569+ return 'stabilize_denied'; // invalid value
 570+ }
 571+ return true;
 572+ }
 573+
 574+ // Doesn't and shouldn't include 'precedence'; checked in FlaggedRevsLogs
 575+ protected function getLogParams() {
 576+ return array(
 577+ 'override' => $this->override, // in case of site changes
 578+ 'autoreview' => $this->autoreview,
 579+ 'expiry' => $this->getExpiry() // TS_MW/infinity
 580+ );
 581+ }
 582+
 583+ protected function updateConfigRow( $reset ) {
 584+ $changed = false;
 585+ $dbw = wfGetDB( DB_MASTER );
 586+ # If setting to site default values and there is a row then erase it
 587+ if ( $reset ) {
 588+ $dbw->delete( 'flaggedpage_config',
 589+ array( 'fpc_page_id' => $this->page->getArticleID() ),
 590+ __METHOD__
 591+ );
 592+ $changed = ( $dbw->affectedRows() != 0 ); // did this do anything?
 593+ # Otherwise, add/replace row if we are not just setting it to the site default
 594+ } elseif ( !$reset ) {
 595+ $dbExpiry = Block::encodeExpiry( $this->getExpiry(), $dbw );
 596+ # Get current config...
 597+ $oldRow = $dbw->selectRow( 'flaggedpage_config',
 598+ array( 'fpc_override', 'fpc_level', 'fpc_expiry' ),
 599+ array( 'fpc_page_id' => $this->page->getArticleID() ),
 600+ __METHOD__,
 601+ 'FOR UPDATE'
 602+ );
 603+ # Check if this is not the same config as the existing row (if any)
 604+ $changed = $this->configIsDifferent( $oldRow,
 605+ $this->override, $this->autoreview, $dbExpiry );
 606+ # If the new config is different, replace the old row...
 607+ if ( $changed ) {
 608+ $dbw->replace( 'flaggedpage_config',
 609+ array( 'PRIMARY' ),
 610+ array(
 611+ 'fpc_page_id' => $this->page->getArticleID(),
 612+ 'fpc_select' => -1, // ignored
 613+ 'fpc_override' => (int)$this->override,
 614+ 'fpc_level' => $this->autoreview,
 615+ 'fpc_expiry' => $dbExpiry
 616+ ),
 617+ __METHOD__
 618+ );
 619+ }
 620+ }
 621+ return $changed;
 622+ }
 623+
 624+ protected function newConfigIsReset() {
 625+ # For protection config, just ignore the fpc_select column
 626+ return ( $this->autoreview == '' );
 627+ }
 628+
 629+ // Checks if new config is different than the existing row
 630+ protected function configIsDifferent( $oldRow, $override, $autoreview, $dbExpiry ) {
 631+ if ( !$oldRow ) {
 632+ return true; // no previous config
 633+ }
 634+ # For protection config, just ignore the fpc_select column
 635+ return ( $oldRow->fpc_override != $override // ...override changed, or...
 636+ || $oldRow->fpc_level != $autoreview // ...autoreview level changed, or...
 637+ || $oldRow->fpc_expiry != $dbExpiry // ...expiry changed
 638+ );
 639+ }
 640+}
Property changes on: trunk/extensions/FlaggedRevs/business/PageStabilityForm.php
___________________________________________________________________
Added: svn:eol-style
1641 + native
Index: trunk/extensions/FlaggedRevs/specialpages/RevisionReview_body.php
@@ -10,7 +10,7 @@
1111 {
1212 protected $form;
1313 protected $page;
14 - protected $skin;
 14+ var $skin; // FIXME: with RevDel_RevisionList stuff
1515
1616 public function __construct() {
1717 parent::__construct( 'RevisionReview', 'review' );
@@ -49,7 +49,6 @@
5050 $form->setApprove( $wgRequest->getCheck( 'wpApprove' ) );
5151 $form->setUnapprove( $wgRequest->getCheck( 'wpUnapprove' ) );
5252 $form->setReject( $wgRequest->getCheck( 'wpReject' ) );
53 - $form->setRejectConfirm( $wgRequest->getBool( 'wpRejectConfirm' ) );
5453 # Rev ID
5554 $form->setOldId( $wgRequest->getInt( 'oldid' ) );
5655 $form->setRefId( $wgRequest->getInt( 'refid' ) );
@@ -97,35 +96,52 @@
9897 $wgOut->returnToMain( false, $this->page );
9998 return;
10099 }
101 - $status = $form->submit();
102 - // Success for either flagging or unflagging
103 - if ( $status === true ) {
104 - $wgOut->setPageTitle( wfMsgHtml( 'actioncomplete' ) );
105 - if ( $form->getAction() === 'approve' ) {
106 - $wgOut->addHTML( $this->approvalSuccessHTML( true ) );
107 - } elseif ( $form->getAction() === 'unapprove' ) {
108 - $wgOut->addHTML( $this->deapprovalSuccessHTML( true ) );
109 - } elseif ( $form->getAction() === 'reject' ) {
110 - $wgOut->redirect( $this->page->getFullUrl() );
 100+ // Use confirmation screen for reject...
 101+ if ( $form->getAction() == 'reject' && !$wgRequest->getBool( 'wpRejectConfirm' ) ) {
 102+ $rejectForm = new RejectConfirmationFormGUI( $form );
 103+ list( $html, $status ) = $rejectForm->getHtml();
 104+ // Success...
 105+ if ( $status === true ) {
 106+ $wgOut->addHtml( $html );
 107+ // Failure...
 108+ } else {
 109+ if ( $status === 'review_bad_oldid' ) {
 110+ $wgOut->showErrorPage( 'internalerror', 'revreview-revnotfound' );
 111+ } else {
 112+ $wgOut->showErrorPage( 'internalerror', $status );
 113+ }
 114+ $wgOut->returnToMain( false, $this->page );
111115 }
112 - } elseif ( $status === false ) {
113 - // Reject confirmation screen. HACKY :(
114 - return;
 116+ // Otherwise submit...
115117 } else {
116 - if ( $status === 'review_denied' ) {
117 - $wgOut->permissionRequired( 'badaccess-group0' ); // protected?
118 - } elseif ( $status === 'review_bad_key' ) {
119 - $wgOut->permissionRequired( 'badaccess-group0' ); // fiddling
120 - } elseif ( $status === 'review_bad_oldid' ) {
121 - $wgOut->showErrorPage( 'internalerror', 'revreview-revnotfound' );
122 - } elseif ( $status === 'review_not_flagged' ) {
123 - $wgOut->redirect( $this->page->getFullUrl() ); // already unflagged
124 - } elseif ( $status === 'review_too_low' ) {
125 - $wgOut->addWikiText( wfMsg( 'revreview-toolow' ) );
 118+ $status = $form->submit();
 119+ // Success...
 120+ if ( $status === true ) {
 121+ $wgOut->setPageTitle( wfMsgHtml( 'actioncomplete' ) );
 122+ if ( $form->getAction() === 'approve' ) {
 123+ $wgOut->addHTML( $this->approvalSuccessHTML( true ) );
 124+ } elseif ( $form->getAction() === 'unapprove' ) {
 125+ $wgOut->addHTML( $this->deapprovalSuccessHTML( true ) );
 126+ } elseif ( $form->getAction() === 'reject' ) {
 127+ $wgOut->redirect( $this->page->getFullUrl() );
 128+ }
 129+ // Failure...
126130 } else {
127 - $wgOut->showErrorPage( 'internalerror', $status );
 131+ if ( $status === 'review_denied' ) {
 132+ $wgOut->permissionRequired( 'badaccess-group0' ); // protected?
 133+ } elseif ( $status === 'review_bad_key' ) {
 134+ $wgOut->permissionRequired( 'badaccess-group0' ); // fiddling
 135+ } elseif ( $status === 'review_bad_oldid' ) {
 136+ $wgOut->showErrorPage( 'internalerror', 'revreview-revnotfound' );
 137+ } elseif ( $status === 'review_not_flagged' ) {
 138+ $wgOut->redirect( $this->page->getFullUrl() ); // already unflagged
 139+ } elseif ( $status === 'review_too_low' ) {
 140+ $wgOut->addWikiText( wfMsg( 'revreview-toolow' ) );
 141+ } else {
 142+ $wgOut->showErrorPage( 'internalerror', $status );
 143+ }
 144+ $wgOut->returnToMain( false, $this->page );
128145 }
129 - $wgOut->returnToMain( false, $this->page );
130146 }
131147 // No form to view (GET)
132148 } else {
Index: trunk/extensions/FlaggedRevs/api/ApiReview.php
@@ -54,33 +54,23 @@
5555 $form->setOldId( $revid );
5656 $form->setApprove( empty( $params['unapprove'] ) );
5757 $form->setUnapprove( !empty( $params['unapprove'] ) );
58 - if ( isset( $params['comment'] ) )
 58+ if ( isset( $params['comment'] ) ) {
5959 $form->setComment( $params['comment'] );
 60+ }
6061 // The flagging parameters have the form 'flag_$name'.
6162 // Extract them and put the values into $form->dims
6263 foreach ( FlaggedRevs::getTags() as $tag ) {
6364 $form->setDim( $tag, (int)$params['flag_' . $tag] );
6465 }
6566 if ( $form->getAction() === 'approve' ) {
66 - $parserOutput = null;
 67+ $article = new FlaggedArticle( $title );
6768 // Now get the template and image parameters needed
68 - // If it is the current revision, try the parser cache first
69 - $article = new FlaggedArticle( $title, $revid );
70 - if ( $rev->isCurrent() ) {
71 - $parserCache = ParserCache::singleton();
72 - $parserOutput = $parserCache->get( $article, $wgOut->parserOptions() );
73 - }
74 - if ( !$parserOutput ) {
75 - // Miss, we have to reparse the page
76 - $text = $article->getContent();
77 - $options = FlaggedRevs::makeParserOptions();
78 - $parserOutput = $wgParser->parse(
79 - $text, $title, $options, true, true, $article->getLatest() );
80 - }
81 - // Set version parameters for review submission
 69+ list( $templateIds, $fileTimeKeys ) =
 70+ RevisionReviewForm::currentIncludeVersions( $article, $rev );
 71+ // Get version parameters for review submission (flat strings)
8272 list( $templateParams, $imageParams, $fileVersion ) =
83 - RevisionReviewForm::getIncludeParams( $article,
84 - $parserOutput->getTemplateIds(), $parserOutput->getImageTimeKeys() );
 73+ RevisionReviewForm::getIncludeParams( $article, $templateIds, $fileTimeKeys );
 74+ // Set the version parameters...
8575 $form->setTemplateParams( $templateParams );
8676 $form->setFileParams( $imageParams );
8777 $form->setFileVersion( $fileVersion );
Index: trunk/extensions/FlaggedRevs/presentation/RevisionReviewFormGUI.php
@@ -0,0 +1,382 @@
 2+<?php
 3+# (c) Aaron Schulz 2011 GPL
 4+if ( !defined( 'MEDIAWIKI' ) ) {
 5+ echo "FlaggedRevs extension\n";
 6+ exit( 1 );
 7+}
 8+/**
 9+ * Main review form UI
 10+ *
 11+ * NOTE: use ONLY for diff-to-stable views and page version views
 12+ */
 13+class RevisionReviewFormGUI {
 14+ protected $user, $article, $rev;
 15+ protected $refId = 0, $topNotice = '';
 16+ protected $templateIDs = null, $imageSHA1Keys = null;
 17+
 18+ /**
 19+ * Generates a brief review form for a page
 20+ * @param User $user
 21+ * @param FlaggedArticle $article
 22+ * @param Revision $rev
 23+ */
 24+ public function __construct( User $user, FlaggedArticle $article, Revision $rev ) {
 25+ $this->user = $user;
 26+ $this->article = $article;
 27+ $this->rev = $rev;
 28+ }
 29+
 30+ /*
 31+ * If $refId > 0:
 32+ * (a) Show "reject" button
 33+ * (b) default the rating tags to those of $rev if it was flagged
 34+ * @param int $refId Left side version ID for diffs, $rev is the right rev
 35+ */
 36+ public function setDiffLeftId( $refId ) {
 37+ $this->refId = $refId;
 38+ }
 39+
 40+ /*
 41+ * Add on a notice inside the review box at the top
 42+ * @param string $topNotice Text to
 43+ */
 44+ public function setTopNotice( $topNotice ) {
 45+ $this->topNotice = (string)$topNotice;
 46+ }
 47+
 48+ /*
 49+ * Set the template/file version parameters corresponding to what the user is viewing
 50+ * @param string $topNotice Text to
 51+ */
 52+ public function setIncludeVersions( array $templateIDs, array $imageSHA1Keys ) {
 53+ $this->templateIDs = $templateIDs;
 54+ $this->imageSHA1Keys = $imageSHA1Keys;
 55+ }
 56+
 57+ /**
 58+ * Generates a brief review form for a page
 59+ * @return Array (html string, error string or true)
 60+ */
 61+ public function getHtml() {
 62+ global $wgOut, $wgLang, $wgParser, $wgEnableParserCache;
 63+ $id = $this->rev->getId();
 64+ if ( $this->rev->isDeleted( Revision::DELETED_TEXT ) ) {
 65+ return array( '', 'review_bad_oldid' ); # The revision must be valid and public
 66+ }
 67+ $article = $this->article; // convenience
 68+
 69+ $srev = $article->getStableRev();
 70+ # See if the version being displayed is flagged...
 71+ if ( $id == $article->getStable() ) {
 72+ $frev = $srev; // avoid query
 73+ } else {
 74+ $frev = FlaggedRevision::newFromTitle( $article->getTitle(), $id );
 75+ }
 76+ $oldFlags = $frev
 77+ ? $frev->getTags() // existing tags
 78+ : FlaggedRevs::quickTags( FR_CHECKED ); // basic tags
 79+ $reviewTime = $frev ? $frev->getTimestamp() : ''; // last review of rev
 80+
 81+ # If we are reviewing updates to a page, start off with the stable revision's
 82+ # flags. Otherwise, we just fill them in with the selected revision's flags.
 83+ # @TODO: do we want to carry over info for other diffs?
 84+ if ( $srev && $srev->getRevId() == $this->refId ) { // diff-to-stable
 85+ $flags = $srev->getTags();
 86+ # Check if user is allowed to renew the stable version.
 87+ # If not, then get the flags for the new revision itself.
 88+ if ( !FlaggedRevs::userCanSetFlags( $this->user, $oldFlags ) ) {
 89+ $flags = $oldFlags;
 90+ }
 91+ # Re-review button is need for template/file only review case
 92+ $reviewIncludes = ( $srev->getRevId() == $id && !$article->stableVersionIsSynced() );
 93+ } else { // views
 94+ $flags = $oldFlags;
 95+ $reviewIncludes = false; // re-review button not needed
 96+ }
 97+
 98+ # Disable form for unprivileged users
 99+ $disabled = array();
 100+ if ( !$article->getTitle()->quickUserCan( 'review' ) ||
 101+ !$article->getTitle()->quickUserCan( 'edit' ) ||
 102+ !FlaggedRevs::userCanSetFlags( $this->user, $flags ) )
 103+ {
 104+ $disabled = array( 'disabled' => 'disabled' );
 105+ }
 106+
 107+ # Begin form...
 108+ $reviewTitle = SpecialPage::getTitleFor( 'RevisionReview' );
 109+ $action = $reviewTitle->getLocalUrl( 'action=submit' );
 110+ $params = array( 'method' => 'post', 'action' => $action, 'id' => 'mw-fr-reviewform' );
 111+ $form = Xml::openElement( 'form', $params );
 112+ $form .= Xml::openElement( 'fieldset',
 113+ array( 'class' => 'flaggedrevs_reviewform noprint' ) );
 114+ # Add appropriate legend text
 115+ $legendMsg = $frev ? 'revreview-reflag' : 'revreview-flag';
 116+ $form .= Xml::openElement( 'legend', array( 'id' => 'mw-fr-reviewformlegend' ) );
 117+ $form .= "<strong>" . wfMsgHtml( $legendMsg ) . "</strong>";
 118+ $form .= Xml::closeElement( 'legend' ) . "\n";
 119+ # Show explanatory text
 120+ $form .= $this->topNotice;
 121+ # Show possible conflict warning msg
 122+ if ( $this->refId ) {
 123+ list( $u, $ts ) =
 124+ FRUserActivity::getUserReviewingDiff( $this->refId, $this->rev->getId() );
 125+ } else {
 126+ list( $u, $ts ) = FRUserActivity::getUserReviewingPage( $this->rev->getPage() );
 127+ }
 128+ if ( $u !== null && $u != $this->user->getName() ) {
 129+ $msg = $this->refId ? 'revreview-poss-conflict-c' : 'revreview-poss-conflict-p';
 130+ $form .= '<p><span class="fr-under-review">' .
 131+ wfMsgExt( $msg, 'parseinline', $u,
 132+ $wgLang->date( $ts, true ), $wgLang->time( $ts, true ) ) .
 133+ '</span></p>';
 134+ }
 135+
 136+ if ( $disabled ) {
 137+ $form .= Xml::openElement( 'div', array( 'class' => 'fr-rating-controls-disabled',
 138+ 'id' => 'fr-rating-controls-disabled' ) );
 139+ } else {
 140+ $form .= Xml::openElement( 'div', array( 'class' => 'fr-rating-controls',
 141+ 'id' => 'fr-rating-controls' ) );
 142+ }
 143+
 144+ # Add main checkboxes/selects
 145+ $form .= Xml::openElement( 'span',
 146+ array( 'id' => 'mw-fr-ratingselects', 'class' => 'fr-rating-options' ) );
 147+ $form .= self::ratingInputs( $this->user, $flags, (bool)$disabled, (bool)$frev );
 148+ $form .= Xml::closeElement( 'span' );
 149+
 150+ # Get template/file version info as needed
 151+ list( $templateIDs, $imageSHA1Keys ) = $this->getIncludeVersions();
 152+ # Convert these into flat string params
 153+ list( $templateParams, $imageParams, $fileVersion ) =
 154+ RevisionReviewForm::getIncludeParams( $article, $templateIDs, $imageSHA1Keys );
 155+
 156+ $form .= Xml::openElement( 'span', array( 'style' => 'white-space: nowrap;' ) );
 157+ # Hide comment input if needed
 158+ if ( !$disabled ) {
 159+ if ( count( FlaggedRevs::getTags() ) > 1 ) {
 160+ $form .= "<br />"; // Don't put too much on one line
 161+ }
 162+ $form .= "<span id='mw-fr-commentbox' style='clear:both'>" .
 163+ Xml::inputLabel( wfMsg( 'revreview-log' ), 'wpReason', 'wpReason', 40, '',
 164+ array( 'class' => 'fr-comment-box' ) ) . "&#160;&#160;&#160;</span>";
 165+ }
 166+ # Determine if there will be reject button
 167+ $rejectId = 0;
 168+ if ( $this->refId == $article->getStable() && $id != $this->refId ) {
 169+ $rejectId = $this->refId; // left rev must be stable and right one newer
 170+ }
 171+ # Add the submit buttons
 172+ $form .= self::submitButtons( $rejectId, $frev, (bool)$disabled, $reviewIncludes );
 173+ # Show stability log if there is anything interesting...
 174+ if ( $article->isPageLocked() ) {
 175+ $form .= ' ' . FlaggedRevsXML::logToggle( 'revreview-log-toggle-show' );
 176+ }
 177+ $form .= Xml::closeElement( 'span' );
 178+ # ..add the actual stability log body here
 179+ if ( $article->isPageLocked() ) {
 180+ $form .= FlaggedRevsXML::stabilityLogExcerpt( $article );
 181+ }
 182+ $form .= Xml::closeElement( 'div' ) . "\n";
 183+
 184+ # Hidden params
 185+ $form .= Html::hidden( 'title', $reviewTitle->getPrefixedText() ) . "\n";
 186+ $form .= Html::hidden( 'target', $article->getTitle()->getPrefixedDBKey() ) . "\n";
 187+ $form .= Html::hidden( 'refid', $this->refId ) . "\n";
 188+ $form .= Html::hidden( 'oldid', $id ) . "\n";
 189+ $form .= Html::hidden( 'action', 'submit' ) . "\n";
 190+ $form .= Html::hidden( 'wpEditToken', $this->user->editToken() ) . "\n";
 191+ $form .= Html::hidden( 'changetime', $reviewTime,
 192+ array( 'id' => 'mw-fr-input-changetime' ) ); // id for JS
 193+ # Add review parameters
 194+ $form .= Html::hidden( 'templateParams', $templateParams ) . "\n";
 195+ $form .= Html::hidden( 'imageParams', $imageParams ) . "\n";
 196+ $form .= Html::hidden( 'fileVersion', $fileVersion ) . "\n";
 197+ # Special token to discourage fiddling...
 198+ $checkCode = RevisionReviewForm::validationKey(
 199+ $templateParams, $imageParams, $fileVersion, $id
 200+ );
 201+ $form .= Html::hidden( 'validatedParams', $checkCode ) . "\n";
 202+
 203+ $form .= Xml::closeElement( 'fieldset' );
 204+ $form .= Xml::closeElement( 'form' );
 205+
 206+ return array( $form, true /* ok */ );
 207+ }
 208+
 209+ /**
 210+ * @param User $user
 211+ * @param array $flags, selected flags
 212+ * @param bool $disabled, form disabled
 213+ * @param bool $reviewed, rev already reviewed
 214+ * @returns string
 215+ * Generates a main tag inputs (checkboxes/radios/selects) for review form
 216+ */
 217+ protected static function ratingInputs( $user, $flags, $disabled, $reviewed ) {
 218+ # Get all available tags for this page/user
 219+ list( $labels, $minLevels ) = self::ratingFormTags( $user, $flags );
 220+ if ( $labels === false ) {
 221+ $disabled = true; // a tag is unsettable
 222+ }
 223+ # If there are no tags, make one checkbox to approve/unapprove
 224+ if ( FlaggedRevs::binaryFlagging() ) {
 225+ return '';
 226+ }
 227+ $items = array();
 228+ # Build rating form...
 229+ if ( $disabled ) {
 230+ // Display the value for each tag as text
 231+ foreach ( FlaggedRevs::getTags() as $quality ) {
 232+ $selected = isset( $flags[$quality] ) ? $flags[$quality] : 0;
 233+ $items[] = FlaggedRevs::getTagMsg( $quality ) . ": " .
 234+ FlaggedRevs::getTagValueMsg( $quality, $selected );
 235+ }
 236+ } else {
 237+ $size = count( $labels, 1 ) - count( $labels );
 238+ foreach ( $labels as $quality => $levels ) {
 239+ $item = '';
 240+ $numLevels = count( $levels );
 241+ $minLevel = $minLevels[$quality];
 242+ # Determine the level selected by default
 243+ if ( !empty( $flags[$quality] ) && isset( $levels[$flags[$quality]] ) ) {
 244+ $selected = $flags[$quality]; // valid non-zero value
 245+ } else {
 246+ $selected = $minLevel;
 247+ }
 248+ # Show label as needed
 249+ if ( !FlaggedRevs::binaryFlagging() ) {
 250+ $item .= Xml::tags( 'label', array( 'for' => "wp$quality" ),
 251+ FlaggedRevs::getTagMsg( $quality ) ) . ":\n";
 252+ }
 253+ # If the sum of qualities of all flags is above 6, use drop down boxes.
 254+ # 6 is an arbitrary value choosen according to screen space and usability.
 255+ if ( $size > 6 ) {
 256+ $attribs = array( 'name' => "wp$quality", 'id' => "wp$quality",
 257+ 'onchange' => "FlaggedRevsReview.updateRatingForm()" );
 258+ $item .= Xml::openElement( 'select', $attribs ) . "\n";
 259+ foreach ( $levels as $i => $name ) {
 260+ $optionClass = array( 'class' => "fr-rating-option-$i" );
 261+ $item .= Xml::option( FlaggedRevs::getTagMsg( $name ), $i,
 262+ ( $i == $selected ), $optionClass ) . "\n";
 263+ }
 264+ $item .= Xml::closeElement( 'select' ) . "\n";
 265+ # If there are more than two levels, current user gets radio buttons
 266+ } elseif ( $numLevels > 2 ) {
 267+ foreach ( $levels as $i => $name ) {
 268+ $attribs = array( 'class' => "fr-rating-option-$i",
 269+ 'onchange' => "FlaggedRevsReview.updateRatingForm()" );
 270+ $item .= Xml::radioLabel( FlaggedRevs::getTagMsg( $name ), "wp$quality",
 271+ $i, "wp$quality" . $i, ( $i == $selected ), $attribs ) . "\n";
 272+ }
 273+ # Otherwise make checkboxes (two levels available for current user)
 274+ } else if ( $numLevels == 2 ) {
 275+ $i = $minLevel;
 276+ $attribs = array( 'class' => "fr-rating-option-$i",
 277+ 'onchange' => "FlaggedRevsReview.updateRatingForm()" );
 278+ $attribs = $attribs + array( 'value' => $i );
 279+ $item .= Xml::checkLabel( wfMsg( 'revreview-' . $levels[$i] ),
 280+ "wp$quality", "wp$quality", ( $selected == $i ), $attribs ) . "\n";
 281+ }
 282+ $items[] = $item;
 283+ }
 284+ }
 285+ return implode( '&#160;&#160;&#160;', $items );
 286+ }
 287+
 288+ protected static function ratingFormTags( $user, $selected ) {
 289+ $labels = array();
 290+ $minLevels = array();
 291+ # Build up all levels available to user
 292+ foreach ( FlaggedRevs::getDimensions() as $tag => $levels ) {
 293+ if ( isset( $selected[$tag] ) &&
 294+ !FlaggedRevs::userCanSetTag( $user, $tag, $selected[$tag] ) )
 295+ {
 296+ return array( false, false ); // form will have to be disabled
 297+ }
 298+ $labels[$tag] = array(); // applicable tag levels
 299+ $minLevels[$tag] = false; // first non-zero level number
 300+ foreach ( $levels as $i => $msg ) {
 301+ # Some levels may be restricted or not applicable...
 302+ if ( !FlaggedRevs::userCanSetTag( $user, $tag, $i ) ) {
 303+ continue; // skip this level
 304+ } else if ( $i > 0 && !$minLevels[$tag] ) {
 305+ $minLevels[$tag] = $i; // first non-zero level number
 306+ }
 307+ $labels[$tag][$i] = $msg; // set label
 308+ }
 309+ if ( !$minLevels[$tag] ) {
 310+ return array( false, false ); // form will have to be disabled
 311+ }
 312+ }
 313+ return array( $labels, $minLevels );
 314+ }
 315+
 316+ /**
 317+ * Generates review form submit buttons
 318+ * @param int $rejectId left rev ID for "reject" on diffs
 319+ * @param FlaggedRevision $frev, the flagged revision, if any
 320+ * @param bool $disabled, is the form disabled?
 321+ * @param bool $reviewIncludes, force the review button to be usable?
 322+ * @returns string
 323+ */
 324+ protected static function submitButtons(
 325+ $rejectId, $frev, $disabled, $reviewIncludes = false
 326+ ) {
 327+ $disAttrib = array( 'disabled' => 'disabled' );
 328+ # ACCEPT BUTTON: accept a revision
 329+ # We may want to re-review to change:
 330+ # (a) notes (b) tags (c) pending template/file changes
 331+ if ( FlaggedRevs::binaryFlagging() ) { // just the buttons
 332+ $applicable = ( !$frev || $reviewIncludes ); // no tags/notes
 333+ $needsChange = false; // no state change possible
 334+ } else { // buttons + ratings
 335+ $applicable = true; // tags might change
 336+ $needsChange = ( $frev && !$reviewIncludes );
 337+ }
 338+ $s = Xml::submitButton( wfMsgHtml( 'revreview-submit-review' ),
 339+ array(
 340+ 'name' => 'wpApprove',
 341+ 'id' => 'mw-fr-submit-accept',
 342+ 'accesskey' => wfMsg( 'revreview-ak-review' ),
 343+ 'title' => wfMsg( 'revreview-tt-flag' ) . ' [' .
 344+ wfMsg( 'revreview-ak-review' ) . ']'
 345+ ) + ( ( $disabled || !$applicable ) ? $disAttrib : array() )
 346+ );
 347+ # REJECT BUTTON: revert from a pending revision to the stable
 348+ if ( $rejectId ) {
 349+ $s .= ' ';
 350+ $s .= Xml::submitButton( wfMsgHtml( 'revreview-submit-reject' ),
 351+ array(
 352+ 'name' => 'wpReject',
 353+ 'id' => 'mw-fr-submit-reject',
 354+ 'title' => wfMsg( 'revreview-tt-reject' ),
 355+ ) + ( $disabled ? $disAttrib : array() )
 356+ );
 357+ }
 358+ # UNACCEPT BUTTON: revoke a revisions acceptance
 359+ # Hide if revision is not flagged
 360+ $s .= ' ';
 361+ $s .= Xml::submitButton( wfMsgHtml( 'revreview-submit-unreview' ),
 362+ array(
 363+ 'name' => 'wpUnapprove',
 364+ 'id' => 'mw-fr-submit-unaccept',
 365+ 'title' => wfMsg( 'revreview-tt-unflag' ),
 366+ 'style' => $frev ? '' : 'display:none'
 367+ ) + ( $disabled ? $disAttrib : array() )
 368+ );
 369+ // Disable buttons unless state changes in some cases (non-JS compatible)
 370+ $s .= "<script type=\"text/javascript\">
 371+ var jsReviewNeedsChange = " . (int)$needsChange . "</script>";
 372+ return $s;
 373+ }
 374+
 375+ protected function getIncludeVersions() {
 376+ # Do we need to get inclusion IDs from parser output?
 377+ if ( $this->templateIDs === null || $this->imageSHA1Keys === null ) {
 378+ list( $this->templateIDs, $this->imageSHA1Keys ) =
 379+ RevisionReviewForm::currentIncludeVersions( $this->article, $this->rev );
 380+ }
 381+ return array( $this->templateIDs, $this->imageSHA1Keys );
 382+ }
 383+}
Property changes on: trunk/extensions/FlaggedRevs/presentation/RevisionReviewFormGUI.php
___________________________________________________________________
Added: svn:eol-style
1384 + native
Index: trunk/extensions/FlaggedRevs/presentation/RejectConfirmationFormGUI.php
@@ -0,0 +1,150 @@
 2+<?php
 3+# (c) Aaron Schulz 2011 GPL
 4+if ( !defined( 'MEDIAWIKI' ) ) {
 5+ echo "FlaggedRevs extension\n";
 6+ exit( 1 );
 7+}
 8+/**
 9+ * Reject confirmation review form UI
 10+ */
 11+class RejectConfirmationFormGUI {
 12+ protected $form, $oldRev, $newRev;
 13+
 14+ public function __construct( RevisionReviewForm $form ) {
 15+ $this->form = $form;
 16+ $this->newRev = Revision::newFromTitle( $form->getPage(), $form->getOldId() );
 17+ $this->oldRev = Revision::newFromTitle( $form->getPage(), $form->getRefId() );
 18+ }
 19+
 20+ /**
 21+ * Output the "are you sure you want to reject this" form
 22+ *
 23+ * A bit hacky, but we don't have a way to pass more complicated
 24+ * UI things back up, since RevisionReview expects either true
 25+ * or a string message key
 26+ * @return Array (html string, error string or true)
 27+ */
 28+ public function getHtml() {
 29+ global $wgLang, $wgContLang;
 30+ $oldRev = $this->oldRev; // convenience
 31+ $newRev = $this->newRev; // convenience
 32+ # Do not mess with archived/deleted revisions
 33+ if ( !$oldRev || $newRev->isDeleted( Revision::DELETED_TEXT ) ) {
 34+ return array( '', 'review_bad_oldid' );
 35+ } elseif ( !$newRev || $newRev->isDeleted( Revision::DELETED_TEXT ) ) {
 36+ return array( '', 'review_bad_oldid' );
 37+ }
 38+
 39+ $form = '<div class="plainlinks">';
 40+
 41+ $dbr = wfGetDB( DB_SLAVE );
 42+ $res = $dbr->select( 'revision',
 43+ Revision::selectFields(),
 44+ array(
 45+ 'rev_page' => $oldRev->getPage(),
 46+ 'rev_id > ' . $dbr->addQuotes( $oldRev->getId() ),
 47+ 'rev_id <= ' . $dbr->addQuotes( $newRev->getId() )
 48+ ),
 49+ __METHOD__,
 50+ array( 'LIMIT' => 251 ) // sanity check
 51+ );
 52+ if ( !$dbr->numRows( $res ) ) {
 53+ return array( '', 'review_bad_oldid' );
 54+ } elseif ( $dbr->numRows( $res ) > 250 ) {
 55+ return array( '', 'review_reject_excessive' );
 56+ }
 57+
 58+ $rejectIds = $rejectAuthors = array();
 59+ $contribs = SpecialPage::getTitleFor( 'Contributions' )->getPrefixedText();
 60+ foreach ( $res as $row ) {
 61+ $rev = new Revision( $row );
 62+ $rejectIds[] = $rev->getId();
 63+ $rejectAuthors[] = $rev->isDeleted( Revision::DELETED_USER )
 64+ ? wfMsg( 'rev-deleted-user' )
 65+ : "[[{$contribs}/{$rev->getUserText()}|{$rev->getUserText()}]]";
 66+ }
 67+ $rejectAuthors = array_values( array_unique( $rejectAuthors ) );
 68+
 69+ // List of revisions being undone...
 70+ $form .= wfMsgExt( 'revreview-reject-text-list', 'parseinline',
 71+ $wgLang->formatNum( count( $rejectIds ) ) );
 72+ $form .= '<ul>';
 73+ // FIXME: we need a generic revision list class...this is bullshit
 74+ $spRevDelete = SpecialPage::getPage( 'RevisionReview' );
 75+ $spRevDelete->skin = $this->form->getUser()->getSkin(); // XXX
 76+ $list = new RevDel_RevisionList( $spRevDelete, $oldRev->getTitle(), $rejectIds );
 77+ for ( $list->reset(); $list->current(); $list->next() ) {
 78+ $item = $list->current();
 79+ if ( $item->canView() ) {
 80+ $form .= $item->getHTML();
 81+ }
 82+ }
 83+ $form .= '</ul>';
 84+ if ( $newRev->isCurrent() ) {
 85+ // Revision this will revert to (when reverting the top X revs)...
 86+ $form .= wfMsgExt( 'revreview-reject-text-revto', 'parseinline',
 87+ $oldRev->getTitle()->getPrefixedDBKey(), $oldRev->getId(),
 88+ $wgLang->timeanddate( $oldRev->getTimestamp(), true )
 89+ );
 90+ }
 91+
 92+ $comment = $this->form->getComment(); // convenience
 93+ // Determine the default edit summary...
 94+ $oldRevAuthor = $oldRev->isDeleted( Revision::DELETED_USER )
 95+ ? wfMsg( 'rev-deleted-user' )
 96+ : $oldRev->getUserText();
 97+ // NOTE: *-cur msg wording not safe for (unlikely) edit auto-merge
 98+ $msg = $newRev->isCurrent()
 99+ ? 'revreview-reject-summary-cur'
 100+ : 'revreview-reject-summary-old';
 101+ $defaultSummary = wfMsgExt( $msg, array( 'parsemag', 'content' ),
 102+ $wgContLang->formatNum( count( $rejectIds ) ),
 103+ $wgContLang->listToText( $rejectAuthors ),
 104+ $oldRev->getId(),
 105+ $oldRevAuthor );
 106+ // If the message is too big, then fallback to the shorter one
 107+ $colonSeparator = wfMsgForContent( 'colon-separator' );
 108+ $maxLen = 255 - count( $colonSeparator ) - count( $comment );
 109+ if ( strlen( $defaultSummary ) > $maxLen ) {
 110+ $msg = $newRev->isCurrent()
 111+ ? 'revreview-reject-summary-cur-short'
 112+ : 'revreview-reject-summary-old-short';
 113+ $defaultSummary = wfMsgExt( $msg, array( 'parsemag', 'content' ),
 114+ $wgContLang->formatNum( count( $rejectIds ) ),
 115+ $oldRev->getId(),
 116+ $oldRevAuthor );
 117+ }
 118+ // Append any review comment...
 119+ if ( $comment != '' ) {
 120+ if ( $defaultSummary != '' ) {
 121+ $defaultSummary .= $colonSeparator;
 122+ }
 123+ $defaultSummary .= $comment;
 124+ }
 125+
 126+ $form .= '</div>';
 127+
 128+ $skin = $this->form->getUser()->getSkin();
 129+ $reviewTitle = SpecialPage::getTitleFor( 'RevisionReview' );
 130+ $form .= Xml::openElement( 'form',
 131+ array( 'method' => 'POST', 'action' => $reviewTitle->getFullUrl() ) );
 132+ $form .= Html::hidden( 'action', 'reject' );
 133+ $form .= Html::hidden( 'wpReject', 1 );
 134+ $form .= Html::hidden( 'wpRejectConfirm', 1 );
 135+ $form .= Html::hidden( 'oldid', $this->form->getOldId() );
 136+ $form .= Html::hidden( 'refid', $this->form->getRefId() );
 137+ $form .= Html::hidden( 'target', $oldRev->getTitle()->getPrefixedDBKey() );
 138+ $form .= Html::hidden( 'wpEditToken', $this->form->getUser()->editToken() );
 139+ $form .= Html::hidden( 'changetime', $newRev->getTimestamp() );
 140+ $form .= Xml::inputLabel( wfMsg( 'revreview-reject-summary' ), 'wpReason',
 141+ 'wpReason', 120, $defaultSummary ) . "<br />";
 142+ $form .= Html::input( 'wpSubmit', wfMsg( 'revreview-reject-confirm' ), 'submit' );
 143+ $form .= ' ';
 144+ $form .= $skin->link( $this->form->getPage(), wfMsg( 'revreview-reject-cancel' ),
 145+ array( 'onClick' => 'history.back()' ),
 146+ array( 'oldid' => $this->form->getRefId(), 'diff' => $this->form->getOldId() ) );
 147+ $form .= Xml::closeElement( 'form' );
 148+
 149+ return array( $form, true );
 150+ }
 151+}
Property changes on: trunk/extensions/FlaggedRevs/presentation/RejectConfirmationFormGUI.php
___________________________________________________________________
Added: svn:eol-style
1152 + native
Index: trunk/extensions/FlaggedRevs/FlaggedArticleView.php
@@ -1070,24 +1070,23 @@
10711071 }
10721072 # Build the review form as needed
10731073 if ( $rev && ( !$this->diffRevs || $this->isReviewableDiff ) ) {
 1074+ $form = new RevisionReviewFormGUI( $wgUser, $this->article, $rev );
 1075+ # Tag default and "reject" button depend on context
 1076+ $form->setDiffLeftId( $this->diffRevs['old'] );
 1077+ # Review notice box goes in top of form
 1078+ $form->setTopNotice( $this->diffNoticeBox );
10741079 # $wgOut may not already have the inclusion IDs, such as for diffonly=1.
10751080 # RevisionReviewForm will fetch them as needed however.
1076 - $templateIDs = $fileSHA1Keys = null;
10771081 if ( $wgOut->getRevisionId() == $rev->getId() ) {
1078 - $templateIDs = $wgOut->getTemplateIds();
1079 - $fileSHA1Keys = $wgOut->getImageTimeKeys();
 1082+ $form->setIncludeVersions( $wgOut->getTemplateIds(), $wgOut->getImageTimeKeys() );
10801083 }
1081 - # Review notice box goes in top of form
1082 - $form = RevisionReviewForm::buildQuickReview(
1083 - $wgUser, $this->article, $rev, $this->diffRevs['old'],
1084 - $this->diffNoticeBox, $templateIDs, $fileSHA1Keys
1085 - );
 1084+ list( $html, $status ) = $form->getHtml();
10861085 # Diff action: place the form at the top of the page
10871086 if ( $this->diffRevs ) {
1088 - $wgOut->prependHTML( $form );
 1087+ $wgOut->prependHTML( $html );
10891088 # View action: place the form at the bottom of the page
10901089 } else {
1091 - $data .= $form;
 1090+ $data .= $html;
10921091 }
10931092 }
10941093 return true;
Index: trunk/extensions/FlaggedRevs/FlaggedRevs.php
@@ -249,7 +249,7 @@
250250 $wgAutoloadClasses['FlaggedRevsApiHooks'] = $dir . 'api/FlaggedRevsApi.hooks.php';
251251 $wgAutoloadClasses['FlaggedRevsUpdaterHooks'] = $dir . 'updater/FlaggedRevsUpdater.hooks.php';
252252
253 -# Object classes...
 253+# Data object classes...
254254 $wgAutoloadClasses['FRExtraCacheUpdate'] = $dir . 'FRExtraCacheUpdate.php';
255255 $wgAutoloadClasses['FRExtraCacheUpdateJob'] = $dir . 'FRExtraCacheUpdate.php';
256256 $wgAutoloadClasses['FRSquidUpdate'] = $dir . 'FRExtraCacheUpdate.php';
@@ -258,11 +258,17 @@
259259 $wgAutoloadClasses['FlaggedArticleView'] = $dir . 'FlaggedArticleView.php';
260260 $wgAutoloadClasses['FlaggedArticle'] = $dir . 'FlaggedArticle.php';
261261 $wgAutoloadClasses['FlaggedRevision'] = $dir . 'FlaggedRevision.php';
262 -$wgAutoloadClasses['RevisionReviewForm'] = $dir . 'forms/RevisionReviewForm.php';
263 -$wgAutoloadClasses['PageStabilityForm'] = $dir . 'forms/PageStabilityForm.php';
264 -$wgAutoloadClasses['PageStabilityGeneralForm'] = $dir . 'forms/PageStabilityForm.php';
265 -$wgAutoloadClasses['PageStabilityProtectForm'] = $dir . 'forms/PageStabilityForm.php';
266262
 263+# Business object classes
 264+$wgAutoloadClasses['RevisionReviewForm'] = $dir . 'business/RevisionReviewForm.php';
 265+$wgAutoloadClasses['PageStabilityForm'] = $dir . 'business/PageStabilityForm.php';
 266+$wgAutoloadClasses['PageStabilityGeneralForm'] = $dir . 'business/PageStabilityForm.php';
 267+$wgAutoloadClasses['PageStabilityProtectForm'] = $dir . 'business/PageStabilityForm.php';
 268+
 269+# Presentation classes...
 270+$wgAutoloadClasses['RevisionReviewFormGUI'] = $dir . 'presentation/RevisionReviewFormGUI.php';
 271+$wgAutoloadClasses['RejectConfirmationFormGUI'] = $dir . 'presentation/RejectConfirmationFormGUI.php';
 272+
267273 # Load main i18n file and special page alias file
268274 $wgExtensionMessagesFiles['FlaggedRevs'] = $langDir . 'FlaggedRevs.i18n.php';
269275 $wgExtensionAliasesFiles['FlaggedRevs'] = $langDir . 'FlaggedRevs.alias.php';

Status & tagging log