r86111 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r86110‎ | r86111 | r86112 >
Date:09:56, 15 April 2011
Author:aaron
Status:ok
Tags:
Comment:
* Moved access layer stuff to /dataclasses
* Moved /tests out of /maintenance
Modified paths:
  • /trunk/extensions/FlaggedRevs/FRDependencyUpdate.php (deleted) (history)
  • /trunk/extensions/FlaggedRevs/FRExtraCacheUpdate.php (deleted) (history)
  • /trunk/extensions/FlaggedRevs/FRInclusionManager.php (deleted) (history)
  • /trunk/extensions/FlaggedRevs/FRUserActivity.php (deleted) (history)
  • /trunk/extensions/FlaggedRevs/FRUserCounters.php (deleted) (history)
  • /trunk/extensions/FlaggedRevs/FlaggedPage.php (deleted) (history)
  • /trunk/extensions/FlaggedRevs/FlaggedPageConfig.php (deleted) (history)
  • /trunk/extensions/FlaggedRevs/FlaggedRevision.php (deleted) (history)
  • /trunk/extensions/FlaggedRevs/FlaggedRevs.class.php (deleted) (history)
  • /trunk/extensions/FlaggedRevs/FlaggedRevs.hooks.php (modified) (history)
  • /trunk/extensions/FlaggedRevs/FlaggedRevs.php (modified) (history)
  • /trunk/extensions/FlaggedRevs/FlaggedRevsLog.php (deleted) (history)
  • /trunk/extensions/FlaggedRevs/dataclasses (added) (history)
  • /trunk/extensions/FlaggedRevs/dataclasses/FRDependencyUpdate.php (added) (history)
  • /trunk/extensions/FlaggedRevs/dataclasses/FRExtraCacheUpdate.php (added) (history)
  • /trunk/extensions/FlaggedRevs/dataclasses/FRInclusionManager.php (added) (history)
  • /trunk/extensions/FlaggedRevs/dataclasses/FRUserActivity.php (added) (history)
  • /trunk/extensions/FlaggedRevs/dataclasses/FRUserCounters.php (added) (history)
  • /trunk/extensions/FlaggedRevs/dataclasses/FlaggedPage.php (added) (history)
  • /trunk/extensions/FlaggedRevs/dataclasses/FlaggedPageConfig.php (added) (history)
  • /trunk/extensions/FlaggedRevs/dataclasses/FlaggedRevision.php (added) (history)
  • /trunk/extensions/FlaggedRevs/dataclasses/FlaggedRevs.class.php (added) (history)
  • /trunk/extensions/FlaggedRevs/dataclasses/FlaggedRevsLog.php (added) (history)
  • /trunk/extensions/FlaggedRevs/maintenance/tests (deleted) (history)
  • /trunk/extensions/FlaggedRevs/tests (added) (history)

Diff [purge]

Index: trunk/extensions/FlaggedRevs/FRUserCounters.php
@@ -1,170 +0,0 @@
2 -<?php
3 -/**
4 - * Class containing utility functions for per-user stats
5 - */
6 -class FRUserCounters {
7 - /**
8 - * Get params for a user
9 - * @param int $uid
10 - * @param int $flags FR_MASTER, FR_FOR_UPDATE
11 - * @param string $dBName, optional wiki name
12 - * @return array
13 - */
14 - public static function getUserParams( $uid, $flags = 0, $dBName = false ) {
15 - $p = array();
16 - $row = self::fetchParamsRow( $uid, $flags, $dBName );
17 - if ( $row ) {
18 - $p = self::expandParams( $row->frp_user_params );
19 - }
20 - self::setUnitializedFields( $p );
21 - return $p;
22 - }
23 -
24 - /**
25 - * Initializes unset param fields to their starting values
26 - * @param &array $p
27 - */
28 - protected static function setUnitializedFields( array &$p ) {
29 - if ( !isset( $p['uniqueContentPages'] ) ) {
30 - $p['uniqueContentPages'] = array();
31 - }
32 - if ( !isset( $p['totalContentEdits'] ) ) {
33 - $p['totalContentEdits'] = 0;
34 - }
35 - if ( !isset( $p['editComments'] ) ) {
36 - $p['editComments'] = 0;
37 - }
38 - if ( !isset( $p['revertedEdits'] ) ) {
39 - $p['revertedEdits'] = 0;
40 - }
41 - }
42 -
43 - /**
44 - * Get the params row for a user
45 - * @param int $uid
46 - * @param int $flags FR_MASTER, FR_FOR_UPDATE
47 - * @param string $dBName, optional wiki name
48 - * @return mixed (false or Row)
49 - */
50 - protected static function fetchParamsRow( $uid, $flags = 0, $dBName = false ) {
51 - $options = array();
52 - if ( $flags & FR_MASTER || $flags & FR_FOR_UPDATE ) {
53 - $db = wfGetDB( DB_MASTER, array(), $dBName );
54 - if ( $flags & FR_FOR_UPDATE ) $options[] = 'FOR UPDATE';
55 - } else {
56 - $db = wfGetDB( DB_SLAVE, array(), $dBName );
57 - }
58 - return $db->selectRow( 'flaggedrevs_promote',
59 - 'frp_user_params',
60 - array( 'frp_user_id' => $uid ),
61 - __METHOD__,
62 - $options
63 - );
64 - }
65 -
66 - /**
67 - * Save params for a user
68 - * @param int $uid
69 - * @param array $params
70 - * @param string $dBName, optional wiki name
71 - * @return bool success
72 - */
73 - public static function saveUserParams( $uid, array $params, $dBName = false ) {
74 - $dbw = wfGetDB( DB_MASTER, array(), $dBName );
75 - $dbw->replace( 'flaggedrevs_promote',
76 - array( 'frp_user_id' ),
77 - array( 'frp_user_id' => $uid,
78 - 'frp_user_params' => self::flattenParams( $params ) ),
79 - __METHOD__
80 - );
81 - return ( $dbw->affectedRows() > 0 );
82 - }
83 -
84 - /**
85 - * Flatten params for a user for DB storage
86 - * Note: param values must be integers
87 - * @param array $params
88 - * @return string
89 - */
90 - protected static function flattenParams( array $params ) {
91 - $flatRows = array();
92 - foreach ( $params as $key => $value ) {
93 - if ( strpos( $key, '=' ) !== false || strpos( $key, "\n" ) !== false ) {
94 - throw new MWException( "flattenParams() - key cannot use '=' or newline" );
95 - }
96 - if ( $key === 'uniqueContentPages' ) { // list
97 - $value = implode( ',', array_map( 'intval', $value ) );
98 - } else {
99 - $value = intval( $value );
100 - }
101 - $flatRows[] = trim( $key ) . '=' . $value;
102 - }
103 - return implode( "\n", $flatRows );
104 - }
105 -
106 - /**
107 - * Expand params for a user from DB storage
108 - * @param string $flatPars
109 - * @return array
110 - */
111 - protected static function expandParams( $flatPars ) {
112 - $p = array(); // init
113 - $flatPars = explode( "\n", trim( $flatPars ) );
114 - foreach ( $flatPars as $pair ) {
115 - $m = explode( '=', trim( $pair ), 2 );
116 - $key = $m[0];
117 - $value = isset( $m[1] ) ? $m[1] : null;
118 - if ( $key === 'uniqueContentPages' ) { // list
119 - $value = ( $value === '' )
120 - ? array() // explode() would make array( 0 => '')
121 - : array_map( 'intval', explode( ',', $value ) );
122 - } else {
123 - $value = intval( $value );
124 - }
125 - $p[$key] = $value;
126 - }
127 - return $p;
128 - }
129 -
130 - /**
131 - * Update users params array for a user on edit
132 - * @param &array $p user params
133 - * @param Article $article the article just edited
134 - * @param string $summary edit summary
135 - * @return bool anything changed
136 - */
137 - public static function updateUserParams( array &$p, Article $article, $summary ) {
138 - global $wgFlaggedRevsAutoconfirm, $wgFlaggedRevsAutopromote;
139 - # Update any special counters for non-null revisions
140 - $changed = false;
141 - if ( $article->getTitle()->isContentPage() ) {
142 - $pages = $p['uniqueContentPages']; // page IDs
143 - # Don't let this get bloated for no reason
144 - $maxUniquePages = 50; // some flexibility
145 - if ( is_array( $wgFlaggedRevsAutoconfirm ) &&
146 - $wgFlaggedRevsAutoconfirm['uniqueContentPages'] > $maxUniquePages )
147 - {
148 - $maxUniquePages = $wgFlaggedRevsAutoconfirm['uniqueContentPages'];
149 - }
150 - if ( is_array( $wgFlaggedRevsAutopromote ) &&
151 - $wgFlaggedRevsAutopromote['uniqueContentPages'] > $maxUniquePages )
152 - {
153 - $maxUniquePages = $wgFlaggedRevsAutopromote['uniqueContentPages'];
154 - }
155 - if ( count( $pages ) < $maxUniquePages // limit the size of this
156 - && !in_array( $article->getId(), $pages ) )
157 - {
158 - $pages[] = $article->getId();
159 - $p['uniqueContentPages'] = $pages;
160 - }
161 - $p['totalContentEdits'] += 1;
162 - $changed = true;
163 - }
164 - // Record non-automatic summary tally
165 - if ( !preg_match( '/^\/\*.*\*\/$/', $summary ) ) {
166 - $p['editComments'] += 1;
167 - $changed = true;
168 - }
169 - return $changed;
170 - }
171 -}
Index: trunk/extensions/FlaggedRevs/FlaggedRevsLog.php
@@ -1,161 +0,0 @@
2 -<?php
3 -
4 -class FlaggedRevsLog {
5 - /**
6 - * $action is a valid review log action
7 - * @return bool
8 - */
9 - public static function isReviewAction( $action ) {
10 - return preg_match( '/^(approve2?(-i|-a|-ia)?|unapprove2?)$/', $action );
11 - }
12 -
13 - /**
14 - * $action is a valid stability log action
15 - * @return bool
16 - */
17 - public static function isStabilityAction( $action ) {
18 - return preg_match( '/^(config|modify|reset)$/', $action );
19 - }
20 -
21 - /**
22 - * $action is a valid review log deprecate action
23 - * @return bool
24 - */
25 - public static function isReviewDeapproval( $action ) {
26 - return ( $action == 'unapprove' || $action == 'unapprove2' );
27 - }
28 -
29 - /**
30 - * Record a log entry on the review action
31 - * @param Title $title
32 - * @param array $dims
33 - * @param array $oldDims
34 - * @param string $comment
35 - * @param int $revId, revision ID
36 - * @param int $stableId, prior stable revision ID
37 - * @param bool $approve, approved? (otherwise unapproved)
38 - * @param bool $auto
39 - */
40 - public static function updateReviewLog(
41 - Title $title, array $dims, array $oldDims,
42 - $comment, $revId, $stableId, $approve, $auto = false
43 - ) {
44 - $log = new LogPage( 'review',
45 - false /* $rc */,
46 - $auto ? "skipUDP" : "UDP" // UDP logging
47 - );
48 - # Tag rating list (e.g. accuracy=x, depth=y, style=z)
49 - $ratings = array();
50 - # Skip rating list if flagging is just an 0/1 feature...
51 - if ( !FlaggedRevs::binaryFlagging() ) {
52 - foreach ( $dims as $quality => $level ) {
53 - $ratings[] = wfMsgForContent( "revreview-$quality" ) .
54 - wfMsgForContent( 'colon-separator' ) .
55 - wfMsgForContent( "revreview-$quality-$level" );
56 - }
57 - }
58 - $isAuto = ( $auto && !FlaggedRevs::isQuality( $dims ) ); // Paranoid check
59 - // Approved revisions
60 - if ( $approve ) {
61 - if ( $isAuto ) {
62 - $comment = wfMsgForContent( 'revreview-auto' ); // override this
63 - }
64 - # Make comma-separated list of ratings
65 - $rating = !empty( $ratings )
66 - ? '[' . implode( ', ', $ratings ) . ']'
67 - : '';
68 - # Append comment with ratings
69 - if ( $rating != '' ) {
70 - $comment .= $comment ? " $rating" : $rating;
71 - }
72 - # Sort into the proper action (useful for filtering)
73 - $action = ( FlaggedRevs::isQuality( $dims ) || FlaggedRevs::isQuality( $oldDims ) ) ?
74 - 'approve2' : 'approve';
75 - if ( !$stableId ) { // first time
76 - $action .= $isAuto ? "-ia" : "-i";
77 - } elseif ( $isAuto ) { // automatic
78 - $action .= "-a";
79 - }
80 - // De-approved revisions
81 - } else {
82 - $action = FlaggedRevs::isQuality( $oldDims ) ?
83 - 'unapprove2' : 'unapprove';
84 - }
85 - $ts = Revision::getTimestampFromId( $title, $revId );
86 - # Param format is <rev id, old stable id, rev timestamp>
87 - $logid = $log->addEntry( $action, $title, $comment, array( $revId, $stableId, $ts ) );
88 - # Make log easily searchable by rev_id
89 - $log->addRelations( 'rev_id', array( $revId ), $logid );
90 - }
91 -
92 - /**
93 - * Record a log entry on the stability config change action
94 - * @param Title $title
95 - * @param array $config
96 - * @param array $oldConfig
97 - * @param string $reason
98 - * @param bool $auto
99 - */
100 - public static function updateStabilityLog(
101 - Title $title, array $config, array $oldConfig, $reason
102 - ) {
103 - $log = new LogPage( 'stable' );
104 - if ( FlaggedPageConfig::configIsReset( $config ) ) {
105 - # We are going back to default settings
106 - $log->addEntry( 'reset', $title, $reason );
107 - } else {
108 - # We are changing to non-default settings
109 - $action = ( $oldConfig === FlaggedPageConfig::getDefaultVisibilitySettings() )
110 - ? 'config' // set a custom configuration
111 - : 'modify'; // modified an existing custom configuration
112 - $log->addEntry( $action, $title, $reason,
113 - FlaggedRevsLog::collapseParams( self::stabilityLogParams( $config ) ) );
114 - }
115 - }
116 -
117 - /**
118 - * Get log params (associate array) from a stability config
119 - * @param array $config
120 - * @return array (associative)
121 - */
122 - public static function stabilityLogParams( array $config ) {
123 - $params = $config;
124 - if ( !FlaggedRevs::useOnlyIfProtected() ) {
125 - $params['precedence'] = 1; // b/c hack for presenting log params...
126 - }
127 - return $params;
128 - }
129 -
130 - /**
131 - * Collapse an associate array into a string
132 - * @param array $pars
133 - * @return string
134 - */
135 - public static function collapseParams( array $pars ) {
136 - $res = array();
137 - foreach ( $pars as $param => $value ) {
138 - // Sanity check...
139 - if ( strpos( $param, '=' ) !== false || strpos( $value, '=' ) !== false ) {
140 - throw new MWException( "collapseParams() - cannot use equal sign" );
141 - } elseif ( strpos( $param, "\n" ) !== false || strpos( $value, "\n" ) !== false ) {
142 - throw new MWException( "collapseParams() - cannot use newline" );
143 - }
144 - $res[] = "{$param}={$value}";
145 - }
146 - return implode( "\n", $res );
147 - }
148 -
149 - /**
150 - * Expand a list of log params into an associative array
151 - * @params array $pars
152 - * @return array (associative)
153 - */
154 - public static function expandParams( array $pars ) {
155 - $res = array();
156 - foreach ( $pars as $paramAndValue ) {
157 - list( $param, $value ) = explode( '=', $paramAndValue, 2 );
158 - $res[$param] = $value;
159 - }
160 - return $res;
161 - }
162 -}
Index: trunk/extensions/FlaggedRevs/FlaggedRevs.class.php
@@ -1,1203 +0,0 @@
2 -<?php
3 -/**
4 - * Class containing utility functions for a FlaggedRevs environment
5 - *
6 - * Class is lazily-initialized, calling load() as needed
7 - */
8 -class FlaggedRevs {
9 - # Tag name/level config
10 - protected static $dimensions = array();
11 - protected static $minSL = array();
12 - protected static $minQL = array();
13 - protected static $minPL = array();
14 - protected static $qualityVersions = false;
15 - protected static $pristineVersions = false;
16 - protected static $tagRestrictions = array();
17 - protected static $binaryFlagging = true;
18 - # Namespace config
19 - protected static $reviewNamespaces = array();
20 - protected static $patrolNamespaces = array();
21 - # Restriction levels/config
22 - protected static $restrictionLevels = array();
23 - # Autoreview config
24 - protected static $autoReviewConfig = 0;
25 -
26 - protected static $loaded = false;
27 -
28 - public static function load() {
29 - global $wgFlaggedRevsTags, $wgFlaggedRevTags;
30 - if ( self::$loaded ) {
31 - return true;
32 - }
33 - self::$loaded = true;
34 - $flaggedRevsTags = null;
35 - if ( isset( $wgFlaggedRevTags ) ) {
36 - $flaggedRevsTags = $wgFlaggedRevTags; // b/c
37 - wfWarn( 'Please use $wgFlaggedRevsTags instead of $wgFlaggedRevTags in config.' );
38 - } elseif ( isset( $wgFlaggedRevsTags ) ) {
39 - $flaggedRevsTags = $wgFlaggedRevsTags;
40 - }
41 - # Assume true, then set to false if needed
42 - if ( !empty( $flaggedRevsTags ) ) {
43 - self::$qualityVersions = true;
44 - self::$pristineVersions = true;
45 - self::$binaryFlagging = ( count( $flaggedRevsTags ) <= 1 );
46 - }
47 - foreach ( $flaggedRevsTags as $tag => $levels ) {
48 - # Sanity checks
49 - $safeTag = htmlspecialchars( $tag );
50 - if ( !preg_match( '/^[a-zA-Z]{1,20}$/', $tag ) || $safeTag !== $tag ) {
51 - throw new MWException( 'FlaggedRevs given invalid tag name!' );
52 - }
53 - # Define "quality" and "pristine" reqs
54 - if ( is_array( $levels ) ) {
55 - $minQL = $levels['quality'];
56 - $minPL = $levels['pristine'];
57 - $ratingLevels = $levels['levels'];
58 - # B/C, $levels is just an integer (minQL)
59 - } else {
60 - global $wgFlaggedRevPristine, $wgFlaggedRevValues;
61 - $ratingLevels = isset( $wgFlaggedRevValues ) ?
62 - $wgFlaggedRevValues : 1;
63 - $minQL = $levels; // an integer
64 - $minPL = isset( $wgFlaggedRevPristine ) ?
65 - $wgFlaggedRevPristine : $ratingLevels + 1;
66 - wfWarn( 'Please update the format of $wgFlaggedRevsTags in config.' );
67 - }
68 - # Set FlaggedRevs tags
69 - self::$dimensions[$tag] = array();
70 - for ( $i = 0; $i <= $ratingLevels; $i++ ) {
71 - self::$dimensions[$tag][$i] = "{$tag}-{$i}";
72 - }
73 - if ( $ratingLevels > 1 ) {
74 - self::$binaryFlagging = false; // more than one level
75 - }
76 - # Sanity checks
77 - if ( !is_integer( $minQL ) || !is_integer( $minPL ) ) {
78 - throw new MWException( 'FlaggedRevs given invalid tag value!' );
79 - }
80 - if ( $minQL > $ratingLevels ) {
81 - self::$qualityVersions = false;
82 - self::$pristineVersions = false;
83 - }
84 - if ( $minPL > $ratingLevels ) {
85 - self::$pristineVersions = false;
86 - }
87 - self::$minQL[$tag] = max( $minQL, 1 );
88 - self::$minPL[$tag] = max( $minPL, 1 );
89 - self::$minSL[$tag] = 1;
90 - }
91 - global $wgFlaggedRevsTagsRestrictions, $wgFlagRestrictions;
92 - if ( isset( $wgFlagRestrictions ) ) {
93 - self::$tagRestrictions = $wgFlagRestrictions; // b/c
94 - wfWarn( 'Please use $wgFlaggedRevsTagsRestrictions instead of $wgFlagRestrictions in config.' );
95 - } else {
96 - self::$tagRestrictions = $wgFlaggedRevsTagsRestrictions;
97 - }
98 - # Make sure that the restriction levels are unique
99 - global $wgFlaggedRevsRestrictionLevels;
100 - self::$restrictionLevels = array_unique( $wgFlaggedRevsRestrictionLevels );
101 - self::$restrictionLevels = array_filter( self::$restrictionLevels, 'strlen' );
102 - # Make sure no talk namespaces are in review namespace
103 - global $wgFlaggedRevsNamespaces, $wgFlaggedRevsPatrolNamespaces;
104 - foreach ( $wgFlaggedRevsNamespaces as $ns ) {
105 - if ( MWNamespace::isTalk( $ns ) ) {
106 - throw new MWException( 'FlaggedRevs given talk namespace in $wgFlaggedRevsNamespaces!' );
107 - } else if ( $ns == NS_MEDIAWIKI ) {
108 - throw new MWException( 'FlaggedRevs given NS_MEDIAWIKI in $wgFlaggedRevsNamespaces!' );
109 - }
110 - }
111 - self::$reviewNamespaces = $wgFlaggedRevsNamespaces;
112 - # Note: reviewable *pages* override patrollable ones
113 - self::$patrolNamespaces = $wgFlaggedRevsPatrolNamespaces;
114 - # Handle $wgFlaggedRevsAutoReview settings
115 - global $wgFlaggedRevsAutoReview, $wgFlaggedRevsAutoReviewNew;
116 - if ( is_int( $wgFlaggedRevsAutoReview ) ) {
117 - self::$autoReviewConfig = $wgFlaggedRevsAutoReview;
118 - } else { // b/c
119 - if ( $wgFlaggedRevsAutoReview ) {
120 - self::$autoReviewConfig = FR_AUTOREVIEW_CHANGES;
121 - }
122 - wfWarn( '$wgFlaggedRevsAutoReview is now a bitfield instead of a boolean.' );
123 - }
124 - if ( isset( $wgFlaggedRevsAutoReviewNew ) ) { // b/c
125 - self::$autoReviewConfig = ( $wgFlaggedRevsAutoReviewNew )
126 - ? self::$autoReviewConfig |= FR_AUTOREVIEW_CREATION
127 - : self::$autoReviewConfig & ~FR_AUTOREVIEW_CREATION;
128 - wfWarn( '$wgFlaggedRevsAutoReviewNew is deprecated; use $wgFlaggedRevsAutoReview.' );
129 - }
130 - }
131 -
132 - # ################ Basic config accessors #################
133 -
134 - /**
135 - * Is there only one tag and it has only one level?
136 - * @return bool
137 - */
138 - public static function binaryFlagging() {
139 - self::load();
140 - return self::$binaryFlagging;
141 - }
142 -
143 - /**
144 - * If there only one tag and it has only one level, return it
145 - * @return string
146 - */
147 - public static function binaryTagName() {
148 - self::load();
149 - if ( !self::binaryFlagging() ) {
150 - return null;
151 - }
152 - $tags = array_keys( self::$dimensions );
153 - return empty( $tags ) ? null : $tags[0];
154 - }
155 -
156 - /**
157 - * Are quality versions enabled?
158 - * @return bool
159 - */
160 - public static function qualityVersions() {
161 - self::load();
162 - return self::$qualityVersions;
163 - }
164 -
165 - /**
166 - * Are pristine versions enabled?
167 - * @return bool
168 - */
169 - public static function pristineVersions() {
170 - self::load();
171 - return self::$pristineVersions;
172 - }
173 -
174 - /**
175 - * Get the highest review tier that is enabled
176 - * @return int One of FR_PRISTINE,FR_QUALITY,FR_CHECKED
177 - */
178 - public static function highestReviewTier() {
179 - self::load();
180 - if ( self::$pristineVersions ) {
181 - return FR_PRISTINE;
182 - } elseif ( self::$qualityVersions ) {
183 - return FR_QUALITY;
184 - }
185 - return FR_CHECKED;
186 - }
187 -
188 - /**
189 - * Allow auto-review edits directly to the stable version by reviewers?
190 - * @return bool
191 - */
192 - public static function autoReviewEdits() {
193 - self::load();
194 - return self::$autoReviewConfig & FR_AUTOREVIEW_CHANGES;
195 - }
196 -
197 - /**
198 - * Auto-review new pages with the minimal level?
199 - * @return bool
200 - */
201 - public static function autoReviewNewPages() {
202 - self::load();
203 - return self::$autoReviewConfig & FR_AUTOREVIEW_CREATION;
204 - }
205 -
206 - /**
207 - * Auto-review of new pages or edits to pages enabled?
208 - * @return bool
209 - */
210 - public static function autoReviewEnabled() {
211 - return self::autoReviewEdits() || self::autoReviewNewPages();
212 - }
213 -
214 - /**
215 - * Get the maximum level that $tag can be autoreviewed to
216 - * @param string $tag
217 - * @return int
218 - */
219 - public static function maxAutoReviewLevel( $tag ) {
220 - global $wgFlaggedRevsTagsAuto;
221 - self::load();
222 - if ( !self::autoReviewEnabled() ) {
223 - return 0; // shouldn't happen
224 - }
225 - if ( isset( $wgFlaggedRevsTagsAuto[$tag] ) ) {
226 - return (int)$wgFlaggedRevsTagsAuto[$tag];
227 - } else {
228 - return 1; // B/C (before $wgFlaggedRevsTagsAuto)
229 - }
230 - }
231 -
232 - /**
233 - * Is a "stable version" used as the default display
234 - * version for all pages in reviewable namespaces?
235 - * @return bool
236 - */
237 - public static function isStableShownByDefault() {
238 - global $wgFlaggedRevsOverride;
239 - if ( self::useOnlyIfProtected() ) {
240 - return false; // must be configured per-page
241 - }
242 - return (bool)$wgFlaggedRevsOverride;
243 - }
244 -
245 - /**
246 - * Are pages reviewable only if they have been manually
247 - * configured by an admin to use a "stable version" as the default?
248 - * @return bool
249 - */
250 - public static function useOnlyIfProtected() {
251 - global $wgFlaggedRevsProtection;
252 - return (bool)$wgFlaggedRevsProtection;
253 - }
254 -
255 - /**
256 - * Return the include handling configuration
257 - * @return int
258 - */
259 - public static function inclusionSetting() {
260 - global $wgFlaggedRevsHandleIncludes;
261 - return $wgFlaggedRevsHandleIncludes;
262 - }
263 -
264 - /**
265 - * Should tags only be shown for unreviewed content for this user?
266 - * @return bool
267 - */
268 - public static function lowProfileUI() {
269 - global $wgFlaggedRevsLowProfile;
270 - return $wgFlaggedRevsLowProfile;
271 - }
272 -
273 - /**
274 - * Are there site defined protection levels for review
275 - * @return bool
276 - */
277 - public static function useProtectionLevels() {
278 - global $wgFlaggedRevsProtection;
279 - return $wgFlaggedRevsProtection && self::getRestrictionLevels();
280 - }
281 -
282 - /**
283 - * Get the autoreview restriction levels available
284 - * @return array
285 - */
286 - public static function getRestrictionLevels() {
287 - self::load();
288 - return self::$restrictionLevels;
289 - }
290 -
291 - /**
292 - * Get the array of tag dimensions and level messages
293 - * @return array
294 - */
295 - public static function getDimensions() {
296 - self::load();
297 - return self::$dimensions;
298 - }
299 -
300 - /**
301 - * Get the associative array of tag dimensions
302 - * (tags => array(levels => msgkey))
303 - * @return array
304 - */
305 - public static function getTags() {
306 - self::load();
307 - return array_keys( self::$dimensions );
308 - }
309 -
310 - /**
311 - * Get the associative array of tag restrictions
312 - * (tags => array(rights => levels))
313 - * @return array
314 - */
315 - public static function getTagRestrictions() {
316 - self::load();
317 - return self::$tagRestrictions;
318 - }
319 -
320 - /**
321 - * Get the UI name for a tag
322 - * @param string $tag
323 - * @return string
324 - */
325 - public static function getTagMsg( $tag ) {
326 - return wfMsgExt( "revreview-$tag", array( 'escapenoentities' ) );
327 - }
328 -
329 - /**
330 - * Get the levels for a tag. Gives map of level to message name.
331 - * @param string $tag
332 - * @return associative array (integer -> string)
333 - */
334 - public static function getTagLevels( $tag ) {
335 - self::load();
336 - return isset( self::$dimensions[$tag] ) ?
337 - self::$dimensions[$tag] : array();
338 - }
339 -
340 - /**
341 - * Get the the UI name for a value of a tag
342 - * @param string $tag
343 - * @param int $value
344 - * @return string
345 - */
346 - public static function getTagValueMsg( $tag, $value ) {
347 - self::load();
348 - if ( !isset( self::$dimensions[$tag] ) ) {
349 - return '';
350 - } elseif ( !isset( self::$dimensions[$tag][$value] ) ) {
351 - return '';
352 - }
353 - # Return empty string if not there
354 - return wfMsgExt( 'revreview-' . self::$dimensions[$tag][$value],
355 - array( 'escapenoentities' ) );
356 - }
357 -
358 - /**
359 - * Are there no actual dimensions?
360 - * @return bool
361 - */
362 - public static function dimensionsEmpty() {
363 - self::load();
364 - return empty( self::$dimensions );
365 - }
366 -
367 - /**
368 - * Get corresponding text for the api output of flagging levels
369 - *
370 - * @param int $level
371 - * @return string
372 - */
373 - public static function getQualityLevelText( $level ) {
374 - static $levelText = array(
375 - 0 => 'stable',
376 - 1 => 'quality',
377 - 2 => 'pristine'
378 - );
379 - if ( isset( $levelText[$level] ) ) {
380 - return $levelText[$level];
381 - } else {
382 - return '';
383 - }
384 - }
385 -
386 - /**
387 - * Get the URL path to where the client side resources are (JS, CSS, images..)
388 - * @return string
389 - */
390 - public static function styleUrlPath() {
391 - global $wgExtensionAssetsPath;
392 - return "$wgExtensionAssetsPath/FlaggedRevs/presentation/modules";
393 - }
394 -
395 - # ################ Permission functions #################
396 -
397 - /*
398 - * Sanity check a (tag,value) pair
399 - * @param string $tag
400 - * @param int $value
401 - * @return bool
402 - */
403 - public static function tagIsValid( $tag, $value ) {
404 - $levels = self::getTagLevels( $tag );
405 - $highest = count( $levels ) - 1;
406 - if ( !$levels || $value < 0 || $value > $highest ) {
407 - return false; // flag range is invalid
408 - }
409 - return true;
410 - }
411 -
412 - /**
413 - * Check if all of the required site flags have a valid value
414 - * @param array $flags
415 - * @return bool
416 - */
417 - public static function flagsAreValid( array $flags ) {
418 - foreach ( self::getDimensions() as $qal => $levels ) {
419 - if ( !isset( $flags[$qal] ) || !self::tagIsValid( $qal, $flags[$qal] ) ) {
420 - return false;
421 - }
422 - }
423 - return true;
424 - }
425 -
426 - /**
427 - * Returns true if a user can set $tag to $value
428 - * @param User $user
429 - * @param string $tag
430 - * @param int $value
431 - * @return bool
432 - */
433 - public static function userCanSetTag( $user, $tag, $value ) {
434 - # Sanity check tag and value
435 - if ( !self::tagIsValid( $tag, $value ) ) {
436 - return false; // flag range is invalid
437 - }
438 - $restrictions = self::getTagRestrictions();
439 - # No restrictions -> full access
440 - if ( !isset( $restrictions[$tag] ) ) {
441 - return true;
442 - }
443 - # Validators always have full access
444 - if ( $user->isAllowed( 'validate' ) ) {
445 - return true;
446 - }
447 - # Check if this user has any right that lets him/her set
448 - # up to this particular value
449 - foreach ( $restrictions[$tag] as $right => $level ) {
450 - if ( $value <= $level && $level > 0 && $user->isAllowed( $right ) ) {
451 - return true;
452 - }
453 - }
454 - return false;
455 - }
456 -
457 - /**
458 - * Returns true if a user can set $flags for a revision via review.
459 - * Requires the same for $oldflags if given.
460 - * @param User $user
461 - * @param array $flags, suggested flags
462 - * @param array $oldflags, pre-existing flags
463 - * @return bool
464 - */
465 - public static function userCanSetFlags( $user, array $flags, $oldflags = array() ) {
466 - if ( !$user->isAllowed( 'review' ) ) {
467 - return false; // User is not able to review pages
468 - }
469 - # Check if all of the required site flags have
470 - # a valid value that the user is allowed to set...
471 - foreach ( self::getDimensions() as $qal => $levels ) {
472 - if ( !isset( $flags[$qal] ) ) {
473 - return false; // unspecified
474 - } elseif ( !self::userCanSetTag( $user, $qal, $flags[$qal] ) ) {
475 - return false; // user cannot set proposed flag
476 - } elseif ( isset( $oldflags[$qal] )
477 - && !self::userCanSetTag( $user, $qal, $oldflags[$qal] ) )
478 - {
479 - return false; // user cannot change old flag
480 - }
481 - }
482 - return true;
483 - }
484 -
485 - /**
486 - * Check if a user can set the autoreview restiction level to $right
487 - * @param User $user
488 - * @param string $right the level
489 - * @return bool
490 - */
491 - public static function userCanSetAutoreviewLevel( $user, $right ) {
492 - if ( $right == '' ) {
493 - return true; // no restrictions (none)
494 - }
495 - if ( !in_array( $right, FlaggedRevs::getRestrictionLevels() ) ) {
496 - return false; // invalid restriction level
497 - }
498 - # Don't let them choose levels above their own rights
499 - if ( $right == 'sysop' ) {
500 - // special case, rewrite sysop to protect and editprotected
501 - if ( !$user->isAllowed( 'protect' ) && !$user->isAllowed( 'editprotected' ) ) {
502 - return false;
503 - }
504 - } elseif ( !$user->isAllowed( $right ) ) {
505 - return false;
506 - }
507 - return true;
508 - }
509 -
510 - # ################ Parsing functions #################
511 -
512 - /**
513 - * All templates and arguments in $text are expanded out
514 - * @param Title $title
515 - * @param string $text wikitext
516 - * @param int $id Source revision Id
517 - * @return array( string wikitext, array of template versions )
518 - */
519 - public static function expandText( Title $title, $text, $id ) {
520 - global $wgParser;
521 - # Notify Parser if includes should be stabilized
522 - $resetManager = false;
523 - $incManager = FRInclusionManager::singleton();
524 - if ( $id && self::inclusionSetting() != FR_INCLUDES_CURRENT ) {
525 - # Use FRInclusionManager to do the template/file version query
526 - # up front unless the versions are already specified there...
527 - if ( !$incManager->parserOutputIsStabilized() ) {
528 - $frev = FlaggedRevision::newFromTitle( $title, $id );
529 - if ( $frev ) {
530 - $incManager->stabilizeParserOutput( $frev );
531 - $resetManager = true; // need to reset when done
532 - }
533 - }
534 - }
535 - $options = self::makeParserOptions(); // default options
536 - $outputText = $wgParser->preprocess( $text, $title, $options, $id );
537 - $pOutput = $wgParser->getOutput();
538 - # Stable parse done!
539 - if ( $resetManager ) {
540 - $incManager->clear(); // reset the FRInclusionManager as needed
541 - }
542 - # Return data array
543 - return array( $outputText, $pOutput->getTemplateIds() );
544 - }
545 -
546 - /**
547 - * Get the HTML output of a revision based on $text.
548 - * @param Title $title
549 - * @param string $text
550 - * @param int $id Source revision Id
551 - * @return ParserOutput
552 - */
553 - public static function parseStableText( Title $title, $text, $id, $parserOptions ) {
554 - global $wgParser;
555 - # Notify Parser if includes should be stabilized
556 - $resetManager = false;
557 - $incManager = FRInclusionManager::singleton();
558 - if ( $id && self::inclusionSetting() != FR_INCLUDES_CURRENT ) {
559 - # Use FRInclusionManager to do the template/file version query
560 - # up front unless the versions are already specified there...
561 - if ( !$incManager->parserOutputIsStabilized() ) {
562 - $frev = FlaggedRevision::newFromTitle( $title, $id );
563 - if ( $frev ) {
564 - $incManager->stabilizeParserOutput( $frev );
565 - $resetManager = true; // need to reset when done
566 - }
567 - }
568 - }
569 - # Parse the new body, wikitext -> html
570 - $parserOut = $wgParser->parse( $text, $title, $parserOptions, true, true, $id );
571 - # Stable parse done!
572 - if ( $resetManager ) {
573 - $incManager->clear(); // reset the FRInclusionManager as needed
574 - }
575 - return $parserOut;
576 - }
577 -
578 - /**
579 - * Get standard parser options
580 - * @param User $user (optional)
581 - * @return ParserOptions
582 - */
583 - public static function makeParserOptions( $user = null ) {
584 - global $wgUser;
585 - $user = $user ? $user : $wgUser; // assume current
586 - $options = ParserOptions::newFromUser( $user );
587 - # Show inclusion/loop reports
588 - $options->enableLimitReport();
589 - # Fix bad HTML
590 - $options->setTidy( true );
591 - return $options;
592 - }
593 -
594 - /**
595 - * Get the page cache for the stable version of an article
596 - * @param Article $article
597 - * @param User $user
598 - * @return mixed (ParserOutput/false)
599 - */
600 - public static function getPageCache( Article $article, $user ) {
601 - global $parserMemc, $wgCacheEpoch;
602 - wfProfileIn( __METHOD__ );
603 - # Make sure it is valid
604 - if ( !$article->getId() ) {
605 - wfProfileOut( __METHOD__ );
606 - return null;
607 - }
608 - $parserCache = ParserCache::singleton();
609 - $key = self::getCacheKey( $parserCache, $article, $user );
610 - # Get the cached HTML
611 - wfDebug( "Trying parser cache $key\n" );
612 - $value = $parserMemc->get( $key );
613 - if ( is_object( $value ) ) {
614 - wfDebug( "Found.\n" );
615 - # Delete if article has changed since the cache was made
616 - $canCache = $article->checkTouched();
617 - $cacheTime = $value->getCacheTime();
618 - $touched = $article->mTouched;
619 - if ( !$canCache || $value->expired( $touched ) ) {
620 - if ( !$canCache ) {
621 - wfIncrStats( "pcache_miss_invalid" );
622 - wfDebug( "Invalid cached redirect, touched $touched, epoch $wgCacheEpoch, cached $cacheTime\n" );
623 - } else {
624 - wfIncrStats( "pcache_miss_expired" );
625 - wfDebug( "Key expired, touched $touched, epoch $wgCacheEpoch, cached $cacheTime\n" );
626 - }
627 - $parserMemc->delete( $key );
628 - $value = false;
629 - } else {
630 - wfIncrStats( "pcache_hit" );
631 - }
632 - } else {
633 - wfDebug( "Parser cache miss.\n" );
634 - wfIncrStats( "pcache_miss_absent" );
635 - $value = false;
636 - }
637 - wfProfileOut( __METHOD__ );
638 - return $value;
639 - }
640 -
641 - /**
642 - * Like ParserCache::getKey() with stable-pcache instead of pcache
643 - */
644 - protected static function getCacheKey( $parserCache, Article $article, $popts ) {
645 - if( $popts instanceof User ) {
646 - $popts = ParserOptions::newFromUser( $popts );
647 - }
648 - $key = $parserCache->getKey( $article, $popts );
649 - $key = str_replace( ':pcache:', ':stable-pcache:', $key );
650 - return $key;
651 - }
652 -
653 - /**
654 - * @param Article $article
655 - * @param ParserOptions $popts
656 - * @param parserOutput $parserOut
657 - * Updates the stable cache of a page with the given $parserOut
658 - */
659 - public static function updatePageCache(
660 - Article $article, $popts, ParserOutput $parserOut = null
661 - ) {
662 - global $parserMemc, $wgParserCacheExpireTime, $wgEnableParserCache;
663 - wfProfileIn( __METHOD__ );
664 - # Make sure it is valid and $wgEnableParserCache is enabled
665 - if ( !$wgEnableParserCache || !$parserOut ) {
666 - wfProfileOut( __METHOD__ );
667 - return false;
668 - }
669 - $parserCache = ParserCache::singleton();
670 - $key = self::getCacheKey( $parserCache, $article, $popts );
671 - # Add cache mark to HTML
672 - $now = wfTimestampNow();
673 - $parserOut->setCacheTime( $now );
674 - # Save the timestamp so that we don't have to load the revision row on view
675 - $parserOut->mTimestamp = $article->getTimestamp();
676 - $parserOut->mText .= "\n<!-- Saved in stable version parser cache with key $key and timestamp $now -->";
677 - # Set expire time
678 - if ( $parserOut->containsOldMagic() ) {
679 - $expire = 3600; // 1 hour
680 - } else {
681 - $expire = $wgParserCacheExpireTime;
682 - }
683 - # Save to objectcache
684 - $parserMemc->set( $key, $parserOut, $expire );
685 - wfProfileOut( __METHOD__ );
686 - return true;
687 - }
688 -
689 - /**
690 - * @param Article $article
691 - * @param parserOutput $parserOut
692 - * Updates the stable-only cache dependency table
693 - */
694 - public static function updateCacheTracking( Article $article, ParserOutput $stableOut ) {
695 - wfProfileIn( __METHOD__ );
696 - if ( !wfReadOnly() ) {
697 - $frDepUpdate = new FRDependencyUpdate( $article->getTitle(), $stableOut );
698 - $frDepUpdate->doUpdate();
699 - }
700 - wfProfileOut( __METHOD__ );
701 - }
702 -
703 - /**
704 - * @param Article $article
705 - * @param bool $synced
706 - * Updates the fp_reviewed field for this article
707 - */
708 - public static function updateSyncStatus( Article $article, $synced ) {
709 - wfProfileIn( __METHOD__ );
710 - if ( !wfReadOnly() ) {
711 - $dbw = wfGetDB( DB_MASTER );
712 - $dbw->update( 'flaggedpages',
713 - array( 'fp_reviewed' => (int)$synced ),
714 - array( 'fp_page_id' => $article->getID() ),
715 - __METHOD__
716 - );
717 - }
718 - wfProfileOut( __METHOD__ );
719 - }
720 -
721 - # ################ Tracking/cache update update functions #################
722 -
723 - /**
724 - * Update the page tables with a new stable version.
725 - * @param Title $title
726 - * @param FlaggedRevision|null $sv, the new stable version (optional)
727 - * @param FlaggedRevision|null $oldSv, the old stable version (optional)
728 - * @return bool stable version text/file changed and FR_INCLUDES_STABLE
729 - */
730 - public static function stableVersionUpdates( Title $title, $sv = null, $oldSv = null ) {
731 - $changed = false;
732 - if ( $oldSv === null ) { // optional
733 - $oldSv = FlaggedRevision::newFromStable( $title, FR_MASTER );
734 - }
735 - if ( $sv === null ) { // optional
736 - $sv = FlaggedRevision::determineStable( $title, FR_MASTER );
737 - }
738 - $article = new FlaggedPage( $title );
739 - if ( !$sv ) {
740 - # Empty flaggedrevs data for this page if there is no stable version
741 - $article->clearStableVersion();
742 - # Check if pages using this need to be refreshed...
743 - if ( FlaggedRevs::inclusionSetting() == FR_INCLUDES_STABLE ) {
744 - $changed = (bool)$oldSv;
745 - }
746 - } else {
747 - # Update flagged page related fields
748 - $article->updateStableVersion( $sv );
749 - # Check if pages using this need to be invalidated/purged...
750 - if ( FlaggedRevs::inclusionSetting() == FR_INCLUDES_STABLE ) {
751 - $changed = (
752 - !$oldSv ||
753 - $sv->getRevId() != $oldSv->getRevId() ||
754 - $sv->getFileTimestamp() != $oldSv->getFileTimestamp() ||
755 - $sv->getFileSha1() != $oldSv->getFileSha1()
756 - );
757 - }
758 - }
759 - # Lazily rebuild dependancies on next parse (we invalidate below)
760 - FlaggedRevs::clearStableOnlyDeps( $title );
761 - # Clear page cache
762 - $title->invalidateCache();
763 - self::purgeSquid( $title );
764 - return $changed;
765 - }
766 -
767 - /**
768 - * @param Title $title
769 - * Updates squid cache for a title. Defers till after main commit().
770 - */
771 - public static function purgeSquid( Title $title ) {
772 - global $wgDeferredUpdateList;
773 - $wgDeferredUpdateList[] = new FRSquidUpdate( $title );
774 - }
775 -
776 - /**
777 - * Do cache updates for when the stable version of a page changed.
778 - * Invalidates/purges pages that include the given page.
779 - * @param Title $title
780 - * @param bool $recursive
781 - */
782 - public static function HTMLCacheUpdates( Title $title ) {
783 - global $wgDeferredUpdateList;
784 - # Invalidate caches of articles which include this page...
785 - $wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'templatelinks' );
786 - if ( $title->getNamespace() == NS_FILE ) {
787 - $wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'imagelinks' );
788 - }
789 - $wgDeferredUpdateList[] = new FRExtraCacheUpdate( $title );
790 - }
791 -
792 - /**
793 - * Invalidates/purges pages where only stable version includes this page.
794 - * @param Title $title
795 - */
796 - public static function extraHTMLCacheUpdate( Title $title ) {
797 - global $wgDeferredUpdateList;
798 - $wgDeferredUpdateList[] = new FRExtraCacheUpdate( $title );
799 - }
800 -
801 - # ################ Revision functions #################
802 -
803 - /**
804 - * Get flags for a revision
805 - * @param Title $title
806 - * @param int $rev_id
807 - * @param $flags, FR_MASTER
808 - * @return array
809 - */
810 - public static function getRevisionTags( Title $title, $rev_id, $flags = 0 ) {
811 - $db = ( $flags & FR_MASTER ) ?
812 - wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
813 - $tags = (string)$db->selectField( 'flaggedrevs',
814 - 'fr_tags',
815 - array( 'fr_rev_id' => $rev_id,
816 - 'fr_page_id' => $title->getArticleId() ),
817 - __METHOD__
818 - );
819 - return FlaggedRevision::expandRevisionTags( strval( $tags ) );
820 - }
821 -
822 - /**
823 - * @param int $page_id
824 - * @param int $rev_id
825 - * @param $flags, FR_MASTER
826 - * @return mixed (int or false)
827 - * Get quality of a revision
828 - */
829 - public static function getRevQuality( $page_id, $rev_id, $flags = 0 ) {
830 - $db = ( $flags & FR_MASTER ) ?
831 - wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
832 - return $db->selectField( 'flaggedrevs',
833 - 'fr_quality',
834 - array( 'fr_page_id' => $page_id, 'fr_rev_id' => $rev_id ),
835 - __METHOD__,
836 - array( 'USE INDEX' => 'PRIMARY' )
837 - );
838 - }
839 -
840 - /**
841 - * @param Title $title
842 - * @param int $rev_id
843 - * @param $flags, FR_MASTER
844 - * @return bool
845 - * Useful for quickly pinging to see if a revision is flagged
846 - */
847 - public static function revIsFlagged( Title $title, $rev_id, $flags = 0 ) {
848 - $quality = self::getRevQuality( $title->getArticleId(), $rev_id, $flags );
849 - return ( $quality !== false );
850 - }
851 -
852 - /**
853 - * Get the "prime" flagged revision of a page
854 - * @param Article $article
855 - * @return mixed (integer/false)
856 - * Will not return a revision if deleted
857 - */
858 - public static function getPrimeFlaggedRevId( Article $article ) {
859 - $dbr = wfGetDB( DB_SLAVE );
860 - # Get the highest quality revision (not necessarily this one).
861 - $oldid = $dbr->selectField( array( 'flaggedrevs', 'revision' ),
862 - 'fr_rev_id',
863 - array(
864 - 'fr_page_id' => $article->getId(),
865 - 'rev_page = fr_page_id',
866 - 'rev_id = fr_rev_id'
867 - ),
868 - __METHOD__,
869 - array(
870 - 'ORDER BY' => 'fr_quality DESC, fr_rev_id DESC',
871 - 'USE INDEX' => array( 'flaggedrevs' => 'page_qal_rev', 'revision' => 'PRIMARY' )
872 - )
873 - );
874 - return $oldid;
875 - }
876 -
877 - /**
878 - * Mark a revision as patrolled if needed
879 - * @param Revision $rev
880 - * @return bool DB write query used
881 - */
882 - public static function markRevisionPatrolled( Revision $rev ) {
883 - $rcid = $rev->isUnpatrolled();
884 - # Make sure it is now marked patrolled...
885 - if ( $rcid ) {
886 - $dbw = wfGetDB( DB_MASTER );
887 - $dbw->update( 'recentchanges',
888 - array( 'rc_patrolled' => 1 ),
889 - array( 'rc_id' => $rcid ),
890 - __METHOD__
891 - );
892 - return true;
893 - }
894 - return false;
895 - }
896 -
897 - # ################ Other utility functions #################
898 -
899 - /**
900 - * @param string $val
901 - * @return Object (val,time) tuple
902 - * Get a memcache storage object
903 - */
904 - public static function makeMemcObj( $val ) {
905 - $data = (object) array();
906 - $data->value = $val;
907 - $data->time = wfTimestampNow();
908 - return $data;
909 - }
910 -
911 - /**
912 - * @param object|false $data makeMemcObj() tuple
913 - * @param Article $article
914 - * @return mixed
915 - * Return memc value if not expired
916 - */
917 - public static function getMemcValue( $data, Article $article ) {
918 - if ( is_object( $data ) && $data->time >= $article->getTouched() ) {
919 - return $data->value;
920 - }
921 - return false;
922 - }
923 -
924 - /**
925 - * @param array $flags
926 - * @return bool, is this revision at basic review condition?
927 - */
928 - public static function isChecked( array $flags ) {
929 - self::load();
930 - return self::tagsAtLevel( $flags, self::$minSL );
931 - }
932 -
933 - /**
934 - * @param array $flags
935 - * @return bool, is this revision at quality review condition?
936 - */
937 - public static function isQuality( array $flags ) {
938 - self::load();
939 - return self::tagsAtLevel( $flags, self::$minQL );
940 - }
941 -
942 - /**
943 - * @param array $flags
944 - * @return bool, is this revision at pristine review condition?
945 - */
946 - public static function isPristine( array $flags ) {
947 - self::load();
948 - return self::tagsAtLevel( $flags, self::$minPL );
949 - }
950 -
951 - // Checks if $flags meets $reqFlagLevels
952 - protected static function tagsAtLevel( array $flags, $reqFlagLevels ) {
953 - self::load();
954 - if ( empty( $flags ) ) {
955 - return false;
956 - }
957 - foreach ( self::$dimensions as $f => $x ) {
958 - if ( !isset( $flags[$f] ) || $reqFlagLevels[$f] > $flags[$f] ) {
959 - return false;
960 - }
961 - }
962 - return true;
963 - }
964 -
965 - /**
966 - * Get the quality tier of review flags
967 - * @param array $flags
968 - * @return int flagging tier (FR_PRISTINE,FR_QUALITY,FR_CHECKED,-1)
969 - */
970 - public static function getLevelTier( array $flags ) {
971 - if ( self::isPristine( $flags ) ) {
972 - return FR_PRISTINE; // 2
973 - } elseif ( self::isQuality( $flags ) ) {
974 - return FR_QUALITY; // 1
975 - } elseif ( self::isChecked( $flags ) ) {
976 - return FR_CHECKED; // 0
977 - }
978 - return -1;
979 - }
980 -
981 - /**
982 - * Get minimum level tags for a tier
983 - * @param int $tier FR_PRISTINE/FR_QUALITY/FR_CHECKED
984 - * @return array
985 - */
986 - public static function quickTags( $tier ) {
987 - self::load();
988 - if ( $tier == FR_PRISTINE ) {
989 - return self::$minPL;
990 - } elseif ( $tier == FR_QUALITY ) {
991 - return self::$minQL;
992 - }
993 - return self::$minSL;
994 - }
995 -
996 - /**
997 - * Get minimum tags that are closest to $oldFlags
998 - * given the site, page, and user rights limitations.
999 - * @param User $user
1000 - * @param array $oldFlags previous stable rev flags
1001 - * @return mixed array or null
1002 - */
1003 - public static function getAutoReviewTags( $user, array $oldFlags ) {
1004 - if ( !self::autoReviewEdits() ) {
1005 - return null; // shouldn't happen
1006 - }
1007 - $flags = array();
1008 - foreach ( self::getTags() as $tag ) {
1009 - # Try to keep this tag val the same as the stable rev's
1010 - $val = isset( $oldFlags[$tag] ) ? $oldFlags[$tag] : 1;
1011 - $val = min( $val, self::maxAutoReviewLevel( $tag ) );
1012 - # Dial down the level to one the user has permission to set
1013 - while ( !self::userCanSetTag( $user, $tag, $val ) ) {
1014 - $val--;
1015 - if ( $val <= 0 ) {
1016 - return null; // all tags vals must be > 0
1017 - }
1018 - }
1019 - $flags[$tag] = $val;
1020 - }
1021 - return $flags;
1022 - }
1023 -
1024 - /**
1025 - * Get the list of reviewable namespaces
1026 - * @return array
1027 - */
1028 - public static function getReviewNamespaces() {
1029 - self::load(); // validates namespaces
1030 - return self::$reviewNamespaces;
1031 - }
1032 -
1033 - /**
1034 - * Get the list of patrollable namespaces
1035 - * @return array
1036 - */
1037 - public static function getPatrolNamespaces() {
1038 - self::load(); // validates namespaces
1039 - return self::$patrolNamespaces;
1040 - }
1041 -
1042 -
1043 - /**
1044 - * Is this page in reviewable namespace?
1045 - * Note: this checks $wgFlaggedRevsWhitelist
1046 - * @param Title, $title
1047 - * @return bool
1048 - */
1049 - public static function inReviewNamespace( Title $title ) {
1050 - global $wgFlaggedRevsWhitelist;
1051 - $namespaces = self::getReviewNamespaces();
1052 - $ns = ( $title->getNamespace() == NS_MEDIA ) ?
1053 - NS_FILE : $title->getNamespace(); // Treat NS_MEDIA as NS_FILE
1054 - # Check for MW: pages and whitelist for exempt pages
1055 - if ( in_array( $title->getPrefixedDBKey(), $wgFlaggedRevsWhitelist ) ) {
1056 - return false;
1057 - }
1058 - return ( in_array( $ns, $namespaces ) );
1059 - }
1060 -
1061 - /**
1062 - * Is this page in patrollable namespace?
1063 - * @param Title, $title
1064 - * @return bool
1065 - */
1066 - public static function inPatrolNamespace( Title $title ) {
1067 - $namespaces = self::getPatrolNamespaces();
1068 - $ns = ( $title->getNamespace() == NS_MEDIA ) ?
1069 - NS_FILE : $title->getNamespace(); // Treat NS_MEDIA as NS_FILE
1070 - return ( in_array( $ns, $namespaces ) );
1071 - }
1072 -
1073 - /**
1074 - * Clear FlaggedRevs tracking tables for this page
1075 - * @param int|array $pageId (int or array)
1076 - */
1077 - public static function clearTrackingRows( $pageId ) {
1078 - $dbw = wfGetDB( DB_MASTER );
1079 - $dbw->delete( 'flaggedpages', array( 'fp_page_id' => $pageId ), __METHOD__ );
1080 - $dbw->delete( 'flaggedrevs_tracking', array( 'ftr_from' => $pageId ), __METHOD__ );
1081 - $dbw->delete( 'flaggedpage_pending', array( 'fpp_page_id' => $pageId ), __METHOD__ );
1082 - }
1083 -
1084 - /**
1085 - * Clear tracking table of stable-only links for this page
1086 - * @param int|array $pageId (int or array)
1087 - */
1088 - public static function clearStableOnlyDeps( $pageId ) {
1089 - $dbw = wfGetDB( DB_MASTER );
1090 - $dbw->delete( 'flaggedrevs_tracking', array( 'ftr_from' => $pageId ), __METHOD__ );
1091 - }
1092 -
1093 - # ################ Auto-review function #################
1094 -
1095 - /**
1096 - * Automatically review an revision and add a log entry in the review log.
1097 - *
1098 - * This is called during edit operations after the new revision is added
1099 - * and the page tables updated, but before LinksUpdate is called.
1100 - *
1101 - * $auto is here for revisions checked off to be reviewed. Auto-review
1102 - * triggers on edit, but we don't want those to count as just automatic.
1103 - * This also makes it so the user's name shows up in the page history.
1104 - *
1105 - * If $flags is given, then they will be the review tags. If not, the one
1106 - * from the stable version will be used or minimal tags if that's not possible.
1107 - * If no appropriate tags can be found, then the review will abort.
1108 - */
1109 - public static function autoReviewEdit(
1110 - Article $article, $user, Revision $rev, array $flags = null, $auto = true
1111 - ) {
1112 - wfProfileIn( __METHOD__ );
1113 - $title = $article->getTitle(); // convenience
1114 - # Get current stable version ID (for logging)
1115 - $oldSv = FlaggedRevision::newFromStable( $title, FR_MASTER );
1116 - $oldSvId = $oldSv ? $oldSv->getRevId() : 0;
1117 - # Set the auto-review tags from the prior stable version.
1118 - # Normally, this should already be done and given here...
1119 - if ( !is_array( $flags ) ) {
1120 - if ( $oldSv ) {
1121 - # Use the last stable version if $flags not given
1122 - if ( $user->isAllowed( 'bot' ) ) {
1123 - $flags = $oldSv->getTags(); // no change for bot edits
1124 - } else {
1125 - # Account for perms/tags...
1126 - $flags = self::getAutoReviewTags( $user, $oldSv->getTags() );
1127 - }
1128 - } else { // new page?
1129 - $flags = self::quickTags( FR_CHECKED ); // use minimal level
1130 - }
1131 - if ( !is_array( $flags ) ) {
1132 - wfProfileOut( __METHOD__ );
1133 - return false; // can't auto-review this revision
1134 - }
1135 - }
1136 - # Get quality tier from flags
1137 - $quality = 0;
1138 - if ( self::isQuality( $flags ) ) {
1139 - $quality = self::isPristine( $flags ) ? 2 : 1;
1140 - }
1141 - # Get review property flags
1142 - $propFlags = $auto ? array( 'auto' ) : array();
1143 -
1144 - # Rev ID is not put into parser on edit, so do the same here.
1145 - # Also, a second parse would be triggered otherwise.
1146 - $editInfo = $article->prepareTextForEdit( $rev->getText() );
1147 - $poutput = $editInfo->output; // revision HTML output
1148 -
1149 - # If this is an image page, store corresponding file info
1150 - $fileData = array( 'name' => null, 'timestamp' => null, 'sha1' => null );
1151 - if ( $title->getNamespace() == NS_FILE ) {
1152 - $file = $article instanceof ImagePage ?
1153 - $article->getFile() : wfFindFile( $title );
1154 - if ( is_object( $file ) && $file->exists() ) {
1155 - $fileData['name'] = $title->getDBkey();
1156 - $fileData['timestamp'] = $file->getTimestamp();
1157 - $fileData['sha1'] = $file->getSha1();
1158 - }
1159 - }
1160 -
1161 - # Our review entry
1162 - $flaggedRevision = new FlaggedRevision( array(
1163 - 'page_id' => $rev->getPage(),
1164 - 'rev_id' => $rev->getId(),
1165 - 'user' => $user->getId(),
1166 - 'timestamp' => $rev->getTimestamp(),
1167 - 'quality' => $quality,
1168 - 'tags' => FlaggedRevision::flattenRevisionTags( $flags ),
1169 - 'img_name' => $fileData['name'],
1170 - 'img_timestamp' => $fileData['timestamp'],
1171 - 'img_sha1' => $fileData['sha1'],
1172 - 'templateVersions' => $poutput->getTemplateIds(),
1173 - 'fileVersions' => $poutput->getImageTimeKeys(),
1174 - 'flags' => implode( ',', $propFlags ),
1175 - ) );
1176 - $flaggedRevision->insertOn();
1177 - # Update the article review log
1178 - FlaggedRevsLog::updateReviewLog( $title,
1179 - $flags, array(), '', $rev->getId(), $oldSvId, true, $auto );
1180 -
1181 - # Update page and tracking tables and clear cache
1182 - FlaggedRevs::stableVersionUpdates( $title );
1183 -
1184 - wfProfileOut( __METHOD__ );
1185 - return true;
1186 - }
1187 -
1188 - /**
1189 - * Get JS script params
1190 - */
1191 - public static function getJSTagParams() {
1192 - self::load();
1193 - # Param to pass to JS function to know if tags are at quality level
1194 - $tagsJS = array();
1195 - foreach ( self::$dimensions as $tag => $x ) {
1196 - $tagsJS[$tag] = array();
1197 - $tagsJS[$tag]['levels'] = count( $x ) - 1;
1198 - $tagsJS[$tag]['quality'] = self::$minQL[$tag];
1199 - $tagsJS[$tag]['pristine'] = self::$minPL[$tag];
1200 - }
1201 - $params = array( 'tags' => (object)$tagsJS );
1202 - return (object)$params;
1203 - }
1204 -}
Index: trunk/extensions/FlaggedRevs/FRExtraCacheUpdate.php
@@ -1,189 +0,0 @@
2 -<?php
3 -/**
4 - * Class containing cache update methods and job construction
5 - * for the special case of purging pages due to dependancies
6 - * contained only in the stable version of pages.
7 - *
8 - * These dependancies should be limited in number as most pages should
9 - * have a stable version synced with the current version.
10 - */
11 -class FRExtraCacheUpdate {
12 - public $mTitle, $mTable;
13 - public $mRowsPerJob, $mRowsPerQuery;
14 -
15 - public function __construct( Title $titleTo ) {
16 - global $wgUpdateRowsPerJob, $wgUpdateRowsPerQuery;
17 - $this->mTitle = $titleTo;
18 - $this->mTable = 'flaggedrevs_tracking';
19 - $this->mRowsPerJob = $wgUpdateRowsPerJob;
20 - $this->mRowsPerQuery = $wgUpdateRowsPerQuery;
21 - }
22 -
23 - public function doUpdate() {
24 - # Fetch the IDs
25 - $dbr = wfGetDB( DB_SLAVE );
26 - $res = $dbr->select( $this->mTable, $this->getFromField(),
27 - $this->getToCondition(), __METHOD__ );
28 - # Check if there is anything to do...
29 - if ( $dbr->numRows( $res ) > 0 ) {
30 - # Do it right now?
31 - if ( $dbr->numRows( $res ) <= $this->mRowsPerJob ) {
32 - $this->invalidateIDs( $res );
33 - # Defer to job queue...
34 - } else {
35 - $this->insertJobs( $res );
36 - }
37 - }
38 - }
39 -
40 - protected function insertJobs( ResultWrapper $res ) {
41 - $numRows = $res->numRows();
42 - if ( !$numRows ) {
43 - return; // sanity check
44 - }
45 - $numBatches = ceil( $numRows / $this->mRowsPerJob );
46 - $realBatchSize = ceil( $numRows / $numBatches );
47 - $jobs = array();
48 - do {
49 - $first = $last = false; // first/last page_id of this batch
50 - # Get $realBatchSize items (or less if not enough)...
51 - for ( $i = 0; $i < $realBatchSize; $i++ ) {
52 - $row = $res->fetchRow();
53 - # Is there another row?
54 - if ( $row ) {
55 - $id = $row[0];
56 - $last = $id; // $id is the last page_id of this batch
57 - if ( $first === false )
58 - $first = $id; // set first page_id of this batch
59 - # Out of rows?
60 - } else {
61 - $id = false;
62 - break;
63 - }
64 - }
65 - # Insert batch into the queue if there is anything there
66 - if ( $first ) {
67 - $params = array(
68 - 'table' => $this->mTable,
69 - 'start' => $first,
70 - 'end' => $last,
71 - );
72 - $jobs[] = new FRExtraCacheUpdateJob( $this->mTitle, $params );
73 - }
74 - $start = $id; // Where the last ID left off
75 - } while ( $start );
76 - Job::batchInsert( $jobs );
77 - }
78 -
79 - public function getFromField() {
80 - return 'ftr_from';
81 - }
82 -
83 - public function getToCondition() {
84 - return array( 'ftr_namespace' => $this->mTitle->getNamespace(),
85 - 'ftr_title' => $this->mTitle->getDBkey() );
86 - }
87 -
88 - /**
89 - * Invalidate a set of IDs, right now
90 - */
91 - public function invalidateIDs( ResultWrapper $res ) {
92 - global $wgUseFileCache, $wgUseSquid;
93 - if ( $res->numRows() == 0 ) return; // sanity check
94 -
95 - $dbw = wfGetDB( DB_MASTER );
96 - $timestamp = $dbw->timestamp();
97 - $done = false;
98 -
99 - while ( !$done ) {
100 - # Get all IDs in this query into an array
101 - $ids = array();
102 - for ( $i = 0; $i < $this->mRowsPerQuery; $i++ ) {
103 - $row = $res->fetchRow();
104 - if ( $row ) {
105 - $ids[] = $row[0];
106 - } else {
107 - $done = true;
108 - break;
109 - }
110 - }
111 - if ( count( $ids ) == 0 ) break;
112 - # Update page_touched
113 - $dbw->update( 'page', array( 'page_touched' => $timestamp ),
114 - array( 'page_id' => $ids ), __METHOD__ );
115 - # Update static caches
116 - if ( $wgUseSquid || $wgUseFileCache ) {
117 - $titles = Title::newFromIDs( $ids );
118 - # Update squid cache
119 - if ( $wgUseSquid ) {
120 - $u = SquidUpdate::newFromTitles( $titles );
121 - $u->doUpdate();
122 - }
123 - # Update file cache
124 - if ( $wgUseFileCache ) {
125 - foreach ( $titles as $title ) {
126 - HTMLFileCache::clearFileCache( $title );
127 - }
128 - }
129 - }
130 - }
131 - }
132 -}
133 -
134 -/**
135 - * Job class for handling deferred FRExtraCacheUpdates
136 - * @ingroup JobQueue
137 - */
138 -class FRExtraCacheUpdateJob extends Job {
139 - var $table, $start, $end;
140 -
141 - /**
142 - * Construct a job
143 - * @param Title $title The title linked to
144 - * @param array $params Job parameters (table, start and end page_ids)
145 - * @param integer $id job_id
146 - */
147 - function __construct( $title, $params, $id = 0 ) {
148 - parent::__construct( 'flaggedrevs_CacheUpdate', $title, $params, $id );
149 - $this->table = $params['table'];
150 - $this->start = $params['start'];
151 - $this->end = $params['end'];
152 - }
153 -
154 - function run() {
155 - $update = new FRExtraCacheUpdate( $this->title );
156 - # Get query conditions
157 - $fromField = $update->getFromField();
158 - $conds = $update->getToCondition();
159 - if ( $this->start ) {
160 - $conds[] = "$fromField >= {$this->start}";
161 - }
162 - if ( $this->end ) {
163 - $conds[] = "$fromField <= {$this->end}";
164 - }
165 - # Run query to get page Ids
166 - $dbr = wfGetDB( DB_SLAVE );
167 - $res = $dbr->select( $this->table, $fromField, $conds, __METHOD__ );
168 - # Invalidate the pages
169 - $update->invalidateIDs( $res );
170 - return true;
171 - }
172 -}
173 -
174 -/**
175 - * Class for handling post-commit squid purge of a page
176 - */
177 -class FRSquidUpdate {
178 - protected $title;
179 -
180 - function __construct( Title $title ) {
181 - $this->title = $title;
182 - }
183 -
184 - function doUpdate() {
185 - # Purge squid for this page only
186 - $this->title->purgeSquid();
187 - # Clear file cache for this page only
188 - HTMLFileCache::clearFileCache( $this->title );
189 - }
190 -}
Index: trunk/extensions/FlaggedRevs/FlaggedRevision.php
@@ -1,793 +0,0 @@
2 -<?php
3 -/**
4 - * Class representing a stable version of a MediaWiki revision
5 - *
6 - * This contains a page revision, a file version, and versions
7 - * of templates and files (to determine template inclusion and thumbnails)
8 - */
9 -class FlaggedRevision {
10 - private $mRevision; // base revision
11 - private $mTemplates; // included template versions
12 - private $mFiles; // included file versions
13 - private $mFileSha1; // file version sha-1 (for revisions of File pages)
14 - private $mFileTimestamp; // file version timestamp (for revisions of File pages)
15 - /* Flagging metadata */
16 - private $mTimestamp;
17 - private $mQuality;
18 - private $mTags;
19 - private $mFlags;
20 - private $mUser; // reviewing user
21 - private $mFileName; // file name when reviewed
22 - /* Redundant fields for lazy-loading */
23 - private $mTitle;
24 - private $mPageId;
25 - private $mRevId;
26 - private $mStableTemplates;
27 - private $mStableFiles;
28 -
29 - /**
30 - * @param Row|array $row (DB row or array)
31 - * @return void
32 - */
33 - public function __construct( $row ) {
34 - if ( is_object( $row ) ) {
35 - $this->mRevId = intval( $row->fr_rev_id );
36 - $this->mPageId = intval( $row->fr_page_id );
37 - $this->mTimestamp = $row->fr_timestamp;
38 - $this->mQuality = intval( $row->fr_quality );
39 - $this->mTags = self::expandRevisionTags( strval( $row->fr_tags ) );
40 - $this->mFlags = explode( ',', $row->fr_flags );
41 - $this->mUser = intval( $row->fr_user );
42 - # Base Revision object
43 - $this->mRevision = new Revision( $row );
44 - # Image page revision relevant params
45 - $this->mFileName = $row->fr_img_name ? $row->fr_img_name : null;
46 - $this->mFileSha1 = $row->fr_img_sha1 ? $row->fr_img_sha1 : null;
47 - $this->mFileTimestamp = $row->fr_img_timestamp ?
48 - $row->fr_img_timestamp : null;
49 - # Optional fields
50 - $this->mTitle = isset( $row->page_namespace ) && isset( $row->page_title )
51 - ? Title::makeTitleSafe( $row->page_namespace, $row->page_title )
52 - : null;
53 - } elseif ( is_array( $row ) ) {
54 - $this->mRevId = intval( $row['rev_id'] );
55 - $this->mPageId = intval( $row['page_id'] );
56 - $this->mTimestamp = $row['timestamp'];
57 - $this->mQuality = intval( $row['quality'] );
58 - $this->mTags = self::expandRevisionTags( strval( $row['tags'] ) );
59 - $this->mFlags = explode( ',', $row['flags'] );
60 - $this->mUser = intval( $row['user'] );
61 - # Image page revision relevant params
62 - $this->mFileName = $row['img_name'] ? $row['img_name'] : null;
63 - $this->mFileSha1 = $row['img_sha1'] ? $row['img_sha1'] : null;
64 - $this->mFileTimestamp = $row['img_timestamp'] ?
65 - $row['img_timestamp'] : null;
66 - # Optional fields
67 - $this->mTemplates = isset( $row['templateVersions'] ) ?
68 - $row['templateVersions'] : null;
69 - $this->mFiles = isset( $row['fileVersions'] ) ?
70 - $row['fileVersions'] : null;
71 - } else {
72 - throw new MWException( 'FlaggedRevision constructor passed invalid row format.' );
73 - }
74 - }
75 -
76 - /**
77 - * Get a FlaggedRevision for a title and rev ID.
78 - * Note: will return NULL if the revision is deleted.
79 - * @param Title $title
80 - * @param int $revId
81 - * @param int $flags (FR_MASTER, FR_FOR_UPDATE)
82 - * @return FlaggedRevision|null (null on failure)
83 - */
84 - public static function newFromTitle( Title $title, $revId, $flags = 0 ) {
85 - if ( !FlaggedRevs::inReviewNamespace( $title ) ) {
86 - return null; // short-circuit
87 - }
88 - $options = array();
89 - # User master/slave as appropriate...
90 - if ( $flags & FR_FOR_UPDATE || $flags & FR_MASTER ) {
91 - $db = wfGetDB( DB_MASTER );
92 - if ( $flags & FR_FOR_UPDATE ) $options[] = 'FOR UPDATE';
93 - $pageId = $title->getArticleID( Title::GAID_FOR_UPDATE );
94 - } else {
95 - $db = wfGetDB( DB_SLAVE );
96 - $pageId = $title->getArticleID();
97 - }
98 - if ( !$pageId || !$revId ) {
99 - return null; // short-circuit query
100 - }
101 - # Skip deleted revisions
102 - $row = $db->selectRow(
103 - array( 'flaggedrevs', 'revision' ),
104 - self::selectFields(),
105 - array(
106 - 'fr_page_id' => $pageId,
107 - 'fr_rev_id' => $revId,
108 - 'rev_id = fr_rev_id',
109 - 'rev_page = fr_page_id',
110 - 'rev_deleted & ' . Revision::DELETED_TEXT => 0
111 - ),
112 - __METHOD__,
113 - $options
114 - );
115 - # Sorted from highest to lowest, so just take the first one if any
116 - if ( $row ) {
117 - $frev = new self( $row );
118 - $frev->mTitle = $title;
119 - return $frev;
120 - }
121 - return null;
122 - }
123 -
124 - /**
125 - * Get a FlaggedRevision of the stable version of a title.
126 - * @param Title $title, page title
127 - * @param int $flags (FR_MASTER, FR_FOR_UPDATE)
128 - * @return FlaggedRevision|null (null on failure)
129 - */
130 - public static function newFromStable( Title $title, $flags = 0 ) {
131 - if ( !FlaggedRevs::inReviewNamespace( $title ) ) {
132 - return null; // short-circuit
133 - }
134 - $options = array();
135 - # User master/slave as appropriate...
136 - if ( $flags & FR_FOR_UPDATE || $flags & FR_MASTER ) {
137 - $db = wfGetDB( DB_MASTER );
138 - if ( $flags & FR_FOR_UPDATE ) $options[] = 'FOR UPDATE';
139 - $pageId = $title->getArticleID( Title::GAID_FOR_UPDATE );
140 - } else {
141 - $db = wfGetDB( DB_SLAVE );
142 - $pageId = $title->getArticleID();
143 - }
144 - if ( !$pageId ) {
145 - return null; // short-circuit query
146 - }
147 - # Check tracking tables
148 - $row = $db->selectRow(
149 - array( 'flaggedpages', 'flaggedrevs', 'revision' ),
150 - self::selectFields(),
151 - array(
152 - 'fp_page_id' => $pageId,
153 - 'fr_page_id = fp_page_id',
154 - 'fr_rev_id = fp_stable',
155 - 'rev_id = fr_rev_id'
156 - ),
157 - __METHOD__,
158 - $options
159 - );
160 - if ( $row ) {
161 - $frev = new self( $row );
162 - $frev->mTitle = $title;
163 - return $frev;
164 - }
165 - return null;
166 - }
167 -
168 - /**
169 - * Get a FlaggedRevision of the stable version of a title.
170 - * Skips tracking tables to figure out new stable version.
171 - * @param Title $title, page title
172 - * @param int $flags (FR_MASTER, FR_FOR_UPDATE)
173 - * @param array $config, optional page config (use to skip queries)
174 - * @param string $precedence (latest,quality,pristine)
175 - * @return FlaggedRevision|null (null on failure)
176 - */
177 - public static function determineStable(
178 - Title $title, $flags = 0, $config = array(), $precedence = 'latest'
179 - ) {
180 - if ( !FlaggedRevs::inReviewNamespace( $title ) ) {
181 - return null; // short-circuit
182 - }
183 - $options = array();
184 - # User master/slave as appropriate...
185 - if ( $flags & FR_FOR_UPDATE || $flags & FR_MASTER ) {
186 - $db = wfGetDB( DB_MASTER );
187 - if ( $flags & FR_FOR_UPDATE ) $options[] = 'FOR UPDATE';
188 - $pageId = $title->getArticleID( Title::GAID_FOR_UPDATE );
189 - } else {
190 - $db = wfGetDB( DB_SLAVE );
191 - $pageId = $title->getArticleID();
192 - }
193 - if ( !$pageId ) {
194 - return null; // short-circuit query
195 - }
196 - # Get visiblity settings...
197 - if ( empty( $config ) ) {
198 - $config = FlaggedPageConfig::getStabilitySettings( $title, $flags );
199 - }
200 - if ( !$config['override'] && FlaggedRevs::useOnlyIfProtected() ) {
201 - return null; // page is not reviewable; no stable version
202 - }
203 - $row = null;
204 - $options['ORDER BY'] = 'fr_rev_id DESC';
205 - # Look for the latest pristine revision...
206 - if ( FlaggedRevs::pristineVersions() && $precedence !== 'latest' ) {
207 - $prow = $db->selectRow(
208 - array( 'flaggedrevs', 'revision' ),
209 - self::selectFields(),
210 - array( 'fr_page_id' => $pageId,
211 - 'fr_quality = ' . FR_PRISTINE,
212 - 'rev_id = fr_rev_id',
213 - 'rev_page = fr_page_id',
214 - 'rev_deleted & ' . Revision::DELETED_TEXT => 0
215 - ),
216 - __METHOD__,
217 - $options
218 - );
219 - # Looks like a plausible revision
220 - $row = $prow ? $prow : $row;
221 - }
222 - if ( $row && $precedence === 'pristine' ) {
223 - // we have what we want already
224 - # Look for the latest quality revision...
225 - } elseif ( FlaggedRevs::qualityVersions() && $precedence !== 'latest' ) {
226 - // If we found a pristine rev above, this one must be newer...
227 - $newerClause = $row ? "fr_rev_id > {$row->fr_rev_id}" : "1 = 1";
228 - $qrow = $db->selectRow(
229 - array( 'flaggedrevs', 'revision' ),
230 - self::selectFields(),
231 - array( 'fr_page_id' => $pageId,
232 - 'fr_quality = ' . FR_QUALITY,
233 - $newerClause,
234 - 'rev_id = fr_rev_id',
235 - 'rev_page = fr_page_id',
236 - 'rev_deleted & ' . Revision::DELETED_TEXT => 0
237 - ),
238 - __METHOD__,
239 - $options
240 - );
241 - $row = $qrow ? $qrow : $row;
242 - }
243 - # Do we have one? If not, try the latest reviewed revision...
244 - if ( !$row ) {
245 - $row = $db->selectRow(
246 - array( 'flaggedrevs', 'revision' ),
247 - self::selectFields(),
248 - array( 'fr_page_id' => $pageId,
249 - 'rev_id = fr_rev_id',
250 - 'rev_page = fr_page_id',
251 - 'rev_deleted & ' . Revision::DELETED_TEXT => 0
252 - ),
253 - __METHOD__,
254 - $options
255 - );
256 - if ( !$row ) return null;
257 - }
258 - $frev = new self( $row );
259 - $frev->mTitle = $title;
260 - return $frev;
261 - }
262 -
263 - /*
264 - * Insert a FlaggedRevision object into the database
265 - *
266 - * @return bool success
267 - */
268 - public function insertOn() {
269 - $dbw = wfGetDB( DB_MASTER );
270 - # Set any flagged revision flags
271 - $this->mFlags = array_merge( $this->mFlags, array( 'dynamic' ) ); // legacy
272 - # Build the inclusion data chunks
273 - $tmpInsertRows = array();
274 - foreach ( (array)$this->mTemplates as $namespace => $titleAndID ) {
275 - foreach ( $titleAndID as $dbkey => $id ) {
276 - $tmpInsertRows[] = array(
277 - 'ft_rev_id' => $this->getRevId(),
278 - 'ft_namespace' => (int)$namespace,
279 - 'ft_title' => $dbkey,
280 - 'ft_tmp_rev_id' => (int)$id
281 - );
282 - }
283 - }
284 - $fileInsertRows = array();
285 - foreach ( (array)$this->mFiles as $dbkey => $timeSHA1 ) {
286 - $fileInsertRows[] = array(
287 - 'fi_rev_id' => $this->getRevId(),
288 - 'fi_name' => $dbkey,
289 - 'fi_img_sha1' => strval( $timeSHA1['sha1'] ),
290 - 'fi_img_timestamp' => $timeSHA1['time'] ? // false => NULL
291 - $dbw->timestamp( $timeSHA1['time'] ) : null
292 - );
293 - }
294 - # Our review entry
295 - $revRow = array(
296 - 'fr_page_id' => $this->getPage(),
297 - 'fr_rev_id' => $this->getRevId(),
298 - 'fr_user' => $this->getUser(),
299 - 'fr_timestamp' => $dbw->timestamp( $this->getTimestamp() ),
300 - 'fr_comment' => '', # not used anymore
301 - 'fr_quality' => $this->getQuality(),
302 - 'fr_tags' => self::flattenRevisionTags( $this->getTags() ),
303 - 'fr_text' => '', # not used anymore
304 - 'fr_flags' => implode( ',', $this->mFlags ),
305 - 'fr_img_name' => $this->getFileName(),
306 - 'fr_img_timestamp' => $dbw->timestampOrNull( $this->getFileTimestamp() ),
307 - 'fr_img_sha1' => $this->getFileSha1()
308 - );
309 - # Update flagged revisions table
310 - $dbw->replace( 'flaggedrevs',
311 - array( array( 'fr_page_id', 'fr_rev_id' ) ), $revRow, __METHOD__ );
312 - # Clear out any previous garbage...
313 - $dbw->delete( 'flaggedtemplates',
314 - array( 'ft_rev_id' => $this->getRevId() ), __METHOD__ );
315 - # ...and insert template version data
316 - if ( $tmpInsertRows ) {
317 - $dbw->insert( 'flaggedtemplates', $tmpInsertRows, __METHOD__, 'IGNORE' );
318 - }
319 - # Clear out any previous garbage...
320 - $dbw->delete( 'flaggedimages',
321 - array( 'fi_rev_id' => $this->getRevId() ), __METHOD__ );
322 - # ...and insert file version data
323 - if ( $fileInsertRows ) {
324 - $dbw->insert( 'flaggedimages', $fileInsertRows, __METHOD__, 'IGNORE' );
325 - }
326 - return true;
327 - }
328 -
329 - /**
330 - * Get select fields for FlaggedRevision DB row (flaggedrevs/revision tables)
331 - * @return array
332 - */
333 - public static function selectFields() {
334 - return array_merge(
335 - Revision::selectFields(),
336 - array( 'fr_rev_id', 'fr_page_id', 'fr_user', 'fr_timestamp', 'fr_quality',
337 - 'fr_tags', 'fr_flags', 'fr_img_name', 'fr_img_sha1', 'fr_img_timestamp' )
338 - );
339 - }
340 -
341 - /**
342 - * @return integer revision ID
343 - */
344 - public function getRevId() {
345 - return $this->mRevId;
346 - }
347 -
348 - /**
349 - * @return Title title
350 - */
351 - public function getTitle() {
352 - if ( is_null( $this->mTitle ) ) {
353 - $this->mTitle = Title::newFromId( $this->mPageId );
354 - }
355 - return $this->mTitle;
356 - }
357 -
358 - /**
359 - * @return integer page ID
360 - */
361 - public function getPage() {
362 - return $this->mPageId;
363 - }
364 -
365 - /**
366 - * Get timestamp of review
367 - * @return string revision timestamp in MW format
368 - */
369 - public function getTimestamp() {
370 - return wfTimestamp( TS_MW, $this->mTimestamp );
371 - }
372 -
373 - /**
374 - * Get the corresponding revision
375 - * @return Revision
376 - */
377 - public function getRevision() {
378 - if ( is_null( $this->mRevision ) ) {
379 - # Get corresponding revision
380 - $rev = Revision::newFromId( $this->mRevId );
381 - # Save to cache
382 - $this->mRevision = $rev ? $rev : false;
383 - }
384 - return $this->mRevision;
385 - }
386 -
387 - /**
388 - * Check if the corresponding revision is the current revision
389 - * Note: here for convenience
390 - * @return bool
391 - */
392 - public function revIsCurrent() {
393 - $rev = $this->getRevision(); // corresponding revision
394 - return ( $rev ? $rev->isCurrent() : false );
395 - }
396 -
397 - /**
398 - * Get timestamp of the corresponding revision
399 - * Note: here for convenience
400 - * @return string revision timestamp in MW format
401 - */
402 - public function getRevTimestamp() {
403 - $rev = $this->getRevision(); // corresponding revision
404 - return ( $rev ? $rev->getTimestamp() : "0" );
405 - }
406 -
407 - /**
408 - * @return integer the user ID of the reviewer
409 - */
410 - public function getUser() {
411 - return $this->mUser;
412 - }
413 -
414 - /**
415 - * @return integer quality level (FR_CHECKED,FR_QUALITY,FR_PRISTINE)
416 - */
417 - public function getQuality() {
418 - return $this->mQuality;
419 - }
420 -
421 - /**
422 - * @return array tag metadata
423 - */
424 - public function getTags() {
425 - return $this->mTags;
426 - }
427 -
428 - /**
429 - * @return string, filename accosciated with this revision.
430 - * This returns NULL for non-image page revisions.
431 - */
432 - public function getFileName() {
433 - return $this->mFileName;
434 - }
435 -
436 - /**
437 - * @return string, sha1 key accosciated with this revision.
438 - * This returns NULL for non-image page revisions.
439 - */
440 - public function getFileSha1() {
441 - return $this->mFileSha1;
442 - }
443 -
444 - /**
445 - * @return string, timestamp accosciated with this revision.
446 - * This returns NULL for non-image page revisions.
447 - */
448 - public function getFileTimestamp() {
449 - return wfTimestampOrNull( TS_MW, $this->mFileTimestamp );
450 - }
451 -
452 - /**
453 - * @param User $user
454 - * @return bool
455 - */
456 - public function userCanSetFlags( $user ) {
457 - return FlaggedRevs::userCanSetFlags( $user, $this->mTags );
458 - }
459 -
460 - /**
461 - * Get original template versions at time of review
462 - * @param int $flags FR_MASTER
463 - * @return array template versions (ns -> dbKey -> rev Id)
464 - * Note: 0 used for template rev Id if it didn't exist
465 - */
466 - public function getTemplateVersions( $flags = 0 ) {
467 - if ( $this->mTemplates == null ) {
468 - $this->mTemplates = array();
469 - $db = ( $flags & FR_MASTER ) ?
470 - wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
471 - $res = $db->select( 'flaggedtemplates',
472 - array( 'ft_namespace', 'ft_title', 'ft_tmp_rev_id' ),
473 - array( 'ft_rev_id' => $this->getRevId() ),
474 - __METHOD__
475 - );
476 - foreach ( $res as $row ) {
477 - if ( !isset( $this->mTemplates[$row->ft_namespace] ) ) {
478 - $this->mTemplates[$row->ft_namespace] = array();
479 - }
480 - $this->mTemplates[$row->ft_namespace][$row->ft_title] = $row->ft_tmp_rev_id;
481 - }
482 - }
483 - return $this->mTemplates;
484 - }
485 -
486 - /**
487 - * Get original template versions at time of review
488 - * @param int $flags FR_MASTER
489 - * @return array file versions (dbKey => array('time' => MW timestamp,'sha1' => sha1) )
490 - * Note: false used for file timestamp/sha1 if it didn't exist
491 - */
492 - public function getFileVersions( $flags = 0 ) {
493 - if ( $this->mFiles == null ) {
494 - $this->mFiles = array();
495 - $db = ( $flags & FR_MASTER ) ?
496 - wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
497 - $res = $db->select( 'flaggedimages',
498 - array( 'fi_name', 'fi_img_timestamp', 'fi_img_sha1' ),
499 - array( 'fi_rev_id' => $this->getRevId() ),
500 - __METHOD__
501 - );
502 - foreach ( $res as $row ) {
503 - $reviewedTS = $reviewedSha1 = false;
504 - $fi_img_timestamp = trim( $row->fi_img_timestamp ); // may have \0's
505 - if ( $fi_img_timestamp ) {
506 - $reviewedTS = wfTimestamp( TS_MW, $fi_img_timestamp );
507 - $reviewedSha1 = strval( $row->fi_img_sha1 );
508 - }
509 - $this->mFiles[$row->fi_name] = array();
510 - $this->mFiles[$row->fi_name]['time'] = $reviewedTS;
511 - $this->mFiles[$row->fi_name]['sha1'] = $reviewedSha1;
512 - }
513 - }
514 - return $this->mFiles;
515 - }
516 -
517 - /**
518 - * Get the current stable version of the templates used at time of review
519 - * @param int $flags FR_MASTER
520 - * @return array template versions (ns -> dbKey -> rev Id)
521 - * Note: 0 used for template rev Id if it doesn't exist
522 - */
523 - public function getStableTemplateVersions( $flags = 0 ) {
524 - if ( $this->mStableTemplates == null ) {
525 - $this->mStableTemplates = array();
526 - $db = ( $flags & FR_MASTER ) ?
527 - wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
528 - $res = $db->select(
529 - array( 'flaggedtemplates', 'page', 'flaggedpages' ),
530 - array( 'ft_namespace', 'ft_title', 'fp_stable' ),
531 - array( 'ft_rev_id' => $this->getRevId() ),
532 - __METHOD__,
533 - array(),
534 - array(
535 - 'page' => array( 'LEFT JOIN',
536 - 'page_namespace = ft_namespace AND page_title = ft_title'),
537 - 'flaggedpages' => array( 'LEFT JOIN', 'fp_page_id = page_id' )
538 - )
539 - );
540 - foreach ( $res as $row ) {
541 - if ( !isset( $this->mStableTemplates[$row->ft_namespace] ) ) {
542 - $this->mStableTemplates[$row->ft_namespace] = array();
543 - }
544 - $revId = (int)$row->fp_stable; // 0 => none
545 - $this->mStableTemplates[$row->ft_namespace][$row->ft_title] = $revId;
546 - }
547 - }
548 - return $this->mStableTemplates;
549 - }
550 -
551 - /**
552 - * Get the current stable version of the files used at time of review
553 - * @param int $flags FR_MASTER
554 - * @return array file versions (dbKey => array('time' => MW timestamp,'sha1' => sha1) )
555 - * Note: false used for file timestamp/sha1 if it didn't exist
556 - */
557 - public function getStableFileVersions( $flags = 0 ) {
558 - if ( $this->mStableFiles == null ) {
559 - $this->mStableFiles = array();
560 - $db = ( $flags & FR_MASTER ) ?
561 - wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
562 - $res = $db->select(
563 - array( 'flaggedimages', 'page', 'flaggedpages', 'flaggedrevs' ),
564 - array( 'fi_name', 'fr_img_timestamp', 'fr_img_sha1' ),
565 - array( 'fi_rev_id' => $this->getRevId() ),
566 - __METHOD__,
567 - array(),
568 - array(
569 - 'page' => array( 'LEFT JOIN',
570 - 'page_namespace = ' . NS_FILE . ' AND page_title = fi_name' ),
571 - 'flaggedpages' => array( 'LEFT JOIN', 'fp_page_id = page_id' ),
572 - 'flaggedrevs' => array( 'LEFT JOIN',
573 - 'fr_page_id = fp_page_id AND fr_rev_id = fp_stable' )
574 - )
575 - );
576 - foreach ( $res as $row ) {
577 - $reviewedTS = $reviewedSha1 = false;
578 - if ( $row->fr_img_timestamp ) {
579 - $reviewedTS = wfTimestamp( TS_MW, $row->fr_img_timestamp );
580 - $reviewedSha1 = strval( $row->fr_img_sha1 );
581 - }
582 - $this->mStableFiles[$row->fi_name] = array();
583 - $this->mStableFiles[$row->fi_name]['time'] = $reviewedTS;
584 - $this->mStableFiles[$row->fi_name]['sha1'] = $reviewedSha1;
585 - }
586 - }
587 - return $this->mStableFiles;
588 - }
589 -
590 - /*
591 - * Fetch pending template changes for this reviewed page version.
592 - * For each template, the "version used" (for stable parsing) is:
593 - * (a) (the latest rev) if FR_INCLUDES_CURRENT. Might be non-existing.
594 - * (b) newest( stable rev, rev at time of review ) if FR_INCLUDES_STABLE
595 - * (c) ( rev at time of review ) if FR_INCLUDES_FREEZE
596 - * Pending changes exist for a template iff the template is used in
597 - * the current rev of this page and one of the following holds:
598 - * (a) Current template is newer than the "version used" above (updated)
599 - * (b) Current template exists and the "version used" was non-existing (created)
600 - * (c) Current template doesn't exist and the "version used" existed (deleted)
601 - *
602 - * @return array of (template title, rev ID in reviewed version) tuples
603 - */
604 - public function findPendingTemplateChanges() {
605 - if ( FlaggedRevs::inclusionSetting() == FR_INCLUDES_CURRENT ) {
606 - return array(); // short-circuit
607 - }
608 - $dbr = wfGetDB( DB_SLAVE );
609 - # Only get templates with stable or "review time" versions.
610 - # Note: ft_tmp_rev_id is nullable (for deadlinks), so use ft_title
611 - if ( FlaggedRevs::inclusionSetting() == FR_INCLUDES_STABLE ) {
612 - $reviewed = "ft_title IS NOT NULL OR fp_stable IS NOT NULL";
613 - } else {
614 - $reviewed = "ft_title IS NOT NULL";
615 - }
616 - $ret = $dbr->select(
617 - array( 'templatelinks', 'flaggedtemplates', 'page', 'flaggedpages' ),
618 - array( 'tl_namespace', 'tl_title', 'fp_stable', 'ft_tmp_rev_id', 'page_latest' ),
619 - array( 'tl_from' => $this->getPage(), $reviewed ), // current version templates
620 - __METHOD__,
621 - array(), /* OPTIONS */
622 - array(
623 - 'flaggedtemplates' => array( 'LEFT JOIN',
624 - array( 'ft_rev_id' => $this->getRevId(),
625 - 'ft_namespace = tl_namespace AND ft_title = tl_title' ) ),
626 - 'page' => array( 'LEFT JOIN',
627 - 'page_namespace = tl_namespace AND page_title = tl_title' ),
628 - 'flaggedpages' => array( 'LEFT JOIN', 'fp_page_id = page_id' )
629 - )
630 - );
631 - $tmpChanges = array();
632 - foreach ( $ret as $row ) {
633 - $title = Title::makeTitleSafe( $row->tl_namespace, $row->tl_title );
634 - $revIdDraft = (int)$row->page_latest; // may be NULL
635 - if ( FlaggedRevs::inclusionSetting() == FR_INCLUDES_STABLE ) {
636 - # Select newest of (stable rev, rev when reviewed) as "version used"
637 - $revIdStable = (int)max( $row->fp_stable, $row->ft_tmp_rev_id );
638 - } else {
639 - $revIdStable = (int)$row->ft_tmp_rev_id; // may be NULL
640 - }
641 - # Compare to current...
642 - $updated = false; // edited/created
643 - if ( $revIdDraft && $revIdDraft != $revIdStable ) {
644 - $dRev = Revision::newFromId( $revIdDraft );
645 - $sRev = Revision::newFromId( $revIdStable );
646 - # Don't do this for null edits (like protection) (bug 25919)
647 - if ( $dRev && $sRev && $dRev->getTextId() != $sRev->getTextId() ) {
648 - $updated = true;
649 - }
650 - }
651 - $deleted = ( !$revIdDraft && $revIdStable ); // later deleted
652 - if ( $deleted || $updated ) {
653 - $tmpChanges[] = array( $title, $revIdStable );
654 - }
655 - }
656 - return $tmpChanges;
657 - }
658 -
659 - /*
660 - * Fetch pending file changes for this reviewed page version.
661 - * For each file, the "version used" (for stable parsing) is:
662 - * (a) (the latest rev) if FR_INCLUDES_CURRENT. Might be non-existing.
663 - * (b) newest( stable rev, rev at time of review ) if FR_INCLUDES_STABLE
664 - * (c) ( rev at time of review ) if FR_INCLUDES_FREEZE
665 - * Pending changes exist for a file iff the file is used in
666 - * the current rev of this page and one of the following holds:
667 - * (a) Current file is newer than the "version used" above (updated)
668 - * (b) Current file exists and the "version used" was non-existing (created)
669 - * (c) Current file doesn't exist and the "version used" existed (deleted)
670 - *
671 - * @param string $noForeign Using 'noForeign' skips foreign file updates (bug 15748)
672 - * @return array of (file title, MW file timestamp in reviewed version) tuples
673 - */
674 - public function findPendingFileChanges( $noForeign = false ) {
675 - if ( FlaggedRevs::inclusionSetting() == FR_INCLUDES_CURRENT ) {
676 - return array(); // short-circuit
677 - }
678 - $dbr = wfGetDB( DB_SLAVE );
679 - # Only get templates with stable or "review time" versions.
680 - # Note: fi_img_timestamp is nullable (for deadlinks), so use fi_name
681 - if ( FlaggedRevs::inclusionSetting() == FR_INCLUDES_STABLE ) {
682 - $reviewed = "fi_name IS NOT NULL OR fr_img_timestamp IS NOT NULL";
683 - } else {
684 - $reviewed = "fi_name IS NOT NULL";
685 - }
686 - $ret = $dbr->select(
687 - array( 'imagelinks', 'flaggedimages', 'page', 'flaggedpages', 'flaggedrevs' ),
688 - array( 'il_to', 'fi_img_timestamp', 'fr_img_timestamp' ),
689 - array( 'il_from' => $this->getPage(), $reviewed ), // current version files
690 - __METHOD__,
691 - array(), /* OPTIONS */
692 - array(
693 - 'flaggedimages' => array( 'LEFT JOIN',
694 - array( 'fi_rev_id' => $this->getRevId(), 'fi_name = il_to' ) ),
695 - 'page' => array( 'LEFT JOIN',
696 - 'page_namespace = ' . NS_FILE . ' AND page_title = il_to' ),
697 - 'flaggedpages' => array( 'LEFT JOIN', 'fp_page_id = page_id' ),
698 - 'flaggedrevs' => array( 'LEFT JOIN',
699 - 'fr_page_id = fp_page_id AND fr_rev_id = fp_stable' )
700 - )
701 - );
702 - $fileChanges = array();
703 - foreach ( $ret as $row ) {
704 - $title = Title::makeTitleSafe( NS_FILE, $row->il_to );
705 - $reviewedTS = trim( $row->fi_img_timestamp ); // may have \0's
706 - $reviewedTS = $reviewedTS ? wfTimestamp( TS_MW, $reviewedTS ) : null;
707 - if ( FlaggedRevs::inclusionSetting() == FR_INCLUDES_STABLE ) {
708 - $stableTS = wfTimestampOrNull( TS_MW, $row->fr_img_timestamp );
709 - # Select newest of (stable rev, rev when reviewed) as "version used"
710 - $tsStable = max( $stableTS, $reviewedTS );
711 - } else {
712 - $tsStable = $reviewedTS;
713 - }
714 - # Compare this version to the current version and check for things
715 - # that would make the stable version unsynced with the draft...
716 - $file = wfFindFile( $title ); // current file version
717 - if ( $file ) { // file exists
718 - if ( $noForeign === 'noForeign' && !$file->isLocal() ) {
719 - # Avoid counting edits to Commons files, which can effect
720 - # many pages, as there is no expedient way to review them.
721 - $updated = !$tsStable; // created (ignore new versions)
722 - } else {
723 - $updated = ( $file->getTimestamp() > $tsStable ); // edited/created
724 - }
725 - $deleted = $tsStable // included file deleted after review
726 - && $file->getTimestamp() != $tsStable
727 - && !wfFindFile( $title, array( 'time' => $tsStable ) );
728 - } else { // file doesn't exists
729 - $updated = false;
730 - $deleted = (bool)$tsStable; // included file deleted after review
731 - }
732 - if ( $deleted || $updated ) {
733 - $fileChanges[] = array( $title, $tsStable );
734 - }
735 - }
736 - return $fileChanges;
737 - }
738 -
739 - /**
740 - * Get text of the corresponding revision
741 - * @return string|false revision timestamp in MW format
742 - */
743 - public function getRevText() {
744 - # Get corresponding revision
745 - $rev = $this->getRevision();
746 - $text = $rev ? $rev->getText() : false;
747 - return $text;
748 - }
749 -
750 - /**
751 - * Get flags for a revision
752 - * @param string $tags
753 - * @return array
754 - */
755 - public static function expandRevisionTags( $tags ) {
756 - $flags = array();
757 - foreach ( FlaggedRevs::getTags() as $tag ) {
758 - $flags[$tag] = 0; // init all flags values to zero
759 - }
760 - $tags = str_replace( '\n', "\n", $tags ); // B/C, old broken rows
761 - // Tag string format is <tag:val\ntag:val\n...>
762 - $tags = explode( "\n", $tags );
763 - foreach ( $tags as $tuple ) {
764 - $set = explode( ':', $tuple, 2 );
765 - if ( count( $set ) == 2 ) {
766 - list( $tag, $value ) = $set;
767 - $value = max( 0, (int)$value ); // validate
768 - # Add only currently recognized tags
769 - if ( isset( $flags[$tag] ) ) {
770 - $levels = FlaggedRevs::getTagLevels( $tag );
771 - # If a level was removed, default to the highest...
772 - $flags[$tag] = min( $value, count( $levels ) - 1 );
773 - }
774 - }
775 - }
776 - return $flags;
777 - }
778 -
779 - /**
780 - * Get flags for a revision
781 - * @param array $tags
782 - * @return string
783 - */
784 - public static function flattenRevisionTags( array $tags ) {
785 - $flags = '';
786 - foreach ( $tags as $tag => $value ) {
787 - # Add only currently recognized ones
788 - if ( FlaggedRevs::getTagLevels( $tag ) ) {
789 - $flags .= $tag . ':' . intval( $value ) . "\n";
790 - }
791 - }
792 - return $flags;
793 - }
794 -}
Index: trunk/extensions/FlaggedRevs/FRDependencyUpdate.php
@@ -1,235 +0,0 @@
2 -<?php
3 -/**
4 - * Class containing update methods for tracking links that
5 - * are only in the stable version of pages. Used only for caching.
6 - */
7 -class FRDependencyUpdate {
8 - protected $title;
9 - protected $sLinks;
10 - protected $sTemplates;
11 - protected $sImages;
12 - protected $sCategories;
13 - protected $dbw;
14 -
15 - public function __construct( Title $title, ParserOutput $stableOutput ) {
16 - $this->title = $title;
17 - # Stable version links
18 - $this->sLinks = $stableOutput->getLinks();
19 - $this->sTemplates = $stableOutput->getTemplates();
20 - $this->sImages = $stableOutput->getImages();
21 - $this->sCategories = $stableOutput->getCategories();
22 - }
23 -
24 - public function doUpdate() {
25 - $deps = array();
26 - # Get any links that are only in the stable version...
27 - $cLinks = $this->getCurrentVersionLinks();
28 - foreach ( $this->sLinks as $ns => $titles ) {
29 - foreach ( $titles as $title => $pageId ) {
30 - if ( !isset( $cLinks[$ns][$title] ) ) {
31 - self::addDependency( $deps, $ns, $title );
32 - }
33 - }
34 - }
35 - # Get any images that are only in the stable version...
36 - $cImages = $this->getCurrentVersionImages();
37 - foreach ( $this->sImages as $image => $n ) {
38 - if ( !isset( $cImages[$image] ) ) {
39 - self::addDependency( $deps, NS_FILE, $image );
40 - }
41 - }
42 - # Get any templates that are only in the stable version...
43 - $cTemplates = $this->getCurrentVersionTemplates();
44 - foreach ( $this->sTemplates as $ns => $titles ) {
45 - foreach ( $titles as $title => $id ) {
46 - if ( !isset( $cTemplates[$ns][$title] ) ) {
47 - self::addDependency( $deps, $ns, $title );
48 - }
49 - }
50 - }
51 - # Get any categories that are only in the stable version...
52 - $cCategories = $this->getCurrentVersionCategories();
53 - foreach ( $this->sCategories as $category => $sort ) {
54 - if ( !isset( $cCategories[$category] ) ) {
55 - self::addDependency( $deps, NS_CATEGORY, $category );
56 - }
57 - }
58 - # Get any dependency tracking changes
59 - $existing = $this->getExistingDeps();
60 - # Do incremental updates...
61 - if ( $existing != $deps ) {
62 - $existing = $this->getExistingDeps( FR_MASTER );
63 - $insertions = $this->getDepInsertions( $existing, $deps );
64 - $deletions = $this->getDepDeletions( $existing, $deps );
65 - $dbw = wfGetDB( DB_MASTER );
66 - # Delete removed links
67 - if ( $deletions ) {
68 - $dbw->delete( 'flaggedrevs_tracking', $deletions, __METHOD__ );
69 - }
70 - # Add any new links
71 - if ( $insertions ) {
72 - $dbw->insert( 'flaggedrevs_tracking', $insertions, __METHOD__, 'IGNORE' );
73 - }
74 - }
75 - }
76 -
77 - /*
78 - * Get existing cache dependancies
79 - * @param int $flags FR_MASTER
80 - * @return array (ns => dbKey => 1)
81 - */
82 - protected function getExistingDeps( $flags = 0 ) {
83 - $db = ( $flags & FR_MASTER ) ?
84 - wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
85 - $res = $db->select( 'flaggedrevs_tracking',
86 - array( 'ftr_namespace', 'ftr_title' ),
87 - array( 'ftr_from' => $this->title->getArticleId() ),
88 - __METHOD__
89 - );
90 - $arr = array();
91 - foreach( $res as $row ) {
92 - if ( !isset( $arr[$row->ftr_namespace] ) ) {
93 - $arr[$row->ftr_namespace] = array();
94 - }
95 - $arr[$row->ftr_namespace][$row->ftr_title] = 1;
96 - }
97 - return $arr;
98 - }
99 -
100 - /*
101 - * Get INSERT rows for cache dependancies in $new but not in $existing
102 - * @return array
103 - */
104 - protected function getDepInsertions( array $existing, array $new ) {
105 - $arr = array();
106 - foreach ( $new as $ns => $dbkeys ) {
107 - if ( isset( $existing[$ns] ) ) {
108 - $diffs = array_diff_key( $dbkeys, $existing[$ns] );
109 - } else {
110 - $diffs = $dbkeys;
111 - }
112 - foreach ( $diffs as $dbk => $id ) {
113 - $arr[] = array(
114 - 'ftr_from' => $this->title->getArticleId(),
115 - 'ftr_namespace' => $ns,
116 - 'ftr_title' => $dbk
117 - );
118 - }
119 - }
120 - return $arr;
121 - }
122 -
123 - /*
124 - * Get WHERE clause to delete items in $existing but not in $new
125 - * @return mixed (array/false)
126 - */
127 - protected function getDepDeletions( array $existing, array $new ) {
128 - $del = array();
129 - foreach ( $existing as $ns => $dbkeys ) {
130 - if ( isset( $new[$ns] ) ) {
131 - $del[$ns] = array_diff_key( $existing[$ns], $new[$ns] );
132 - } else {
133 - $del[$ns] = $existing[$ns];
134 - }
135 - }
136 - if ( $del ) {
137 - $clause = self::makeWhereFrom2d( $del, wfGetDB( DB_MASTER ) );
138 - if ( $clause ) {
139 - return array( $clause, 'ftr_from' => $this->title->getArticleId() );
140 - }
141 - }
142 - return false;
143 - }
144 -
145 - // Make WHERE clause to match $arr titles
146 - protected static function makeWhereFrom2d( &$arr, $db ) {
147 - $lb = new LinkBatch();
148 - $lb->setArray( $arr );
149 - return $lb->constructSet( 'ftr', $db );
150 - }
151 -
152 - protected static function addDependency( array &$deps, $ns, $dbKey ) {
153 - if ( !isset( $deps[$ns] ) ) {
154 - $deps[$ns] = array();
155 - }
156 - $deps[$ns][$dbKey] = 1;
157 - }
158 -
159 - /**
160 - * Get an array of existing links, as a 2-D array
161 - * @return array (ns => dbKey => 1)
162 - */
163 - protected function getCurrentVersionLinks() {
164 - $dbr = wfGetDB( DB_SLAVE );
165 - $res = $dbr->select( 'pagelinks',
166 - array( 'pl_namespace', 'pl_title' ),
167 - array( 'pl_from' => $this->title->getArticleId() ),
168 - __METHOD__
169 - );
170 - $arr = array();
171 - foreach( $res as $row ) {
172 - if ( !isset( $arr[$row->pl_namespace] ) ) {
173 - $arr[$row->pl_namespace] = array();
174 - }
175 - $arr[$row->pl_namespace][$row->pl_title] = 1;
176 - }
177 - return $arr;
178 - }
179 -
180 - /**
181 - * Get an array of existing templates, as a 2-D array
182 - * @return array (ns => dbKey => 1)
183 - */
184 - protected function getCurrentVersionTemplates() {
185 - $dbr = wfGetDB( DB_SLAVE );
186 - $res = $dbr->select( 'templatelinks',
187 - array( 'tl_namespace', 'tl_title' ),
188 - array( 'tl_from' => $this->title->getArticleId() ),
189 - __METHOD__
190 - );
191 - $arr = array();
192 - foreach( $res as $row ) {
193 - if ( !isset( $arr[$row->tl_namespace] ) ) {
194 - $arr[$row->tl_namespace] = array();
195 - }
196 - $arr[$row->tl_namespace][$row->tl_title] = 1;
197 - }
198 - return $arr;
199 - }
200 -
201 - /**
202 - * Get an array of existing images, image names in the keys
203 - * @return array (dbKey => 1)
204 - */
205 - protected function getCurrentVersionImages() {
206 - $dbr = wfGetDB( DB_SLAVE );
207 - $res = $dbr->select( 'imagelinks',
208 - array( 'il_to' ),
209 - array( 'il_from' => $this->title->getArticleId() ),
210 - __METHOD__
211 - );
212 - $arr = array();
213 - foreach( $res as $row ) {
214 - $arr[$row->il_to] = 1;
215 - }
216 - return $arr;
217 - }
218 -
219 - /**
220 - * Get an array of existing categories, with the name in the key and sort key in the value.
221 - * @return array (category => sortkey)
222 - */
223 - protected function getCurrentVersionCategories() {
224 - $dbr = wfGetDB( DB_SLAVE );
225 - $res = $dbr->select( 'categorylinks',
226 - array( 'cl_to', 'cl_sortkey' ),
227 - array( 'cl_from' => $this->title->getArticleId() ),
228 - __METHOD__
229 - );
230 - $arr = array();
231 - foreach( $res as $row ) {
232 - $arr[$row->cl_to] = $row->cl_sortkey;
233 - }
234 - return $arr;
235 - }
236 -}
Index: trunk/extensions/FlaggedRevs/FlaggedPageConfig.php
@@ -1,242 +0,0 @@
2 -<?php
3 -/*
4 -* Page stability configuration functions
5 -*/
6 -class FlaggedPageConfig {
7 - /**
8 - * Get visibility settings/restrictions for a page
9 - * @param Title $title, page title
10 - * @param int $flags, FR_MASTER
11 - * @return array (associative) (select,override,autoreview,expiry)
12 - */
13 - public static function getStabilitySettings( Title $title, $flags = 0 ) {
14 - $db = ( $flags & FR_MASTER ) ?
15 - wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
16 - $row = $db->selectRow( 'flaggedpage_config',
17 - self::selectFields(),
18 - array( 'fpc_page_id' => $title->getArticleID() ),
19 - __METHOD__
20 - );
21 - return self::getVisibilitySettingsFromRow( $row );
22 - }
23 -
24 - /**
25 - * @return array basic select fields for FlaggedPageConfig DB row
26 - */
27 - public static function selectFields() {
28 - return array( 'fpc_override', 'fpc_level', 'fpc_expiry' );
29 - }
30 -
31 - /**
32 - * Get page configuration settings from a DB row
33 - */
34 - public static function getVisibilitySettingsFromRow( $row ) {
35 - if ( $row ) {
36 - # This code should be refactored, now that it's being used more generally.
37 - $expiry = Block::decodeExpiry( $row->fpc_expiry );
38 - # Only apply the settings if they haven't expired
39 - if ( !$expiry || $expiry < wfTimestampNow() ) {
40 - $row = null; // expired
41 - self::purgeExpiredConfigurations();
42 - }
43 - }
44 - // Is there a non-expired row?
45 - if ( $row ) {
46 - $level = $row->fpc_level;
47 - if ( !self::isValidRestriction( $row->fpc_level ) ) {
48 - $level = ''; // site default; ignore fpc_level
49 - }
50 - $config = array(
51 - 'override' => $row->fpc_override ? 1 : 0,
52 - 'autoreview' => $level,
53 - 'expiry' => Block::decodeExpiry( $row->fpc_expiry ) // TS_MW
54 - );
55 - # If there are protection levels defined check if this is valid...
56 - if ( FlaggedRevs::useProtectionLevels() ) {
57 - $level = self::getProtectionLevel( $config );
58 - if ( $level == 'invalid' || $level == 'none' ) {
59 - // If 'none', make sure expiry is 'infinity'
60 - $config = self::getDefaultVisibilitySettings(); // revert to default (none)
61 - }
62 - }
63 - } else {
64 - # Return the default config if this page doesn't have its own
65 - $config = self::getDefaultVisibilitySettings();
66 - }
67 - return $config;
68 - }
69 -
70 - /**
71 - * Get default stability configuration settings
72 - * @return array
73 - */
74 - public static function getDefaultVisibilitySettings() {
75 - return array(
76 - # Keep this consistent: 1 => override, 0 => don't
77 - 'override' => FlaggedRevs::isStableShownByDefault() ? 1 : 0,
78 - 'autoreview' => '',
79 - 'expiry' => 'infinity'
80 - );
81 - }
82 -
83 - /**
84 - * Set the stability configuration settings for a page
85 - * @param Title $title
86 - * @param array $config
87 - * @return bool Row changed
88 - */
89 - public static function setStabilitySettings( Title $title, array $config ) {
90 - $dbw = wfGetDB( DB_MASTER );
91 - # If setting to site default values and there is a row then erase it
92 - if ( self::configIsReset( $config ) ) {
93 - $dbw->delete( 'flaggedpage_config',
94 - array( 'fpc_page_id' => $title->getArticleID() ),
95 - __METHOD__
96 - );
97 - $changed = ( $dbw->affectedRows() != 0 ); // did this do anything?
98 - # Otherwise, add/replace row if we are not just setting it to the site default
99 - } else {
100 - $dbExpiry = Block::encodeExpiry( $config['expiry'], $dbw );
101 - # Get current config...
102 - $oldRow = $dbw->selectRow( 'flaggedpage_config',
103 - array( 'fpc_override', 'fpc_level', 'fpc_expiry' ),
104 - array( 'fpc_page_id' => $title->getArticleID() ),
105 - __METHOD__,
106 - 'FOR UPDATE' // lock
107 - );
108 - # Check if this is not the same config as the existing (if any) row
109 - $changed = ( !$oldRow // no previous config
110 - || $oldRow->fpc_override != $config['override'] // ...override changed, or...
111 - || $oldRow->fpc_level != $config['autoreview'] // ...autoreview level changed, or...
112 - || $oldRow->fpc_expiry != $dbExpiry // ...expiry changed
113 - );
114 - # If the new config is different, replace the old row...
115 - if ( $changed ) {
116 - $dbw->replace( 'flaggedpage_config',
117 - array( 'PRIMARY' ),
118 - array(
119 - 'fpc_page_id' => $title->getArticleID(),
120 - 'fpc_select' => -1, // unused
121 - 'fpc_override' => (int)$config['override'],
122 - 'fpc_level' => $config['autoreview'],
123 - 'fpc_expiry' => $dbExpiry
124 - ),
125 - __METHOD__
126 - );
127 - }
128 - }
129 - return $changed;
130 - }
131 -
132 - /**
133 - * Does this config equal the default settings?
134 - * @param array $config
135 - * @return bool
136 - */
137 - public static function configIsReset( array $config ) {
138 - if ( FlaggedRevs::useOnlyIfProtected() ) {
139 - return ( $config['autoreview'] == '' );
140 - } else {
141 - return ( $config['override'] == FlaggedRevs::isStableShownByDefault()
142 - && $config['autoreview'] == '' );
143 - }
144 - }
145 -
146 - /**
147 - * Find what protection level a config is in
148 - * @param array $config
149 - * @return string
150 - */
151 - public static function getProtectionLevel( array $config ) {
152 - if ( !FlaggedRevs::useProtectionLevels() ) {
153 - throw new MWException( '$wgFlaggedRevsProtection is disabled' );
154 - }
155 - $defaultConfig = self::getDefaultVisibilitySettings();
156 - # Check if the page is not protected at all...
157 - if ( $config['override'] == $defaultConfig['override']
158 - && $config['autoreview'] == '' )
159 - {
160 - return "none"; // not protected
161 - }
162 - # All protection levels have 'override' on
163 - if ( $config['override'] ) {
164 - # The levels are defined by the 'autoreview' settings
165 - if ( in_array( $config['autoreview'], FlaggedRevs::getRestrictionLevels() ) ) {
166 - return $config['autoreview'];
167 - }
168 - }
169 - return "invalid";
170 - }
171 -
172 - /**
173 - * Check if an fpc_level value is valid
174 - * @param string $right
175 - */
176 - protected static function isValidRestriction( $right ) {
177 - if ( $right == '' ) {
178 - return true; // no restrictions (none)
179 - }
180 - return in_array( $right, FlaggedRevs::getRestrictionLevels(), true );
181 - }
182 -
183 - /**
184 - * Purge expired restrictions from the flaggedpage_config table.
185 - * The stable version of pages may change and invalidation may be required.
186 - */
187 - public static function purgeExpiredConfigurations() {
188 - if ( wfReadOnly() ) return;
189 - # Get a separate master session for this transaction (deadlock avoidance)
190 - $lb = wfGetLBFactory()->newMainLB();
191 - $dbw = $lb->getConnection( DB_MASTER );
192 - # Find pages with expired configs...
193 - $config = self::getDefaultVisibilitySettings(); // config is to be reset
194 - $encCutoff = $dbw->addQuotes( $dbw->timestamp() );
195 - $ret = $dbw->select(
196 - array( 'flaggedpage_config', 'page' ),
197 - array( 'fpc_page_id', 'page_namespace', 'page_title' ),
198 - array( 'page_id = fpc_page_id', 'fpc_expiry < ' . $encCutoff ),
199 - __METHOD__
200 - // array( 'FOR UPDATE' )
201 - );
202 - # Figured out to do with each page...
203 - $pagesClearConfig = array();
204 - $pagesClearTracking = $titlesClearTracking = array();
205 - foreach ( $ret as $row ) {
206 - # If FlaggedRevs got "turned off" (in protection config)
207 - # for this page, then clear it from the tracking tables...
208 - if ( FlaggedRevs::useOnlyIfProtected() && !$config['override'] ) {
209 - $pagesClearTracking[] = $row->fpc_page_id; // no stable version
210 - $titlesClearTracking[] = Title::newFromRow( $row ); // no stable version
211 - }
212 - $pagesClearConfig[] = $row->fpc_page_id; // page with expired config
213 - }
214 - # Clear the expired config for these pages...
215 - if ( count( $pagesClearConfig ) ) {
216 - $dbw->delete( 'flaggedpage_config',
217 - array( 'fpc_page_id' => $pagesClearConfig, 'fpc_expiry < ' . $encCutoff ),
218 - __METHOD__
219 - );
220 - }
221 - # Clear the tracking rows and update page_touched for the
222 - # pages in $pagesClearConfig that do now have a stable version...
223 - if ( count( $pagesClearTracking ) ) {
224 - FlaggedRevs::clearTrackingRows( $pagesClearTracking );
225 - $dbw->update( 'page',
226 - array( 'page_touched' => $dbw->timestamp() ),
227 - array( 'page_id' => $pagesClearTracking ),
228 - __METHOD__
229 - );
230 - }
231 - # Also, clear their squid caches and purge other pages that use this page.
232 - # NOTE: all of these updates are deferred via $wgDeferredUpdateList.
233 - foreach ( $titlesClearTracking as $title ) {
234 - FlaggedRevs::purgeSquid( $title );
235 - if ( FlaggedRevs::inclusionSetting() == FR_INCLUDES_STABLE ) {
236 - FlaggedRevs::HTMLCacheUpdates( $title ); // purge pages that use this page
237 - }
238 - }
239 - # Commit this transaction and close session
240 - $lb->commitMasterChanges();
241 - $lb->closeAll();
242 - }
243 -}
Index: trunk/extensions/FlaggedRevs/FRUserActivity.php
@@ -1,176 +0,0 @@
2 -<?php
3 -/*
4 -* Class of utility functions for getting/tracking user activity
5 -*/
6 -class FRUserActivity {
7 - /**
8 - * Get number of active users watching a page
9 - * @param Title $title
10 - * @return int
11 - */
12 - public static function numUsersWatchingPage( Title $title ) {
13 - global $wgMemc, $wgCookieExpiration;
14 - # Check the cache...
15 - $key = wfMemcKey( 'flaggedrevs', 'usersWatching', $title->getArticleID() );
16 - $val = $wgMemc->get( $key );
17 - if ( is_int( $val ) ) {
18 - return $val; // cache hit
19 - }
20 - # Get number of active editors watching this page...
21 - $dbr = wfGetDB( DB_SLAVE );
22 - $cutoff = $dbr->timestamp( wfTimestamp( TS_UNIX ) - 2 * $wgCookieExpiration );
23 - $count = (int)$dbr->selectField(
24 - array( 'watchlist', 'user' ),
25 - 'COUNT(*)',
26 - array(
27 - 'wl_namespace' => $title->getNamespace(),
28 - 'wl_title' => $title->getDBkey(),
29 - 'wl_user = user_id',
30 - 'user_touched > ' . $dbr->addQuotes( $cutoff ) // logged in or out
31 - ),
32 - __METHOD__,
33 - array( 'USE INDEX' => array( 'watchlist' => 'namespace_title' ) )
34 - );
35 - if ( $count > 10 ) {
36 - # Save new value to cache (more aggresive for larger counts)
37 - $wgMemc->set( $key, $count, ( $count > 200 ) ? 30*60 : 5*60 );
38 - }
39 -
40 - return $count;
41 - }
42 -
43 - /*
44 - * Get who is currently reviewing a page
45 - * @param int $pageId
46 - * @return array (username or null, MW timestamp or null)
47 - */
48 - public static function getUserReviewingPage( $pageId ) {
49 - global $wgMemc;
50 - $key = wfMemcKey( 'flaggedrevs', 'userReviewingPage', $pageId );
51 - $val = $wgMemc->get( $key );
52 - if ( is_array( $val ) && count( $val ) == 2 ) {
53 - return $val;
54 - }
55 - return array( null, null );
56 - }
57 -
58 - /*
59 - * Check is someone is currently reviewing a page
60 - * @param int $pageId
61 - * @return bool
62 - */
63 - public static function pageIsUnderReview( $pageId ) {
64 - $m = self::getUserReviewingPage( $pageId );
65 - return ( $m[0] !== null );
66 - }
67 -
68 - /*
69 - * Set the flag for who is reviewing a page if not already set by someone
70 - * @param User $user
71 - * @param int $pageId
72 - * @return bool flag set
73 - */
74 - public static function setUserReviewingPage( $user, $pageId ) {
75 - global $wgMemc;
76 - $key = wfMemcKey( 'flaggedrevs', 'userReviewingPage', $pageId );
77 - $val = array( $user->getName(), wfTimestampNow() );
78 - $wasSet = false;
79 -
80 - $wgMemc->lock( $key, 4 ); // 4 sec timeout
81 - if ( !$wgMemc->get( $key ) ) { // no flag set
82 - $wgMemc->set( $key, $val, 20*60 ); // 20 min
83 - $wasSet = true;
84 - }
85 - $wgMemc->unlock( $key );
86 -
87 - return $wasSet;
88 - }
89 -
90 - /*
91 - * Clear the flag for who is reviewing a page
92 - * @param User $user
93 - * @param int $pageId
94 - */
95 - public static function clearUserReviewingPage( $user, $pageId ) {
96 - global $wgMemc;
97 - $key = wfMemcKey( 'flaggedrevs', 'userReviewingPage', $pageId );
98 - $wgMemc->lock( $key, 4 ); // 4 sec timeout
99 - $val = $wgMemc->get( $key );
100 - if ( is_array( $val ) && count( $val ) == 2 ) { // flag set
101 - list( $u, $ts ) = $val;
102 - if ( $u === $user->getName() ) {
103 - $wgMemc->delete( $key );
104 - }
105 - }
106 - $this->unlock();
107 - }
108 -
109 - /*
110 - * Get who is currently reviewing a diff
111 - * @param int $oldId
112 - * @param int $newId
113 - * @return array (username or null, MW timestamp or null)
114 - */
115 - public static function getUserReviewingDiff( $oldId, $newId ) {
116 - global $wgMemc;
117 - $key = wfMemcKey( 'flaggedrevs', 'userReviewingDiff', $oldId, $newId );
118 - $val = $wgMemc->get( $key );
119 - if ( is_array( $val ) && count( $val ) == 2 ) {
120 - return $val;
121 - }
122 - return array( null, null );
123 - }
124 -
125 - /*
126 - * Check is someone is currently reviewing a diff
127 - * @param int $oldId
128 - * @param int $newId
129 - * @return bool
130 - */
131 - public static function diffIsUnderReview( $oldId, $newId ) {
132 - $m = self::getUserReviewingDiff( $oldId, $newId );
133 - return ( $m[0] !== null );
134 - }
135 -
136 - /*
137 - * Set the flag for who is reviewing a diff if not already set by someone
138 - * @param User $user
139 - * @param int $pageId
140 - * @return bool flag set
141 - */
142 - public static function setUserReviewingDiff( $user, $oldId, $newId ) {
143 - global $wgMemc;
144 - $key = wfMemcKey( 'flaggedrevs', 'userReviewingDiff', $oldId, $newId );
145 - $val = array( $user->getName(), wfTimestampNow() );
146 - $wasSet = false;
147 -
148 - $wgMemc->lock( $key, 4 ); // 4 sec timeout
149 - if ( !$wgMemc->get( $key ) ) { // no flag set
150 - $wgMemc->set( $key, $val, 6*20 ); // 6 min
151 - $wasSet = true;
152 - }
153 - $wgMemc->unlock( $key );
154 -
155 - return $wasSet;
156 - }
157 -
158 - /*
159 - * Clear the flag for who is reviewing a diff
160 - * @param User $user
161 - * @param int $oldId
162 - * @param int $newId
163 - */
164 - public static function clearUserReviewingDiff( $user, $oldId, $newId ) {
165 - global $wgMemc;
166 - $key = wfMemcKey( 'flaggedrevs', 'userReviewingDiff', $oldId, $newId );
167 - $wgMemc->lock( $key, 4 ); // 4 sec timeout
168 - $val = $wgMemc->get( $key );
169 - if ( is_array( $val ) && count( $val ) == 2 ) { // flag set
170 - list( $u, $ts ) = $val;
171 - if ( $u === $user->getName() ) {
172 - $wgMemc->delete( $key );
173 - }
174 - }
175 - $this->unlock();
176 - }
177 -}
Index: trunk/extensions/FlaggedRevs/FlaggedPage.php
@@ -1,541 +0,0 @@
2 -<?php
3 -/**
4 - * Class representing a MediaWiki article and history
5 - *
6 - * FlaggedPage::getTitleInstance() is preferred over constructor calls
7 - */
8 -class FlaggedPage extends Article {
9 - /* Process cache variables */
10 - protected $stable = 0;
11 - protected $stableRev = null;
12 - protected $revsArePending = null;
13 - protected $pendingRevCount = null;
14 - protected $pageConfig = null;
15 - protected $syncedInTracking = null;
16 -
17 - protected $imagePage = null; // for file pages
18 -
19 - /**
20 - * Get a FlaggedPage for a given title
21 - * @param Title
22 - * @return FlaggedPage
23 - */
24 - public static function getTitleInstance( Title $title ) {
25 - // Check if there is already an instance on this title
26 - if ( !isset( $title->flaggedRevsArticle ) ) {
27 - $title->flaggedRevsArticle = new self( $title );
28 - }
29 - return $title->flaggedRevsArticle;
30 - }
31 -
32 - /**
33 - * Get a FlaggedPage for a given article
34 - * @param Article
35 - * @return FlaggedPage
36 - */
37 - public static function getArticleInstance( Article $article ) {
38 - return self::getTitleInstance( $article->mTitle );
39 - }
40 -
41 - /**
42 - * Clear object process cache values
43 - * @return void
44 - */
45 - public function clear() {
46 - $this->stable = 0;
47 - $this->stableRev = null;
48 - $this->revsArePending = null;
49 - $this->pendingRevCount = null;
50 - $this->pageConfig = null;
51 - $this->syncedInTracking = null;
52 - $this->imagePage = null;
53 - parent::clear(); // call super!
54 - }
55 -
56 - /**
57 - * Get the current file version of this file page
58 - * @TODO: kind of hacky
59 - * @return mixed (File/false)
60 - */
61 - public function getFile() {
62 - if ( $this->mTitle->getNamespace() != NS_FILE ) {
63 - return false; // not a file page
64 - }
65 - if ( is_null( $this->imagePage ) ) {
66 - $this->imagePage = new ImagePage( $this->mTitle );
67 - }
68 - return $this->imagePage->getFile();
69 - }
70 -
71 - /**
72 - * Get the displayed file version of this file page
73 - * @TODO: kind of hacky
74 - * @return mixed (File/false)
75 - */
76 - public function getDisplayedFile() {
77 - if ( $this->mTitle->getNamespace() != NS_FILE ) {
78 - return false; // not a file page
79 - }
80 - if ( is_null( $this->imagePage ) ) {
81 - $this->imagePage = new ImagePage( $this->mTitle );
82 - }
83 - return $this->imagePage->getDisplayedFile();
84 - }
85 -
86 - /**
87 - * Is the stable version shown by default for this page?
88 - * @return bool
89 - */
90 - public function isStableShownByDefault() {
91 - if ( !$this->isReviewable() ) {
92 - return false; // no stable versions can exist
93 - }
94 - $config = $this->getStabilitySettings(); // page configuration
95 - return (bool)$config['override'];
96 - }
97 -
98 - /**
99 - * Do edits have to be reviewed before being shown by default (going live)?
100 - * @return bool
101 - */
102 - public function editsRequireReview() {
103 - return (
104 - $this->isReviewable() && // reviewable page
105 - $this->isStableShownByDefault() && // and stable versions override
106 - $this->getStableRev() // and there is a stable version
107 - );
108 - }
109 -
110 - /**
111 - * Are edits to this page currently pending?
112 - * @return bool
113 - */
114 - public function revsArePending() {
115 - if ( !$this->mDataLoaded ) {
116 - $this->loadPageData();
117 - }
118 - return $this->revsArePending;
119 - }
120 -
121 - /**
122 - * Get number of revs since the stable revision
123 - * Note: slower than revsArePending()
124 - * @param int $flags FR_MASTER (be sure to use loadFromDB( FR_MASTER ) if set)
125 - * @return int
126 - */
127 - public function getPendingRevCount( $flags = 0 ) {
128 - global $wgMemc, $wgParserCacheExpireTime;
129 - if ( !$this->mDataLoaded ) {
130 - $this->loadPageData();
131 - }
132 - # Pending count deferred even after page data load
133 - if ( $this->pendingRevCount !== null ) {
134 - return $this->pendingRevCount; // use process cache
135 - }
136 - $srev = $this->getStableRev();
137 - if ( !$srev ) {
138 - return 0; // none
139 - }
140 - $count = null;
141 - $sRevId = $srev->getRevId();
142 - # Try the cache...
143 - $key = wfMemcKey( 'flaggedrevs', 'countPending', $this->getId() );
144 - if ( !( $flags & FR_MASTER ) ) {
145 - $tuple = FlaggedRevs::getMemcValue( $wgMemc->get( $key ), $this );
146 - # Items is cached and newer that page_touched...
147 - if ( $tuple !== false ) {
148 - # Confirm that cache value was made against the same stable rev Id.
149 - # This avoids lengthy cache pollution if $sRevId is outdated.
150 - list( $cRevId, $cPending ) = explode( '-', $tuple, 2 );
151 - if ( $cRevId == $sRevId ) {
152 - $count = (int)$cPending;
153 - }
154 - }
155 - }
156 - # Otherwise, fetch result from DB as needed...
157 - if ( is_null( $count ) ) {
158 - $db = ( $flags & FR_MASTER ) ?
159 - wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
160 - $srevTS = $db->timestamp( $srev->getRevTimestamp() );
161 - $count = $db->selectField( 'revision', 'COUNT(*)',
162 - array( 'rev_page' => $this->getId(),
163 - 'rev_timestamp > ' . $db->addQuotes( $srevTS ) ), // bug 15515
164 - __METHOD__ );
165 - # Save result to cache...
166 - $data = FlaggedRevs::makeMemcObj( "{$sRevId}-{$count}" );
167 - $wgMemc->set( $key, $data, $wgParserCacheExpireTime );
168 - }
169 - $this->pendingRevCount = $count;
170 - return $this->pendingRevCount;
171 - }
172 -
173 - /**
174 - * Checks if the stable version is synced with the current revision
175 - * Note: slower than getPendingRevCount()
176 - * @return bool
177 - */
178 - public function stableVersionIsSynced() {
179 - global $wgMemc, $wgParserCacheExpireTime;
180 - $srev = $this->getStableRev();
181 - if ( !$srev ) {
182 - return true;
183 - }
184 - # Stable text revision must be the same as the current
185 - if ( $this->revsArePending() ) {
186 - return false;
187 - # Stable file revision must be the same as the current
188 - } elseif ( $this->mTitle->getNamespace() == NS_FILE ) {
189 - $file = $this->getFile(); // current upload version
190 - if ( $file && $file->getTimestamp() > $srev->getFileTimestamp() ) {
191 - return false;
192 - }
193 - }
194 - # If using the current version of includes, there is nothing else to check.
195 - if ( FlaggedRevs::inclusionSetting() == FR_INCLUDES_CURRENT ) {
196 - return true; // short-circuit
197 - }
198 - # Try the cache...
199 - $key = wfMemcKey( 'flaggedrevs', 'includesSynced', $this->getId() );
200 - $value = FlaggedRevs::getMemcValue( $wgMemc->get( $key ), $this );
201 - if ( $value === "true" ) {
202 - return true;
203 - } elseif ( $value === "false" ) {
204 - return false;
205 - }
206 - # Since the stable and current revisions have the same text and only outputs,
207 - # the only other things to check for are template and file differences in the output.
208 - # (a) Check if the current output has a newer template/file used
209 - # (b) Check if the stable version has a file/template that was deleted
210 - $synced = ( !$srev->findPendingTemplateChanges()
211 - && !$srev->findPendingFileChanges( 'noForeign' ) );
212 - # Save to cache. This will be updated whenever the page is touched.
213 - $data = FlaggedRevs::makeMemcObj( $synced ? "true" : "false" );
214 - $wgMemc->set( $key, $data, $wgParserCacheExpireTime );
215 -
216 - return $synced;
217 - }
218 -
219 - /**
220 - * Are template/file changes and ONLY template/file changes pending?
221 - * @return bool
222 - */
223 - public function onlyTemplatesOrFilesPending() {
224 - return ( !$this->revsArePending() && !$this->stableVersionIsSynced() );
225 - }
226 -
227 - /**
228 - * Is this page less open than the site defaults?
229 - * @return bool
230 - */
231 - public function isPageLocked() {
232 - return ( !FlaggedRevs::isStableShownByDefault() && $this->isStableShownByDefault() );
233 - }
234 -
235 - /**
236 - * Is this page more open than the site defaults?
237 - * @return bool
238 - */
239 - public function isPageUnlocked() {
240 - return ( FlaggedRevs::isStableShownByDefault() && !$this->isStableShownByDefault() );
241 - }
242 -
243 - /**
244 - * Tags are only shown for unreviewed content and this page is not locked/unlocked?
245 - * @return bool
246 - */
247 - public function lowProfileUI() {
248 - return FlaggedRevs::lowProfileUI() &&
249 - FlaggedRevs::isStableShownByDefault() == $this->isStableShownByDefault();
250 - }
251 -
252 - /**
253 - * Is this article reviewable?
254 - * @return bool
255 - */
256 - public function isReviewable() {
257 - if ( !FlaggedRevs::inReviewNamespace( $this->mTitle ) ) {
258 - return false;
259 - }
260 - # Check if flagging is disabled for this page via config
261 - if ( FlaggedRevs::useOnlyIfProtected() ) {
262 - $config = $this->getStabilitySettings(); // page configuration
263 - return (bool)$config['override']; // stable is default or flagging disabled
264 - }
265 - return true;
266 - }
267 -
268 - /**
269 - * Is this page in patrollable?
270 - * @return bool
271 - */
272 - public function isPatrollable() {
273 - if ( !FlaggedRevs::inPatrolNamespace( $this->mTitle ) ) {
274 - return false;
275 - }
276 - return !$this->isReviewable(); // pages that are reviewable are not patrollable
277 - }
278 -
279 - /**
280 - * Get the stable revision ID
281 - * @return int
282 - */
283 - public function getStable() {
284 - if ( !$this->mDataLoaded ) {
285 - $this->loadPageData();
286 - }
287 - return (int)$this->stable;
288 - }
289 -
290 - /**
291 - * Get the stable revision
292 - * @return mixed (FlaggedRevision/null)
293 - */
294 - public function getStableRev() {
295 - if ( !$this->mDataLoaded ) {
296 - $this->loadPageData();
297 - }
298 - # Stable rev deferred even after page data load
299 - if ( $this->stableRev === null ) {
300 - $srev = FlaggedRevision::newFromTitle( $this->mTitle, $this->stable );
301 - $this->stableRev = $srev ? $srev : false; // cache negative hits too
302 - }
303 - return $this->stableRev ? $this->stableRev : null; // false => null
304 - }
305 -
306 - /**
307 - * Get visiblity restrictions on page
308 - * @return array (select,override)
309 - */
310 - public function getStabilitySettings() {
311 - if ( !$this->mDataLoaded ) {
312 - $this->loadPageData();
313 - }
314 - return $this->pageConfig;
315 - }
316 -
317 - /*
318 - * Get the fp_reviewed value for this page
319 - * @return bool
320 - */
321 - public function syncedInTracking() {
322 - if ( !$this->mDataLoaded ) {
323 - $this->loadPageData();
324 - }
325 - return $this->syncedInTracking;
326 - }
327 -
328 - /**
329 - * Fetch a page record with the given conditions
330 - * @param $dbr Database object
331 - * @param $conditions Array
332 - * @return mixed Database result resource, or false on failure
333 - */
334 - protected function pageData( $dbr, $conditions ) {
335 - $row = $dbr->selectRow(
336 - array( 'page', 'flaggedpages', 'flaggedpage_config' ),
337 - array_merge(
338 - Article::selectFields(),
339 - FlaggedPageConfig::selectFields(),
340 - array( 'fp_pending_since', 'fp_stable', 'fp_reviewed' ) ),
341 - $conditions,
342 - __METHOD__,
343 - array(),
344 - array(
345 - 'flaggedpages' => array( 'LEFT JOIN', 'fp_page_id = page_id' ),
346 - 'flaggedpage_config' => array( 'LEFT JOIN', 'fpc_page_id = page_id' ) )
347 - );
348 - return $row;
349 - }
350 -
351 - /**
352 - * Set the page field data loaded from some source
353 - * @param $data Database row object or "fromdb"
354 - * @return void
355 - */
356 - public function loadPageData( $data = 'fromdb' ) {
357 - $this->mDataLoaded = true; // sanity
358 - # Fetch data from DB as needed...
359 - if ( $data === 'fromdb' ) {
360 - $data = $this->pageDataFromTitle( wfGetDB( DB_SLAVE ), $this->mTitle );
361 - }
362 - # Load in primary page data...
363 - parent::loadPageData( $data /* Row obj */ );
364 - # Load in FlaggedRevs page data...
365 - $this->stable = 0; // 0 => "found nothing"
366 - $this->stableRev = null; // defer this one...
367 - $this->revsArePending = false; // false => "found nothing" or "none pending"
368 - $this->pendingRevCount = null; // defer this one...
369 - $this->pageConfig = FlaggedPageConfig::getDefaultVisibilitySettings(); // default
370 - $this->syncedInTracking = true; // false => "unreviewed" or "synced"
371 - # Load in Row data if the page exists...
372 - if ( $data ) {
373 - if ( $data->fpc_override !== null ) { // page config row found
374 - $this->pageConfig = FlaggedPageConfig::getVisibilitySettingsFromRow( $data );
375 - }
376 - if ( $data->fp_stable !== null ) { // stable rev found
377 - $this->stable = (int)$data->fp_stable;
378 - $this->revsArePending = ( $data->fp_pending_since !== null ); // revs await review
379 - $this->syncedInTracking = (bool)$data->fp_reviewed;
380 - }
381 - }
382 - }
383 -
384 - /**
385 - * Set the page field data loaded from the DB
386 - * @param int $flags FR_MASTER
387 - * @param $data Database row object or "fromdb"
388 - */
389 - public function loadFromDB( $flags = 0 ) {
390 - $db = ( $flags & FR_MASTER ) ?
391 - wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
392 - $this->loadPageData( $this->pageDataFromTitle( $db, $this->mTitle ) );
393 - }
394 -
395 - /**
396 - * Updates the flagging tracking tables for this page
397 - * @param FlaggedRevision $srev The new stable version
398 - * @param int|null $latest The latest rev ID (optional)
399 - * @return bool Updates were done
400 - */
401 - public function updateStableVersion( FlaggedRevision $srev, $latest = null ) {
402 - $rev = $srev->getRevision();
403 - if ( !$this->exists() || !$rev ) {
404 - return false; // no bogus entries
405 - }
406 - # Get the latest revision ID if not set
407 - if ( !$latest ) {
408 - $latest = $this->mTitle->getLatestRevID( Title::GAID_FOR_UPDATE );
409 - }
410 - $dbw = wfGetDB( DB_MASTER );
411 - # Get the highest quality revision (not necessarily this one)...
412 - if ( $srev->getQuality() === FlaggedRevs::highestReviewTier() ) {
413 - $maxQuality = $srev->getQuality(); // save a query
414 - } else {
415 - $maxQuality = $dbw->selectField( array( 'flaggedrevs', 'revision' ),
416 - 'fr_quality',
417 - array( 'fr_page_id' => $this->getId(),
418 - 'rev_id = fr_rev_id',
419 - 'rev_page = fr_page_id',
420 - 'rev_deleted & ' . Revision::DELETED_TEXT => 0
421 - ),
422 - __METHOD__,
423 - array( 'ORDER BY' => 'fr_quality DESC', 'LIMIT' => 1 )
424 - );
425 - $maxQuality = max( $maxQuality, $srev->getQuality() ); // sanity
426 - }
427 - # Get the timestamp of the first edit after the stable version (if any)...
428 - $nextTimestamp = null;
429 - if ( $rev->getId() != $latest ) {
430 - $timestamp = $dbw->timestamp( $rev->getTimestamp() );
431 - $nextEditTS = $dbw->selectField( 'revision',
432 - 'rev_timestamp',
433 - array(
434 - 'rev_page' => $this->getId(),
435 - "rev_timestamp > " . $dbw->addQuotes( $timestamp ) ),
436 - __METHOD__,
437 - array( 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 )
438 - );
439 - if ( $nextEditTS ) { // sanity check
440 - $nextTimestamp = $nextEditTS;
441 - }
442 - }
443 - # Get the new page sync status...
444 - $synced = !(
445 - $nextTimestamp !== null || // edits pending
446 - $srev->findPendingTemplateChanges() || // template changes pending
447 - $srev->findPendingFileChanges( 'noForeign' ) // file changes pending
448 - );
449 - # Alter table metadata
450 - $dbw->replace( 'flaggedpages',
451 - array( 'fp_page_id' ),
452 - array(
453 - 'fp_page_id' => $this->getId(),
454 - 'fp_stable' => $rev->getId(),
455 - 'fp_reviewed' => $synced ? 1 : 0,
456 - 'fp_quality' => ( $maxQuality === false ) ? null : $maxQuality,
457 - 'fp_pending_since' => $dbw->timestampOrNull( $nextTimestamp )
458 - ),
459 - __METHOD__
460 - );
461 - # Update pending edit tracking table
462 - self::updatePendingList( $this->getId(), $latest );
463 - return true;
464 - }
465 -
466 - /**
467 - * Updates the flagging tracking tables for this page
468 - * @return void
469 - */
470 - public function clearStableVersion() {
471 - if ( !$this->exists() ) {
472 - return; // nothing to do
473 - }
474 - $dbw = wfGetDB( DB_MASTER );
475 - $dbw->delete( 'flaggedpages',
476 - array( 'fp_page_id' => $this->getId() ), __METHOD__ );
477 - $dbw->delete( 'flaggedpage_pending',
478 - array( 'fpp_page_id' => $this->getId() ), __METHOD__ );
479 - }
480 -
481 - /**
482 - * Updates the flaggedpage_pending table
483 - * @param int $pageId Page ID
484 - * @abstract int $latest Latest revision
485 - * @return void
486 - */
487 - protected static function updatePendingList( $pageId, $latest ) {
488 - $data = array();
489 - $level = FlaggedRevs::highestReviewTier();
490 - # Update pending times for each level, going from highest to lowest
491 - $dbw = wfGetDB( DB_MASTER );
492 - $higherLevelId = 0;
493 - $higherLevelTS = '';
494 - while ( $level >= 0 ) {
495 - # Get the latest revision of this level...
496 - $row = $dbw->selectRow( array( 'flaggedrevs', 'revision' ),
497 - array( 'fr_rev_id', 'rev_timestamp' ),
498 - array( 'fr_page_id' => $pageId,
499 - 'fr_quality' => $level,
500 - 'rev_id = fr_rev_id',
501 - 'rev_page = fr_page_id',
502 - 'rev_deleted & ' . Revision::DELETED_TEXT => 0,
503 - 'rev_id > ' . intval( $higherLevelId )
504 - ),
505 - __METHOD__,
506 - array( 'ORDER BY' => 'fr_rev_id DESC', 'LIMIT' => 1 )
507 - );
508 - # If there is a revision of this level, track it...
509 - # Revisions reviewed to one level count as reviewed
510 - # at the lower levels (i.e. quality -> checked).
511 - if ( $row ) {
512 - $id = $row->fr_rev_id;
513 - $ts = $row->rev_timestamp;
514 - } else {
515 - $id = $higherLevelId; // use previous (quality -> checked)
516 - $ts = $higherLevelTS; // use previous (quality -> checked)
517 - }
518 - # Get edits that actually are pending...
519 - if ( $id && $latest > $id ) {
520 - # Get the timestamp of the edit after this version (if any)
521 - $nextTimestamp = $dbw->selectField( 'revision',
522 - 'rev_timestamp',
523 - array( 'rev_page' => $pageId, "rev_timestamp > " . $dbw->addQuotes( $ts ) ),
524 - __METHOD__,
525 - array( 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 )
526 - );
527 - $data[] = array(
528 - 'fpp_page_id' => $pageId,
529 - 'fpp_quality' => $level,
530 - 'fpp_rev_id' => $id,
531 - 'fpp_pending_since' => $nextTimestamp
532 - );
533 - $higherLevelId = $id;
534 - $higherLevelTS = $ts;
535 - }
536 - $level--;
537 - }
538 - # Clear any old junk, and insert new rows
539 - $dbw->delete( 'flaggedpage_pending', array( 'fpp_page_id' => $pageId ), __METHOD__ );
540 - $dbw->insert( 'flaggedpage_pending', $data, __METHOD__ );
541 - }
542 -}
Index: trunk/extensions/FlaggedRevs/FRInclusionManager.php
@@ -1,205 +0,0 @@
2 -<?php
3 -/**
4 - * Class containing template/file version usage requirements for
5 - * Parser based on the source text (being parsed) revision ID.
6 - * If no requirements are set, the page is parsed as normal.
7 - *
8 - * Parser hooks check this to determine what template/file version to use.
9 - */
10 -class FRInclusionManager {
11 - protected $reviewedVersions = null; // files/templates at review time
12 - protected $stableVersions = array(); // stable versions of files/templates
13 -
14 - protected static $instance = null;
15 -
16 - public static function singleton() {
17 - if ( self::$instance == null ) {
18 - self::$instance = new self();
19 - }
20 - return self::$instance;
21 - }
22 - protected function __clone() { }
23 -
24 - protected function __construct() {
25 - $this->stableVersions['templates'] = array();
26 - $this->stableVersions['files'] = array();
27 - }
28 -
29 - /**
30 - * Reset all template/image version data
31 - * @return void
32 - */
33 - public function clear() {
34 - $this->reviewedVersions = null;
35 - $this->stableVersions['templates'] = array();
36 - $this->stableVersions['files'] = array();
37 - }
38 -
39 - /**
40 - * (a) Stabilize inclusions in Parser output
41 - * (b) Set the template/image versions used in the flagged version of a revision
42 - * @param array $tmpParams (ns => dbKey => revId )
43 - * @param array $imgParams (dbKey => array('time' => MW timestamp,'sha1' => sha1) )
44 - */
45 - public function setReviewedVersions( array $tmpParams, array $imgParams ) {
46 - $this->reviewedVersions = array();
47 - $this->reviewedVersions['templates'] = self::formatTemplateArray( $tmpParams );
48 - $this->reviewedVersions['files'] = self::formatFileArray( $imgParams );
49 - }
50 -
51 - /**
52 - * Set the stable versions of some template/images
53 - * @param array $tmpParams (ns => dbKey => revId )
54 - * @param array $imgParams (dbKey => array('time' => MW timestamp,'sha1' => sha1) )
55 - */
56 - public function setStableVersionCache( array $tmpParams, array $imgParams ) {
57 - $this->stableVersions['templates'] = self::formatTemplateArray( $tmpParams );
58 - $this->stableVersions['files'] = self::formatFileArray( $imgParams );
59 - }
60 -
61 - /**
62 - * Clean up a template version array
63 - * @param array $tmpParams (ns => dbKey => revId )
64 - * @return array
65 - */
66 - protected function formatTemplateArray( array $params ) {
67 - $res = array();
68 - foreach ( $params as $ns => $templates ) {
69 - $res[$ns] = array();
70 - foreach ( $templates as $dbKey => $revId ) {
71 - $res[$ns][$dbKey] = (int)$revId;
72 - }
73 - }
74 - return $res;
75 - }
76 -
77 - /**
78 - * Clean up a file version array
79 - * @param array $imgParams (dbKey => array('time' => MW timestamp,'sha1' => sha1) )
80 - * @return array
81 - */
82 - protected function formatFileArray( array $params ) {
83 - $res = array();
84 - foreach ( $params as $dbKey => $timeKey ) {
85 - $time = '0'; // missing
86 - $sha1 = false;
87 - if ( $timeKey['time'] ) {
88 - $time = $timeKey['time'];
89 - $sha1 = strval( $timeKey['sha1'] );
90 - }
91 - $res[$dbKey] = array( 'time' => $time, 'sha1' => $sha1 );
92 - }
93 - return $res;
94 - }
95 -
96 - /**
97 - * (a) Stabilize inclusions in Parser output
98 - * (b) Load all of the "review time" versions of template/files from $frev
99 - * (c) Load their stable version counterparts (avoids DB hits)
100 - * Note: Used when calling FlaggedRevs::parseStableText().
101 - * @param FlaggedRevision $frev
102 - * @return void
103 - */
104 - public function stabilizeParserOutput( FlaggedRevision $frev ) {
105 - $tStbVersions = $fStbVersions = array(); // stable versions
106 - $tRevVersions = $frev->getTemplateVersions();
107 - $fRevVersions = $frev->getFileVersions();
108 - # We can preload *most* of the stable version IDs the parser will need...
109 - if ( FlaggedRevs::inclusionSetting() == FR_INCLUDES_STABLE ) {
110 - $tStbVersions = $frev->getStableTemplateVersions();
111 - $fStbVersions = $frev->getStableFileVersions();
112 - }
113 - $this->setReviewedVersions( $tRevVersions, $fRevVersions );
114 - $this->setStableVersionCache( $tStbVersions, $fStbVersions );
115 - }
116 -
117 - /**
118 - * Should Parser stabilize includes?
119 - * @return bool
120 - */
121 - public function parserOutputIsStabilized() {
122 - return is_array( $this->reviewedVersions );
123 - }
124 -
125 - /**
126 - * Get the "review time" template version for parser
127 - * @param Title $title
128 - * @return mixed (int/null)
129 - */
130 - public function getReviewedTemplateVersion( Title $title ) {
131 - if ( !is_array( $this->reviewedVersions ) ) {
132 - throw new MWException( "prepareForParse() nor setReviewedVersions() called yet" );
133 - }
134 - $dbKey = $title->getDBkey();
135 - $namespace = $title->getNamespace();
136 - if ( isset( $this->reviewedVersions['templates'][$namespace][$dbKey] ) ) {
137 - return $this->reviewedVersions['templates'][$namespace][$dbKey];
138 - }
139 - return null; // missing version
140 - }
141 -
142 - /**
143 - * Get the "review time" file version for parser
144 - * @param Title $title
145 - * @return array (MW timestamp/'0'/null, sha1/''/null )
146 - */
147 - public function getReviewedFileVersion( Title $title ) {
148 - if ( !is_array( $this->reviewedVersions ) ) {
149 - throw new MWException( "prepareForParse() nor setReviewedVersions() called yet" );
150 - }
151 - $dbKey = $title->getDBkey();
152 - # All NS_FILE, no need to check namespace
153 - if ( isset( $this->reviewedVersions['files'][$dbKey] ) ) {
154 - $time = $this->reviewedVersions['files'][$dbKey]['time'];
155 - $sha1 = $this->reviewedVersions['files'][$dbKey]['sha1'];
156 - return array( $time, $sha1 );
157 - }
158 - return array( null, null ); // missing version
159 - }
160 -
161 - /**
162 - * Get the stable version of a template
163 - * @param Title $title
164 - * @return int
165 - */
166 - public function getStableTemplateVersion( Title $title ) {
167 - $dbKey = $title->getDBkey();
168 - $namespace = $title->getNamespace();
169 - $id = null;
170 - if ( isset( $this->stableVersions['templates'][$namespace][$dbKey] ) ) {
171 - $id = $this->stableVersions['templates'][$namespace][$dbKey];
172 - }
173 - if ( $id === null ) { // cache miss
174 - $srev = FlaggedRevision::newFromStable( $title );
175 - $id = $srev ? $srev->getRevId() : 0;
176 - }
177 - $this->stableVersions['templates'][$namespace][$dbKey] = $id; // cache
178 - return $id;
179 - }
180 -
181 - /**
182 - * Get the stable version of a file
183 - * @param Title $title
184 - * @return array (MW timestamp/'0', sha1/'')
185 - */
186 - public function getStableFileVersion( Title $title ) {
187 - $dbKey = $title->getDBkey();
188 - $time = '0'; // missing
189 - $sha1 = false;
190 - # All NS_FILE, no need to check namespace
191 - if ( isset( $this->stableVersions['files'][$dbKey] ) ) {
192 - $time = $this->stableVersions['files'][$dbKey]['time'];
193 - $sha1 = $this->stableVersions['files'][$dbKey]['sha1'];
194 - return array( $time, $sha1 );
195 - }
196 - $srev = FlaggedRevision::newFromStable( $title );
197 - if ( $srev && $srev->getFileTimestamp() ) {
198 - $time = $srev->getFileTimestamp();
199 - $sha1 = $srev->getFileSha1();
200 - }
201 - $this->stableVersions['files'][$dbKey] = array();
202 - $this->stableVersions['files'][$dbKey]['time'] = $time;
203 - $this->stableVersions['files'][$dbKey]['sha1'] = $sha1;
204 - return array( $time, $sha1 );
205 - }
206 -}
Index: trunk/extensions/FlaggedRevs/FlaggedRevs.php
@@ -240,12 +240,21 @@
241241 $wgExtensionMessagesFiles['FlaggedRevs'] = $langDir . 'FlaggedRevs.i18n.php';
242242 $wgExtensionAliasesFiles['FlaggedRevs'] = $langDir . 'FlaggedRevs.alias.php';
243243
 244+$accessDir = $dir . 'dataclasses/';
244245 # Utility classes...
245 -$wgAutoloadClasses['FlaggedRevs'] = $dir . 'FlaggedRevs.class.php';
246 -$wgAutoloadClasses['FRUserCounters'] = $dir . 'FRUserCounters.php';
247 -$wgAutoloadClasses['FRUserActivity'] = $dir . 'FRUserActivity.php';
248 -$wgAutoloadClasses['FlaggedPageConfig'] = $dir . 'FlaggedPageConfig.php';
249 -$wgAutoloadClasses['FlaggedRevsLog'] = $dir . 'FlaggedRevsLog.php';
 246+$wgAutoloadClasses['FlaggedRevs'] = $accessDir . 'FlaggedRevs.class.php';
 247+$wgAutoloadClasses['FRUserCounters'] = $accessDir . 'FRUserCounters.php';
 248+$wgAutoloadClasses['FRUserActivity'] = $accessDir . 'FRUserActivity.php';
 249+$wgAutoloadClasses['FlaggedPageConfig'] = $accessDir . 'FlaggedPageConfig.php';
 250+$wgAutoloadClasses['FlaggedRevsLog'] = $accessDir . 'FlaggedRevsLog.php';
 251+# Data object classes...
 252+$wgAutoloadClasses['FRExtraCacheUpdate'] = $accessDir . 'FRExtraCacheUpdate.php';
 253+$wgAutoloadClasses['FRExtraCacheUpdateJob'] = $accessDir . 'FRExtraCacheUpdate.php';
 254+$wgAutoloadClasses['FRSquidUpdate'] = $accessDir . 'FRExtraCacheUpdate.php';
 255+$wgAutoloadClasses['FRDependencyUpdate'] = $accessDir . 'FRDependencyUpdate.php';
 256+$wgAutoloadClasses['FRInclusionManager'] = $accessDir . 'FRInclusionManager.php';
 257+$wgAutoloadClasses['FlaggedPage'] = $accessDir . 'FlaggedPage.php';
 258+$wgAutoloadClasses['FlaggedRevision'] = $accessDir . 'FlaggedRevision.php';
250259
251260 # Event handler classes...
252261 $wgAutoloadClasses['FlaggedRevsHooks'] = $dir . 'FlaggedRevs.hooks.php';
@@ -253,15 +262,6 @@
254263 $wgAutoloadClasses['FlaggedRevsApiHooks'] = $dir . 'api/FlaggedRevsApi.hooks.php';
255264 $wgAutoloadClasses['FlaggedRevsUpdaterHooks'] = $dir . 'schema/FlaggedRevsUpdater.hooks.php';
256265
257 -# Data object classes...
258 -$wgAutoloadClasses['FRExtraCacheUpdate'] = $dir . 'FRExtraCacheUpdate.php';
259 -$wgAutoloadClasses['FRExtraCacheUpdateJob'] = $dir . 'FRExtraCacheUpdate.php';
260 -$wgAutoloadClasses['FRSquidUpdate'] = $dir . 'FRExtraCacheUpdate.php';
261 -$wgAutoloadClasses['FRDependencyUpdate'] = $dir . 'FRDependencyUpdate.php';
262 -$wgAutoloadClasses['FRInclusionManager'] = $dir . 'FRInclusionManager.php';
263 -$wgAutoloadClasses['FlaggedPage'] = $dir . 'FlaggedPage.php';
264 -$wgAutoloadClasses['FlaggedRevision'] = $dir . 'FlaggedRevision.php';
265 -
266266 # Business object classes
267267 $wgAutoloadClasses['FRGenericSubmitForm'] = $dir . 'business/FRGenericSubmitForm.php';
268268 $wgAutoloadClasses['RevisionReviewForm'] = $dir . 'business/RevisionReviewForm.php';
Index: trunk/extensions/FlaggedRevs/FlaggedRevs.hooks.php
@@ -1008,8 +1008,8 @@
10091009 }
10101010
10111011 public static function getUnitTests( &$files ) {
1012 - $files[] = dirname( __FILE__ ) . '/maintenance/tests/FRInclusionManagerTest.php';
1013 - $files[] = dirname( __FILE__ ) . '/maintenance/tests/FRUserCountersTest.php';
 1012+ $files[] = dirname( __FILE__ ) . '/tests/FRInclusionManagerTest.php';
 1013+ $files[] = dirname( __FILE__ ) . '/tests/FRUserCountersTest.php';
10141014 return true;
10151015 }
10161016
Index: trunk/extensions/FlaggedRevs/tests/FRUserCountersTest.php
@@ -0,0 +1,96 @@
 2+<?php
 3+
 4+class FRUserCountersTest extends PHPUnit_Framework_TestCase {
 5+ /**
 6+ * Prepares the environment before running a test.
 7+ */
 8+ protected function setUp() {
 9+ parent::setUp();
 10+ }
 11+
 12+ /**
 13+ * Cleans up the environment after running a test.
 14+ */
 15+ protected function tearDown() {
 16+ parent::tearDown();
 17+ }
 18+
 19+ /**
 20+ * Constructs the test case.
 21+ */
 22+ public function __construct() {}
 23+
 24+ public function testGetAndSaveUserParams() {
 25+ $p = FRUserCounters::getUserParams( -1 );
 26+ $expected = array(
 27+ 'uniqueContentPages' => array(),
 28+ 'totalContentEdits' => 0,
 29+ 'editComments' => 0,
 30+ 'revertedEdits' => 0
 31+ );
 32+ $this->assertEquals( $expected, $p, "Initial params" );
 33+
 34+ $expected = array(
 35+ 'uniqueContentPages' => array(),
 36+ 'totalContentEdits' => 666,
 37+ 'editComments' => 666,
 38+ 'revertedEdits' => 13
 39+ );
 40+ FRUserCounters::saveUserParams( 1, $expected );
 41+ $ps = FRUserCounters::getUserParams( 1 );
 42+ $this->assertEquals( $expected, $ps, "Param save and fetch from DB 1" );
 43+
 44+ $expected = array(
 45+ 'uniqueContentPages' => array(23,55),
 46+ 'totalContentEdits' => 666,
 47+ 'editComments' => 666,
 48+ 'revertedEdits' => 13
 49+ );
 50+ FRUserCounters::saveUserParams( 1, $expected );
 51+ $ps = FRUserCounters::getUserParams( 1 );
 52+ $this->assertEquals( $expected, $ps, "Param save and fetch from DB 2" );
 53+ }
 54+
 55+ public function testUpdateUserParams() {
 56+ $p = FRUserCounters::getUserParams( -1 );
 57+ # Assumes (main) IN content namespace
 58+ $title = Title::makeTitleSafe( 0, 'helloworld' );
 59+ $article = new Article( $title );
 60+
 61+ $copyP = $p;
 62+ FRUserCounters::updateUserParams( $copyP, $article, "Manual edit comment" );
 63+ $this->assertEquals( $p['editComments']+1, $copyP['editComments'], "Manual summary" );
 64+
 65+ $copyP = $p;
 66+ FRUserCounters::updateUserParams( $copyP, $article, "/* section */" );
 67+ $this->assertEquals( $p['editComments'], $copyP['editComments'], "Auto summary" );
 68+
 69+ $copyP = $p;
 70+ FRUserCounters::updateUserParams( $copyP, $article, "edit summary" );
 71+ $this->assertEquals( $p['totalContentEdits']+1, $copyP['totalContentEdits'],
 72+ "Content edit count on content edit" );
 73+ $expected = $p['uniqueContentPages'];
 74+ $expected[] = 0;
 75+ $this->assertEquals( $expected, $copyP['uniqueContentPages'],
 76+ "Unique content pages on content edit" );
 77+
 78+ # Assumes (user) NOT IN content namespace
 79+ $title = Title::makeTitleSafe( NS_USER, 'helloworld' );
 80+ $article = new Article( $title );
 81+
 82+ $copyP = $p;
 83+ FRUserCounters::updateUserParams( $copyP, $article, "Manual edit comment" );
 84+ $this->assertEquals( $p['editComments']+1, $copyP['editComments'], "Manual summary" );
 85+
 86+ $copyP = $p;
 87+ FRUserCounters::updateUserParams( $copyP, $article, "/* section */" );
 88+ $this->assertEquals( $p['editComments'], $copyP['editComments'], "Auto summary" );
 89+
 90+ $copyP = $p;
 91+ FRUserCounters::updateUserParams( $copyP, $article, "edit summary" );
 92+ $this->assertEquals( $p['totalContentEdits'], $copyP['totalContentEdits'],
 93+ "Content edit count on non-content edit" );
 94+ $this->assertEquals( $p['uniqueContentPages'], $copyP['uniqueContentPages'],
 95+ "Unique content pages on non-content edit" );
 96+ }
 97+}
Property changes on: trunk/extensions/FlaggedRevs/tests/FRUserCountersTest.php
___________________________________________________________________
Added: svn:eol-style
198 + native
Index: trunk/extensions/FlaggedRevs/tests/FRInclusionManagerTest.php
@@ -0,0 +1,140 @@
 2+<?php
 3+
 4+class FRInclusionManagerTest extends PHPUnit_Framework_TestCase {
 5+ /* starting input */
 6+ protected static $inputTemplates = array(
 7+ 10 => array('XX' => '1242', 'YY' => '0', 'KK' => false),
 8+ 4 => array('Cite' => '30', 'Moo' => 0),
 9+ 0 => array('ZZ' => 464, '0' => 13)
 10+ );
 11+ protected static $inputFiles = array(
 12+ 'FileXX' => array('time' => '20100405192110', 'sha1' => 'abc1'),
 13+ 'FileYY' => array('time' => '20000403101300', 'sha1' => 1134),
 14+ 'FileZZ' => array('time' => '0', 'sha1' => ''),
 15+ 'Filele' => array('time' => 0, 'sha1' => ''),
 16+ 'FileKK' => array('time' => false, 'sha1' => false),
 17+ '0' => array('time' => '20000203101350', 'sha1' => 'ae33'),
 18+ );
 19+ /* output to test against (<test,NS,dbkey,expected rev ID>) */
 20+ protected static $reviewedOutputTemplates = array(
 21+ array( "Output template version when given '1224'", 10, 'XX', 1242 ),
 22+ array( "Output template version when given '0'", 10, 'YY', 0 ),
 23+ array( "Output template version when given false", 10, 'KK', 0 ),
 24+ array( "Output template version when given '30'", 4, 'Cite', 30 ),
 25+ array( "Output template version when given 0", 4, 'Moo', 0 ),
 26+ array( "Output template version when given 464", 0, 'ZZ', 464 ),
 27+ array( "Output template version when given 13", 0, '0', 13 ),
 28+ array( "Output template version when not given", 0, 'Notexists', null ),
 29+ );
 30+ protected static $stableOutputTemplates = array(
 31+ array( "Output template version when given '1224'", 10, 'XX', 1242 ),
 32+ array( "Output template version when given '0'", 10, 'YY', 0 ),
 33+ array( "Output template version when given false", 10, 'KK', 0 ),
 34+ array( "Output template version when given '30'", 4, 'Cite', 30 ),
 35+ array( "Output template version when given 0", 4, 'Moo', 0 ),
 36+ array( "Output template version when given 464", 0, 'ZZ', 464 ),
 37+ array( "Output template version when given 13", 0, '0', 13 ),
 38+ array( "Output template version when not given", 0, 'NotexistsPage1111', 0 ),
 39+ );
 40+ /* output to test against (<test,dbkey,expected TS,expected sha1>) */
 41+ protected static $reviewedOutputFiles = array(
 42+ array( "Output file version when given '20100405192110'/'abc1'",
 43+ 'FileXX', '20100405192110', 'abc1'),
 44+ array( "Output file version when given '20000403101300'/'ffc2'",
 45+ 'FileYY', '20000403101300', '1134'),
 46+ array( "Output file version when given '0'/''", 'FileZZ', '0', false),
 47+ array( "Output file version when given false/''", 'FileKK', '0', false),
 48+ array( "Output file version when given 0/''", 'Filele', '0', false),
 49+ array( "Output file version when not given", 'Notgiven', null, null),
 50+ );
 51+ protected static $stableOutputFiles = array(
 52+ array( "Output file version when given '20100405192110'/'abc1'",
 53+ 'FileXX', '20100405192110', 'abc1'),
 54+ array( "Output file version when given '20000403101300'/'ffc2'",
 55+ 'FileYY', '20000403101300', '1134'),
 56+ array( "Output file version when given '0'/''", 'FileZZ', '0', false),
 57+ array( "Output file version when given false/''", 'FileKK', '0', false),
 58+ array( "Output file version when given 0/''", 'Filele', '0', false),
 59+ array( "Output file version when not given", 'NotexistsPage1111', '0', false),
 60+ );
 61+
 62+ /**
 63+ * Prepares the environment before running a test.
 64+ */
 65+ protected function setUp() {
 66+ parent::setUp();
 67+ }
 68+
 69+ /**
 70+ * Cleans up the environment after running a test.
 71+ */
 72+ protected function tearDown() {
 73+ parent::tearDown();
 74+ FRInclusionManager::singleton()->clear();
 75+ }
 76+
 77+ /**
 78+ * Constructs the test case.
 79+ */
 80+ public function __construct() {}
 81+
 82+ public function testManagerInitial() {
 83+ $im = FRInclusionManager::singleton();
 84+ $this->assertEquals( false, $im->parserOutputIsStabilized(), "Starts off empty" );
 85+ }
 86+
 87+ public function testManagerClear() {
 88+ $im = FRInclusionManager::singleton();
 89+ $im->setReviewedVersions( self::$inputTemplates, self::$inputFiles );
 90+ $im->clear();
 91+ $this->assertEquals( false, $im->parserOutputIsStabilized(), "Empty on clear()" );
 92+ }
 93+
 94+ public function testReviewedTemplateVersions() {
 95+ $im = FRInclusionManager::singleton();
 96+ $im->setReviewedVersions( self::$inputTemplates, self::$inputFiles );
 97+ foreach ( self::$reviewedOutputTemplates as $triple ) {
 98+ list($test,$ns,$dbKey,$expId) = $triple;
 99+ $title = Title::makeTitleSafe( $ns, $dbKey );
 100+ $actual = $im->getReviewedTemplateVersion( $title );
 101+ $this->assertEquals( $expId, $actual, "Rev ID test: $test" );
 102+ }
 103+ }
 104+
 105+ public function testReviewedFileVersions() {
 106+ $im = FRInclusionManager::singleton();
 107+ $im->setReviewedVersions( self::$inputTemplates, self::$inputFiles );
 108+ foreach ( self::$reviewedOutputFiles as $triple ) {
 109+ list($test,$dbKey,$expTS,$expSha1) = $triple;
 110+ $title = Title::makeTitleSafe( NS_FILE, $dbKey );
 111+ list($actualTS,$actualSha1) = $im->getReviewedFileVersion( $title );
 112+ $this->assertEquals( $expTS, $actualTS, "Timestamp test: $test" );
 113+ $this->assertEquals( $expSha1, $actualSha1, "Sha1 test: $test" );
 114+ }
 115+ }
 116+
 117+ public function testStableTemplateVersions() {
 118+ $im = FRInclusionManager::singleton();
 119+ $im->setReviewedVersions( array(), array() );
 120+ $im->setStableVersionCache( self::$inputTemplates, self::$inputFiles );
 121+ foreach ( self::$stableOutputTemplates as $triple ) {
 122+ list($test,$ns,$dbKey,$expId) = $triple;
 123+ $title = Title::makeTitleSafe( $ns, $dbKey );
 124+ $actual = $im->getStableTemplateVersion( $title );
 125+ $this->assertEquals( $expId, $actual, "Rev ID test: $test" );
 126+ }
 127+ }
 128+
 129+ public function testStableFileVersions() {
 130+ $im = FRInclusionManager::singleton();
 131+ $im->setReviewedVersions( array(), array() );
 132+ $im->setStableVersionCache( self::$inputTemplates, self::$inputFiles );
 133+ foreach ( self::$stableOutputFiles as $triple ) {
 134+ list($test,$dbKey,$expTS,$expSha1) = $triple;
 135+ $title = Title::makeTitleSafe( NS_FILE, $dbKey );
 136+ list($actualTS,$actualSha1) = $im->getStableFileVersion( $title );
 137+ $this->assertEquals( $expTS, $actualTS, "Timestamp test: $test" );
 138+ $this->assertEquals( $expSha1, $actualSha1, "Sha1 test: $test" );
 139+ }
 140+ }
 141+}
Property changes on: trunk/extensions/FlaggedRevs/tests/FRInclusionManagerTest.php
___________________________________________________________________
Added: svn:eol-style
1142 + native
Index: trunk/extensions/FlaggedRevs/dataclasses/FRDependencyUpdate.php
@@ -0,0 +1,235 @@
 2+<?php
 3+/**
 4+ * Class containing update methods for tracking links that
 5+ * are only in the stable version of pages. Used only for caching.
 6+ */
 7+class FRDependencyUpdate {
 8+ protected $title;
 9+ protected $sLinks;
 10+ protected $sTemplates;
 11+ protected $sImages;
 12+ protected $sCategories;
 13+ protected $dbw;
 14+
 15+ public function __construct( Title $title, ParserOutput $stableOutput ) {
 16+ $this->title = $title;
 17+ # Stable version links
 18+ $this->sLinks = $stableOutput->getLinks();
 19+ $this->sTemplates = $stableOutput->getTemplates();
 20+ $this->sImages = $stableOutput->getImages();
 21+ $this->sCategories = $stableOutput->getCategories();
 22+ }
 23+
 24+ public function doUpdate() {
 25+ $deps = array();
 26+ # Get any links that are only in the stable version...
 27+ $cLinks = $this->getCurrentVersionLinks();
 28+ foreach ( $this->sLinks as $ns => $titles ) {
 29+ foreach ( $titles as $title => $pageId ) {
 30+ if ( !isset( $cLinks[$ns][$title] ) ) {
 31+ self::addDependency( $deps, $ns, $title );
 32+ }
 33+ }
 34+ }
 35+ # Get any images that are only in the stable version...
 36+ $cImages = $this->getCurrentVersionImages();
 37+ foreach ( $this->sImages as $image => $n ) {
 38+ if ( !isset( $cImages[$image] ) ) {
 39+ self::addDependency( $deps, NS_FILE, $image );
 40+ }
 41+ }
 42+ # Get any templates that are only in the stable version...
 43+ $cTemplates = $this->getCurrentVersionTemplates();
 44+ foreach ( $this->sTemplates as $ns => $titles ) {
 45+ foreach ( $titles as $title => $id ) {
 46+ if ( !isset( $cTemplates[$ns][$title] ) ) {
 47+ self::addDependency( $deps, $ns, $title );
 48+ }
 49+ }
 50+ }
 51+ # Get any categories that are only in the stable version...
 52+ $cCategories = $this->getCurrentVersionCategories();
 53+ foreach ( $this->sCategories as $category => $sort ) {
 54+ if ( !isset( $cCategories[$category] ) ) {
 55+ self::addDependency( $deps, NS_CATEGORY, $category );
 56+ }
 57+ }
 58+ # Get any dependency tracking changes
 59+ $existing = $this->getExistingDeps();
 60+ # Do incremental updates...
 61+ if ( $existing != $deps ) {
 62+ $existing = $this->getExistingDeps( FR_MASTER );
 63+ $insertions = $this->getDepInsertions( $existing, $deps );
 64+ $deletions = $this->getDepDeletions( $existing, $deps );
 65+ $dbw = wfGetDB( DB_MASTER );
 66+ # Delete removed links
 67+ if ( $deletions ) {
 68+ $dbw->delete( 'flaggedrevs_tracking', $deletions, __METHOD__ );
 69+ }
 70+ # Add any new links
 71+ if ( $insertions ) {
 72+ $dbw->insert( 'flaggedrevs_tracking', $insertions, __METHOD__, 'IGNORE' );
 73+ }
 74+ }
 75+ }
 76+
 77+ /*
 78+ * Get existing cache dependancies
 79+ * @param int $flags FR_MASTER
 80+ * @return array (ns => dbKey => 1)
 81+ */
 82+ protected function getExistingDeps( $flags = 0 ) {
 83+ $db = ( $flags & FR_MASTER ) ?
 84+ wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
 85+ $res = $db->select( 'flaggedrevs_tracking',
 86+ array( 'ftr_namespace', 'ftr_title' ),
 87+ array( 'ftr_from' => $this->title->getArticleId() ),
 88+ __METHOD__
 89+ );
 90+ $arr = array();
 91+ foreach( $res as $row ) {
 92+ if ( !isset( $arr[$row->ftr_namespace] ) ) {
 93+ $arr[$row->ftr_namespace] = array();
 94+ }
 95+ $arr[$row->ftr_namespace][$row->ftr_title] = 1;
 96+ }
 97+ return $arr;
 98+ }
 99+
 100+ /*
 101+ * Get INSERT rows for cache dependancies in $new but not in $existing
 102+ * @return array
 103+ */
 104+ protected function getDepInsertions( array $existing, array $new ) {
 105+ $arr = array();
 106+ foreach ( $new as $ns => $dbkeys ) {
 107+ if ( isset( $existing[$ns] ) ) {
 108+ $diffs = array_diff_key( $dbkeys, $existing[$ns] );
 109+ } else {
 110+ $diffs = $dbkeys;
 111+ }
 112+ foreach ( $diffs as $dbk => $id ) {
 113+ $arr[] = array(
 114+ 'ftr_from' => $this->title->getArticleId(),
 115+ 'ftr_namespace' => $ns,
 116+ 'ftr_title' => $dbk
 117+ );
 118+ }
 119+ }
 120+ return $arr;
 121+ }
 122+
 123+ /*
 124+ * Get WHERE clause to delete items in $existing but not in $new
 125+ * @return mixed (array/false)
 126+ */
 127+ protected function getDepDeletions( array $existing, array $new ) {
 128+ $del = array();
 129+ foreach ( $existing as $ns => $dbkeys ) {
 130+ if ( isset( $new[$ns] ) ) {
 131+ $del[$ns] = array_diff_key( $existing[$ns], $new[$ns] );
 132+ } else {
 133+ $del[$ns] = $existing[$ns];
 134+ }
 135+ }
 136+ if ( $del ) {
 137+ $clause = self::makeWhereFrom2d( $del, wfGetDB( DB_MASTER ) );
 138+ if ( $clause ) {
 139+ return array( $clause, 'ftr_from' => $this->title->getArticleId() );
 140+ }
 141+ }
 142+ return false;
 143+ }
 144+
 145+ // Make WHERE clause to match $arr titles
 146+ protected static function makeWhereFrom2d( &$arr, $db ) {
 147+ $lb = new LinkBatch();
 148+ $lb->setArray( $arr );
 149+ return $lb->constructSet( 'ftr', $db );
 150+ }
 151+
 152+ protected static function addDependency( array &$deps, $ns, $dbKey ) {
 153+ if ( !isset( $deps[$ns] ) ) {
 154+ $deps[$ns] = array();
 155+ }
 156+ $deps[$ns][$dbKey] = 1;
 157+ }
 158+
 159+ /**
 160+ * Get an array of existing links, as a 2-D array
 161+ * @return array (ns => dbKey => 1)
 162+ */
 163+ protected function getCurrentVersionLinks() {
 164+ $dbr = wfGetDB( DB_SLAVE );
 165+ $res = $dbr->select( 'pagelinks',
 166+ array( 'pl_namespace', 'pl_title' ),
 167+ array( 'pl_from' => $this->title->getArticleId() ),
 168+ __METHOD__
 169+ );
 170+ $arr = array();
 171+ foreach( $res as $row ) {
 172+ if ( !isset( $arr[$row->pl_namespace] ) ) {
 173+ $arr[$row->pl_namespace] = array();
 174+ }
 175+ $arr[$row->pl_namespace][$row->pl_title] = 1;
 176+ }
 177+ return $arr;
 178+ }
 179+
 180+ /**
 181+ * Get an array of existing templates, as a 2-D array
 182+ * @return array (ns => dbKey => 1)
 183+ */
 184+ protected function getCurrentVersionTemplates() {
 185+ $dbr = wfGetDB( DB_SLAVE );
 186+ $res = $dbr->select( 'templatelinks',
 187+ array( 'tl_namespace', 'tl_title' ),
 188+ array( 'tl_from' => $this->title->getArticleId() ),
 189+ __METHOD__
 190+ );
 191+ $arr = array();
 192+ foreach( $res as $row ) {
 193+ if ( !isset( $arr[$row->tl_namespace] ) ) {
 194+ $arr[$row->tl_namespace] = array();
 195+ }
 196+ $arr[$row->tl_namespace][$row->tl_title] = 1;
 197+ }
 198+ return $arr;
 199+ }
 200+
 201+ /**
 202+ * Get an array of existing images, image names in the keys
 203+ * @return array (dbKey => 1)
 204+ */
 205+ protected function getCurrentVersionImages() {
 206+ $dbr = wfGetDB( DB_SLAVE );
 207+ $res = $dbr->select( 'imagelinks',
 208+ array( 'il_to' ),
 209+ array( 'il_from' => $this->title->getArticleId() ),
 210+ __METHOD__
 211+ );
 212+ $arr = array();
 213+ foreach( $res as $row ) {
 214+ $arr[$row->il_to] = 1;
 215+ }
 216+ return $arr;
 217+ }
 218+
 219+ /**
 220+ * Get an array of existing categories, with the name in the key and sort key in the value.
 221+ * @return array (category => sortkey)
 222+ */
 223+ protected function getCurrentVersionCategories() {
 224+ $dbr = wfGetDB( DB_SLAVE );
 225+ $res = $dbr->select( 'categorylinks',
 226+ array( 'cl_to', 'cl_sortkey' ),
 227+ array( 'cl_from' => $this->title->getArticleId() ),
 228+ __METHOD__
 229+ );
 230+ $arr = array();
 231+ foreach( $res as $row ) {
 232+ $arr[$row->cl_to] = $row->cl_sortkey;
 233+ }
 234+ return $arr;
 235+ }
 236+}
Property changes on: trunk/extensions/FlaggedRevs/dataclasses/FRDependencyUpdate.php
___________________________________________________________________
Added: svn:eol-style
1237 + native
Index: trunk/extensions/FlaggedRevs/dataclasses/FRUserCounters.php
@@ -0,0 +1,170 @@
 2+<?php
 3+/**
 4+ * Class containing utility functions for per-user stats
 5+ */
 6+class FRUserCounters {
 7+ /**
 8+ * Get params for a user
 9+ * @param int $uid
 10+ * @param int $flags FR_MASTER, FR_FOR_UPDATE
 11+ * @param string $dBName, optional wiki name
 12+ * @return array
 13+ */
 14+ public static function getUserParams( $uid, $flags = 0, $dBName = false ) {
 15+ $p = array();
 16+ $row = self::fetchParamsRow( $uid, $flags, $dBName );
 17+ if ( $row ) {
 18+ $p = self::expandParams( $row->frp_user_params );
 19+ }
 20+ self::setUnitializedFields( $p );
 21+ return $p;
 22+ }
 23+
 24+ /**
 25+ * Initializes unset param fields to their starting values
 26+ * @param &array $p
 27+ */
 28+ protected static function setUnitializedFields( array &$p ) {
 29+ if ( !isset( $p['uniqueContentPages'] ) ) {
 30+ $p['uniqueContentPages'] = array();
 31+ }
 32+ if ( !isset( $p['totalContentEdits'] ) ) {
 33+ $p['totalContentEdits'] = 0;
 34+ }
 35+ if ( !isset( $p['editComments'] ) ) {
 36+ $p['editComments'] = 0;
 37+ }
 38+ if ( !isset( $p['revertedEdits'] ) ) {
 39+ $p['revertedEdits'] = 0;
 40+ }
 41+ }
 42+
 43+ /**
 44+ * Get the params row for a user
 45+ * @param int $uid
 46+ * @param int $flags FR_MASTER, FR_FOR_UPDATE
 47+ * @param string $dBName, optional wiki name
 48+ * @return mixed (false or Row)
 49+ */
 50+ protected static function fetchParamsRow( $uid, $flags = 0, $dBName = false ) {
 51+ $options = array();
 52+ if ( $flags & FR_MASTER || $flags & FR_FOR_UPDATE ) {
 53+ $db = wfGetDB( DB_MASTER, array(), $dBName );
 54+ if ( $flags & FR_FOR_UPDATE ) $options[] = 'FOR UPDATE';
 55+ } else {
 56+ $db = wfGetDB( DB_SLAVE, array(), $dBName );
 57+ }
 58+ return $db->selectRow( 'flaggedrevs_promote',
 59+ 'frp_user_params',
 60+ array( 'frp_user_id' => $uid ),
 61+ __METHOD__,
 62+ $options
 63+ );
 64+ }
 65+
 66+ /**
 67+ * Save params for a user
 68+ * @param int $uid
 69+ * @param array $params
 70+ * @param string $dBName, optional wiki name
 71+ * @return bool success
 72+ */
 73+ public static function saveUserParams( $uid, array $params, $dBName = false ) {
 74+ $dbw = wfGetDB( DB_MASTER, array(), $dBName );
 75+ $dbw->replace( 'flaggedrevs_promote',
 76+ array( 'frp_user_id' ),
 77+ array( 'frp_user_id' => $uid,
 78+ 'frp_user_params' => self::flattenParams( $params ) ),
 79+ __METHOD__
 80+ );
 81+ return ( $dbw->affectedRows() > 0 );
 82+ }
 83+
 84+ /**
 85+ * Flatten params for a user for DB storage
 86+ * Note: param values must be integers
 87+ * @param array $params
 88+ * @return string
 89+ */
 90+ protected static function flattenParams( array $params ) {
 91+ $flatRows = array();
 92+ foreach ( $params as $key => $value ) {
 93+ if ( strpos( $key, '=' ) !== false || strpos( $key, "\n" ) !== false ) {
 94+ throw new MWException( "flattenParams() - key cannot use '=' or newline" );
 95+ }
 96+ if ( $key === 'uniqueContentPages' ) { // list
 97+ $value = implode( ',', array_map( 'intval', $value ) );
 98+ } else {
 99+ $value = intval( $value );
 100+ }
 101+ $flatRows[] = trim( $key ) . '=' . $value;
 102+ }
 103+ return implode( "\n", $flatRows );
 104+ }
 105+
 106+ /**
 107+ * Expand params for a user from DB storage
 108+ * @param string $flatPars
 109+ * @return array
 110+ */
 111+ protected static function expandParams( $flatPars ) {
 112+ $p = array(); // init
 113+ $flatPars = explode( "\n", trim( $flatPars ) );
 114+ foreach ( $flatPars as $pair ) {
 115+ $m = explode( '=', trim( $pair ), 2 );
 116+ $key = $m[0];
 117+ $value = isset( $m[1] ) ? $m[1] : null;
 118+ if ( $key === 'uniqueContentPages' ) { // list
 119+ $value = ( $value === '' )
 120+ ? array() // explode() would make array( 0 => '')
 121+ : array_map( 'intval', explode( ',', $value ) );
 122+ } else {
 123+ $value = intval( $value );
 124+ }
 125+ $p[$key] = $value;
 126+ }
 127+ return $p;
 128+ }
 129+
 130+ /**
 131+ * Update users params array for a user on edit
 132+ * @param &array $p user params
 133+ * @param Article $article the article just edited
 134+ * @param string $summary edit summary
 135+ * @return bool anything changed
 136+ */
 137+ public static function updateUserParams( array &$p, Article $article, $summary ) {
 138+ global $wgFlaggedRevsAutoconfirm, $wgFlaggedRevsAutopromote;
 139+ # Update any special counters for non-null revisions
 140+ $changed = false;
 141+ if ( $article->getTitle()->isContentPage() ) {
 142+ $pages = $p['uniqueContentPages']; // page IDs
 143+ # Don't let this get bloated for no reason
 144+ $maxUniquePages = 50; // some flexibility
 145+ if ( is_array( $wgFlaggedRevsAutoconfirm ) &&
 146+ $wgFlaggedRevsAutoconfirm['uniqueContentPages'] > $maxUniquePages )
 147+ {
 148+ $maxUniquePages = $wgFlaggedRevsAutoconfirm['uniqueContentPages'];
 149+ }
 150+ if ( is_array( $wgFlaggedRevsAutopromote ) &&
 151+ $wgFlaggedRevsAutopromote['uniqueContentPages'] > $maxUniquePages )
 152+ {
 153+ $maxUniquePages = $wgFlaggedRevsAutopromote['uniqueContentPages'];
 154+ }
 155+ if ( count( $pages ) < $maxUniquePages // limit the size of this
 156+ && !in_array( $article->getId(), $pages ) )
 157+ {
 158+ $pages[] = $article->getId();
 159+ $p['uniqueContentPages'] = $pages;
 160+ }
 161+ $p['totalContentEdits'] += 1;
 162+ $changed = true;
 163+ }
 164+ // Record non-automatic summary tally
 165+ if ( !preg_match( '/^\/\*.*\*\/$/', $summary ) ) {
 166+ $p['editComments'] += 1;
 167+ $changed = true;
 168+ }
 169+ return $changed;
 170+ }
 171+}
Property changes on: trunk/extensions/FlaggedRevs/dataclasses/FRUserCounters.php
___________________________________________________________________
Added: svn:eol-style
1172 + native
Index: trunk/extensions/FlaggedRevs/dataclasses/FlaggedRevsLog.php
@@ -0,0 +1,161 @@
 2+<?php
 3+
 4+class FlaggedRevsLog {
 5+ /**
 6+ * $action is a valid review log action
 7+ * @return bool
 8+ */
 9+ public static function isReviewAction( $action ) {
 10+ return preg_match( '/^(approve2?(-i|-a|-ia)?|unapprove2?)$/', $action );
 11+ }
 12+
 13+ /**
 14+ * $action is a valid stability log action
 15+ * @return bool
 16+ */
 17+ public static function isStabilityAction( $action ) {
 18+ return preg_match( '/^(config|modify|reset)$/', $action );
 19+ }
 20+
 21+ /**
 22+ * $action is a valid review log deprecate action
 23+ * @return bool
 24+ */
 25+ public static function isReviewDeapproval( $action ) {
 26+ return ( $action == 'unapprove' || $action == 'unapprove2' );
 27+ }
 28+
 29+ /**
 30+ * Record a log entry on the review action
 31+ * @param Title $title
 32+ * @param array $dims
 33+ * @param array $oldDims
 34+ * @param string $comment
 35+ * @param int $revId, revision ID
 36+ * @param int $stableId, prior stable revision ID
 37+ * @param bool $approve, approved? (otherwise unapproved)
 38+ * @param bool $auto
 39+ */
 40+ public static function updateReviewLog(
 41+ Title $title, array $dims, array $oldDims,
 42+ $comment, $revId, $stableId, $approve, $auto = false
 43+ ) {
 44+ $log = new LogPage( 'review',
 45+ false /* $rc */,
 46+ $auto ? "skipUDP" : "UDP" // UDP logging
 47+ );
 48+ # Tag rating list (e.g. accuracy=x, depth=y, style=z)
 49+ $ratings = array();
 50+ # Skip rating list if flagging is just an 0/1 feature...
 51+ if ( !FlaggedRevs::binaryFlagging() ) {
 52+ foreach ( $dims as $quality => $level ) {
 53+ $ratings[] = wfMsgForContent( "revreview-$quality" ) .
 54+ wfMsgForContent( 'colon-separator' ) .
 55+ wfMsgForContent( "revreview-$quality-$level" );
 56+ }
 57+ }
 58+ $isAuto = ( $auto && !FlaggedRevs::isQuality( $dims ) ); // Paranoid check
 59+ // Approved revisions
 60+ if ( $approve ) {
 61+ if ( $isAuto ) {
 62+ $comment = wfMsgForContent( 'revreview-auto' ); // override this
 63+ }
 64+ # Make comma-separated list of ratings
 65+ $rating = !empty( $ratings )
 66+ ? '[' . implode( ', ', $ratings ) . ']'
 67+ : '';
 68+ # Append comment with ratings
 69+ if ( $rating != '' ) {
 70+ $comment .= $comment ? " $rating" : $rating;
 71+ }
 72+ # Sort into the proper action (useful for filtering)
 73+ $action = ( FlaggedRevs::isQuality( $dims ) || FlaggedRevs::isQuality( $oldDims ) ) ?
 74+ 'approve2' : 'approve';
 75+ if ( !$stableId ) { // first time
 76+ $action .= $isAuto ? "-ia" : "-i";
 77+ } elseif ( $isAuto ) { // automatic
 78+ $action .= "-a";
 79+ }
 80+ // De-approved revisions
 81+ } else {
 82+ $action = FlaggedRevs::isQuality( $oldDims ) ?
 83+ 'unapprove2' : 'unapprove';
 84+ }
 85+ $ts = Revision::getTimestampFromId( $title, $revId );
 86+ # Param format is <rev id, old stable id, rev timestamp>
 87+ $logid = $log->addEntry( $action, $title, $comment, array( $revId, $stableId, $ts ) );
 88+ # Make log easily searchable by rev_id
 89+ $log->addRelations( 'rev_id', array( $revId ), $logid );
 90+ }
 91+
 92+ /**
 93+ * Record a log entry on the stability config change action
 94+ * @param Title $title
 95+ * @param array $config
 96+ * @param array $oldConfig
 97+ * @param string $reason
 98+ * @param bool $auto
 99+ */
 100+ public static function updateStabilityLog(
 101+ Title $title, array $config, array $oldConfig, $reason
 102+ ) {
 103+ $log = new LogPage( 'stable' );
 104+ if ( FlaggedPageConfig::configIsReset( $config ) ) {
 105+ # We are going back to default settings
 106+ $log->addEntry( 'reset', $title, $reason );
 107+ } else {
 108+ # We are changing to non-default settings
 109+ $action = ( $oldConfig === FlaggedPageConfig::getDefaultVisibilitySettings() )
 110+ ? 'config' // set a custom configuration
 111+ : 'modify'; // modified an existing custom configuration
 112+ $log->addEntry( $action, $title, $reason,
 113+ FlaggedRevsLog::collapseParams( self::stabilityLogParams( $config ) ) );
 114+ }
 115+ }
 116+
 117+ /**
 118+ * Get log params (associate array) from a stability config
 119+ * @param array $config
 120+ * @return array (associative)
 121+ */
 122+ public static function stabilityLogParams( array $config ) {
 123+ $params = $config;
 124+ if ( !FlaggedRevs::useOnlyIfProtected() ) {
 125+ $params['precedence'] = 1; // b/c hack for presenting log params...
 126+ }
 127+ return $params;
 128+ }
 129+
 130+ /**
 131+ * Collapse an associate array into a string
 132+ * @param array $pars
 133+ * @return string
 134+ */
 135+ public static function collapseParams( array $pars ) {
 136+ $res = array();
 137+ foreach ( $pars as $param => $value ) {
 138+ // Sanity check...
 139+ if ( strpos( $param, '=' ) !== false || strpos( $value, '=' ) !== false ) {
 140+ throw new MWException( "collapseParams() - cannot use equal sign" );
 141+ } elseif ( strpos( $param, "\n" ) !== false || strpos( $value, "\n" ) !== false ) {
 142+ throw new MWException( "collapseParams() - cannot use newline" );
 143+ }
 144+ $res[] = "{$param}={$value}";
 145+ }
 146+ return implode( "\n", $res );
 147+ }
 148+
 149+ /**
 150+ * Expand a list of log params into an associative array
 151+ * @params array $pars
 152+ * @return array (associative)
 153+ */
 154+ public static function expandParams( array $pars ) {
 155+ $res = array();
 156+ foreach ( $pars as $paramAndValue ) {
 157+ list( $param, $value ) = explode( '=', $paramAndValue, 2 );
 158+ $res[$param] = $value;
 159+ }
 160+ return $res;
 161+ }
 162+}
Property changes on: trunk/extensions/FlaggedRevs/dataclasses/FlaggedRevsLog.php
___________________________________________________________________
Added: svn:eol-style
1163 + native
Index: trunk/extensions/FlaggedRevs/dataclasses/FlaggedPageConfig.php
@@ -0,0 +1,242 @@
 2+<?php
 3+/*
 4+* Page stability configuration functions
 5+*/
 6+class FlaggedPageConfig {
 7+ /**
 8+ * Get visibility settings/restrictions for a page
 9+ * @param Title $title, page title
 10+ * @param int $flags, FR_MASTER
 11+ * @return array (associative) (select,override,autoreview,expiry)
 12+ */
 13+ public static function getStabilitySettings( Title $title, $flags = 0 ) {
 14+ $db = ( $flags & FR_MASTER ) ?
 15+ wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
 16+ $row = $db->selectRow( 'flaggedpage_config',
 17+ self::selectFields(),
 18+ array( 'fpc_page_id' => $title->getArticleID() ),
 19+ __METHOD__
 20+ );
 21+ return self::getVisibilitySettingsFromRow( $row );
 22+ }
 23+
 24+ /**
 25+ * @return array basic select fields for FlaggedPageConfig DB row
 26+ */
 27+ public static function selectFields() {
 28+ return array( 'fpc_override', 'fpc_level', 'fpc_expiry' );
 29+ }
 30+
 31+ /**
 32+ * Get page configuration settings from a DB row
 33+ */
 34+ public static function getVisibilitySettingsFromRow( $row ) {
 35+ if ( $row ) {
 36+ # This code should be refactored, now that it's being used more generally.
 37+ $expiry = Block::decodeExpiry( $row->fpc_expiry );
 38+ # Only apply the settings if they haven't expired
 39+ if ( !$expiry || $expiry < wfTimestampNow() ) {
 40+ $row = null; // expired
 41+ self::purgeExpiredConfigurations();
 42+ }
 43+ }
 44+ // Is there a non-expired row?
 45+ if ( $row ) {
 46+ $level = $row->fpc_level;
 47+ if ( !self::isValidRestriction( $row->fpc_level ) ) {
 48+ $level = ''; // site default; ignore fpc_level
 49+ }
 50+ $config = array(
 51+ 'override' => $row->fpc_override ? 1 : 0,
 52+ 'autoreview' => $level,
 53+ 'expiry' => Block::decodeExpiry( $row->fpc_expiry ) // TS_MW
 54+ );
 55+ # If there are protection levels defined check if this is valid...
 56+ if ( FlaggedRevs::useProtectionLevels() ) {
 57+ $level = self::getProtectionLevel( $config );
 58+ if ( $level == 'invalid' || $level == 'none' ) {
 59+ // If 'none', make sure expiry is 'infinity'
 60+ $config = self::getDefaultVisibilitySettings(); // revert to default (none)
 61+ }
 62+ }
 63+ } else {
 64+ # Return the default config if this page doesn't have its own
 65+ $config = self::getDefaultVisibilitySettings();
 66+ }
 67+ return $config;
 68+ }
 69+
 70+ /**
 71+ * Get default stability configuration settings
 72+ * @return array
 73+ */
 74+ public static function getDefaultVisibilitySettings() {
 75+ return array(
 76+ # Keep this consistent: 1 => override, 0 => don't
 77+ 'override' => FlaggedRevs::isStableShownByDefault() ? 1 : 0,
 78+ 'autoreview' => '',
 79+ 'expiry' => 'infinity'
 80+ );
 81+ }
 82+
 83+ /**
 84+ * Set the stability configuration settings for a page
 85+ * @param Title $title
 86+ * @param array $config
 87+ * @return bool Row changed
 88+ */
 89+ public static function setStabilitySettings( Title $title, array $config ) {
 90+ $dbw = wfGetDB( DB_MASTER );
 91+ # If setting to site default values and there is a row then erase it
 92+ if ( self::configIsReset( $config ) ) {
 93+ $dbw->delete( 'flaggedpage_config',
 94+ array( 'fpc_page_id' => $title->getArticleID() ),
 95+ __METHOD__
 96+ );
 97+ $changed = ( $dbw->affectedRows() != 0 ); // did this do anything?
 98+ # Otherwise, add/replace row if we are not just setting it to the site default
 99+ } else {
 100+ $dbExpiry = Block::encodeExpiry( $config['expiry'], $dbw );
 101+ # Get current config...
 102+ $oldRow = $dbw->selectRow( 'flaggedpage_config',
 103+ array( 'fpc_override', 'fpc_level', 'fpc_expiry' ),
 104+ array( 'fpc_page_id' => $title->getArticleID() ),
 105+ __METHOD__,
 106+ 'FOR UPDATE' // lock
 107+ );
 108+ # Check if this is not the same config as the existing (if any) row
 109+ $changed = ( !$oldRow // no previous config
 110+ || $oldRow->fpc_override != $config['override'] // ...override changed, or...
 111+ || $oldRow->fpc_level != $config['autoreview'] // ...autoreview level changed, or...
 112+ || $oldRow->fpc_expiry != $dbExpiry // ...expiry changed
 113+ );
 114+ # If the new config is different, replace the old row...
 115+ if ( $changed ) {
 116+ $dbw->replace( 'flaggedpage_config',
 117+ array( 'PRIMARY' ),
 118+ array(
 119+ 'fpc_page_id' => $title->getArticleID(),
 120+ 'fpc_select' => -1, // unused
 121+ 'fpc_override' => (int)$config['override'],
 122+ 'fpc_level' => $config['autoreview'],
 123+ 'fpc_expiry' => $dbExpiry
 124+ ),
 125+ __METHOD__
 126+ );
 127+ }
 128+ }
 129+ return $changed;
 130+ }
 131+
 132+ /**
 133+ * Does this config equal the default settings?
 134+ * @param array $config
 135+ * @return bool
 136+ */
 137+ public static function configIsReset( array $config ) {
 138+ if ( FlaggedRevs::useOnlyIfProtected() ) {
 139+ return ( $config['autoreview'] == '' );
 140+ } else {
 141+ return ( $config['override'] == FlaggedRevs::isStableShownByDefault()
 142+ && $config['autoreview'] == '' );
 143+ }
 144+ }
 145+
 146+ /**
 147+ * Find what protection level a config is in
 148+ * @param array $config
 149+ * @return string
 150+ */
 151+ public static function getProtectionLevel( array $config ) {
 152+ if ( !FlaggedRevs::useProtectionLevels() ) {
 153+ throw new MWException( '$wgFlaggedRevsProtection is disabled' );
 154+ }
 155+ $defaultConfig = self::getDefaultVisibilitySettings();
 156+ # Check if the page is not protected at all...
 157+ if ( $config['override'] == $defaultConfig['override']
 158+ && $config['autoreview'] == '' )
 159+ {
 160+ return "none"; // not protected
 161+ }
 162+ # All protection levels have 'override' on
 163+ if ( $config['override'] ) {
 164+ # The levels are defined by the 'autoreview' settings
 165+ if ( in_array( $config['autoreview'], FlaggedRevs::getRestrictionLevels() ) ) {
 166+ return $config['autoreview'];
 167+ }
 168+ }
 169+ return "invalid";
 170+ }
 171+
 172+ /**
 173+ * Check if an fpc_level value is valid
 174+ * @param string $right
 175+ */
 176+ protected static function isValidRestriction( $right ) {
 177+ if ( $right == '' ) {
 178+ return true; // no restrictions (none)
 179+ }
 180+ return in_array( $right, FlaggedRevs::getRestrictionLevels(), true );
 181+ }
 182+
 183+ /**
 184+ * Purge expired restrictions from the flaggedpage_config table.
 185+ * The stable version of pages may change and invalidation may be required.
 186+ */
 187+ public static function purgeExpiredConfigurations() {
 188+ if ( wfReadOnly() ) return;
 189+ # Get a separate master session for this transaction (deadlock avoidance)
 190+ $lb = wfGetLBFactory()->newMainLB();
 191+ $dbw = $lb->getConnection( DB_MASTER );
 192+ # Find pages with expired configs...
 193+ $config = self::getDefaultVisibilitySettings(); // config is to be reset
 194+ $encCutoff = $dbw->addQuotes( $dbw->timestamp() );
 195+ $ret = $dbw->select(
 196+ array( 'flaggedpage_config', 'page' ),
 197+ array( 'fpc_page_id', 'page_namespace', 'page_title' ),
 198+ array( 'page_id = fpc_page_id', 'fpc_expiry < ' . $encCutoff ),
 199+ __METHOD__
 200+ // array( 'FOR UPDATE' )
 201+ );
 202+ # Figured out to do with each page...
 203+ $pagesClearConfig = array();
 204+ $pagesClearTracking = $titlesClearTracking = array();
 205+ foreach ( $ret as $row ) {
 206+ # If FlaggedRevs got "turned off" (in protection config)
 207+ # for this page, then clear it from the tracking tables...
 208+ if ( FlaggedRevs::useOnlyIfProtected() && !$config['override'] ) {
 209+ $pagesClearTracking[] = $row->fpc_page_id; // no stable version
 210+ $titlesClearTracking[] = Title::newFromRow( $row ); // no stable version
 211+ }
 212+ $pagesClearConfig[] = $row->fpc_page_id; // page with expired config
 213+ }
 214+ # Clear the expired config for these pages...
 215+ if ( count( $pagesClearConfig ) ) {
 216+ $dbw->delete( 'flaggedpage_config',
 217+ array( 'fpc_page_id' => $pagesClearConfig, 'fpc_expiry < ' . $encCutoff ),
 218+ __METHOD__
 219+ );
 220+ }
 221+ # Clear the tracking rows and update page_touched for the
 222+ # pages in $pagesClearConfig that do now have a stable version...
 223+ if ( count( $pagesClearTracking ) ) {
 224+ FlaggedRevs::clearTrackingRows( $pagesClearTracking );
 225+ $dbw->update( 'page',
 226+ array( 'page_touched' => $dbw->timestamp() ),
 227+ array( 'page_id' => $pagesClearTracking ),
 228+ __METHOD__
 229+ );
 230+ }
 231+ # Also, clear their squid caches and purge other pages that use this page.
 232+ # NOTE: all of these updates are deferred via $wgDeferredUpdateList.
 233+ foreach ( $titlesClearTracking as $title ) {
 234+ FlaggedRevs::purgeSquid( $title );
 235+ if ( FlaggedRevs::inclusionSetting() == FR_INCLUDES_STABLE ) {
 236+ FlaggedRevs::HTMLCacheUpdates( $title ); // purge pages that use this page
 237+ }
 238+ }
 239+ # Commit this transaction and close session
 240+ $lb->commitMasterChanges();
 241+ $lb->closeAll();
 242+ }
 243+}
Property changes on: trunk/extensions/FlaggedRevs/dataclasses/FlaggedPageConfig.php
___________________________________________________________________
Added: svn:eol-style
1244 + native
Index: trunk/extensions/FlaggedRevs/dataclasses/FlaggedRevs.class.php
@@ -0,0 +1,1203 @@
 2+<?php
 3+/**
 4+ * Class containing utility functions for a FlaggedRevs environment
 5+ *
 6+ * Class is lazily-initialized, calling load() as needed
 7+ */
 8+class FlaggedRevs {
 9+ # Tag name/level config
 10+ protected static $dimensions = array();
 11+ protected static $minSL = array();
 12+ protected static $minQL = array();
 13+ protected static $minPL = array();
 14+ protected static $qualityVersions = false;
 15+ protected static $pristineVersions = false;
 16+ protected static $tagRestrictions = array();
 17+ protected static $binaryFlagging = true;
 18+ # Namespace config
 19+ protected static $reviewNamespaces = array();
 20+ protected static $patrolNamespaces = array();
 21+ # Restriction levels/config
 22+ protected static $restrictionLevels = array();
 23+ # Autoreview config
 24+ protected static $autoReviewConfig = 0;
 25+
 26+ protected static $loaded = false;
 27+
 28+ public static function load() {
 29+ global $wgFlaggedRevsTags, $wgFlaggedRevTags;
 30+ if ( self::$loaded ) {
 31+ return true;
 32+ }
 33+ self::$loaded = true;
 34+ $flaggedRevsTags = null;
 35+ if ( isset( $wgFlaggedRevTags ) ) {
 36+ $flaggedRevsTags = $wgFlaggedRevTags; // b/c
 37+ wfWarn( 'Please use $wgFlaggedRevsTags instead of $wgFlaggedRevTags in config.' );
 38+ } elseif ( isset( $wgFlaggedRevsTags ) ) {
 39+ $flaggedRevsTags = $wgFlaggedRevsTags;
 40+ }
 41+ # Assume true, then set to false if needed
 42+ if ( !empty( $flaggedRevsTags ) ) {
 43+ self::$qualityVersions = true;
 44+ self::$pristineVersions = true;
 45+ self::$binaryFlagging = ( count( $flaggedRevsTags ) <= 1 );
 46+ }
 47+ foreach ( $flaggedRevsTags as $tag => $levels ) {
 48+ # Sanity checks
 49+ $safeTag = htmlspecialchars( $tag );
 50+ if ( !preg_match( '/^[a-zA-Z]{1,20}$/', $tag ) || $safeTag !== $tag ) {
 51+ throw new MWException( 'FlaggedRevs given invalid tag name!' );
 52+ }
 53+ # Define "quality" and "pristine" reqs
 54+ if ( is_array( $levels ) ) {
 55+ $minQL = $levels['quality'];
 56+ $minPL = $levels['pristine'];
 57+ $ratingLevels = $levels['levels'];
 58+ # B/C, $levels is just an integer (minQL)
 59+ } else {
 60+ global $wgFlaggedRevPristine, $wgFlaggedRevValues;
 61+ $ratingLevels = isset( $wgFlaggedRevValues ) ?
 62+ $wgFlaggedRevValues : 1;
 63+ $minQL = $levels; // an integer
 64+ $minPL = isset( $wgFlaggedRevPristine ) ?
 65+ $wgFlaggedRevPristine : $ratingLevels + 1;
 66+ wfWarn( 'Please update the format of $wgFlaggedRevsTags in config.' );
 67+ }
 68+ # Set FlaggedRevs tags
 69+ self::$dimensions[$tag] = array();
 70+ for ( $i = 0; $i <= $ratingLevels; $i++ ) {
 71+ self::$dimensions[$tag][$i] = "{$tag}-{$i}";
 72+ }
 73+ if ( $ratingLevels > 1 ) {
 74+ self::$binaryFlagging = false; // more than one level
 75+ }
 76+ # Sanity checks
 77+ if ( !is_integer( $minQL ) || !is_integer( $minPL ) ) {
 78+ throw new MWException( 'FlaggedRevs given invalid tag value!' );
 79+ }
 80+ if ( $minQL > $ratingLevels ) {
 81+ self::$qualityVersions = false;
 82+ self::$pristineVersions = false;
 83+ }
 84+ if ( $minPL > $ratingLevels ) {
 85+ self::$pristineVersions = false;
 86+ }
 87+ self::$minQL[$tag] = max( $minQL, 1 );
 88+ self::$minPL[$tag] = max( $minPL, 1 );
 89+ self::$minSL[$tag] = 1;
 90+ }
 91+ global $wgFlaggedRevsTagsRestrictions, $wgFlagRestrictions;
 92+ if ( isset( $wgFlagRestrictions ) ) {
 93+ self::$tagRestrictions = $wgFlagRestrictions; // b/c
 94+ wfWarn( 'Please use $wgFlaggedRevsTagsRestrictions instead of $wgFlagRestrictions in config.' );
 95+ } else {
 96+ self::$tagRestrictions = $wgFlaggedRevsTagsRestrictions;
 97+ }
 98+ # Make sure that the restriction levels are unique
 99+ global $wgFlaggedRevsRestrictionLevels;
 100+ self::$restrictionLevels = array_unique( $wgFlaggedRevsRestrictionLevels );
 101+ self::$restrictionLevels = array_filter( self::$restrictionLevels, 'strlen' );
 102+ # Make sure no talk namespaces are in review namespace
 103+ global $wgFlaggedRevsNamespaces, $wgFlaggedRevsPatrolNamespaces;
 104+ foreach ( $wgFlaggedRevsNamespaces as $ns ) {
 105+ if ( MWNamespace::isTalk( $ns ) ) {
 106+ throw new MWException( 'FlaggedRevs given talk namespace in $wgFlaggedRevsNamespaces!' );
 107+ } else if ( $ns == NS_MEDIAWIKI ) {
 108+ throw new MWException( 'FlaggedRevs given NS_MEDIAWIKI in $wgFlaggedRevsNamespaces!' );
 109+ }
 110+ }
 111+ self::$reviewNamespaces = $wgFlaggedRevsNamespaces;
 112+ # Note: reviewable *pages* override patrollable ones
 113+ self::$patrolNamespaces = $wgFlaggedRevsPatrolNamespaces;
 114+ # Handle $wgFlaggedRevsAutoReview settings
 115+ global $wgFlaggedRevsAutoReview, $wgFlaggedRevsAutoReviewNew;
 116+ if ( is_int( $wgFlaggedRevsAutoReview ) ) {
 117+ self::$autoReviewConfig = $wgFlaggedRevsAutoReview;
 118+ } else { // b/c
 119+ if ( $wgFlaggedRevsAutoReview ) {
 120+ self::$autoReviewConfig = FR_AUTOREVIEW_CHANGES;
 121+ }
 122+ wfWarn( '$wgFlaggedRevsAutoReview is now a bitfield instead of a boolean.' );
 123+ }
 124+ if ( isset( $wgFlaggedRevsAutoReviewNew ) ) { // b/c
 125+ self::$autoReviewConfig = ( $wgFlaggedRevsAutoReviewNew )
 126+ ? self::$autoReviewConfig |= FR_AUTOREVIEW_CREATION
 127+ : self::$autoReviewConfig & ~FR_AUTOREVIEW_CREATION;
 128+ wfWarn( '$wgFlaggedRevsAutoReviewNew is deprecated; use $wgFlaggedRevsAutoReview.' );
 129+ }
 130+ }
 131+
 132+ # ################ Basic config accessors #################
 133+
 134+ /**
 135+ * Is there only one tag and it has only one level?
 136+ * @return bool
 137+ */
 138+ public static function binaryFlagging() {
 139+ self::load();
 140+ return self::$binaryFlagging;
 141+ }
 142+
 143+ /**
 144+ * If there only one tag and it has only one level, return it
 145+ * @return string
 146+ */
 147+ public static function binaryTagName() {
 148+ self::load();
 149+ if ( !self::binaryFlagging() ) {
 150+ return null;
 151+ }
 152+ $tags = array_keys( self::$dimensions );
 153+ return empty( $tags ) ? null : $tags[0];
 154+ }
 155+
 156+ /**
 157+ * Are quality versions enabled?
 158+ * @return bool
 159+ */
 160+ public static function qualityVersions() {
 161+ self::load();
 162+ return self::$qualityVersions;
 163+ }
 164+
 165+ /**
 166+ * Are pristine versions enabled?
 167+ * @return bool
 168+ */
 169+ public static function pristineVersions() {
 170+ self::load();
 171+ return self::$pristineVersions;
 172+ }
 173+
 174+ /**
 175+ * Get the highest review tier that is enabled
 176+ * @return int One of FR_PRISTINE,FR_QUALITY,FR_CHECKED
 177+ */
 178+ public static function highestReviewTier() {
 179+ self::load();
 180+ if ( self::$pristineVersions ) {
 181+ return FR_PRISTINE;
 182+ } elseif ( self::$qualityVersions ) {
 183+ return FR_QUALITY;
 184+ }
 185+ return FR_CHECKED;
 186+ }
 187+
 188+ /**
 189+ * Allow auto-review edits directly to the stable version by reviewers?
 190+ * @return bool
 191+ */
 192+ public static function autoReviewEdits() {
 193+ self::load();
 194+ return self::$autoReviewConfig & FR_AUTOREVIEW_CHANGES;
 195+ }
 196+
 197+ /**
 198+ * Auto-review new pages with the minimal level?
 199+ * @return bool
 200+ */
 201+ public static function autoReviewNewPages() {
 202+ self::load();
 203+ return self::$autoReviewConfig & FR_AUTOREVIEW_CREATION;
 204+ }
 205+
 206+ /**
 207+ * Auto-review of new pages or edits to pages enabled?
 208+ * @return bool
 209+ */
 210+ public static function autoReviewEnabled() {
 211+ return self::autoReviewEdits() || self::autoReviewNewPages();
 212+ }
 213+
 214+ /**
 215+ * Get the maximum level that $tag can be autoreviewed to
 216+ * @param string $tag
 217+ * @return int
 218+ */
 219+ public static function maxAutoReviewLevel( $tag ) {
 220+ global $wgFlaggedRevsTagsAuto;
 221+ self::load();
 222+ if ( !self::autoReviewEnabled() ) {
 223+ return 0; // shouldn't happen
 224+ }
 225+ if ( isset( $wgFlaggedRevsTagsAuto[$tag] ) ) {
 226+ return (int)$wgFlaggedRevsTagsAuto[$tag];
 227+ } else {
 228+ return 1; // B/C (before $wgFlaggedRevsTagsAuto)
 229+ }
 230+ }
 231+
 232+ /**
 233+ * Is a "stable version" used as the default display
 234+ * version for all pages in reviewable namespaces?
 235+ * @return bool
 236+ */
 237+ public static function isStableShownByDefault() {
 238+ global $wgFlaggedRevsOverride;
 239+ if ( self::useOnlyIfProtected() ) {
 240+ return false; // must be configured per-page
 241+ }
 242+ return (bool)$wgFlaggedRevsOverride;
 243+ }
 244+
 245+ /**
 246+ * Are pages reviewable only if they have been manually
 247+ * configured by an admin to use a "stable version" as the default?
 248+ * @return bool
 249+ */
 250+ public static function useOnlyIfProtected() {
 251+ global $wgFlaggedRevsProtection;
 252+ return (bool)$wgFlaggedRevsProtection;
 253+ }
 254+
 255+ /**
 256+ * Return the include handling configuration
 257+ * @return int
 258+ */
 259+ public static function inclusionSetting() {
 260+ global $wgFlaggedRevsHandleIncludes;
 261+ return $wgFlaggedRevsHandleIncludes;
 262+ }
 263+
 264+ /**
 265+ * Should tags only be shown for unreviewed content for this user?
 266+ * @return bool
 267+ */
 268+ public static function lowProfileUI() {
 269+ global $wgFlaggedRevsLowProfile;
 270+ return $wgFlaggedRevsLowProfile;
 271+ }
 272+
 273+ /**
 274+ * Are there site defined protection levels for review
 275+ * @return bool
 276+ */
 277+ public static function useProtectionLevels() {
 278+ global $wgFlaggedRevsProtection;
 279+ return $wgFlaggedRevsProtection && self::getRestrictionLevels();
 280+ }
 281+
 282+ /**
 283+ * Get the autoreview restriction levels available
 284+ * @return array
 285+ */
 286+ public static function getRestrictionLevels() {
 287+ self::load();
 288+ return self::$restrictionLevels;
 289+ }
 290+
 291+ /**
 292+ * Get the array of tag dimensions and level messages
 293+ * @return array
 294+ */
 295+ public static function getDimensions() {
 296+ self::load();
 297+ return self::$dimensions;
 298+ }
 299+
 300+ /**
 301+ * Get the associative array of tag dimensions
 302+ * (tags => array(levels => msgkey))
 303+ * @return array
 304+ */
 305+ public static function getTags() {
 306+ self::load();
 307+ return array_keys( self::$dimensions );
 308+ }
 309+
 310+ /**
 311+ * Get the associative array of tag restrictions
 312+ * (tags => array(rights => levels))
 313+ * @return array
 314+ */
 315+ public static function getTagRestrictions() {
 316+ self::load();
 317+ return self::$tagRestrictions;
 318+ }
 319+
 320+ /**
 321+ * Get the UI name for a tag
 322+ * @param string $tag
 323+ * @return string
 324+ */
 325+ public static function getTagMsg( $tag ) {
 326+ return wfMsgExt( "revreview-$tag", array( 'escapenoentities' ) );
 327+ }
 328+
 329+ /**
 330+ * Get the levels for a tag. Gives map of level to message name.
 331+ * @param string $tag
 332+ * @return associative array (integer -> string)
 333+ */
 334+ public static function getTagLevels( $tag ) {
 335+ self::load();
 336+ return isset( self::$dimensions[$tag] ) ?
 337+ self::$dimensions[$tag] : array();
 338+ }
 339+
 340+ /**
 341+ * Get the the UI name for a value of a tag
 342+ * @param string $tag
 343+ * @param int $value
 344+ * @return string
 345+ */
 346+ public static function getTagValueMsg( $tag, $value ) {
 347+ self::load();
 348+ if ( !isset( self::$dimensions[$tag] ) ) {
 349+ return '';
 350+ } elseif ( !isset( self::$dimensions[$tag][$value] ) ) {
 351+ return '';
 352+ }
 353+ # Return empty string if not there
 354+ return wfMsgExt( 'revreview-' . self::$dimensions[$tag][$value],
 355+ array( 'escapenoentities' ) );
 356+ }
 357+
 358+ /**
 359+ * Are there no actual dimensions?
 360+ * @return bool
 361+ */
 362+ public static function dimensionsEmpty() {
 363+ self::load();
 364+ return empty( self::$dimensions );
 365+ }
 366+
 367+ /**
 368+ * Get corresponding text for the api output of flagging levels
 369+ *
 370+ * @param int $level
 371+ * @return string
 372+ */
 373+ public static function getQualityLevelText( $level ) {
 374+ static $levelText = array(
 375+ 0 => 'stable',
 376+ 1 => 'quality',
 377+ 2 => 'pristine'
 378+ );
 379+ if ( isset( $levelText[$level] ) ) {
 380+ return $levelText[$level];
 381+ } else {
 382+ return '';
 383+ }
 384+ }
 385+
 386+ /**
 387+ * Get the URL path to where the client side resources are (JS, CSS, images..)
 388+ * @return string
 389+ */
 390+ public static function styleUrlPath() {
 391+ global $wgExtensionAssetsPath;
 392+ return "$wgExtensionAssetsPath/FlaggedRevs/presentation/modules";
 393+ }
 394+
 395+ # ################ Permission functions #################
 396+
 397+ /*
 398+ * Sanity check a (tag,value) pair
 399+ * @param string $tag
 400+ * @param int $value
 401+ * @return bool
 402+ */
 403+ public static function tagIsValid( $tag, $value ) {
 404+ $levels = self::getTagLevels( $tag );
 405+ $highest = count( $levels ) - 1;
 406+ if ( !$levels || $value < 0 || $value > $highest ) {
 407+ return false; // flag range is invalid
 408+ }
 409+ return true;
 410+ }
 411+
 412+ /**
 413+ * Check if all of the required site flags have a valid value
 414+ * @param array $flags
 415+ * @return bool
 416+ */
 417+ public static function flagsAreValid( array $flags ) {
 418+ foreach ( self::getDimensions() as $qal => $levels ) {
 419+ if ( !isset( $flags[$qal] ) || !self::tagIsValid( $qal, $flags[$qal] ) ) {
 420+ return false;
 421+ }
 422+ }
 423+ return true;
 424+ }
 425+
 426+ /**
 427+ * Returns true if a user can set $tag to $value
 428+ * @param User $user
 429+ * @param string $tag
 430+ * @param int $value
 431+ * @return bool
 432+ */
 433+ public static function userCanSetTag( $user, $tag, $value ) {
 434+ # Sanity check tag and value
 435+ if ( !self::tagIsValid( $tag, $value ) ) {
 436+ return false; // flag range is invalid
 437+ }
 438+ $restrictions = self::getTagRestrictions();
 439+ # No restrictions -> full access
 440+ if ( !isset( $restrictions[$tag] ) ) {
 441+ return true;
 442+ }
 443+ # Validators always have full access
 444+ if ( $user->isAllowed( 'validate' ) ) {
 445+ return true;
 446+ }
 447+ # Check if this user has any right that lets him/her set
 448+ # up to this particular value
 449+ foreach ( $restrictions[$tag] as $right => $level ) {
 450+ if ( $value <= $level && $level > 0 && $user->isAllowed( $right ) ) {
 451+ return true;
 452+ }
 453+ }
 454+ return false;
 455+ }
 456+
 457+ /**
 458+ * Returns true if a user can set $flags for a revision via review.
 459+ * Requires the same for $oldflags if given.
 460+ * @param User $user
 461+ * @param array $flags, suggested flags
 462+ * @param array $oldflags, pre-existing flags
 463+ * @return bool
 464+ */
 465+ public static function userCanSetFlags( $user, array $flags, $oldflags = array() ) {
 466+ if ( !$user->isAllowed( 'review' ) ) {
 467+ return false; // User is not able to review pages
 468+ }
 469+ # Check if all of the required site flags have
 470+ # a valid value that the user is allowed to set...
 471+ foreach ( self::getDimensions() as $qal => $levels ) {
 472+ if ( !isset( $flags[$qal] ) ) {
 473+ return false; // unspecified
 474+ } elseif ( !self::userCanSetTag( $user, $qal, $flags[$qal] ) ) {
 475+ return false; // user cannot set proposed flag
 476+ } elseif ( isset( $oldflags[$qal] )
 477+ && !self::userCanSetTag( $user, $qal, $oldflags[$qal] ) )
 478+ {
 479+ return false; // user cannot change old flag
 480+ }
 481+ }
 482+ return true;
 483+ }
 484+
 485+ /**
 486+ * Check if a user can set the autoreview restiction level to $right
 487+ * @param User $user
 488+ * @param string $right the level
 489+ * @return bool
 490+ */
 491+ public static function userCanSetAutoreviewLevel( $user, $right ) {
 492+ if ( $right == '' ) {
 493+ return true; // no restrictions (none)
 494+ }
 495+ if ( !in_array( $right, FlaggedRevs::getRestrictionLevels() ) ) {
 496+ return false; // invalid restriction level
 497+ }
 498+ # Don't let them choose levels above their own rights
 499+ if ( $right == 'sysop' ) {
 500+ // special case, rewrite sysop to protect and editprotected
 501+ if ( !$user->isAllowed( 'protect' ) && !$user->isAllowed( 'editprotected' ) ) {
 502+ return false;
 503+ }
 504+ } elseif ( !$user->isAllowed( $right ) ) {
 505+ return false;
 506+ }
 507+ return true;
 508+ }
 509+
 510+ # ################ Parsing functions #################
 511+
 512+ /**
 513+ * All templates and arguments in $text are expanded out
 514+ * @param Title $title
 515+ * @param string $text wikitext
 516+ * @param int $id Source revision Id
 517+ * @return array( string wikitext, array of template versions )
 518+ */
 519+ public static function expandText( Title $title, $text, $id ) {
 520+ global $wgParser;
 521+ # Notify Parser if includes should be stabilized
 522+ $resetManager = false;
 523+ $incManager = FRInclusionManager::singleton();
 524+ if ( $id && self::inclusionSetting() != FR_INCLUDES_CURRENT ) {
 525+ # Use FRInclusionManager to do the template/file version query
 526+ # up front unless the versions are already specified there...
 527+ if ( !$incManager->parserOutputIsStabilized() ) {
 528+ $frev = FlaggedRevision::newFromTitle( $title, $id );
 529+ if ( $frev ) {
 530+ $incManager->stabilizeParserOutput( $frev );
 531+ $resetManager = true; // need to reset when done
 532+ }
 533+ }
 534+ }
 535+ $options = self::makeParserOptions(); // default options
 536+ $outputText = $wgParser->preprocess( $text, $title, $options, $id );
 537+ $pOutput = $wgParser->getOutput();
 538+ # Stable parse done!
 539+ if ( $resetManager ) {
 540+ $incManager->clear(); // reset the FRInclusionManager as needed
 541+ }
 542+ # Return data array
 543+ return array( $outputText, $pOutput->getTemplateIds() );
 544+ }
 545+
 546+ /**
 547+ * Get the HTML output of a revision based on $text.
 548+ * @param Title $title
 549+ * @param string $text
 550+ * @param int $id Source revision Id
 551+ * @return ParserOutput
 552+ */
 553+ public static function parseStableText( Title $title, $text, $id, $parserOptions ) {
 554+ global $wgParser;
 555+ # Notify Parser if includes should be stabilized
 556+ $resetManager = false;
 557+ $incManager = FRInclusionManager::singleton();
 558+ if ( $id && self::inclusionSetting() != FR_INCLUDES_CURRENT ) {
 559+ # Use FRInclusionManager to do the template/file version query
 560+ # up front unless the versions are already specified there...
 561+ if ( !$incManager->parserOutputIsStabilized() ) {
 562+ $frev = FlaggedRevision::newFromTitle( $title, $id );
 563+ if ( $frev ) {
 564+ $incManager->stabilizeParserOutput( $frev );
 565+ $resetManager = true; // need to reset when done
 566+ }
 567+ }
 568+ }
 569+ # Parse the new body, wikitext -> html
 570+ $parserOut = $wgParser->parse( $text, $title, $parserOptions, true, true, $id );
 571+ # Stable parse done!
 572+ if ( $resetManager ) {
 573+ $incManager->clear(); // reset the FRInclusionManager as needed
 574+ }
 575+ return $parserOut;
 576+ }
 577+
 578+ /**
 579+ * Get standard parser options
 580+ * @param User $user (optional)
 581+ * @return ParserOptions
 582+ */
 583+ public static function makeParserOptions( $user = null ) {
 584+ global $wgUser;
 585+ $user = $user ? $user : $wgUser; // assume current
 586+ $options = ParserOptions::newFromUser( $user );
 587+ # Show inclusion/loop reports
 588+ $options->enableLimitReport();
 589+ # Fix bad HTML
 590+ $options->setTidy( true );
 591+ return $options;
 592+ }
 593+
 594+ /**
 595+ * Get the page cache for the stable version of an article
 596+ * @param Article $article
 597+ * @param User $user
 598+ * @return mixed (ParserOutput/false)
 599+ */
 600+ public static function getPageCache( Article $article, $user ) {
 601+ global $parserMemc, $wgCacheEpoch;
 602+ wfProfileIn( __METHOD__ );
 603+ # Make sure it is valid
 604+ if ( !$article->getId() ) {
 605+ wfProfileOut( __METHOD__ );
 606+ return null;
 607+ }
 608+ $parserCache = ParserCache::singleton();
 609+ $key = self::getCacheKey( $parserCache, $article, $user );
 610+ # Get the cached HTML
 611+ wfDebug( "Trying parser cache $key\n" );
 612+ $value = $parserMemc->get( $key );
 613+ if ( is_object( $value ) ) {
 614+ wfDebug( "Found.\n" );
 615+ # Delete if article has changed since the cache was made
 616+ $canCache = $article->checkTouched();
 617+ $cacheTime = $value->getCacheTime();
 618+ $touched = $article->mTouched;
 619+ if ( !$canCache || $value->expired( $touched ) ) {
 620+ if ( !$canCache ) {
 621+ wfIncrStats( "pcache_miss_invalid" );
 622+ wfDebug( "Invalid cached redirect, touched $touched, epoch $wgCacheEpoch, cached $cacheTime\n" );
 623+ } else {
 624+ wfIncrStats( "pcache_miss_expired" );
 625+ wfDebug( "Key expired, touched $touched, epoch $wgCacheEpoch, cached $cacheTime\n" );
 626+ }
 627+ $parserMemc->delete( $key );
 628+ $value = false;
 629+ } else {
 630+ wfIncrStats( "pcache_hit" );
 631+ }
 632+ } else {
 633+ wfDebug( "Parser cache miss.\n" );
 634+ wfIncrStats( "pcache_miss_absent" );
 635+ $value = false;
 636+ }
 637+ wfProfileOut( __METHOD__ );
 638+ return $value;
 639+ }
 640+
 641+ /**
 642+ * Like ParserCache::getKey() with stable-pcache instead of pcache
 643+ */
 644+ protected static function getCacheKey( $parserCache, Article $article, $popts ) {
 645+ if( $popts instanceof User ) {
 646+ $popts = ParserOptions::newFromUser( $popts );
 647+ }
 648+ $key = $parserCache->getKey( $article, $popts );
 649+ $key = str_replace( ':pcache:', ':stable-pcache:', $key );
 650+ return $key;
 651+ }
 652+
 653+ /**
 654+ * @param Article $article
 655+ * @param ParserOptions $popts
 656+ * @param parserOutput $parserOut
 657+ * Updates the stable cache of a page with the given $parserOut
 658+ */
 659+ public static function updatePageCache(
 660+ Article $article, $popts, ParserOutput $parserOut = null
 661+ ) {
 662+ global $parserMemc, $wgParserCacheExpireTime, $wgEnableParserCache;
 663+ wfProfileIn( __METHOD__ );
 664+ # Make sure it is valid and $wgEnableParserCache is enabled
 665+ if ( !$wgEnableParserCache || !$parserOut ) {
 666+ wfProfileOut( __METHOD__ );
 667+ return false;
 668+ }
 669+ $parserCache = ParserCache::singleton();
 670+ $key = self::getCacheKey( $parserCache, $article, $popts );
 671+ # Add cache mark to HTML
 672+ $now = wfTimestampNow();
 673+ $parserOut->setCacheTime( $now );
 674+ # Save the timestamp so that we don't have to load the revision row on view
 675+ $parserOut->mTimestamp = $article->getTimestamp();
 676+ $parserOut->mText .= "\n<!-- Saved in stable version parser cache with key $key and timestamp $now -->";
 677+ # Set expire time
 678+ if ( $parserOut->containsOldMagic() ) {
 679+ $expire = 3600; // 1 hour
 680+ } else {
 681+ $expire = $wgParserCacheExpireTime;
 682+ }
 683+ # Save to objectcache
 684+ $parserMemc->set( $key, $parserOut, $expire );
 685+ wfProfileOut( __METHOD__ );
 686+ return true;
 687+ }
 688+
 689+ /**
 690+ * @param Article $article
 691+ * @param parserOutput $parserOut
 692+ * Updates the stable-only cache dependency table
 693+ */
 694+ public static function updateCacheTracking( Article $article, ParserOutput $stableOut ) {
 695+ wfProfileIn( __METHOD__ );
 696+ if ( !wfReadOnly() ) {
 697+ $frDepUpdate = new FRDependencyUpdate( $article->getTitle(), $stableOut );
 698+ $frDepUpdate->doUpdate();
 699+ }
 700+ wfProfileOut( __METHOD__ );
 701+ }
 702+
 703+ /**
 704+ * @param Article $article
 705+ * @param bool $synced
 706+ * Updates the fp_reviewed field for this article
 707+ */
 708+ public static function updateSyncStatus( Article $article, $synced ) {
 709+ wfProfileIn( __METHOD__ );
 710+ if ( !wfReadOnly() ) {
 711+ $dbw = wfGetDB( DB_MASTER );
 712+ $dbw->update( 'flaggedpages',
 713+ array( 'fp_reviewed' => (int)$synced ),
 714+ array( 'fp_page_id' => $article->getID() ),
 715+ __METHOD__
 716+ );
 717+ }
 718+ wfProfileOut( __METHOD__ );
 719+ }
 720+
 721+ # ################ Tracking/cache update update functions #################
 722+
 723+ /**
 724+ * Update the page tables with a new stable version.
 725+ * @param Title $title
 726+ * @param FlaggedRevision|null $sv, the new stable version (optional)
 727+ * @param FlaggedRevision|null $oldSv, the old stable version (optional)
 728+ * @return bool stable version text/file changed and FR_INCLUDES_STABLE
 729+ */
 730+ public static function stableVersionUpdates( Title $title, $sv = null, $oldSv = null ) {
 731+ $changed = false;
 732+ if ( $oldSv === null ) { // optional
 733+ $oldSv = FlaggedRevision::newFromStable( $title, FR_MASTER );
 734+ }
 735+ if ( $sv === null ) { // optional
 736+ $sv = FlaggedRevision::determineStable( $title, FR_MASTER );
 737+ }
 738+ $article = new FlaggedPage( $title );
 739+ if ( !$sv ) {
 740+ # Empty flaggedrevs data for this page if there is no stable version
 741+ $article->clearStableVersion();
 742+ # Check if pages using this need to be refreshed...
 743+ if ( FlaggedRevs::inclusionSetting() == FR_INCLUDES_STABLE ) {
 744+ $changed = (bool)$oldSv;
 745+ }
 746+ } else {
 747+ # Update flagged page related fields
 748+ $article->updateStableVersion( $sv );
 749+ # Check if pages using this need to be invalidated/purged...
 750+ if ( FlaggedRevs::inclusionSetting() == FR_INCLUDES_STABLE ) {
 751+ $changed = (
 752+ !$oldSv ||
 753+ $sv->getRevId() != $oldSv->getRevId() ||
 754+ $sv->getFileTimestamp() != $oldSv->getFileTimestamp() ||
 755+ $sv->getFileSha1() != $oldSv->getFileSha1()
 756+ );
 757+ }
 758+ }
 759+ # Lazily rebuild dependancies on next parse (we invalidate below)
 760+ FlaggedRevs::clearStableOnlyDeps( $title );
 761+ # Clear page cache
 762+ $title->invalidateCache();
 763+ self::purgeSquid( $title );
 764+ return $changed;
 765+ }
 766+
 767+ /**
 768+ * @param Title $title
 769+ * Updates squid cache for a title. Defers till after main commit().
 770+ */
 771+ public static function purgeSquid( Title $title ) {
 772+ global $wgDeferredUpdateList;
 773+ $wgDeferredUpdateList[] = new FRSquidUpdate( $title );
 774+ }
 775+
 776+ /**
 777+ * Do cache updates for when the stable version of a page changed.
 778+ * Invalidates/purges pages that include the given page.
 779+ * @param Title $title
 780+ * @param bool $recursive
 781+ */
 782+ public static function HTMLCacheUpdates( Title $title ) {
 783+ global $wgDeferredUpdateList;
 784+ # Invalidate caches of articles which include this page...
 785+ $wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'templatelinks' );
 786+ if ( $title->getNamespace() == NS_FILE ) {
 787+ $wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'imagelinks' );
 788+ }
 789+ $wgDeferredUpdateList[] = new FRExtraCacheUpdate( $title );
 790+ }
 791+
 792+ /**
 793+ * Invalidates/purges pages where only stable version includes this page.
 794+ * @param Title $title
 795+ */
 796+ public static function extraHTMLCacheUpdate( Title $title ) {
 797+ global $wgDeferredUpdateList;
 798+ $wgDeferredUpdateList[] = new FRExtraCacheUpdate( $title );
 799+ }
 800+
 801+ # ################ Revision functions #################
 802+
 803+ /**
 804+ * Get flags for a revision
 805+ * @param Title $title
 806+ * @param int $rev_id
 807+ * @param $flags, FR_MASTER
 808+ * @return array
 809+ */
 810+ public static function getRevisionTags( Title $title, $rev_id, $flags = 0 ) {
 811+ $db = ( $flags & FR_MASTER ) ?
 812+ wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
 813+ $tags = (string)$db->selectField( 'flaggedrevs',
 814+ 'fr_tags',
 815+ array( 'fr_rev_id' => $rev_id,
 816+ 'fr_page_id' => $title->getArticleId() ),
 817+ __METHOD__
 818+ );
 819+ return FlaggedRevision::expandRevisionTags( strval( $tags ) );
 820+ }
 821+
 822+ /**
 823+ * @param int $page_id
 824+ * @param int $rev_id
 825+ * @param $flags, FR_MASTER
 826+ * @return mixed (int or false)
 827+ * Get quality of a revision
 828+ */
 829+ public static function getRevQuality( $page_id, $rev_id, $flags = 0 ) {
 830+ $db = ( $flags & FR_MASTER ) ?
 831+ wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
 832+ return $db->selectField( 'flaggedrevs',
 833+ 'fr_quality',
 834+ array( 'fr_page_id' => $page_id, 'fr_rev_id' => $rev_id ),
 835+ __METHOD__,
 836+ array( 'USE INDEX' => 'PRIMARY' )
 837+ );
 838+ }
 839+
 840+ /**
 841+ * @param Title $title
 842+ * @param int $rev_id
 843+ * @param $flags, FR_MASTER
 844+ * @return bool
 845+ * Useful for quickly pinging to see if a revision is flagged
 846+ */
 847+ public static function revIsFlagged( Title $title, $rev_id, $flags = 0 ) {
 848+ $quality = self::getRevQuality( $title->getArticleId(), $rev_id, $flags );
 849+ return ( $quality !== false );
 850+ }
 851+
 852+ /**
 853+ * Get the "prime" flagged revision of a page
 854+ * @param Article $article
 855+ * @return mixed (integer/false)
 856+ * Will not return a revision if deleted
 857+ */
 858+ public static function getPrimeFlaggedRevId( Article $article ) {
 859+ $dbr = wfGetDB( DB_SLAVE );
 860+ # Get the highest quality revision (not necessarily this one).
 861+ $oldid = $dbr->selectField( array( 'flaggedrevs', 'revision' ),
 862+ 'fr_rev_id',
 863+ array(
 864+ 'fr_page_id' => $article->getId(),
 865+ 'rev_page = fr_page_id',
 866+ 'rev_id = fr_rev_id'
 867+ ),
 868+ __METHOD__,
 869+ array(
 870+ 'ORDER BY' => 'fr_quality DESC, fr_rev_id DESC',
 871+ 'USE INDEX' => array( 'flaggedrevs' => 'page_qal_rev', 'revision' => 'PRIMARY' )
 872+ )
 873+ );
 874+ return $oldid;
 875+ }
 876+
 877+ /**
 878+ * Mark a revision as patrolled if needed
 879+ * @param Revision $rev
 880+ * @return bool DB write query used
 881+ */
 882+ public static function markRevisionPatrolled( Revision $rev ) {
 883+ $rcid = $rev->isUnpatrolled();
 884+ # Make sure it is now marked patrolled...
 885+ if ( $rcid ) {
 886+ $dbw = wfGetDB( DB_MASTER );
 887+ $dbw->update( 'recentchanges',
 888+ array( 'rc_patrolled' => 1 ),
 889+ array( 'rc_id' => $rcid ),
 890+ __METHOD__
 891+ );
 892+ return true;
 893+ }
 894+ return false;
 895+ }
 896+
 897+ # ################ Other utility functions #################
 898+
 899+ /**
 900+ * @param string $val
 901+ * @return Object (val,time) tuple
 902+ * Get a memcache storage object
 903+ */
 904+ public static function makeMemcObj( $val ) {
 905+ $data = (object) array();
 906+ $data->value = $val;
 907+ $data->time = wfTimestampNow();
 908+ return $data;
 909+ }
 910+
 911+ /**
 912+ * @param object|false $data makeMemcObj() tuple
 913+ * @param Article $article
 914+ * @return mixed
 915+ * Return memc value if not expired
 916+ */
 917+ public static function getMemcValue( $data, Article $article ) {
 918+ if ( is_object( $data ) && $data->time >= $article->getTouched() ) {
 919+ return $data->value;
 920+ }
 921+ return false;
 922+ }
 923+
 924+ /**
 925+ * @param array $flags
 926+ * @return bool, is this revision at basic review condition?
 927+ */
 928+ public static function isChecked( array $flags ) {
 929+ self::load();
 930+ return self::tagsAtLevel( $flags, self::$minSL );
 931+ }
 932+
 933+ /**
 934+ * @param array $flags
 935+ * @return bool, is this revision at quality review condition?
 936+ */
 937+ public static function isQuality( array $flags ) {
 938+ self::load();
 939+ return self::tagsAtLevel( $flags, self::$minQL );
 940+ }
 941+
 942+ /**
 943+ * @param array $flags
 944+ * @return bool, is this revision at pristine review condition?
 945+ */
 946+ public static function isPristine( array $flags ) {
 947+ self::load();
 948+ return self::tagsAtLevel( $flags, self::$minPL );
 949+ }
 950+
 951+ // Checks if $flags meets $reqFlagLevels
 952+ protected static function tagsAtLevel( array $flags, $reqFlagLevels ) {
 953+ self::load();
 954+ if ( empty( $flags ) ) {
 955+ return false;
 956+ }
 957+ foreach ( self::$dimensions as $f => $x ) {
 958+ if ( !isset( $flags[$f] ) || $reqFlagLevels[$f] > $flags[$f] ) {
 959+ return false;
 960+ }
 961+ }
 962+ return true;
 963+ }
 964+
 965+ /**
 966+ * Get the quality tier of review flags
 967+ * @param array $flags
 968+ * @return int flagging tier (FR_PRISTINE,FR_QUALITY,FR_CHECKED,-1)
 969+ */
 970+ public static function getLevelTier( array $flags ) {
 971+ if ( self::isPristine( $flags ) ) {
 972+ return FR_PRISTINE; // 2
 973+ } elseif ( self::isQuality( $flags ) ) {
 974+ return FR_QUALITY; // 1
 975+ } elseif ( self::isChecked( $flags ) ) {
 976+ return FR_CHECKED; // 0
 977+ }
 978+ return -1;
 979+ }
 980+
 981+ /**
 982+ * Get minimum level tags for a tier
 983+ * @param int $tier FR_PRISTINE/FR_QUALITY/FR_CHECKED
 984+ * @return array
 985+ */
 986+ public static function quickTags( $tier ) {
 987+ self::load();
 988+ if ( $tier == FR_PRISTINE ) {
 989+ return self::$minPL;
 990+ } elseif ( $tier == FR_QUALITY ) {
 991+ return self::$minQL;
 992+ }
 993+ return self::$minSL;
 994+ }
 995+
 996+ /**
 997+ * Get minimum tags that are closest to $oldFlags
 998+ * given the site, page, and user rights limitations.
 999+ * @param User $user
 1000+ * @param array $oldFlags previous stable rev flags
 1001+ * @return mixed array or null
 1002+ */
 1003+ public static function getAutoReviewTags( $user, array $oldFlags ) {
 1004+ if ( !self::autoReviewEdits() ) {
 1005+ return null; // shouldn't happen
 1006+ }
 1007+ $flags = array();
 1008+ foreach ( self::getTags() as $tag ) {
 1009+ # Try to keep this tag val the same as the stable rev's
 1010+ $val = isset( $oldFlags[$tag] ) ? $oldFlags[$tag] : 1;
 1011+ $val = min( $val, self::maxAutoReviewLevel( $tag ) );
 1012+ # Dial down the level to one the user has permission to set
 1013+ while ( !self::userCanSetTag( $user, $tag, $val ) ) {
 1014+ $val--;
 1015+ if ( $val <= 0 ) {
 1016+ return null; // all tags vals must be > 0
 1017+ }
 1018+ }
 1019+ $flags[$tag] = $val;
 1020+ }
 1021+ return $flags;
 1022+ }
 1023+
 1024+ /**
 1025+ * Get the list of reviewable namespaces
 1026+ * @return array
 1027+ */
 1028+ public static function getReviewNamespaces() {
 1029+ self::load(); // validates namespaces
 1030+ return self::$reviewNamespaces;
 1031+ }
 1032+
 1033+ /**
 1034+ * Get the list of patrollable namespaces
 1035+ * @return array
 1036+ */
 1037+ public static function getPatrolNamespaces() {
 1038+ self::load(); // validates namespaces
 1039+ return self::$patrolNamespaces;
 1040+ }
 1041+
 1042+
 1043+ /**
 1044+ * Is this page in reviewable namespace?
 1045+ * Note: this checks $wgFlaggedRevsWhitelist
 1046+ * @param Title, $title
 1047+ * @return bool
 1048+ */
 1049+ public static function inReviewNamespace( Title $title ) {
 1050+ global $wgFlaggedRevsWhitelist;
 1051+ $namespaces = self::getReviewNamespaces();
 1052+ $ns = ( $title->getNamespace() == NS_MEDIA ) ?
 1053+ NS_FILE : $title->getNamespace(); // Treat NS_MEDIA as NS_FILE
 1054+ # Check for MW: pages and whitelist for exempt pages
 1055+ if ( in_array( $title->getPrefixedDBKey(), $wgFlaggedRevsWhitelist ) ) {
 1056+ return false;
 1057+ }
 1058+ return ( in_array( $ns, $namespaces ) );
 1059+ }
 1060+
 1061+ /**
 1062+ * Is this page in patrollable namespace?
 1063+ * @param Title, $title
 1064+ * @return bool
 1065+ */
 1066+ public static function inPatrolNamespace( Title $title ) {
 1067+ $namespaces = self::getPatrolNamespaces();
 1068+ $ns = ( $title->getNamespace() == NS_MEDIA ) ?
 1069+ NS_FILE : $title->getNamespace(); // Treat NS_MEDIA as NS_FILE
 1070+ return ( in_array( $ns, $namespaces ) );
 1071+ }
 1072+
 1073+ /**
 1074+ * Clear FlaggedRevs tracking tables for this page
 1075+ * @param int|array $pageId (int or array)
 1076+ */
 1077+ public static function clearTrackingRows( $pageId ) {
 1078+ $dbw = wfGetDB( DB_MASTER );
 1079+ $dbw->delete( 'flaggedpages', array( 'fp_page_id' => $pageId ), __METHOD__ );
 1080+ $dbw->delete( 'flaggedrevs_tracking', array( 'ftr_from' => $pageId ), __METHOD__ );
 1081+ $dbw->delete( 'flaggedpage_pending', array( 'fpp_page_id' => $pageId ), __METHOD__ );
 1082+ }
 1083+
 1084+ /**
 1085+ * Clear tracking table of stable-only links for this page
 1086+ * @param int|array $pageId (int or array)
 1087+ */
 1088+ public static function clearStableOnlyDeps( $pageId ) {
 1089+ $dbw = wfGetDB( DB_MASTER );
 1090+ $dbw->delete( 'flaggedrevs_tracking', array( 'ftr_from' => $pageId ), __METHOD__ );
 1091+ }
 1092+
 1093+ # ################ Auto-review function #################
 1094+
 1095+ /**
 1096+ * Automatically review an revision and add a log entry in the review log.
 1097+ *
 1098+ * This is called during edit operations after the new revision is added
 1099+ * and the page tables updated, but before LinksUpdate is called.
 1100+ *
 1101+ * $auto is here for revisions checked off to be reviewed. Auto-review
 1102+ * triggers on edit, but we don't want those to count as just automatic.
 1103+ * This also makes it so the user's name shows up in the page history.
 1104+ *
 1105+ * If $flags is given, then they will be the review tags. If not, the one
 1106+ * from the stable version will be used or minimal tags if that's not possible.
 1107+ * If no appropriate tags can be found, then the review will abort.
 1108+ */
 1109+ public static function autoReviewEdit(
 1110+ Article $article, $user, Revision $rev, array $flags = null, $auto = true
 1111+ ) {
 1112+ wfProfileIn( __METHOD__ );
 1113+ $title = $article->getTitle(); // convenience
 1114+ # Get current stable version ID (for logging)
 1115+ $oldSv = FlaggedRevision::newFromStable( $title, FR_MASTER );
 1116+ $oldSvId = $oldSv ? $oldSv->getRevId() : 0;
 1117+ # Set the auto-review tags from the prior stable version.
 1118+ # Normally, this should already be done and given here...
 1119+ if ( !is_array( $flags ) ) {
 1120+ if ( $oldSv ) {
 1121+ # Use the last stable version if $flags not given
 1122+ if ( $user->isAllowed( 'bot' ) ) {
 1123+ $flags = $oldSv->getTags(); // no change for bot edits
 1124+ } else {
 1125+ # Account for perms/tags...
 1126+ $flags = self::getAutoReviewTags( $user, $oldSv->getTags() );
 1127+ }
 1128+ } else { // new page?
 1129+ $flags = self::quickTags( FR_CHECKED ); // use minimal level
 1130+ }
 1131+ if ( !is_array( $flags ) ) {
 1132+ wfProfileOut( __METHOD__ );
 1133+ return false; // can't auto-review this revision
 1134+ }
 1135+ }
 1136+ # Get quality tier from flags
 1137+ $quality = 0;
 1138+ if ( self::isQuality( $flags ) ) {
 1139+ $quality = self::isPristine( $flags ) ? 2 : 1;
 1140+ }
 1141+ # Get review property flags
 1142+ $propFlags = $auto ? array( 'auto' ) : array();
 1143+
 1144+ # Rev ID is not put into parser on edit, so do the same here.
 1145+ # Also, a second parse would be triggered otherwise.
 1146+ $editInfo = $article->prepareTextForEdit( $rev->getText() );
 1147+ $poutput = $editInfo->output; // revision HTML output
 1148+
 1149+ # If this is an image page, store corresponding file info
 1150+ $fileData = array( 'name' => null, 'timestamp' => null, 'sha1' => null );
 1151+ if ( $title->getNamespace() == NS_FILE ) {
 1152+ $file = $article instanceof ImagePage ?
 1153+ $article->getFile() : wfFindFile( $title );
 1154+ if ( is_object( $file ) && $file->exists() ) {
 1155+ $fileData['name'] = $title->getDBkey();
 1156+ $fileData['timestamp'] = $file->getTimestamp();
 1157+ $fileData['sha1'] = $file->getSha1();
 1158+ }
 1159+ }
 1160+
 1161+ # Our review entry
 1162+ $flaggedRevision = new FlaggedRevision( array(
 1163+ 'page_id' => $rev->getPage(),
 1164+ 'rev_id' => $rev->getId(),
 1165+ 'user' => $user->getId(),
 1166+ 'timestamp' => $rev->getTimestamp(),
 1167+ 'quality' => $quality,
 1168+ 'tags' => FlaggedRevision::flattenRevisionTags( $flags ),
 1169+ 'img_name' => $fileData['name'],
 1170+ 'img_timestamp' => $fileData['timestamp'],
 1171+ 'img_sha1' => $fileData['sha1'],
 1172+ 'templateVersions' => $poutput->getTemplateIds(),
 1173+ 'fileVersions' => $poutput->getImageTimeKeys(),
 1174+ 'flags' => implode( ',', $propFlags ),
 1175+ ) );
 1176+ $flaggedRevision->insertOn();
 1177+ # Update the article review log
 1178+ FlaggedRevsLog::updateReviewLog( $title,
 1179+ $flags, array(), '', $rev->getId(), $oldSvId, true, $auto );
 1180+
 1181+ # Update page and tracking tables and clear cache
 1182+ FlaggedRevs::stableVersionUpdates( $title );
 1183+
 1184+ wfProfileOut( __METHOD__ );
 1185+ return true;
 1186+ }
 1187+
 1188+ /**
 1189+ * Get JS script params
 1190+ */
 1191+ public static function getJSTagParams() {
 1192+ self::load();
 1193+ # Param to pass to JS function to know if tags are at quality level
 1194+ $tagsJS = array();
 1195+ foreach ( self::$dimensions as $tag => $x ) {
 1196+ $tagsJS[$tag] = array();
 1197+ $tagsJS[$tag]['levels'] = count( $x ) - 1;
 1198+ $tagsJS[$tag]['quality'] = self::$minQL[$tag];
 1199+ $tagsJS[$tag]['pristine'] = self::$minPL[$tag];
 1200+ }
 1201+ $params = array( 'tags' => (object)$tagsJS );
 1202+ return (object)$params;
 1203+ }
 1204+}
Property changes on: trunk/extensions/FlaggedRevs/dataclasses/FlaggedRevs.class.php
___________________________________________________________________
Added: svn:eol-style
11205 + native
Index: trunk/extensions/FlaggedRevs/dataclasses/FRExtraCacheUpdate.php
@@ -0,0 +1,189 @@
 2+<?php
 3+/**
 4+ * Class containing cache update methods and job construction
 5+ * for the special case of purging pages due to dependancies
 6+ * contained only in the stable version of pages.
 7+ *
 8+ * These dependancies should be limited in number as most pages should
 9+ * have a stable version synced with the current version.
 10+ */
 11+class FRExtraCacheUpdate {
 12+ public $mTitle, $mTable;
 13+ public $mRowsPerJob, $mRowsPerQuery;
 14+
 15+ public function __construct( Title $titleTo ) {
 16+ global $wgUpdateRowsPerJob, $wgUpdateRowsPerQuery;
 17+ $this->mTitle = $titleTo;
 18+ $this->mTable = 'flaggedrevs_tracking';
 19+ $this->mRowsPerJob = $wgUpdateRowsPerJob;
 20+ $this->mRowsPerQuery = $wgUpdateRowsPerQuery;
 21+ }
 22+
 23+ public function doUpdate() {
 24+ # Fetch the IDs
 25+ $dbr = wfGetDB( DB_SLAVE );
 26+ $res = $dbr->select( $this->mTable, $this->getFromField(),
 27+ $this->getToCondition(), __METHOD__ );
 28+ # Check if there is anything to do...
 29+ if ( $dbr->numRows( $res ) > 0 ) {
 30+ # Do it right now?
 31+ if ( $dbr->numRows( $res ) <= $this->mRowsPerJob ) {
 32+ $this->invalidateIDs( $res );
 33+ # Defer to job queue...
 34+ } else {
 35+ $this->insertJobs( $res );
 36+ }
 37+ }
 38+ }
 39+
 40+ protected function insertJobs( ResultWrapper $res ) {
 41+ $numRows = $res->numRows();
 42+ if ( !$numRows ) {
 43+ return; // sanity check
 44+ }
 45+ $numBatches = ceil( $numRows / $this->mRowsPerJob );
 46+ $realBatchSize = ceil( $numRows / $numBatches );
 47+ $jobs = array();
 48+ do {
 49+ $first = $last = false; // first/last page_id of this batch
 50+ # Get $realBatchSize items (or less if not enough)...
 51+ for ( $i = 0; $i < $realBatchSize; $i++ ) {
 52+ $row = $res->fetchRow();
 53+ # Is there another row?
 54+ if ( $row ) {
 55+ $id = $row[0];
 56+ $last = $id; // $id is the last page_id of this batch
 57+ if ( $first === false )
 58+ $first = $id; // set first page_id of this batch
 59+ # Out of rows?
 60+ } else {
 61+ $id = false;
 62+ break;
 63+ }
 64+ }
 65+ # Insert batch into the queue if there is anything there
 66+ if ( $first ) {
 67+ $params = array(
 68+ 'table' => $this->mTable,
 69+ 'start' => $first,
 70+ 'end' => $last,
 71+ );
 72+ $jobs[] = new FRExtraCacheUpdateJob( $this->mTitle, $params );
 73+ }
 74+ $start = $id; // Where the last ID left off
 75+ } while ( $start );
 76+ Job::batchInsert( $jobs );
 77+ }
 78+
 79+ public function getFromField() {
 80+ return 'ftr_from';
 81+ }
 82+
 83+ public function getToCondition() {
 84+ return array( 'ftr_namespace' => $this->mTitle->getNamespace(),
 85+ 'ftr_title' => $this->mTitle->getDBkey() );
 86+ }
 87+
 88+ /**
 89+ * Invalidate a set of IDs, right now
 90+ */
 91+ public function invalidateIDs( ResultWrapper $res ) {
 92+ global $wgUseFileCache, $wgUseSquid;
 93+ if ( $res->numRows() == 0 ) return; // sanity check
 94+
 95+ $dbw = wfGetDB( DB_MASTER );
 96+ $timestamp = $dbw->timestamp();
 97+ $done = false;
 98+
 99+ while ( !$done ) {
 100+ # Get all IDs in this query into an array
 101+ $ids = array();
 102+ for ( $i = 0; $i < $this->mRowsPerQuery; $i++ ) {
 103+ $row = $res->fetchRow();
 104+ if ( $row ) {
 105+ $ids[] = $row[0];
 106+ } else {
 107+ $done = true;
 108+ break;
 109+ }
 110+ }
 111+ if ( count( $ids ) == 0 ) break;
 112+ # Update page_touched
 113+ $dbw->update( 'page', array( 'page_touched' => $timestamp ),
 114+ array( 'page_id' => $ids ), __METHOD__ );
 115+ # Update static caches
 116+ if ( $wgUseSquid || $wgUseFileCache ) {
 117+ $titles = Title::newFromIDs( $ids );
 118+ # Update squid cache
 119+ if ( $wgUseSquid ) {
 120+ $u = SquidUpdate::newFromTitles( $titles );
 121+ $u->doUpdate();
 122+ }
 123+ # Update file cache
 124+ if ( $wgUseFileCache ) {
 125+ foreach ( $titles as $title ) {
 126+ HTMLFileCache::clearFileCache( $title );
 127+ }
 128+ }
 129+ }
 130+ }
 131+ }
 132+}
 133+
 134+/**
 135+ * Job class for handling deferred FRExtraCacheUpdates
 136+ * @ingroup JobQueue
 137+ */
 138+class FRExtraCacheUpdateJob extends Job {
 139+ var $table, $start, $end;
 140+
 141+ /**
 142+ * Construct a job
 143+ * @param Title $title The title linked to
 144+ * @param array $params Job parameters (table, start and end page_ids)
 145+ * @param integer $id job_id
 146+ */
 147+ function __construct( $title, $params, $id = 0 ) {
 148+ parent::__construct( 'flaggedrevs_CacheUpdate', $title, $params, $id );
 149+ $this->table = $params['table'];
 150+ $this->start = $params['start'];
 151+ $this->end = $params['end'];
 152+ }
 153+
 154+ function run() {
 155+ $update = new FRExtraCacheUpdate( $this->title );
 156+ # Get query conditions
 157+ $fromField = $update->getFromField();
 158+ $conds = $update->getToCondition();
 159+ if ( $this->start ) {
 160+ $conds[] = "$fromField >= {$this->start}";
 161+ }
 162+ if ( $this->end ) {
 163+ $conds[] = "$fromField <= {$this->end}";
 164+ }
 165+ # Run query to get page Ids
 166+ $dbr = wfGetDB( DB_SLAVE );
 167+ $res = $dbr->select( $this->table, $fromField, $conds, __METHOD__ );
 168+ # Invalidate the pages
 169+ $update->invalidateIDs( $res );
 170+ return true;
 171+ }
 172+}
 173+
 174+/**
 175+ * Class for handling post-commit squid purge of a page
 176+ */
 177+class FRSquidUpdate {
 178+ protected $title;
 179+
 180+ function __construct( Title $title ) {
 181+ $this->title = $title;
 182+ }
 183+
 184+ function doUpdate() {
 185+ # Purge squid for this page only
 186+ $this->title->purgeSquid();
 187+ # Clear file cache for this page only
 188+ HTMLFileCache::clearFileCache( $this->title );
 189+ }
 190+}
Property changes on: trunk/extensions/FlaggedRevs/dataclasses/FRExtraCacheUpdate.php
___________________________________________________________________
Added: svn:eol-style
1191 + native
Index: trunk/extensions/FlaggedRevs/dataclasses/FRUserActivity.php
@@ -0,0 +1,176 @@
 2+<?php
 3+/*
 4+* Class of utility functions for getting/tracking user activity
 5+*/
 6+class FRUserActivity {
 7+ /**
 8+ * Get number of active users watching a page
 9+ * @param Title $title
 10+ * @return int
 11+ */
 12+ public static function numUsersWatchingPage( Title $title ) {
 13+ global $wgMemc, $wgCookieExpiration;
 14+ # Check the cache...
 15+ $key = wfMemcKey( 'flaggedrevs', 'usersWatching', $title->getArticleID() );
 16+ $val = $wgMemc->get( $key );
 17+ if ( is_int( $val ) ) {
 18+ return $val; // cache hit
 19+ }
 20+ # Get number of active editors watching this page...
 21+ $dbr = wfGetDB( DB_SLAVE );
 22+ $cutoff = $dbr->timestamp( wfTimestamp( TS_UNIX ) - 2 * $wgCookieExpiration );
 23+ $count = (int)$dbr->selectField(
 24+ array( 'watchlist', 'user' ),
 25+ 'COUNT(*)',
 26+ array(
 27+ 'wl_namespace' => $title->getNamespace(),
 28+ 'wl_title' => $title->getDBkey(),
 29+ 'wl_user = user_id',
 30+ 'user_touched > ' . $dbr->addQuotes( $cutoff ) // logged in or out
 31+ ),
 32+ __METHOD__,
 33+ array( 'USE INDEX' => array( 'watchlist' => 'namespace_title' ) )
 34+ );
 35+ if ( $count > 10 ) {
 36+ # Save new value to cache (more aggresive for larger counts)
 37+ $wgMemc->set( $key, $count, ( $count > 200 ) ? 30*60 : 5*60 );
 38+ }
 39+
 40+ return $count;
 41+ }
 42+
 43+ /*
 44+ * Get who is currently reviewing a page
 45+ * @param int $pageId
 46+ * @return array (username or null, MW timestamp or null)
 47+ */
 48+ public static function getUserReviewingPage( $pageId ) {
 49+ global $wgMemc;
 50+ $key = wfMemcKey( 'flaggedrevs', 'userReviewingPage', $pageId );
 51+ $val = $wgMemc->get( $key );
 52+ if ( is_array( $val ) && count( $val ) == 2 ) {
 53+ return $val;
 54+ }
 55+ return array( null, null );
 56+ }
 57+
 58+ /*
 59+ * Check is someone is currently reviewing a page
 60+ * @param int $pageId
 61+ * @return bool
 62+ */
 63+ public static function pageIsUnderReview( $pageId ) {
 64+ $m = self::getUserReviewingPage( $pageId );
 65+ return ( $m[0] !== null );
 66+ }
 67+
 68+ /*
 69+ * Set the flag for who is reviewing a page if not already set by someone
 70+ * @param User $user
 71+ * @param int $pageId
 72+ * @return bool flag set
 73+ */
 74+ public static function setUserReviewingPage( $user, $pageId ) {
 75+ global $wgMemc;
 76+ $key = wfMemcKey( 'flaggedrevs', 'userReviewingPage', $pageId );
 77+ $val = array( $user->getName(), wfTimestampNow() );
 78+ $wasSet = false;
 79+
 80+ $wgMemc->lock( $key, 4 ); // 4 sec timeout
 81+ if ( !$wgMemc->get( $key ) ) { // no flag set
 82+ $wgMemc->set( $key, $val, 20*60 ); // 20 min
 83+ $wasSet = true;
 84+ }
 85+ $wgMemc->unlock( $key );
 86+
 87+ return $wasSet;
 88+ }
 89+
 90+ /*
 91+ * Clear the flag for who is reviewing a page
 92+ * @param User $user
 93+ * @param int $pageId
 94+ */
 95+ public static function clearUserReviewingPage( $user, $pageId ) {
 96+ global $wgMemc;
 97+ $key = wfMemcKey( 'flaggedrevs', 'userReviewingPage', $pageId );
 98+ $wgMemc->lock( $key, 4 ); // 4 sec timeout
 99+ $val = $wgMemc->get( $key );
 100+ if ( is_array( $val ) && count( $val ) == 2 ) { // flag set
 101+ list( $u, $ts ) = $val;
 102+ if ( $u === $user->getName() ) {
 103+ $wgMemc->delete( $key );
 104+ }
 105+ }
 106+ $this->unlock();
 107+ }
 108+
 109+ /*
 110+ * Get who is currently reviewing a diff
 111+ * @param int $oldId
 112+ * @param int $newId
 113+ * @return array (username or null, MW timestamp or null)
 114+ */
 115+ public static function getUserReviewingDiff( $oldId, $newId ) {
 116+ global $wgMemc;
 117+ $key = wfMemcKey( 'flaggedrevs', 'userReviewingDiff', $oldId, $newId );
 118+ $val = $wgMemc->get( $key );
 119+ if ( is_array( $val ) && count( $val ) == 2 ) {
 120+ return $val;
 121+ }
 122+ return array( null, null );
 123+ }
 124+
 125+ /*
 126+ * Check is someone is currently reviewing a diff
 127+ * @param int $oldId
 128+ * @param int $newId
 129+ * @return bool
 130+ */
 131+ public static function diffIsUnderReview( $oldId, $newId ) {
 132+ $m = self::getUserReviewingDiff( $oldId, $newId );
 133+ return ( $m[0] !== null );
 134+ }
 135+
 136+ /*
 137+ * Set the flag for who is reviewing a diff if not already set by someone
 138+ * @param User $user
 139+ * @param int $pageId
 140+ * @return bool flag set
 141+ */
 142+ public static function setUserReviewingDiff( $user, $oldId, $newId ) {
 143+ global $wgMemc;
 144+ $key = wfMemcKey( 'flaggedrevs', 'userReviewingDiff', $oldId, $newId );
 145+ $val = array( $user->getName(), wfTimestampNow() );
 146+ $wasSet = false;
 147+
 148+ $wgMemc->lock( $key, 4 ); // 4 sec timeout
 149+ if ( !$wgMemc->get( $key ) ) { // no flag set
 150+ $wgMemc->set( $key, $val, 6*20 ); // 6 min
 151+ $wasSet = true;
 152+ }
 153+ $wgMemc->unlock( $key );
 154+
 155+ return $wasSet;
 156+ }
 157+
 158+ /*
 159+ * Clear the flag for who is reviewing a diff
 160+ * @param User $user
 161+ * @param int $oldId
 162+ * @param int $newId
 163+ */
 164+ public static function clearUserReviewingDiff( $user, $oldId, $newId ) {
 165+ global $wgMemc;
 166+ $key = wfMemcKey( 'flaggedrevs', 'userReviewingDiff', $oldId, $newId );
 167+ $wgMemc->lock( $key, 4 ); // 4 sec timeout
 168+ $val = $wgMemc->get( $key );
 169+ if ( is_array( $val ) && count( $val ) == 2 ) { // flag set
 170+ list( $u, $ts ) = $val;
 171+ if ( $u === $user->getName() ) {
 172+ $wgMemc->delete( $key );
 173+ }
 174+ }
 175+ $this->unlock();
 176+ }
 177+}
Property changes on: trunk/extensions/FlaggedRevs/dataclasses/FRUserActivity.php
___________________________________________________________________
Added: svn:eol-style
1178 + native
Index: trunk/extensions/FlaggedRevs/dataclasses/FRInclusionManager.php
@@ -0,0 +1,205 @@
 2+<?php
 3+/**
 4+ * Class containing template/file version usage requirements for
 5+ * Parser based on the source text (being parsed) revision ID.
 6+ * If no requirements are set, the page is parsed as normal.
 7+ *
 8+ * Parser hooks check this to determine what template/file version to use.
 9+ */
 10+class FRInclusionManager {
 11+ protected $reviewedVersions = null; // files/templates at review time
 12+ protected $stableVersions = array(); // stable versions of files/templates
 13+
 14+ protected static $instance = null;
 15+
 16+ public static function singleton() {
 17+ if ( self::$instance == null ) {
 18+ self::$instance = new self();
 19+ }
 20+ return self::$instance;
 21+ }
 22+ protected function __clone() { }
 23+
 24+ protected function __construct() {
 25+ $this->stableVersions['templates'] = array();
 26+ $this->stableVersions['files'] = array();
 27+ }
 28+
 29+ /**
 30+ * Reset all template/image version data
 31+ * @return void
 32+ */
 33+ public function clear() {
 34+ $this->reviewedVersions = null;
 35+ $this->stableVersions['templates'] = array();
 36+ $this->stableVersions['files'] = array();
 37+ }
 38+
 39+ /**
 40+ * (a) Stabilize inclusions in Parser output
 41+ * (b) Set the template/image versions used in the flagged version of a revision
 42+ * @param array $tmpParams (ns => dbKey => revId )
 43+ * @param array $imgParams (dbKey => array('time' => MW timestamp,'sha1' => sha1) )
 44+ */
 45+ public function setReviewedVersions( array $tmpParams, array $imgParams ) {
 46+ $this->reviewedVersions = array();
 47+ $this->reviewedVersions['templates'] = self::formatTemplateArray( $tmpParams );
 48+ $this->reviewedVersions['files'] = self::formatFileArray( $imgParams );
 49+ }
 50+
 51+ /**
 52+ * Set the stable versions of some template/images
 53+ * @param array $tmpParams (ns => dbKey => revId )
 54+ * @param array $imgParams (dbKey => array('time' => MW timestamp,'sha1' => sha1) )
 55+ */
 56+ public function setStableVersionCache( array $tmpParams, array $imgParams ) {
 57+ $this->stableVersions['templates'] = self::formatTemplateArray( $tmpParams );
 58+ $this->stableVersions['files'] = self::formatFileArray( $imgParams );
 59+ }
 60+
 61+ /**
 62+ * Clean up a template version array
 63+ * @param array $tmpParams (ns => dbKey => revId )
 64+ * @return array
 65+ */
 66+ protected function formatTemplateArray( array $params ) {
 67+ $res = array();
 68+ foreach ( $params as $ns => $templates ) {
 69+ $res[$ns] = array();
 70+ foreach ( $templates as $dbKey => $revId ) {
 71+ $res[$ns][$dbKey] = (int)$revId;
 72+ }
 73+ }
 74+ return $res;
 75+ }
 76+
 77+ /**
 78+ * Clean up a file version array
 79+ * @param array $imgParams (dbKey => array('time' => MW timestamp,'sha1' => sha1) )
 80+ * @return array
 81+ */
 82+ protected function formatFileArray( array $params ) {
 83+ $res = array();
 84+ foreach ( $params as $dbKey => $timeKey ) {
 85+ $time = '0'; // missing
 86+ $sha1 = false;
 87+ if ( $timeKey['time'] ) {
 88+ $time = $timeKey['time'];
 89+ $sha1 = strval( $timeKey['sha1'] );
 90+ }
 91+ $res[$dbKey] = array( 'time' => $time, 'sha1' => $sha1 );
 92+ }
 93+ return $res;
 94+ }
 95+
 96+ /**
 97+ * (a) Stabilize inclusions in Parser output
 98+ * (b) Load all of the "review time" versions of template/files from $frev
 99+ * (c) Load their stable version counterparts (avoids DB hits)
 100+ * Note: Used when calling FlaggedRevs::parseStableText().
 101+ * @param FlaggedRevision $frev
 102+ * @return void
 103+ */
 104+ public function stabilizeParserOutput( FlaggedRevision $frev ) {
 105+ $tStbVersions = $fStbVersions = array(); // stable versions
 106+ $tRevVersions = $frev->getTemplateVersions();
 107+ $fRevVersions = $frev->getFileVersions();
 108+ # We can preload *most* of the stable version IDs the parser will need...
 109+ if ( FlaggedRevs::inclusionSetting() == FR_INCLUDES_STABLE ) {
 110+ $tStbVersions = $frev->getStableTemplateVersions();
 111+ $fStbVersions = $frev->getStableFileVersions();
 112+ }
 113+ $this->setReviewedVersions( $tRevVersions, $fRevVersions );
 114+ $this->setStableVersionCache( $tStbVersions, $fStbVersions );
 115+ }
 116+
 117+ /**
 118+ * Should Parser stabilize includes?
 119+ * @return bool
 120+ */
 121+ public function parserOutputIsStabilized() {
 122+ return is_array( $this->reviewedVersions );
 123+ }
 124+
 125+ /**
 126+ * Get the "review time" template version for parser
 127+ * @param Title $title
 128+ * @return mixed (int/null)
 129+ */
 130+ public function getReviewedTemplateVersion( Title $title ) {
 131+ if ( !is_array( $this->reviewedVersions ) ) {
 132+ throw new MWException( "prepareForParse() nor setReviewedVersions() called yet" );
 133+ }
 134+ $dbKey = $title->getDBkey();
 135+ $namespace = $title->getNamespace();
 136+ if ( isset( $this->reviewedVersions['templates'][$namespace][$dbKey] ) ) {
 137+ return $this->reviewedVersions['templates'][$namespace][$dbKey];
 138+ }
 139+ return null; // missing version
 140+ }
 141+
 142+ /**
 143+ * Get the "review time" file version for parser
 144+ * @param Title $title
 145+ * @return array (MW timestamp/'0'/null, sha1/''/null )
 146+ */
 147+ public function getReviewedFileVersion( Title $title ) {
 148+ if ( !is_array( $this->reviewedVersions ) ) {
 149+ throw new MWException( "prepareForParse() nor setReviewedVersions() called yet" );
 150+ }
 151+ $dbKey = $title->getDBkey();
 152+ # All NS_FILE, no need to check namespace
 153+ if ( isset( $this->reviewedVersions['files'][$dbKey] ) ) {
 154+ $time = $this->reviewedVersions['files'][$dbKey]['time'];
 155+ $sha1 = $this->reviewedVersions['files'][$dbKey]['sha1'];
 156+ return array( $time, $sha1 );
 157+ }
 158+ return array( null, null ); // missing version
 159+ }
 160+
 161+ /**
 162+ * Get the stable version of a template
 163+ * @param Title $title
 164+ * @return int
 165+ */
 166+ public function getStableTemplateVersion( Title $title ) {
 167+ $dbKey = $title->getDBkey();
 168+ $namespace = $title->getNamespace();
 169+ $id = null;
 170+ if ( isset( $this->stableVersions['templates'][$namespace][$dbKey] ) ) {
 171+ $id = $this->stableVersions['templates'][$namespace][$dbKey];
 172+ }
 173+ if ( $id === null ) { // cache miss
 174+ $srev = FlaggedRevision::newFromStable( $title );
 175+ $id = $srev ? $srev->getRevId() : 0;
 176+ }
 177+ $this->stableVersions['templates'][$namespace][$dbKey] = $id; // cache
 178+ return $id;
 179+ }
 180+
 181+ /**
 182+ * Get the stable version of a file
 183+ * @param Title $title
 184+ * @return array (MW timestamp/'0', sha1/'')
 185+ */
 186+ public function getStableFileVersion( Title $title ) {
 187+ $dbKey = $title->getDBkey();
 188+ $time = '0'; // missing
 189+ $sha1 = false;
 190+ # All NS_FILE, no need to check namespace
 191+ if ( isset( $this->stableVersions['files'][$dbKey] ) ) {
 192+ $time = $this->stableVersions['files'][$dbKey]['time'];
 193+ $sha1 = $this->stableVersions['files'][$dbKey]['sha1'];
 194+ return array( $time, $sha1 );
 195+ }
 196+ $srev = FlaggedRevision::newFromStable( $title );
 197+ if ( $srev && $srev->getFileTimestamp() ) {
 198+ $time = $srev->getFileTimestamp();
 199+ $sha1 = $srev->getFileSha1();
 200+ }
 201+ $this->stableVersions['files'][$dbKey] = array();
 202+ $this->stableVersions['files'][$dbKey]['time'] = $time;
 203+ $this->stableVersions['files'][$dbKey]['sha1'] = $sha1;
 204+ return array( $time, $sha1 );
 205+ }
 206+}
Property changes on: trunk/extensions/FlaggedRevs/dataclasses/FRInclusionManager.php
___________________________________________________________________
Added: svn:eol-style
1207 + native
Index: trunk/extensions/FlaggedRevs/dataclasses/FlaggedPage.php
@@ -0,0 +1,541 @@
 2+<?php
 3+/**
 4+ * Class representing a MediaWiki article and history
 5+ *
 6+ * FlaggedPage::getTitleInstance() is preferred over constructor calls
 7+ */
 8+class FlaggedPage extends Article {
 9+ /* Process cache variables */
 10+ protected $stable = 0;
 11+ protected $stableRev = null;
 12+ protected $revsArePending = null;
 13+ protected $pendingRevCount = null;
 14+ protected $pageConfig = null;
 15+ protected $syncedInTracking = null;
 16+
 17+ protected $imagePage = null; // for file pages
 18+
 19+ /**
 20+ * Get a FlaggedPage for a given title
 21+ * @param Title
 22+ * @return FlaggedPage
 23+ */
 24+ public static function getTitleInstance( Title $title ) {
 25+ // Check if there is already an instance on this title
 26+ if ( !isset( $title->flaggedRevsArticle ) ) {
 27+ $title->flaggedRevsArticle = new self( $title );
 28+ }
 29+ return $title->flaggedRevsArticle;
 30+ }
 31+
 32+ /**
 33+ * Get a FlaggedPage for a given article
 34+ * @param Article
 35+ * @return FlaggedPage
 36+ */
 37+ public static function getArticleInstance( Article $article ) {
 38+ return self::getTitleInstance( $article->mTitle );
 39+ }
 40+
 41+ /**
 42+ * Clear object process cache values
 43+ * @return void
 44+ */
 45+ public function clear() {
 46+ $this->stable = 0;
 47+ $this->stableRev = null;
 48+ $this->revsArePending = null;
 49+ $this->pendingRevCount = null;
 50+ $this->pageConfig = null;
 51+ $this->syncedInTracking = null;
 52+ $this->imagePage = null;
 53+ parent::clear(); // call super!
 54+ }
 55+
 56+ /**
 57+ * Get the current file version of this file page
 58+ * @TODO: kind of hacky
 59+ * @return mixed (File/false)
 60+ */
 61+ public function getFile() {
 62+ if ( $this->mTitle->getNamespace() != NS_FILE ) {
 63+ return false; // not a file page
 64+ }
 65+ if ( is_null( $this->imagePage ) ) {
 66+ $this->imagePage = new ImagePage( $this->mTitle );
 67+ }
 68+ return $this->imagePage->getFile();
 69+ }
 70+
 71+ /**
 72+ * Get the displayed file version of this file page
 73+ * @TODO: kind of hacky
 74+ * @return mixed (File/false)
 75+ */
 76+ public function getDisplayedFile() {
 77+ if ( $this->mTitle->getNamespace() != NS_FILE ) {
 78+ return false; // not a file page
 79+ }
 80+ if ( is_null( $this->imagePage ) ) {
 81+ $this->imagePage = new ImagePage( $this->mTitle );
 82+ }
 83+ return $this->imagePage->getDisplayedFile();
 84+ }
 85+
 86+ /**
 87+ * Is the stable version shown by default for this page?
 88+ * @return bool
 89+ */
 90+ public function isStableShownByDefault() {
 91+ if ( !$this->isReviewable() ) {
 92+ return false; // no stable versions can exist
 93+ }
 94+ $config = $this->getStabilitySettings(); // page configuration
 95+ return (bool)$config['override'];
 96+ }
 97+
 98+ /**
 99+ * Do edits have to be reviewed before being shown by default (going live)?
 100+ * @return bool
 101+ */
 102+ public function editsRequireReview() {
 103+ return (
 104+ $this->isReviewable() && // reviewable page
 105+ $this->isStableShownByDefault() && // and stable versions override
 106+ $this->getStableRev() // and there is a stable version
 107+ );
 108+ }
 109+
 110+ /**
 111+ * Are edits to this page currently pending?
 112+ * @return bool
 113+ */
 114+ public function revsArePending() {
 115+ if ( !$this->mDataLoaded ) {
 116+ $this->loadPageData();
 117+ }
 118+ return $this->revsArePending;
 119+ }
 120+
 121+ /**
 122+ * Get number of revs since the stable revision
 123+ * Note: slower than revsArePending()
 124+ * @param int $flags FR_MASTER (be sure to use loadFromDB( FR_MASTER ) if set)
 125+ * @return int
 126+ */
 127+ public function getPendingRevCount( $flags = 0 ) {
 128+ global $wgMemc, $wgParserCacheExpireTime;
 129+ if ( !$this->mDataLoaded ) {
 130+ $this->loadPageData();
 131+ }
 132+ # Pending count deferred even after page data load
 133+ if ( $this->pendingRevCount !== null ) {
 134+ return $this->pendingRevCount; // use process cache
 135+ }
 136+ $srev = $this->getStableRev();
 137+ if ( !$srev ) {
 138+ return 0; // none
 139+ }
 140+ $count = null;
 141+ $sRevId = $srev->getRevId();
 142+ # Try the cache...
 143+ $key = wfMemcKey( 'flaggedrevs', 'countPending', $this->getId() );
 144+ if ( !( $flags & FR_MASTER ) ) {
 145+ $tuple = FlaggedRevs::getMemcValue( $wgMemc->get( $key ), $this );
 146+ # Items is cached and newer that page_touched...
 147+ if ( $tuple !== false ) {
 148+ # Confirm that cache value was made against the same stable rev Id.
 149+ # This avoids lengthy cache pollution if $sRevId is outdated.
 150+ list( $cRevId, $cPending ) = explode( '-', $tuple, 2 );
 151+ if ( $cRevId == $sRevId ) {
 152+ $count = (int)$cPending;
 153+ }
 154+ }
 155+ }
 156+ # Otherwise, fetch result from DB as needed...
 157+ if ( is_null( $count ) ) {
 158+ $db = ( $flags & FR_MASTER ) ?
 159+ wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
 160+ $srevTS = $db->timestamp( $srev->getRevTimestamp() );
 161+ $count = $db->selectField( 'revision', 'COUNT(*)',
 162+ array( 'rev_page' => $this->getId(),
 163+ 'rev_timestamp > ' . $db->addQuotes( $srevTS ) ), // bug 15515
 164+ __METHOD__ );
 165+ # Save result to cache...
 166+ $data = FlaggedRevs::makeMemcObj( "{$sRevId}-{$count}" );
 167+ $wgMemc->set( $key, $data, $wgParserCacheExpireTime );
 168+ }
 169+ $this->pendingRevCount = $count;
 170+ return $this->pendingRevCount;
 171+ }
 172+
 173+ /**
 174+ * Checks if the stable version is synced with the current revision
 175+ * Note: slower than getPendingRevCount()
 176+ * @return bool
 177+ */
 178+ public function stableVersionIsSynced() {
 179+ global $wgMemc, $wgParserCacheExpireTime;
 180+ $srev = $this->getStableRev();
 181+ if ( !$srev ) {
 182+ return true;
 183+ }
 184+ # Stable text revision must be the same as the current
 185+ if ( $this->revsArePending() ) {
 186+ return false;
 187+ # Stable file revision must be the same as the current
 188+ } elseif ( $this->mTitle->getNamespace() == NS_FILE ) {
 189+ $file = $this->getFile(); // current upload version
 190+ if ( $file && $file->getTimestamp() > $srev->getFileTimestamp() ) {
 191+ return false;
 192+ }
 193+ }
 194+ # If using the current version of includes, there is nothing else to check.
 195+ if ( FlaggedRevs::inclusionSetting() == FR_INCLUDES_CURRENT ) {
 196+ return true; // short-circuit
 197+ }
 198+ # Try the cache...
 199+ $key = wfMemcKey( 'flaggedrevs', 'includesSynced', $this->getId() );
 200+ $value = FlaggedRevs::getMemcValue( $wgMemc->get( $key ), $this );
 201+ if ( $value === "true" ) {
 202+ return true;
 203+ } elseif ( $value === "false" ) {
 204+ return false;
 205+ }
 206+ # Since the stable and current revisions have the same text and only outputs,
 207+ # the only other things to check for are template and file differences in the output.
 208+ # (a) Check if the current output has a newer template/file used
 209+ # (b) Check if the stable version has a file/template that was deleted
 210+ $synced = ( !$srev->findPendingTemplateChanges()
 211+ && !$srev->findPendingFileChanges( 'noForeign' ) );
 212+ # Save to cache. This will be updated whenever the page is touched.
 213+ $data = FlaggedRevs::makeMemcObj( $synced ? "true" : "false" );
 214+ $wgMemc->set( $key, $data, $wgParserCacheExpireTime );
 215+
 216+ return $synced;
 217+ }
 218+
 219+ /**
 220+ * Are template/file changes and ONLY template/file changes pending?
 221+ * @return bool
 222+ */
 223+ public function onlyTemplatesOrFilesPending() {
 224+ return ( !$this->revsArePending() && !$this->stableVersionIsSynced() );
 225+ }
 226+
 227+ /**
 228+ * Is this page less open than the site defaults?
 229+ * @return bool
 230+ */
 231+ public function isPageLocked() {
 232+ return ( !FlaggedRevs::isStableShownByDefault() && $this->isStableShownByDefault() );
 233+ }
 234+
 235+ /**
 236+ * Is this page more open than the site defaults?
 237+ * @return bool
 238+ */
 239+ public function isPageUnlocked() {
 240+ return ( FlaggedRevs::isStableShownByDefault() && !$this->isStableShownByDefault() );
 241+ }
 242+
 243+ /**
 244+ * Tags are only shown for unreviewed content and this page is not locked/unlocked?
 245+ * @return bool
 246+ */
 247+ public function lowProfileUI() {
 248+ return FlaggedRevs::lowProfileUI() &&
 249+ FlaggedRevs::isStableShownByDefault() == $this->isStableShownByDefault();
 250+ }
 251+
 252+ /**
 253+ * Is this article reviewable?
 254+ * @return bool
 255+ */
 256+ public function isReviewable() {
 257+ if ( !FlaggedRevs::inReviewNamespace( $this->mTitle ) ) {
 258+ return false;
 259+ }
 260+ # Check if flagging is disabled for this page via config
 261+ if ( FlaggedRevs::useOnlyIfProtected() ) {
 262+ $config = $this->getStabilitySettings(); // page configuration
 263+ return (bool)$config['override']; // stable is default or flagging disabled
 264+ }
 265+ return true;
 266+ }
 267+
 268+ /**
 269+ * Is this page in patrollable?
 270+ * @return bool
 271+ */
 272+ public function isPatrollable() {
 273+ if ( !FlaggedRevs::inPatrolNamespace( $this->mTitle ) ) {
 274+ return false;
 275+ }
 276+ return !$this->isReviewable(); // pages that are reviewable are not patrollable
 277+ }
 278+
 279+ /**
 280+ * Get the stable revision ID
 281+ * @return int
 282+ */
 283+ public function getStable() {
 284+ if ( !$this->mDataLoaded ) {
 285+ $this->loadPageData();
 286+ }
 287+ return (int)$this->stable;
 288+ }
 289+
 290+ /**
 291+ * Get the stable revision
 292+ * @return mixed (FlaggedRevision/null)
 293+ */
 294+ public function getStableRev() {
 295+ if ( !$this->mDataLoaded ) {
 296+ $this->loadPageData();
 297+ }
 298+ # Stable rev deferred even after page data load
 299+ if ( $this->stableRev === null ) {
 300+ $srev = FlaggedRevision::newFromTitle( $this->mTitle, $this->stable );
 301+ $this->stableRev = $srev ? $srev : false; // cache negative hits too
 302+ }
 303+ return $this->stableRev ? $this->stableRev : null; // false => null
 304+ }
 305+
 306+ /**
 307+ * Get visiblity restrictions on page
 308+ * @return array (select,override)
 309+ */
 310+ public function getStabilitySettings() {
 311+ if ( !$this->mDataLoaded ) {
 312+ $this->loadPageData();
 313+ }
 314+ return $this->pageConfig;
 315+ }
 316+
 317+ /*
 318+ * Get the fp_reviewed value for this page
 319+ * @return bool
 320+ */
 321+ public function syncedInTracking() {
 322+ if ( !$this->mDataLoaded ) {
 323+ $this->loadPageData();
 324+ }
 325+ return $this->syncedInTracking;
 326+ }
 327+
 328+ /**
 329+ * Fetch a page record with the given conditions
 330+ * @param $dbr Database object
 331+ * @param $conditions Array
 332+ * @return mixed Database result resource, or false on failure
 333+ */
 334+ protected function pageData( $dbr, $conditions ) {
 335+ $row = $dbr->selectRow(
 336+ array( 'page', 'flaggedpages', 'flaggedpage_config' ),
 337+ array_merge(
 338+ Article::selectFields(),
 339+ FlaggedPageConfig::selectFields(),
 340+ array( 'fp_pending_since', 'fp_stable', 'fp_reviewed' ) ),
 341+ $conditions,
 342+ __METHOD__,
 343+ array(),
 344+ array(
 345+ 'flaggedpages' => array( 'LEFT JOIN', 'fp_page_id = page_id' ),
 346+ 'flaggedpage_config' => array( 'LEFT JOIN', 'fpc_page_id = page_id' ) )
 347+ );
 348+ return $row;
 349+ }
 350+
 351+ /**
 352+ * Set the page field data loaded from some source
 353+ * @param $data Database row object or "fromdb"
 354+ * @return void
 355+ */
 356+ public function loadPageData( $data = 'fromdb' ) {
 357+ $this->mDataLoaded = true; // sanity
 358+ # Fetch data from DB as needed...
 359+ if ( $data === 'fromdb' ) {
 360+ $data = $this->pageDataFromTitle( wfGetDB( DB_SLAVE ), $this->mTitle );
 361+ }
 362+ # Load in primary page data...
 363+ parent::loadPageData( $data /* Row obj */ );
 364+ # Load in FlaggedRevs page data...
 365+ $this->stable = 0; // 0 => "found nothing"
 366+ $this->stableRev = null; // defer this one...
 367+ $this->revsArePending = false; // false => "found nothing" or "none pending"
 368+ $this->pendingRevCount = null; // defer this one...
 369+ $this->pageConfig = FlaggedPageConfig::getDefaultVisibilitySettings(); // default
 370+ $this->syncedInTracking = true; // false => "unreviewed" or "synced"
 371+ # Load in Row data if the page exists...
 372+ if ( $data ) {
 373+ if ( $data->fpc_override !== null ) { // page config row found
 374+ $this->pageConfig = FlaggedPageConfig::getVisibilitySettingsFromRow( $data );
 375+ }
 376+ if ( $data->fp_stable !== null ) { // stable rev found
 377+ $this->stable = (int)$data->fp_stable;
 378+ $this->revsArePending = ( $data->fp_pending_since !== null ); // revs await review
 379+ $this->syncedInTracking = (bool)$data->fp_reviewed;
 380+ }
 381+ }
 382+ }
 383+
 384+ /**
 385+ * Set the page field data loaded from the DB
 386+ * @param int $flags FR_MASTER
 387+ * @param $data Database row object or "fromdb"
 388+ */
 389+ public function loadFromDB( $flags = 0 ) {
 390+ $db = ( $flags & FR_MASTER ) ?
 391+ wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
 392+ $this->loadPageData( $this->pageDataFromTitle( $db, $this->mTitle ) );
 393+ }
 394+
 395+ /**
 396+ * Updates the flagging tracking tables for this page
 397+ * @param FlaggedRevision $srev The new stable version
 398+ * @param int|null $latest The latest rev ID (optional)
 399+ * @return bool Updates were done
 400+ */
 401+ public function updateStableVersion( FlaggedRevision $srev, $latest = null ) {
 402+ $rev = $srev->getRevision();
 403+ if ( !$this->exists() || !$rev ) {
 404+ return false; // no bogus entries
 405+ }
 406+ # Get the latest revision ID if not set
 407+ if ( !$latest ) {
 408+ $latest = $this->mTitle->getLatestRevID( Title::GAID_FOR_UPDATE );
 409+ }
 410+ $dbw = wfGetDB( DB_MASTER );
 411+ # Get the highest quality revision (not necessarily this one)...
 412+ if ( $srev->getQuality() === FlaggedRevs::highestReviewTier() ) {
 413+ $maxQuality = $srev->getQuality(); // save a query
 414+ } else {
 415+ $maxQuality = $dbw->selectField( array( 'flaggedrevs', 'revision' ),
 416+ 'fr_quality',
 417+ array( 'fr_page_id' => $this->getId(),
 418+ 'rev_id = fr_rev_id',
 419+ 'rev_page = fr_page_id',
 420+ 'rev_deleted & ' . Revision::DELETED_TEXT => 0
 421+ ),
 422+ __METHOD__,
 423+ array( 'ORDER BY' => 'fr_quality DESC', 'LIMIT' => 1 )
 424+ );
 425+ $maxQuality = max( $maxQuality, $srev->getQuality() ); // sanity
 426+ }
 427+ # Get the timestamp of the first edit after the stable version (if any)...
 428+ $nextTimestamp = null;
 429+ if ( $rev->getId() != $latest ) {
 430+ $timestamp = $dbw->timestamp( $rev->getTimestamp() );
 431+ $nextEditTS = $dbw->selectField( 'revision',
 432+ 'rev_timestamp',
 433+ array(
 434+ 'rev_page' => $this->getId(),
 435+ "rev_timestamp > " . $dbw->addQuotes( $timestamp ) ),
 436+ __METHOD__,
 437+ array( 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 )
 438+ );
 439+ if ( $nextEditTS ) { // sanity check
 440+ $nextTimestamp = $nextEditTS;
 441+ }
 442+ }
 443+ # Get the new page sync status...
 444+ $synced = !(
 445+ $nextTimestamp !== null || // edits pending
 446+ $srev->findPendingTemplateChanges() || // template changes pending
 447+ $srev->findPendingFileChanges( 'noForeign' ) // file changes pending
 448+ );
 449+ # Alter table metadata
 450+ $dbw->replace( 'flaggedpages',
 451+ array( 'fp_page_id' ),
 452+ array(
 453+ 'fp_page_id' => $this->getId(),
 454+ 'fp_stable' => $rev->getId(),
 455+ 'fp_reviewed' => $synced ? 1 : 0,
 456+ 'fp_quality' => ( $maxQuality === false ) ? null : $maxQuality,
 457+ 'fp_pending_since' => $dbw->timestampOrNull( $nextTimestamp )
 458+ ),
 459+ __METHOD__
 460+ );
 461+ # Update pending edit tracking table
 462+ self::updatePendingList( $this->getId(), $latest );
 463+ return true;
 464+ }
 465+
 466+ /**
 467+ * Updates the flagging tracking tables for this page
 468+ * @return void
 469+ */
 470+ public function clearStableVersion() {
 471+ if ( !$this->exists() ) {
 472+ return; // nothing to do
 473+ }
 474+ $dbw = wfGetDB( DB_MASTER );
 475+ $dbw->delete( 'flaggedpages',
 476+ array( 'fp_page_id' => $this->getId() ), __METHOD__ );
 477+ $dbw->delete( 'flaggedpage_pending',
 478+ array( 'fpp_page_id' => $this->getId() ), __METHOD__ );
 479+ }
 480+
 481+ /**
 482+ * Updates the flaggedpage_pending table
 483+ * @param int $pageId Page ID
 484+ * @abstract int $latest Latest revision
 485+ * @return void
 486+ */
 487+ protected static function updatePendingList( $pageId, $latest ) {
 488+ $data = array();
 489+ $level = FlaggedRevs::highestReviewTier();
 490+ # Update pending times for each level, going from highest to lowest
 491+ $dbw = wfGetDB( DB_MASTER );
 492+ $higherLevelId = 0;
 493+ $higherLevelTS = '';
 494+ while ( $level >= 0 ) {
 495+ # Get the latest revision of this level...
 496+ $row = $dbw->selectRow( array( 'flaggedrevs', 'revision' ),
 497+ array( 'fr_rev_id', 'rev_timestamp' ),
 498+ array( 'fr_page_id' => $pageId,
 499+ 'fr_quality' => $level,
 500+ 'rev_id = fr_rev_id',
 501+ 'rev_page = fr_page_id',
 502+ 'rev_deleted & ' . Revision::DELETED_TEXT => 0,
 503+ 'rev_id > ' . intval( $higherLevelId )
 504+ ),
 505+ __METHOD__,
 506+ array( 'ORDER BY' => 'fr_rev_id DESC', 'LIMIT' => 1 )
 507+ );
 508+ # If there is a revision of this level, track it...
 509+ # Revisions reviewed to one level count as reviewed
 510+ # at the lower levels (i.e. quality -> checked).
 511+ if ( $row ) {
 512+ $id = $row->fr_rev_id;
 513+ $ts = $row->rev_timestamp;
 514+ } else {
 515+ $id = $higherLevelId; // use previous (quality -> checked)
 516+ $ts = $higherLevelTS; // use previous (quality -> checked)
 517+ }
 518+ # Get edits that actually are pending...
 519+ if ( $id && $latest > $id ) {
 520+ # Get the timestamp of the edit after this version (if any)
 521+ $nextTimestamp = $dbw->selectField( 'revision',
 522+ 'rev_timestamp',
 523+ array( 'rev_page' => $pageId, "rev_timestamp > " . $dbw->addQuotes( $ts ) ),
 524+ __METHOD__,
 525+ array( 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 )
 526+ );
 527+ $data[] = array(
 528+ 'fpp_page_id' => $pageId,
 529+ 'fpp_quality' => $level,
 530+ 'fpp_rev_id' => $id,
 531+ 'fpp_pending_since' => $nextTimestamp
 532+ );
 533+ $higherLevelId = $id;
 534+ $higherLevelTS = $ts;
 535+ }
 536+ $level--;
 537+ }
 538+ # Clear any old junk, and insert new rows
 539+ $dbw->delete( 'flaggedpage_pending', array( 'fpp_page_id' => $pageId ), __METHOD__ );
 540+ $dbw->insert( 'flaggedpage_pending', $data, __METHOD__ );
 541+ }
 542+}
Property changes on: trunk/extensions/FlaggedRevs/dataclasses/FlaggedPage.php
___________________________________________________________________
Added: svn:eol-style
1543 + native
Index: trunk/extensions/FlaggedRevs/dataclasses/FlaggedRevision.php
@@ -0,0 +1,793 @@
 2+<?php
 3+/**
 4+ * Class representing a stable version of a MediaWiki revision
 5+ *
 6+ * This contains a page revision, a file version, and versions
 7+ * of templates and files (to determine template inclusion and thumbnails)
 8+ */
 9+class FlaggedRevision {
 10+ private $mRevision; // base revision
 11+ private $mTemplates; // included template versions
 12+ private $mFiles; // included file versions
 13+ private $mFileSha1; // file version sha-1 (for revisions of File pages)
 14+ private $mFileTimestamp; // file version timestamp (for revisions of File pages)
 15+ /* Flagging metadata */
 16+ private $mTimestamp;
 17+ private $mQuality;
 18+ private $mTags;
 19+ private $mFlags;
 20+ private $mUser; // reviewing user
 21+ private $mFileName; // file name when reviewed
 22+ /* Redundant fields for lazy-loading */
 23+ private $mTitle;
 24+ private $mPageId;
 25+ private $mRevId;
 26+ private $mStableTemplates;
 27+ private $mStableFiles;
 28+
 29+ /**
 30+ * @param Row|array $row (DB row or array)
 31+ * @return void
 32+ */
 33+ public function __construct( $row ) {
 34+ if ( is_object( $row ) ) {
 35+ $this->mRevId = intval( $row->fr_rev_id );
 36+ $this->mPageId = intval( $row->fr_page_id );
 37+ $this->mTimestamp = $row->fr_timestamp;
 38+ $this->mQuality = intval( $row->fr_quality );
 39+ $this->mTags = self::expandRevisionTags( strval( $row->fr_tags ) );
 40+ $this->mFlags = explode( ',', $row->fr_flags );
 41+ $this->mUser = intval( $row->fr_user );
 42+ # Base Revision object
 43+ $this->mRevision = new Revision( $row );
 44+ # Image page revision relevant params
 45+ $this->mFileName = $row->fr_img_name ? $row->fr_img_name : null;
 46+ $this->mFileSha1 = $row->fr_img_sha1 ? $row->fr_img_sha1 : null;
 47+ $this->mFileTimestamp = $row->fr_img_timestamp ?
 48+ $row->fr_img_timestamp : null;
 49+ # Optional fields
 50+ $this->mTitle = isset( $row->page_namespace ) && isset( $row->page_title )
 51+ ? Title::makeTitleSafe( $row->page_namespace, $row->page_title )
 52+ : null;
 53+ } elseif ( is_array( $row ) ) {
 54+ $this->mRevId = intval( $row['rev_id'] );
 55+ $this->mPageId = intval( $row['page_id'] );
 56+ $this->mTimestamp = $row['timestamp'];
 57+ $this->mQuality = intval( $row['quality'] );
 58+ $this->mTags = self::expandRevisionTags( strval( $row['tags'] ) );
 59+ $this->mFlags = explode( ',', $row['flags'] );
 60+ $this->mUser = intval( $row['user'] );
 61+ # Image page revision relevant params
 62+ $this->mFileName = $row['img_name'] ? $row['img_name'] : null;
 63+ $this->mFileSha1 = $row['img_sha1'] ? $row['img_sha1'] : null;
 64+ $this->mFileTimestamp = $row['img_timestamp'] ?
 65+ $row['img_timestamp'] : null;
 66+ # Optional fields
 67+ $this->mTemplates = isset( $row['templateVersions'] ) ?
 68+ $row['templateVersions'] : null;
 69+ $this->mFiles = isset( $row['fileVersions'] ) ?
 70+ $row['fileVersions'] : null;
 71+ } else {
 72+ throw new MWException( 'FlaggedRevision constructor passed invalid row format.' );
 73+ }
 74+ }
 75+
 76+ /**
 77+ * Get a FlaggedRevision for a title and rev ID.
 78+ * Note: will return NULL if the revision is deleted.
 79+ * @param Title $title
 80+ * @param int $revId
 81+ * @param int $flags (FR_MASTER, FR_FOR_UPDATE)
 82+ * @return FlaggedRevision|null (null on failure)
 83+ */
 84+ public static function newFromTitle( Title $title, $revId, $flags = 0 ) {
 85+ if ( !FlaggedRevs::inReviewNamespace( $title ) ) {
 86+ return null; // short-circuit
 87+ }
 88+ $options = array();
 89+ # User master/slave as appropriate...
 90+ if ( $flags & FR_FOR_UPDATE || $flags & FR_MASTER ) {
 91+ $db = wfGetDB( DB_MASTER );
 92+ if ( $flags & FR_FOR_UPDATE ) $options[] = 'FOR UPDATE';
 93+ $pageId = $title->getArticleID( Title::GAID_FOR_UPDATE );
 94+ } else {
 95+ $db = wfGetDB( DB_SLAVE );
 96+ $pageId = $title->getArticleID();
 97+ }
 98+ if ( !$pageId || !$revId ) {
 99+ return null; // short-circuit query
 100+ }
 101+ # Skip deleted revisions
 102+ $row = $db->selectRow(
 103+ array( 'flaggedrevs', 'revision' ),
 104+ self::selectFields(),
 105+ array(
 106+ 'fr_page_id' => $pageId,
 107+ 'fr_rev_id' => $revId,
 108+ 'rev_id = fr_rev_id',
 109+ 'rev_page = fr_page_id',
 110+ 'rev_deleted & ' . Revision::DELETED_TEXT => 0
 111+ ),
 112+ __METHOD__,
 113+ $options
 114+ );
 115+ # Sorted from highest to lowest, so just take the first one if any
 116+ if ( $row ) {
 117+ $frev = new self( $row );
 118+ $frev->mTitle = $title;
 119+ return $frev;
 120+ }
 121+ return null;
 122+ }
 123+
 124+ /**
 125+ * Get a FlaggedRevision of the stable version of a title.
 126+ * @param Title $title, page title
 127+ * @param int $flags (FR_MASTER, FR_FOR_UPDATE)
 128+ * @return FlaggedRevision|null (null on failure)
 129+ */
 130+ public static function newFromStable( Title $title, $flags = 0 ) {
 131+ if ( !FlaggedRevs::inReviewNamespace( $title ) ) {
 132+ return null; // short-circuit
 133+ }
 134+ $options = array();
 135+ # User master/slave as appropriate...
 136+ if ( $flags & FR_FOR_UPDATE || $flags & FR_MASTER ) {
 137+ $db = wfGetDB( DB_MASTER );
 138+ if ( $flags & FR_FOR_UPDATE ) $options[] = 'FOR UPDATE';
 139+ $pageId = $title->getArticleID( Title::GAID_FOR_UPDATE );
 140+ } else {
 141+ $db = wfGetDB( DB_SLAVE );
 142+ $pageId = $title->getArticleID();
 143+ }
 144+ if ( !$pageId ) {
 145+ return null; // short-circuit query
 146+ }
 147+ # Check tracking tables
 148+ $row = $db->selectRow(
 149+ array( 'flaggedpages', 'flaggedrevs', 'revision' ),
 150+ self::selectFields(),
 151+ array(
 152+ 'fp_page_id' => $pageId,
 153+ 'fr_page_id = fp_page_id',
 154+ 'fr_rev_id = fp_stable',
 155+ 'rev_id = fr_rev_id'
 156+ ),
 157+ __METHOD__,
 158+ $options
 159+ );
 160+ if ( $row ) {
 161+ $frev = new self( $row );
 162+ $frev->mTitle = $title;
 163+ return $frev;
 164+ }
 165+ return null;
 166+ }
 167+
 168+ /**
 169+ * Get a FlaggedRevision of the stable version of a title.
 170+ * Skips tracking tables to figure out new stable version.
 171+ * @param Title $title, page title
 172+ * @param int $flags (FR_MASTER, FR_FOR_UPDATE)
 173+ * @param array $config, optional page config (use to skip queries)
 174+ * @param string $precedence (latest,quality,pristine)
 175+ * @return FlaggedRevision|null (null on failure)
 176+ */
 177+ public static function determineStable(
 178+ Title $title, $flags = 0, $config = array(), $precedence = 'latest'
 179+ ) {
 180+ if ( !FlaggedRevs::inReviewNamespace( $title ) ) {
 181+ return null; // short-circuit
 182+ }
 183+ $options = array();
 184+ # User master/slave as appropriate...
 185+ if ( $flags & FR_FOR_UPDATE || $flags & FR_MASTER ) {
 186+ $db = wfGetDB( DB_MASTER );
 187+ if ( $flags & FR_FOR_UPDATE ) $options[] = 'FOR UPDATE';
 188+ $pageId = $title->getArticleID( Title::GAID_FOR_UPDATE );
 189+ } else {
 190+ $db = wfGetDB( DB_SLAVE );
 191+ $pageId = $title->getArticleID();
 192+ }
 193+ if ( !$pageId ) {
 194+ return null; // short-circuit query
 195+ }
 196+ # Get visiblity settings...
 197+ if ( empty( $config ) ) {
 198+ $config = FlaggedPageConfig::getStabilitySettings( $title, $flags );
 199+ }
 200+ if ( !$config['override'] && FlaggedRevs::useOnlyIfProtected() ) {
 201+ return null; // page is not reviewable; no stable version
 202+ }
 203+ $row = null;
 204+ $options['ORDER BY'] = 'fr_rev_id DESC';
 205+ # Look for the latest pristine revision...
 206+ if ( FlaggedRevs::pristineVersions() && $precedence !== 'latest' ) {
 207+ $prow = $db->selectRow(
 208+ array( 'flaggedrevs', 'revision' ),
 209+ self::selectFields(),
 210+ array( 'fr_page_id' => $pageId,
 211+ 'fr_quality = ' . FR_PRISTINE,
 212+ 'rev_id = fr_rev_id',
 213+ 'rev_page = fr_page_id',
 214+ 'rev_deleted & ' . Revision::DELETED_TEXT => 0
 215+ ),
 216+ __METHOD__,
 217+ $options
 218+ );
 219+ # Looks like a plausible revision
 220+ $row = $prow ? $prow : $row;
 221+ }
 222+ if ( $row && $precedence === 'pristine' ) {
 223+ // we have what we want already
 224+ # Look for the latest quality revision...
 225+ } elseif ( FlaggedRevs::qualityVersions() && $precedence !== 'latest' ) {
 226+ // If we found a pristine rev above, this one must be newer...
 227+ $newerClause = $row ? "fr_rev_id > {$row->fr_rev_id}" : "1 = 1";
 228+ $qrow = $db->selectRow(
 229+ array( 'flaggedrevs', 'revision' ),
 230+ self::selectFields(),
 231+ array( 'fr_page_id' => $pageId,
 232+ 'fr_quality = ' . FR_QUALITY,
 233+ $newerClause,
 234+ 'rev_id = fr_rev_id',
 235+ 'rev_page = fr_page_id',
 236+ 'rev_deleted & ' . Revision::DELETED_TEXT => 0
 237+ ),
 238+ __METHOD__,
 239+ $options
 240+ );
 241+ $row = $qrow ? $qrow : $row;
 242+ }
 243+ # Do we have one? If not, try the latest reviewed revision...
 244+ if ( !$row ) {
 245+ $row = $db->selectRow(
 246+ array( 'flaggedrevs', 'revision' ),
 247+ self::selectFields(),
 248+ array( 'fr_page_id' => $pageId,
 249+ 'rev_id = fr_rev_id',
 250+ 'rev_page = fr_page_id',
 251+ 'rev_deleted & ' . Revision::DELETED_TEXT => 0
 252+ ),
 253+ __METHOD__,
 254+ $options
 255+ );
 256+ if ( !$row ) return null;
 257+ }
 258+ $frev = new self( $row );
 259+ $frev->mTitle = $title;
 260+ return $frev;
 261+ }
 262+
 263+ /*
 264+ * Insert a FlaggedRevision object into the database
 265+ *
 266+ * @return bool success
 267+ */
 268+ public function insertOn() {
 269+ $dbw = wfGetDB( DB_MASTER );
 270+ # Set any flagged revision flags
 271+ $this->mFlags = array_merge( $this->mFlags, array( 'dynamic' ) ); // legacy
 272+ # Build the inclusion data chunks
 273+ $tmpInsertRows = array();
 274+ foreach ( (array)$this->mTemplates as $namespace => $titleAndID ) {
 275+ foreach ( $titleAndID as $dbkey => $id ) {
 276+ $tmpInsertRows[] = array(
 277+ 'ft_rev_id' => $this->getRevId(),
 278+ 'ft_namespace' => (int)$namespace,
 279+ 'ft_title' => $dbkey,
 280+ 'ft_tmp_rev_id' => (int)$id
 281+ );
 282+ }
 283+ }
 284+ $fileInsertRows = array();
 285+ foreach ( (array)$this->mFiles as $dbkey => $timeSHA1 ) {
 286+ $fileInsertRows[] = array(
 287+ 'fi_rev_id' => $this->getRevId(),
 288+ 'fi_name' => $dbkey,
 289+ 'fi_img_sha1' => strval( $timeSHA1['sha1'] ),
 290+ 'fi_img_timestamp' => $timeSHA1['time'] ? // false => NULL
 291+ $dbw->timestamp( $timeSHA1['time'] ) : null
 292+ );
 293+ }
 294+ # Our review entry
 295+ $revRow = array(
 296+ 'fr_page_id' => $this->getPage(),
 297+ 'fr_rev_id' => $this->getRevId(),
 298+ 'fr_user' => $this->getUser(),
 299+ 'fr_timestamp' => $dbw->timestamp( $this->getTimestamp() ),
 300+ 'fr_comment' => '', # not used anymore
 301+ 'fr_quality' => $this->getQuality(),
 302+ 'fr_tags' => self::flattenRevisionTags( $this->getTags() ),
 303+ 'fr_text' => '', # not used anymore
 304+ 'fr_flags' => implode( ',', $this->mFlags ),
 305+ 'fr_img_name' => $this->getFileName(),
 306+ 'fr_img_timestamp' => $dbw->timestampOrNull( $this->getFileTimestamp() ),
 307+ 'fr_img_sha1' => $this->getFileSha1()
 308+ );
 309+ # Update flagged revisions table
 310+ $dbw->replace( 'flaggedrevs',
 311+ array( array( 'fr_page_id', 'fr_rev_id' ) ), $revRow, __METHOD__ );
 312+ # Clear out any previous garbage...
 313+ $dbw->delete( 'flaggedtemplates',
 314+ array( 'ft_rev_id' => $this->getRevId() ), __METHOD__ );
 315+ # ...and insert template version data
 316+ if ( $tmpInsertRows ) {
 317+ $dbw->insert( 'flaggedtemplates', $tmpInsertRows, __METHOD__, 'IGNORE' );
 318+ }
 319+ # Clear out any previous garbage...
 320+ $dbw->delete( 'flaggedimages',
 321+ array( 'fi_rev_id' => $this->getRevId() ), __METHOD__ );
 322+ # ...and insert file version data
 323+ if ( $fileInsertRows ) {
 324+ $dbw->insert( 'flaggedimages', $fileInsertRows, __METHOD__, 'IGNORE' );
 325+ }
 326+ return true;
 327+ }
 328+
 329+ /**
 330+ * Get select fields for FlaggedRevision DB row (flaggedrevs/revision tables)
 331+ * @return array
 332+ */
 333+ public static function selectFields() {
 334+ return array_merge(
 335+ Revision::selectFields(),
 336+ array( 'fr_rev_id', 'fr_page_id', 'fr_user', 'fr_timestamp', 'fr_quality',
 337+ 'fr_tags', 'fr_flags', 'fr_img_name', 'fr_img_sha1', 'fr_img_timestamp' )
 338+ );
 339+ }
 340+
 341+ /**
 342+ * @return integer revision ID
 343+ */
 344+ public function getRevId() {
 345+ return $this->mRevId;
 346+ }
 347+
 348+ /**
 349+ * @return Title title
 350+ */
 351+ public function getTitle() {
 352+ if ( is_null( $this->mTitle ) ) {
 353+ $this->mTitle = Title::newFromId( $this->mPageId );
 354+ }
 355+ return $this->mTitle;
 356+ }
 357+
 358+ /**
 359+ * @return integer page ID
 360+ */
 361+ public function getPage() {
 362+ return $this->mPageId;
 363+ }
 364+
 365+ /**
 366+ * Get timestamp of review
 367+ * @return string revision timestamp in MW format
 368+ */
 369+ public function getTimestamp() {
 370+ return wfTimestamp( TS_MW, $this->mTimestamp );
 371+ }
 372+
 373+ /**
 374+ * Get the corresponding revision
 375+ * @return Revision
 376+ */
 377+ public function getRevision() {
 378+ if ( is_null( $this->mRevision ) ) {
 379+ # Get corresponding revision
 380+ $rev = Revision::newFromId( $this->mRevId );
 381+ # Save to cache
 382+ $this->mRevision = $rev ? $rev : false;
 383+ }
 384+ return $this->mRevision;
 385+ }
 386+
 387+ /**
 388+ * Check if the corresponding revision is the current revision
 389+ * Note: here for convenience
 390+ * @return bool
 391+ */
 392+ public function revIsCurrent() {
 393+ $rev = $this->getRevision(); // corresponding revision
 394+ return ( $rev ? $rev->isCurrent() : false );
 395+ }
 396+
 397+ /**
 398+ * Get timestamp of the corresponding revision
 399+ * Note: here for convenience
 400+ * @return string revision timestamp in MW format
 401+ */
 402+ public function getRevTimestamp() {
 403+ $rev = $this->getRevision(); // corresponding revision
 404+ return ( $rev ? $rev->getTimestamp() : "0" );
 405+ }
 406+
 407+ /**
 408+ * @return integer the user ID of the reviewer
 409+ */
 410+ public function getUser() {
 411+ return $this->mUser;
 412+ }
 413+
 414+ /**
 415+ * @return integer quality level (FR_CHECKED,FR_QUALITY,FR_PRISTINE)
 416+ */
 417+ public function getQuality() {
 418+ return $this->mQuality;
 419+ }
 420+
 421+ /**
 422+ * @return array tag metadata
 423+ */
 424+ public function getTags() {
 425+ return $this->mTags;
 426+ }
 427+
 428+ /**
 429+ * @return string, filename accosciated with this revision.
 430+ * This returns NULL for non-image page revisions.
 431+ */
 432+ public function getFileName() {
 433+ return $this->mFileName;
 434+ }
 435+
 436+ /**
 437+ * @return string, sha1 key accosciated with this revision.
 438+ * This returns NULL for non-image page revisions.
 439+ */
 440+ public function getFileSha1() {
 441+ return $this->mFileSha1;
 442+ }
 443+
 444+ /**
 445+ * @return string, timestamp accosciated with this revision.
 446+ * This returns NULL for non-image page revisions.
 447+ */
 448+ public function getFileTimestamp() {
 449+ return wfTimestampOrNull( TS_MW, $this->mFileTimestamp );
 450+ }
 451+
 452+ /**
 453+ * @param User $user
 454+ * @return bool
 455+ */
 456+ public function userCanSetFlags( $user ) {
 457+ return FlaggedRevs::userCanSetFlags( $user, $this->mTags );
 458+ }
 459+
 460+ /**
 461+ * Get original template versions at time of review
 462+ * @param int $flags FR_MASTER
 463+ * @return array template versions (ns -> dbKey -> rev Id)
 464+ * Note: 0 used for template rev Id if it didn't exist
 465+ */
 466+ public function getTemplateVersions( $flags = 0 ) {
 467+ if ( $this->mTemplates == null ) {
 468+ $this->mTemplates = array();
 469+ $db = ( $flags & FR_MASTER ) ?
 470+ wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
 471+ $res = $db->select( 'flaggedtemplates',
 472+ array( 'ft_namespace', 'ft_title', 'ft_tmp_rev_id' ),
 473+ array( 'ft_rev_id' => $this->getRevId() ),
 474+ __METHOD__
 475+ );
 476+ foreach ( $res as $row ) {
 477+ if ( !isset( $this->mTemplates[$row->ft_namespace] ) ) {
 478+ $this->mTemplates[$row->ft_namespace] = array();
 479+ }
 480+ $this->mTemplates[$row->ft_namespace][$row->ft_title] = $row->ft_tmp_rev_id;
 481+ }
 482+ }
 483+ return $this->mTemplates;
 484+ }
 485+
 486+ /**
 487+ * Get original template versions at time of review
 488+ * @param int $flags FR_MASTER
 489+ * @return array file versions (dbKey => array('time' => MW timestamp,'sha1' => sha1) )
 490+ * Note: false used for file timestamp/sha1 if it didn't exist
 491+ */
 492+ public function getFileVersions( $flags = 0 ) {
 493+ if ( $this->mFiles == null ) {
 494+ $this->mFiles = array();
 495+ $db = ( $flags & FR_MASTER ) ?
 496+ wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
 497+ $res = $db->select( 'flaggedimages',
 498+ array( 'fi_name', 'fi_img_timestamp', 'fi_img_sha1' ),
 499+ array( 'fi_rev_id' => $this->getRevId() ),
 500+ __METHOD__
 501+ );
 502+ foreach ( $res as $row ) {
 503+ $reviewedTS = $reviewedSha1 = false;
 504+ $fi_img_timestamp = trim( $row->fi_img_timestamp ); // may have \0's
 505+ if ( $fi_img_timestamp ) {
 506+ $reviewedTS = wfTimestamp( TS_MW, $fi_img_timestamp );
 507+ $reviewedSha1 = strval( $row->fi_img_sha1 );
 508+ }
 509+ $this->mFiles[$row->fi_name] = array();
 510+ $this->mFiles[$row->fi_name]['time'] = $reviewedTS;
 511+ $this->mFiles[$row->fi_name]['sha1'] = $reviewedSha1;
 512+ }
 513+ }
 514+ return $this->mFiles;
 515+ }
 516+
 517+ /**
 518+ * Get the current stable version of the templates used at time of review
 519+ * @param int $flags FR_MASTER
 520+ * @return array template versions (ns -> dbKey -> rev Id)
 521+ * Note: 0 used for template rev Id if it doesn't exist
 522+ */
 523+ public function getStableTemplateVersions( $flags = 0 ) {
 524+ if ( $this->mStableTemplates == null ) {
 525+ $this->mStableTemplates = array();
 526+ $db = ( $flags & FR_MASTER ) ?
 527+ wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
 528+ $res = $db->select(
 529+ array( 'flaggedtemplates', 'page', 'flaggedpages' ),
 530+ array( 'ft_namespace', 'ft_title', 'fp_stable' ),
 531+ array( 'ft_rev_id' => $this->getRevId() ),
 532+ __METHOD__,
 533+ array(),
 534+ array(
 535+ 'page' => array( 'LEFT JOIN',
 536+ 'page_namespace = ft_namespace AND page_title = ft_title'),
 537+ 'flaggedpages' => array( 'LEFT JOIN', 'fp_page_id = page_id' )
 538+ )
 539+ );
 540+ foreach ( $res as $row ) {
 541+ if ( !isset( $this->mStableTemplates[$row->ft_namespace] ) ) {
 542+ $this->mStableTemplates[$row->ft_namespace] = array();
 543+ }
 544+ $revId = (int)$row->fp_stable; // 0 => none
 545+ $this->mStableTemplates[$row->ft_namespace][$row->ft_title] = $revId;
 546+ }
 547+ }
 548+ return $this->mStableTemplates;
 549+ }
 550+
 551+ /**
 552+ * Get the current stable version of the files used at time of review
 553+ * @param int $flags FR_MASTER
 554+ * @return array file versions (dbKey => array('time' => MW timestamp,'sha1' => sha1) )
 555+ * Note: false used for file timestamp/sha1 if it didn't exist
 556+ */
 557+ public function getStableFileVersions( $flags = 0 ) {
 558+ if ( $this->mStableFiles == null ) {
 559+ $this->mStableFiles = array();
 560+ $db = ( $flags & FR_MASTER ) ?
 561+ wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
 562+ $res = $db->select(
 563+ array( 'flaggedimages', 'page', 'flaggedpages', 'flaggedrevs' ),
 564+ array( 'fi_name', 'fr_img_timestamp', 'fr_img_sha1' ),
 565+ array( 'fi_rev_id' => $this->getRevId() ),
 566+ __METHOD__,
 567+ array(),
 568+ array(
 569+ 'page' => array( 'LEFT JOIN',
 570+ 'page_namespace = ' . NS_FILE . ' AND page_title = fi_name' ),
 571+ 'flaggedpages' => array( 'LEFT JOIN', 'fp_page_id = page_id' ),
 572+ 'flaggedrevs' => array( 'LEFT JOIN',
 573+ 'fr_page_id = fp_page_id AND fr_rev_id = fp_stable' )
 574+ )
 575+ );
 576+ foreach ( $res as $row ) {
 577+ $reviewedTS = $reviewedSha1 = false;
 578+ if ( $row->fr_img_timestamp ) {
 579+ $reviewedTS = wfTimestamp( TS_MW, $row->fr_img_timestamp );
 580+ $reviewedSha1 = strval( $row->fr_img_sha1 );
 581+ }
 582+ $this->mStableFiles[$row->fi_name] = array();
 583+ $this->mStableFiles[$row->fi_name]['time'] = $reviewedTS;
 584+ $this->mStableFiles[$row->fi_name]['sha1'] = $reviewedSha1;
 585+ }
 586+ }
 587+ return $this->mStableFiles;
 588+ }
 589+
 590+ /*
 591+ * Fetch pending template changes for this reviewed page version.
 592+ * For each template, the "version used" (for stable parsing) is:
 593+ * (a) (the latest rev) if FR_INCLUDES_CURRENT. Might be non-existing.
 594+ * (b) newest( stable rev, rev at time of review ) if FR_INCLUDES_STABLE
 595+ * (c) ( rev at time of review ) if FR_INCLUDES_FREEZE
 596+ * Pending changes exist for a template iff the template is used in
 597+ * the current rev of this page and one of the following holds:
 598+ * (a) Current template is newer than the "version used" above (updated)
 599+ * (b) Current template exists and the "version used" was non-existing (created)
 600+ * (c) Current template doesn't exist and the "version used" existed (deleted)
 601+ *
 602+ * @return array of (template title, rev ID in reviewed version) tuples
 603+ */
 604+ public function findPendingTemplateChanges() {
 605+ if ( FlaggedRevs::inclusionSetting() == FR_INCLUDES_CURRENT ) {
 606+ return array(); // short-circuit
 607+ }
 608+ $dbr = wfGetDB( DB_SLAVE );
 609+ # Only get templates with stable or "review time" versions.
 610+ # Note: ft_tmp_rev_id is nullable (for deadlinks), so use ft_title
 611+ if ( FlaggedRevs::inclusionSetting() == FR_INCLUDES_STABLE ) {
 612+ $reviewed = "ft_title IS NOT NULL OR fp_stable IS NOT NULL";
 613+ } else {
 614+ $reviewed = "ft_title IS NOT NULL";
 615+ }
 616+ $ret = $dbr->select(
 617+ array( 'templatelinks', 'flaggedtemplates', 'page', 'flaggedpages' ),
 618+ array( 'tl_namespace', 'tl_title', 'fp_stable', 'ft_tmp_rev_id', 'page_latest' ),
 619+ array( 'tl_from' => $this->getPage(), $reviewed ), // current version templates
 620+ __METHOD__,
 621+ array(), /* OPTIONS */
 622+ array(
 623+ 'flaggedtemplates' => array( 'LEFT JOIN',
 624+ array( 'ft_rev_id' => $this->getRevId(),
 625+ 'ft_namespace = tl_namespace AND ft_title = tl_title' ) ),
 626+ 'page' => array( 'LEFT JOIN',
 627+ 'page_namespace = tl_namespace AND page_title = tl_title' ),
 628+ 'flaggedpages' => array( 'LEFT JOIN', 'fp_page_id = page_id' )
 629+ )
 630+ );
 631+ $tmpChanges = array();
 632+ foreach ( $ret as $row ) {
 633+ $title = Title::makeTitleSafe( $row->tl_namespace, $row->tl_title );
 634+ $revIdDraft = (int)$row->page_latest; // may be NULL
 635+ if ( FlaggedRevs::inclusionSetting() == FR_INCLUDES_STABLE ) {
 636+ # Select newest of (stable rev, rev when reviewed) as "version used"
 637+ $revIdStable = (int)max( $row->fp_stable, $row->ft_tmp_rev_id );
 638+ } else {
 639+ $revIdStable = (int)$row->ft_tmp_rev_id; // may be NULL
 640+ }
 641+ # Compare to current...
 642+ $updated = false; // edited/created
 643+ if ( $revIdDraft && $revIdDraft != $revIdStable ) {
 644+ $dRev = Revision::newFromId( $revIdDraft );
 645+ $sRev = Revision::newFromId( $revIdStable );
 646+ # Don't do this for null edits (like protection) (bug 25919)
 647+ if ( $dRev && $sRev && $dRev->getTextId() != $sRev->getTextId() ) {
 648+ $updated = true;
 649+ }
 650+ }
 651+ $deleted = ( !$revIdDraft && $revIdStable ); // later deleted
 652+ if ( $deleted || $updated ) {
 653+ $tmpChanges[] = array( $title, $revIdStable );
 654+ }
 655+ }
 656+ return $tmpChanges;
 657+ }
 658+
 659+ /*
 660+ * Fetch pending file changes for this reviewed page version.
 661+ * For each file, the "version used" (for stable parsing) is:
 662+ * (a) (the latest rev) if FR_INCLUDES_CURRENT. Might be non-existing.
 663+ * (b) newest( stable rev, rev at time of review ) if FR_INCLUDES_STABLE
 664+ * (c) ( rev at time of review ) if FR_INCLUDES_FREEZE
 665+ * Pending changes exist for a file iff the file is used in
 666+ * the current rev of this page and one of the following holds:
 667+ * (a) Current file is newer than the "version used" above (updated)
 668+ * (b) Current file exists and the "version used" was non-existing (created)
 669+ * (c) Current file doesn't exist and the "version used" existed (deleted)
 670+ *
 671+ * @param string $noForeign Using 'noForeign' skips foreign file updates (bug 15748)
 672+ * @return array of (file title, MW file timestamp in reviewed version) tuples
 673+ */
 674+ public function findPendingFileChanges( $noForeign = false ) {
 675+ if ( FlaggedRevs::inclusionSetting() == FR_INCLUDES_CURRENT ) {
 676+ return array(); // short-circuit
 677+ }
 678+ $dbr = wfGetDB( DB_SLAVE );
 679+ # Only get templates with stable or "review time" versions.
 680+ # Note: fi_img_timestamp is nullable (for deadlinks), so use fi_name
 681+ if ( FlaggedRevs::inclusionSetting() == FR_INCLUDES_STABLE ) {
 682+ $reviewed = "fi_name IS NOT NULL OR fr_img_timestamp IS NOT NULL";
 683+ } else {
 684+ $reviewed = "fi_name IS NOT NULL";
 685+ }
 686+ $ret = $dbr->select(
 687+ array( 'imagelinks', 'flaggedimages', 'page', 'flaggedpages', 'flaggedrevs' ),
 688+ array( 'il_to', 'fi_img_timestamp', 'fr_img_timestamp' ),
 689+ array( 'il_from' => $this->getPage(), $reviewed ), // current version files
 690+ __METHOD__,
 691+ array(), /* OPTIONS */
 692+ array(
 693+ 'flaggedimages' => array( 'LEFT JOIN',
 694+ array( 'fi_rev_id' => $this->getRevId(), 'fi_name = il_to' ) ),
 695+ 'page' => array( 'LEFT JOIN',
 696+ 'page_namespace = ' . NS_FILE . ' AND page_title = il_to' ),
 697+ 'flaggedpages' => array( 'LEFT JOIN', 'fp_page_id = page_id' ),
 698+ 'flaggedrevs' => array( 'LEFT JOIN',
 699+ 'fr_page_id = fp_page_id AND fr_rev_id = fp_stable' )
 700+ )
 701+ );
 702+ $fileChanges = array();
 703+ foreach ( $ret as $row ) {
 704+ $title = Title::makeTitleSafe( NS_FILE, $row->il_to );
 705+ $reviewedTS = trim( $row->fi_img_timestamp ); // may have \0's
 706+ $reviewedTS = $reviewedTS ? wfTimestamp( TS_MW, $reviewedTS ) : null;
 707+ if ( FlaggedRevs::inclusionSetting() == FR_INCLUDES_STABLE ) {
 708+ $stableTS = wfTimestampOrNull( TS_MW, $row->fr_img_timestamp );
 709+ # Select newest of (stable rev, rev when reviewed) as "version used"
 710+ $tsStable = max( $stableTS, $reviewedTS );
 711+ } else {
 712+ $tsStable = $reviewedTS;
 713+ }
 714+ # Compare this version to the current version and check for things
 715+ # that would make the stable version unsynced with the draft...
 716+ $file = wfFindFile( $title ); // current file version
 717+ if ( $file ) { // file exists
 718+ if ( $noForeign === 'noForeign' && !$file->isLocal() ) {
 719+ # Avoid counting edits to Commons files, which can effect
 720+ # many pages, as there is no expedient way to review them.
 721+ $updated = !$tsStable; // created (ignore new versions)
 722+ } else {
 723+ $updated = ( $file->getTimestamp() > $tsStable ); // edited/created
 724+ }
 725+ $deleted = $tsStable // included file deleted after review
 726+ && $file->getTimestamp() != $tsStable
 727+ && !wfFindFile( $title, array( 'time' => $tsStable ) );
 728+ } else { // file doesn't exists
 729+ $updated = false;
 730+ $deleted = (bool)$tsStable; // included file deleted after review
 731+ }
 732+ if ( $deleted || $updated ) {
 733+ $fileChanges[] = array( $title, $tsStable );
 734+ }
 735+ }
 736+ return $fileChanges;
 737+ }
 738+
 739+ /**
 740+ * Get text of the corresponding revision
 741+ * @return string|false revision timestamp in MW format
 742+ */
 743+ public function getRevText() {
 744+ # Get corresponding revision
 745+ $rev = $this->getRevision();
 746+ $text = $rev ? $rev->getText() : false;
 747+ return $text;
 748+ }
 749+
 750+ /**
 751+ * Get flags for a revision
 752+ * @param string $tags
 753+ * @return array
 754+ */
 755+ public static function expandRevisionTags( $tags ) {
 756+ $flags = array();
 757+ foreach ( FlaggedRevs::getTags() as $tag ) {
 758+ $flags[$tag] = 0; // init all flags values to zero
 759+ }
 760+ $tags = str_replace( '\n', "\n", $tags ); // B/C, old broken rows
 761+ // Tag string format is <tag:val\ntag:val\n...>
 762+ $tags = explode( "\n", $tags );
 763+ foreach ( $tags as $tuple ) {
 764+ $set = explode( ':', $tuple, 2 );
 765+ if ( count( $set ) == 2 ) {
 766+ list( $tag, $value ) = $set;
 767+ $value = max( 0, (int)$value ); // validate
 768+ # Add only currently recognized tags
 769+ if ( isset( $flags[$tag] ) ) {
 770+ $levels = FlaggedRevs::getTagLevels( $tag );
 771+ # If a level was removed, default to the highest...
 772+ $flags[$tag] = min( $value, count( $levels ) - 1 );
 773+ }
 774+ }
 775+ }
 776+ return $flags;
 777+ }
 778+
 779+ /**
 780+ * Get flags for a revision
 781+ * @param array $tags
 782+ * @return string
 783+ */
 784+ public static function flattenRevisionTags( array $tags ) {
 785+ $flags = '';
 786+ foreach ( $tags as $tag => $value ) {
 787+ # Add only currently recognized ones
 788+ if ( FlaggedRevs::getTagLevels( $tag ) ) {
 789+ $flags .= $tag . ':' . intval( $value ) . "\n";
 790+ }
 791+ }
 792+ return $flags;
 793+ }
 794+}
Property changes on: trunk/extensions/FlaggedRevs/dataclasses/FlaggedRevision.php
___________________________________________________________________
Added: svn:eol-style
1795 + native

Status & tagging log