These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * Helper class for category membership changes |
||
4 | * |
||
5 | * This program is free software; you can redistribute it and/or modify |
||
6 | * it under the terms of the GNU General Public License as published by |
||
7 | * the Free Software Foundation; either version 2 of the License, or |
||
8 | * (at your option) any later version. |
||
9 | * |
||
10 | * This program is distributed in the hope that it will be useful, |
||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
13 | * GNU General Public License for more details. |
||
14 | * |
||
15 | * You should have received a copy of the GNU General Public License along |
||
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
18 | * http://www.gnu.org/copyleft/gpl.html |
||
19 | * |
||
20 | * @file |
||
21 | * @author Kai Nissen |
||
22 | * @author Addshore |
||
23 | * @since 1.27 |
||
24 | */ |
||
25 | |||
26 | use Wikimedia\Assert\Assert; |
||
27 | |||
28 | class CategoryMembershipChange { |
||
29 | |||
30 | const CATEGORY_ADDITION = 1; |
||
31 | const CATEGORY_REMOVAL = -1; |
||
32 | |||
33 | /** |
||
34 | * @var string Current timestamp, set during CategoryMembershipChange::__construct() |
||
35 | */ |
||
36 | private $timestamp; |
||
37 | |||
38 | /** |
||
39 | * @var Title Title instance of the categorized page |
||
40 | */ |
||
41 | private $pageTitle; |
||
42 | |||
43 | /** |
||
44 | * @var Revision|null Latest Revision instance of the categorized page |
||
45 | */ |
||
46 | private $revision; |
||
47 | |||
48 | /** |
||
49 | * @var int |
||
50 | * Number of pages this WikiPage is embedded by |
||
51 | * Set by CategoryMembershipChange::checkTemplateLinks() |
||
52 | */ |
||
53 | private $numTemplateLinks = 0; |
||
54 | |||
55 | /** |
||
56 | * @var callable|null |
||
57 | */ |
||
58 | private $newForCategorizationCallback = null; |
||
59 | |||
60 | /** |
||
61 | * @param Title $pageTitle Title instance of the categorized page |
||
62 | * @param Revision $revision Latest Revision instance of the categorized page |
||
63 | * |
||
64 | * @throws MWException |
||
65 | */ |
||
66 | public function __construct( Title $pageTitle, Revision $revision = null ) { |
||
67 | $this->pageTitle = $pageTitle; |
||
68 | if ( $revision === null ) { |
||
69 | $this->timestamp = wfTimestampNow(); |
||
0 ignored issues
–
show
|
|||
70 | } else { |
||
71 | $this->timestamp = $revision->getTimestamp(); |
||
0 ignored issues
–
show
It seems like
$revision->getTimestamp() can also be of type false . However, the property $timestamp is declared as type string . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
Loading history...
|
|||
72 | } |
||
73 | $this->revision = $revision; |
||
74 | $this->newForCategorizationCallback = [ 'RecentChange', 'newForCategorization' ]; |
||
75 | } |
||
76 | |||
77 | /** |
||
78 | * Overrides the default new for categorization callback |
||
79 | * This is intended for use while testing and will fail if MW_PHPUNIT_TEST is not defined. |
||
80 | * |
||
81 | * @param callable $callback |
||
82 | * @see RecentChange::newForCategorization for callback signiture |
||
83 | * |
||
84 | * @throws MWException |
||
85 | */ |
||
86 | public function overrideNewForCategorizationCallback( $callback ) { |
||
87 | if ( !defined( 'MW_PHPUNIT_TEST' ) ) { |
||
88 | throw new MWException( 'Cannot override newForCategorization callback in operation.' ); |
||
89 | } |
||
90 | Assert::parameterType( 'callable', $callback, '$callback' ); |
||
91 | $this->newForCategorizationCallback = $callback; |
||
92 | } |
||
93 | |||
94 | /** |
||
95 | * Determines the number of template links for recursive link updates |
||
96 | */ |
||
97 | public function checkTemplateLinks() { |
||
98 | $this->numTemplateLinks = $this->pageTitle->getBacklinkCache()->getNumLinks( 'templatelinks' ); |
||
99 | } |
||
100 | |||
101 | /** |
||
102 | * Create a recentchanges entry for category additions |
||
103 | * |
||
104 | * @param Title $categoryTitle |
||
105 | */ |
||
106 | public function triggerCategoryAddedNotification( Title $categoryTitle ) { |
||
107 | $this->createRecentChangesEntry( $categoryTitle, self::CATEGORY_ADDITION ); |
||
108 | } |
||
109 | |||
110 | /** |
||
111 | * Create a recentchanges entry for category removals |
||
112 | * |
||
113 | * @param Title $categoryTitle |
||
114 | */ |
||
115 | public function triggerCategoryRemovedNotification( Title $categoryTitle ) { |
||
116 | $this->createRecentChangesEntry( $categoryTitle, self::CATEGORY_REMOVAL ); |
||
117 | } |
||
118 | |||
119 | /** |
||
120 | * Create a recentchanges entry using RecentChange::notifyCategorization() |
||
121 | * |
||
122 | * @param Title $categoryTitle |
||
123 | * @param int $type |
||
124 | */ |
||
125 | private function createRecentChangesEntry( Title $categoryTitle, $type ) { |
||
126 | $this->notifyCategorization( |
||
127 | $this->timestamp, |
||
128 | $categoryTitle, |
||
129 | $this->getUser(), |
||
0 ignored issues
–
show
|
|||
130 | $this->getChangeMessageText( |
||
131 | $type, |
||
132 | [ 'prefixedText' => $this->pageTitle->getPrefixedText() ], |
||
133 | $this->numTemplateLinks |
||
134 | ), |
||
135 | $this->pageTitle, |
||
136 | $this->getPreviousRevisionTimestamp(), |
||
137 | $this->revision |
||
138 | ); |
||
139 | } |
||
140 | |||
141 | /** |
||
142 | * @param string $timestamp Timestamp of the recent change to occur in TS_MW format |
||
143 | * @param Title $categoryTitle Title of the category a page is being added to or removed from |
||
144 | * @param User $user User object of the user that made the change |
||
145 | * @param string $comment Change summary |
||
146 | * @param Title $pageTitle Title of the page that is being added or removed |
||
147 | * @param string $lastTimestamp Parent revision timestamp of this change in TS_MW format |
||
148 | * @param Revision|null $revision |
||
149 | * |
||
150 | * @throws MWException |
||
151 | */ |
||
152 | private function notifyCategorization( |
||
153 | $timestamp, |
||
154 | Title $categoryTitle, |
||
155 | User $user = null, |
||
156 | $comment, |
||
157 | Title $pageTitle, |
||
158 | $lastTimestamp, |
||
159 | $revision |
||
160 | ) { |
||
161 | $deleted = $revision ? $revision->getVisibility() & Revision::SUPPRESSED_USER : 0; |
||
162 | $newRevId = $revision ? $revision->getId() : 0; |
||
163 | |||
164 | /** |
||
165 | * T109700 - Default bot flag to true when there is no corresponding RC entry |
||
166 | * This means all changes caused by parser functions & Lua on reparse are marked as bot |
||
167 | * Also in the case no RC entry could be found due to replica DB lag |
||
168 | */ |
||
169 | $bot = 1; |
||
170 | $lastRevId = 0; |
||
171 | $ip = ''; |
||
172 | |||
173 | # If no revision is given, the change was probably triggered by parser functions |
||
174 | if ( $revision !== null ) { |
||
175 | $correspondingRc = $this->revision->getRecentChange(); |
||
176 | if ( $correspondingRc === null ) { |
||
177 | $correspondingRc = $this->revision->getRecentChange( Revision::READ_LATEST ); |
||
178 | } |
||
179 | if ( $correspondingRc !== null ) { |
||
180 | $bot = $correspondingRc->getAttribute( 'rc_bot' ) ?: 0; |
||
181 | $ip = $correspondingRc->getAttribute( 'rc_ip' ) ?: ''; |
||
182 | $lastRevId = $correspondingRc->getAttribute( 'rc_last_oldid' ) ?: 0; |
||
183 | } |
||
184 | } |
||
185 | |||
186 | /** @var RecentChange $rc */ |
||
187 | $rc = call_user_func_array( |
||
188 | $this->newForCategorizationCallback, |
||
189 | [ |
||
190 | $timestamp, |
||
191 | $categoryTitle, |
||
192 | $user, |
||
193 | $comment, |
||
194 | $pageTitle, |
||
195 | $lastRevId, |
||
196 | $newRevId, |
||
197 | $lastTimestamp, |
||
198 | $bot, |
||
199 | $ip, |
||
200 | $deleted |
||
201 | ] |
||
202 | ); |
||
203 | $rc->save(); |
||
204 | } |
||
205 | |||
206 | /** |
||
207 | * Get the user associated with this change. |
||
208 | * |
||
209 | * If there is no revision associated with the change and thus no editing user |
||
210 | * fallback to a default. |
||
211 | * |
||
212 | * False will be returned if the user name specified in the |
||
213 | * 'autochange-username' message is invalid. |
||
214 | * |
||
215 | * @return User|bool |
||
216 | */ |
||
217 | private function getUser() { |
||
218 | if ( $this->revision ) { |
||
219 | $userId = $this->revision->getUser( Revision::RAW ); |
||
220 | if ( $userId === 0 ) { |
||
221 | return User::newFromName( $this->revision->getUserText( Revision::RAW ), false ); |
||
0 ignored issues
–
show
It seems like
$this->revision->getUserText(\Revision::RAW) targeting Revision::getUserText() can also be of type boolean ; however, User::newFromName() does only seem to accept string , maybe add an additional type check?
This check looks at variables that are passed out again to other methods. If the outgoing method call has stricter type requirements than the method itself, an issue is raised. An additional type check may prevent trouble.
Loading history...
|
|||
222 | } else { |
||
223 | return User::newFromId( $userId ); |
||
224 | } |
||
225 | } |
||
226 | |||
227 | $username = wfMessage( 'autochange-username' )->inContentLanguage()->text(); |
||
228 | $user = User::newFromName( $username ); |
||
229 | # User::newFromName() can return false on a badly configured wiki. |
||
230 | if ( $user && !$user->isLoggedIn() ) { |
||
231 | $user->addToDatabase(); |
||
232 | } |
||
233 | |||
234 | return $user; |
||
235 | } |
||
236 | |||
237 | /** |
||
238 | * Returns the change message according to the type of category membership change |
||
239 | * |
||
240 | * The message keys created in this method may be one of: |
||
241 | * - recentchanges-page-added-to-category |
||
242 | * - recentchanges-page-added-to-category-bundled |
||
243 | * - recentchanges-page-removed-from-category |
||
244 | * - recentchanges-page-removed-from-category-bundled |
||
245 | * |
||
246 | * @param int $type may be CategoryMembershipChange::CATEGORY_ADDITION |
||
247 | * or CategoryMembershipChange::CATEGORY_REMOVAL |
||
248 | * @param array $params |
||
249 | * - prefixedText: result of Title::->getPrefixedText() |
||
250 | * @param int $numTemplateLinks |
||
251 | * |
||
252 | * @return string |
||
253 | */ |
||
254 | private function getChangeMessageText( $type, array $params, $numTemplateLinks ) { |
||
255 | $array = [ |
||
256 | self::CATEGORY_ADDITION => 'recentchanges-page-added-to-category', |
||
257 | self::CATEGORY_REMOVAL => 'recentchanges-page-removed-from-category', |
||
258 | ]; |
||
259 | |||
260 | $msgKey = $array[$type]; |
||
261 | |||
262 | if ( intval( $numTemplateLinks ) > 0 ) { |
||
263 | $msgKey .= '-bundled'; |
||
264 | } |
||
265 | |||
266 | return wfMessage( $msgKey, $params )->inContentLanguage()->text(); |
||
267 | } |
||
268 | |||
269 | /** |
||
270 | * Returns the timestamp of the page's previous revision or null if the latest revision |
||
271 | * does not refer to a parent revision |
||
272 | * |
||
273 | * @return null|string |
||
274 | */ |
||
275 | private function getPreviousRevisionTimestamp() { |
||
276 | $previousRev = Revision::newFromId( |
||
277 | $this->pageTitle->getPreviousRevisionID( $this->pageTitle->getLatestRevID() ) |
||
0 ignored issues
–
show
|
|||
278 | ); |
||
279 | |||
280 | return $previousRev ? $previousRev->getTimestamp() : null; |
||
281 | } |
||
282 | |||
283 | } |
||
284 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.