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 @@ |
241 | 241 | $wgExtensionMessagesFiles['FlaggedRevs'] = $langDir . 'FlaggedRevs.i18n.php'; |
242 | 242 | $wgExtensionAliasesFiles['FlaggedRevs'] = $langDir . 'FlaggedRevs.alias.php'; |
243 | 243 | |
| 244 | +$accessDir = $dir . 'dataclasses/'; |
244 | 245 | # 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'; |
250 | 259 | |
251 | 260 | # Event handler classes... |
252 | 261 | $wgAutoloadClasses['FlaggedRevsHooks'] = $dir . 'FlaggedRevs.hooks.php'; |
— | — | @@ -253,15 +262,6 @@ |
254 | 263 | $wgAutoloadClasses['FlaggedRevsApiHooks'] = $dir . 'api/FlaggedRevsApi.hooks.php'; |
255 | 264 | $wgAutoloadClasses['FlaggedRevsUpdaterHooks'] = $dir . 'schema/FlaggedRevsUpdater.hooks.php'; |
256 | 265 | |
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 | | - |
266 | 266 | # Business object classes |
267 | 267 | $wgAutoloadClasses['FRGenericSubmitForm'] = $dir . 'business/FRGenericSubmitForm.php'; |
268 | 268 | $wgAutoloadClasses['RevisionReviewForm'] = $dir . 'business/RevisionReviewForm.php'; |
Index: trunk/extensions/FlaggedRevs/FlaggedRevs.hooks.php |
— | — | @@ -1008,8 +1008,8 @@ |
1009 | 1009 | } |
1010 | 1010 | |
1011 | 1011 | 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'; |
1014 | 1014 | return true; |
1015 | 1015 | } |
1016 | 1016 | |
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 |
1 | 98 | + 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 |
1 | 142 | + 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 |
1 | 237 | + 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 |
1 | 172 | + 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 |
1 | 163 | + 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 |
1 | 244 | + 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 |
1 | 1205 | + 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 |
1 | 191 | + 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 |
1 | 178 | + 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 |
1 | 207 | + 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 |
1 | 543 | + 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 |
1 | 795 | + native |