-
Notifications
You must be signed in to change notification settings - Fork 1
/
PageAssessmentsDAO.php
356 lines (332 loc) · 11.6 KB
/
PageAssessmentsDAO.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* PageAssessments extension body
*
* @file
* @ingroup Extensions
*/
namespace MediaWiki\Extension\PageAssessments;
use DBAccessObjectUtils;
use IDBAccessObject;
use MediaWiki\MediaWikiServices;
use Parser;
use Title;
class PageAssessmentsDAO implements IDBAccessObject {
/** @var array Instance cache associating project IDs with project names */
protected static $projectNames = [];
/**
* Driver function that handles updating assessment data in database
* @param Title $titleObj Title object of the subject page
* @param array $assessmentData Data for all assessments compiled
* @param mixed|null $ticket Transaction ticket
*/
public static function doUpdates( $titleObj, $assessmentData, $ticket = null ) {
global $wgUpdateRowsPerQuery, $wgPageAssessmentsSubprojects;
$factory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
$ticket = $ticket ?: $factory->getEmptyTransactionTicket( __METHOD__ );
$pageId = $titleObj->getArticleID();
$revisionId = $titleObj->getLatestRevID();
// Compile a list of projects found in the parserData to find out which
// assessment records need to be inserted, deleted, or updated.
$projects = [];
foreach ( $assessmentData as $key => $parserData ) {
// If the name of the project is set...
if ( isset( $parserData[0] ) && $parserData[0] !== '' ) {
// Clean the project name.
$projectName = self::cleanProjectTitle( $parserData[0] );
// Replace the original project name with the cleaned project
// name in the assessment data, since we'll need it to match later.
$assessmentData[$key][0] = $projectName;
// Get the corresponding ID from page_assessments_projects table.
$projectId = self::getProjectId( $projectName );
// If there is no existing project by that name, add it to the table.
if ( $projectId === false ) {
if ( $wgPageAssessmentsSubprojects ) {
// Extract possible parent from the project name.
$parentId = self::extractParentProjectId( $projectName );
// Insert project data into the database table.
$projectId = self::insertProject( $projectName, $parentId );
} else {
$projectId = self::insertProject( $projectName );
}
}
// Add the project's ID to the array.
$projects[$projectName] = $projectId;
}
}
// Get a list of all the projects previously assigned to the page.
$projectsInDb = self::getAllProjects( $pageId, self::READ_LATEST );
$toInsert = array_diff( $projects, $projectsInDb );
$toDelete = array_diff( $projectsInDb, $projects );
$toUpdate = array_intersect( $projects, $projectsInDb );
$i = 0;
// Add and update assessment records to the database
foreach ( $assessmentData as $parserData ) {
// Make sure the name of the project is set.
if ( !isset( $parserData[0] ) || $parserData[0] == '' ) {
continue;
}
$projectId = $projects[$parserData[0]];
if ( $projectId && $pageId ) {
$class = $parserData[1];
$importance = $parserData[2];
$values = [
'pa_page_id' => $pageId,
'pa_project_id' => $projectId,
'pa_class' => $class,
'pa_importance' => $importance,
'pa_page_revision' => $revisionId
];
if ( in_array( $projectId, $toInsert ) ) {
self::insertRecord( $values );
} elseif ( in_array( $projectId, $toUpdate ) ) {
self::updateRecord( $values );
}
// Check for database lag if there's a huge number of assessments
if ( $i > 0 && $i % $wgUpdateRowsPerQuery == 0 ) {
$factory->commitAndWaitForReplication( __METHOD__, $ticket );
}
$i++;
}
}
// Delete records from the database
foreach ( $toDelete as $project ) {
$values = [
'pa_page_id' => $pageId,
'pa_project_id' => $project
];
self::deleteRecord( $values );
// Check for database lag if there's a huge number of deleted assessments
if ( $i > 0 && $i % $wgUpdateRowsPerQuery == 0 ) {
$factory->commitAndWaitForReplication( __METHOD__, $ticket );
}
$i++;
}
}
/**
* Get name for the given wikiproject
* @param int $projectId The ID of the project
* @return string|false The name of the project or false if not found
*/
public static function getProjectName( $projectId ) {
// Check for a valid project ID
if ( $projectId > 0 ) {
// See if the project name is already in the instance cache
if ( isset( self::$projectNames[$projectId] ) ) {
return self::$projectNames[$projectId];
} else {
$dbr = wfGetDB( DB_REPLICA );
$projectName = $dbr->selectField(
'page_assessments_projects',
'pap_project_title',
[ 'pap_project_id' => $projectId ],
__METHOD__
);
// Store the project name in instance cache
self::$projectNames[$projectId] = $projectName;
return $projectName;
}
}
return false;
}
/**
* Extract parent from a project name and return the ID. For example, if the
* project name is "Novels/Crime task force", the parent will be "Novels",
* i.e. WikiProject Novels.
*
* @param string $projectName Project title
* @return int|false project ID or false if not found
*/
protected static function extractParentProjectId( $projectName ) {
$projectNameParts = explode( '/', $projectName );
if ( count( $projectNameParts ) > 1 && $projectNameParts[0] !== '' ) {
return self::getProjectId( $projectNameParts[0] );
}
return false;
}
/**
* Get project ID for a given wikiproject title
* @param string $project Project title
* @return int|false project ID or false if not found
*/
public static function getProjectId( $project ) {
$dbr = wfGetDB( DB_REPLICA );
return $dbr->selectField(
'page_assessments_projects',
'pap_project_id',
[ 'pap_project_title' => $project ],
__METHOD__
);
}
/**
* Insert a new wikiproject into the projects table
* @param string $project Wikiproject title
* @param int|null $parentId ID of the parent project (for subprojects) (optional)
* @return int Insert Id for new project
*/
public static function insertProject( $project, $parentId = null ) {
$dbw = wfGetDB( DB_PRIMARY );
$values = [ 'pap_project_title' => $project ];
if ( $parentId ) {
$values[ 'pap_parent_id' ] = (int)$parentId;
}
// Use IGNORE in case two projects with the same name are added at once.
// This normally shouldn't happen, but is possible perhaps from clicking
// 'Publish changes' twice in very quick succession. (See T286671)
$dbw->insert( 'page_assessments_projects', $values, __METHOD__, [ 'IGNORE' ] );
$id = $dbw->insertId();
return $id;
}
/**
* Clean up the title of the project (or subproject)
*
* Since the project title comes from a template parameter, it can basically
* be anything. This function accounts for common cases where editors put
* extra stuff into the parameter besides just the name of the project.
* @param string $project WikiProject title
* @return string Cleaned-up WikiProject title
*/
public static function cleanProjectTitle( $project ) {
// Remove any bold formatting.
$project = str_replace( "'''", "", $project );
// Remove "the" prefix for subprojects (common on English Wikipedia).
// This is case-sensitive on purpose, as there are some legitimate
// subproject titles starting with "The", e.g. "The Canterbury Tales".
$project = str_replace( "/the ", "/", $project );
// Truncate to 255 characters to avoid DB warnings.
return substr( $project, 0, 255 );
}
/**
* Update record in DB if there are new values
* @param array $values New values to be entered into the DB
* @return bool true
*/
public static function updateRecord( $values ) {
$dbr = wfGetDB( DB_REPLICA );
$conds = [
'pa_page_id' => $values['pa_page_id'],
'pa_project_id' => $values['pa_project_id']
];
// Check if there are no updates to be done
$record = $dbr->select(
'page_assessments',
[ 'pa_class', 'pa_importance', 'pa_project_id', 'pa_page_id' ],
$conds,
__METHOD__
);
foreach ( $record as $row ) {
if ( $row->pa_importance == $values['pa_importance'] &&
$row->pa_class == $values['pa_class']
) {
// Return if no update is needed
return true;
}
}
// Make updates if there are changes
$dbw = wfGetDB( DB_PRIMARY );
$dbw->update( 'page_assessments', $values, $conds, __METHOD__ );
return true;
}
/**
* Insert a new record in DB
* @param array $values New values to be entered into the DB
* @return bool true
*/
public static function insertRecord( $values ) {
$dbw = wfGetDB( DB_PRIMARY );
// Use IGNORE in case 2 records for the same project are added at once.
// This normally shouldn't happen, but is possible. (See T152080)
$dbw->insert( 'page_assessments', $values, __METHOD__, [ 'IGNORE' ] );
return true;
}
/**
* Get all projects associated with a given page (as project IDs)
* @param int $pageId Page ID
* @param int $flags IDBAccessObject::READ_* constant. This can be used to
* force reading from the primary database. See docs at IDBAccessObject.php.
* @return array $results All projects associated with given page
*/
public static function getAllProjects( $pageId, $flags = self::READ_NORMAL ) {
list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
$db = wfGetDB( $index );
$res = $db->select(
'page_assessments',
'pa_project_id',
[ 'pa_page_id' => $pageId ],
__METHOD__,
$options
);
$results = [];
if ( $res ) {
foreach ( $res as $row ) {
$results[] = $row->pa_project_id;
}
}
return $results;
}
/**
* Delete a record from DB
* @param array $values Conditions for looking up records to delete
* @return bool true
*/
public static function deleteRecord( $values ) {
$dbw = wfGetDB( DB_PRIMARY );
$conds = [
'pa_page_id' => $values['pa_page_id'],
'pa_project_id' => $values['pa_project_id']
];
$dbw->delete( 'page_assessments', $conds, __METHOD__ );
return true;
}
/**
* Delete all records for a given page when page is deleted
* Note: We don't take care of undeletions explicitly, the records are restored
* when the page is parsed again.
* @param int $id Page ID of deleted page
* @return bool true
*/
public static function deleteRecordsForPage( $id ) {
$dbw = wfGetDB( DB_PRIMARY );
$conds = [
'pa_page_id' => $id,
];
$dbw->delete( 'page_assessments', $conds, __METHOD__ );
return true;
}
/**
* Function called on parser init
* @param Parser $parser Parser object
* @param string $project Wikiproject name
* @param string $class Class of article
* @param string $importance Importance of article
*/
public static function cacheAssessment(
Parser $parser,
$project = '',
$class = '',
$importance = ''
) {
$parserData = $parser->getOutput()->getExtensionData( 'ext-pageassessment-assessmentdata' );
$values = [ $project, $class, $importance ];
if ( $parserData == null ) {
$parserData = [];
}
$parserData[] = $values;
$parser->getOutput()->setExtensionData( 'ext-pageassessment-assessmentdata', $parserData );
}
}