Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 421 |
|
0.00% |
0 / 14 |
CRAP | |
0.00% |
0 / 1 |
CargoTables | |
0.00% |
0 / 421 |
|
0.00% |
0 / 14 |
7140 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 122 |
|
0.00% |
0 / 1 |
506 | |||
displayNumRowsForTable | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
displayNumColumnsForTable | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getTableLinkedToView | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
20 | |||
getActionButton | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
2 | |||
getActionIcon | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
2 | |||
setAllowedActions | |
0.00% |
0 / 37 |
|
0.00% |
0 / 1 |
20 | |||
deriveListOfColumnsFromUserAllowedActions | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
getActionLinksForTable | |
0.00% |
0 / 50 |
|
0.00% |
0 / 1 |
240 | |||
tableTemplatesText | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
42 | |||
displayListOfTables | |
0.00% |
0 / 143 |
|
0.00% |
0 / 1 |
462 | |||
displayActionLinks | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
getGroupName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * Defines a special page that shows the contents of a single table in |
4 | * the Cargo database. |
5 | * |
6 | * @author Yaron Koren |
7 | * @author Megan Cutrofello |
8 | * @ingroup Cargo |
9 | */ |
10 | |
11 | use MediaWiki\Html\Html; |
12 | use MediaWiki\MediaWikiServices; |
13 | use MediaWiki\Title\Title; |
14 | |
15 | class CargoTables extends IncludableSpecialPage { |
16 | |
17 | private $templatesThatDeclareTables; |
18 | private $templatesThatAttachToTables; |
19 | |
20 | private static $actionList = null; |
21 | |
22 | /** |
23 | * Constructor |
24 | */ |
25 | public function __construct() { |
26 | parent::__construct( 'CargoTables' ); |
27 | $this->templatesThatDeclareTables = CargoUtils::getAllPageProps( 'CargoTableName' ); |
28 | $this->templatesThatAttachToTables = CargoUtils::getAllPageProps( 'CargoAttachedTable' ); |
29 | } |
30 | |
31 | public function execute( $tableName ) { |
32 | $out = $this->getOutput(); |
33 | $req = $this->getRequest(); |
34 | $user = $this->getUser(); |
35 | $this->setHeaders(); |
36 | |
37 | $out->addModules( [ |
38 | 'ext.cargo.main', |
39 | 'ext.cargo.cargotables', |
40 | ] ); |
41 | $out->addModuleStyles( [ |
42 | 'mediawiki.pager.styles' |
43 | ] ); |
44 | |
45 | if ( $tableName == '' ) { |
46 | $out->addHTML( $this->displayListOfTables() ); |
47 | |
48 | return; |
49 | } |
50 | |
51 | if ( !CargoUtils::tableFullyExists( $tableName ) ) { |
52 | $out->addHTML( Html::element( 'div', [ 'class' => 'error' ], |
53 | $this->msg( "cargo-unknowntable", $tableName )->escaped() ) ); |
54 | |
55 | return; |
56 | } |
57 | |
58 | $ctURL = SpecialPage::getTitleFor( 'CargoTables' )->getFullURL(); |
59 | $viewURL = "$ctURL/$tableName"; |
60 | |
61 | if ( $req->getCheck( '_replacement' ) ) { |
62 | $pageTitle = |
63 | $this->msg( 'cargo-cargotables-viewreplacement', '"' . $tableName . '"' )->escaped(); |
64 | $tableLink = Html::element( 'a', [ 'href' => $viewURL ], $tableName ); |
65 | $text = $this->msg( 'cargo-cargotables-replacementtable' )->rawParams( $tableLink )->escaped(); |
66 | if ( $user->isAllowed( 'recreatecargodata' ) ) { |
67 | $switchURL = |
68 | SpecialPage::getTitleFor( 'SwitchCargoTable' )->getFullURL() . "/$tableName"; |
69 | $text .= ' ' . Html::element( 'a', [ 'href' => $switchURL ], |
70 | $this->msg( "cargo-cargotables-switch" )->escaped() ); |
71 | |
72 | if ( $user->isAllowed( 'deletecargodata' ) ) { |
73 | $deleteURL = |
74 | SpecialPage::getTitleFor( 'DeleteCargoTable' )->getFullURL() . |
75 | "/$tableName"; |
76 | $deleteURL .= strpos( $deleteURL, '?' ) ? '&' : '?'; |
77 | $deleteURL .= "_replacement"; |
78 | $text .= ' ' . |
79 | $this->msg( 'cargo-cargotables-deletereplacement', $deleteURL )->parse(); |
80 | } |
81 | } |
82 | $out->addHtml( Html::warningBox( $text ) ); |
83 | $tableName .= '__NEXT'; |
84 | } else { |
85 | $pageTitle = $this->msg( 'cargo-cargotables-viewtable', $tableName )->escaped(); |
86 | if ( CargoUtils::tableFullyExists( $tableName . '__NEXT' ) ) { |
87 | $text = Html::warningBox( $this->msg( 'cargo-cargotables-hasreplacement' )->escaped() ); |
88 | $out->addHtml( $text ); |
89 | } |
90 | } |
91 | |
92 | $out->setPageTitle( $pageTitle ); |
93 | |
94 | // Mimic the appearance of a subpage to link back to |
95 | // Special:CargoTables. |
96 | $ctPage = CargoUtils::getSpecialPage( 'CargoTables' ); |
97 | $mainPageLink = |
98 | CargoUtils::makeLink( $this->getLinkRenderer(), $ctPage->getPageTitle(), |
99 | htmlspecialchars( $ctPage->getDescription() ) ); |
100 | $out->setSubtitle( '< ' . $mainPageLink ); |
101 | |
102 | $tableSchemas = CargoUtils::getTableSchemas( [ $tableName ] ); |
103 | $fieldDescriptions = $tableSchemas[$tableName]->mFieldDescriptions; |
104 | |
105 | // Display the table structure. |
106 | $structureDesc = '<p>' . $this->msg( 'cargo-cargotables-tablestructure' )->escaped() . '</p>'; |
107 | $structureDesc .= '<ol>'; |
108 | foreach ( $fieldDescriptions as $fieldName => $fieldDescription ) { |
109 | $fieldDesc = '<strong>' . $fieldName . '</strong> - '; |
110 | $fieldDesc .= $fieldDescription->prettyPrintTypeAndAttributes(); |
111 | $structureDesc .= Html::rawElement( 'li', null, $fieldDesc ) . "\n"; |
112 | } |
113 | $structureDesc .= '</ol>'; |
114 | $out->addHTML( $structureDesc ); |
115 | |
116 | // Then, display a count. |
117 | $cdb = CargoUtils::getDB(); |
118 | $numRows = $cdb->selectRowCount( $tableName, '*', null, __METHOD__ ); |
119 | $numRowsMessage = |
120 | $this->msg( 'cargo-cargotables-totalrows' )->numParams( $numRows )->parse(); |
121 | $out->addWikiTextAsInterface( $numRowsMessage . "\n" ); |
122 | |
123 | // Display "Recreate data" link. |
124 | if ( array_key_exists( $tableName, $this->templatesThatDeclareTables ) ) { |
125 | $templatesThatDeclareThisTable = $this->templatesThatDeclareTables[$tableName]; |
126 | } |
127 | if ( isset( $templatesThatDeclareThisTable ) && count( $templatesThatDeclareThisTable ) > 0 ) { |
128 | // Most likely, there's only one. |
129 | $templateID = $templatesThatDeclareThisTable[0]; |
130 | $templateTitle = Title::newFromID( $templateID ); |
131 | $recreateDataURL = $templateTitle->getLocalURL( [ 'action' => 'recreatedata' ] ); |
132 | $recreateDataMessage = $this->msg( 'recreatedata' )->escaped(); |
133 | $recreateDataLink = Html::element( 'a', [ 'href' => $recreateDataURL ], $recreateDataMessage ); |
134 | $out->addHTML( '<p>' . $recreateDataLink . '.</p>' ); |
135 | } |
136 | |
137 | // Then, show the actual table, via a query. |
138 | $sqlQuery = new CargoSQLQuery(); |
139 | $sqlQuery->mTablesStr = $tableName; |
140 | $sqlQuery->mAliasedTableNames = [ $tableName => $tableName ]; |
141 | |
142 | $sqlQuery->mTableSchemas = $tableSchemas; |
143 | |
144 | $aliasedFieldNames = [ $this->msg( 'nstab-main' )->parse() => '_pageName' ]; |
145 | foreach ( $fieldDescriptions as $fieldName => $fieldDescription ) { |
146 | // Skip "hidden" fields. |
147 | if ( property_exists( $fieldDescription, 'hidden' ) ) { |
148 | continue; |
149 | } |
150 | |
151 | if ( $fieldName[0] != '_' ) { |
152 | $fieldAlias = str_replace( '_', ' ', $fieldName ); |
153 | } else { |
154 | $fieldAlias = $fieldName; |
155 | } |
156 | $fieldType = $fieldDescription->mType; |
157 | // Special handling for URLs, to avoid them |
158 | // overwhelming the page. |
159 | // @TODO - something similar should be done for lists |
160 | // of URLs. |
161 | if ( $fieldType == 'URL' && !$fieldDescription->mIsList ) { |
162 | $quotedFieldName = $cdb->addIdentifierQuotes( $fieldName ); |
163 | // Thankfully, there's a message in core |
164 | // MediaWiki that seems to just be "URL". |
165 | $fieldName = |
166 | "CONCAT('[', $quotedFieldName, ' " . |
167 | $this->msg( 'version-entrypoints-header-url' )->parse() . "]')"; |
168 | // Only display this if the URL is not blank. |
169 | // Unfortunately, IF is not consistently |
170 | // supported in PostgreSQL - just leave this |
171 | // logic out if we're using Postgres. |
172 | if ( $cdb->getType() !== 'postgres' ) { |
173 | $fieldName = "IF ($quotedFieldName <> '', $fieldName, '')"; |
174 | } |
175 | } |
176 | |
177 | if ( $fieldDescription->mIsList ) { |
178 | $aliasedFieldNames[$fieldAlias] = $fieldName . '__full'; |
179 | } elseif ( $fieldType == 'Coordinates' ) { |
180 | $aliasedFieldNames[$fieldAlias] = $fieldName . '__full'; |
181 | } else { |
182 | $aliasedFieldNames[$fieldAlias] = $fieldName; |
183 | } |
184 | } |
185 | |
186 | $sqlQuery->mAliasedFieldNames = $aliasedFieldNames; |
187 | $sqlQuery->mOrigAliasedFieldNames = $aliasedFieldNames; |
188 | // Set mFieldsStr in case we need to show a "More" link |
189 | // at the end. |
190 | $fieldsStr = ''; |
191 | foreach ( $aliasedFieldNames as $alias => $fieldName ) { |
192 | $fieldsStr .= "$fieldName=$alias,"; |
193 | } |
194 | // Remove the comma at the end. |
195 | $sqlQuery->mFieldsStr = trim( $fieldsStr, ',' ); |
196 | |
197 | $sqlQuery->setDescriptionsAndTableNamesForFields(); |
198 | $sqlQuery->handleDateFields(); |
199 | $sqlQuery->setOrderBy(); |
200 | $sqlQuery->mQueryLimit = 100; |
201 | |
202 | $queryResults = $sqlQuery->run(); |
203 | |
204 | $displayParams = []; |
205 | $displayParams['max display chars'] = 300; |
206 | $displayParams['edit link'] = 'yes'; |
207 | |
208 | $queryDisplayer = CargoQueryDisplayer::newFromSQLQuery( $sqlQuery ); |
209 | $queryDisplayer->mDisplayParams = $displayParams; |
210 | $formattedQueryResults = $queryDisplayer->getFormattedQueryResults( $queryResults ); |
211 | |
212 | $tableFormat = new CargoTableFormat( $this->getOutput() ); |
213 | $text = |
214 | $tableFormat->display( $queryResults, $formattedQueryResults, |
215 | $sqlQuery->mFieldDescriptions, $displayParams ); |
216 | |
217 | // If there are (seemingly) more results than what we showed, |
218 | // show a "View more" link that links to Special:ViewData. |
219 | if ( count( $queryResults ) == $sqlQuery->mQueryLimit ) { |
220 | $text .= $queryDisplayer->viewMoreResultsLink(); |
221 | } |
222 | |
223 | $out->addHTML( $text ); |
224 | } |
225 | |
226 | public function displayNumRowsForTable( $cdb, $tableName ) { |
227 | global $wgCargoDecimalMark; |
228 | global $wgCargoDigitGroupingCharacter; |
229 | |
230 | $res = $cdb->select( $tableName, 'COUNT(*) AS total', '', __METHOD__ ); |
231 | $row = $res->fetchRow(); |
232 | |
233 | return number_format( intval( $row['total'] ), 0, $wgCargoDecimalMark, |
234 | $wgCargoDigitGroupingCharacter ); |
235 | } |
236 | |
237 | public function displayNumColumnsForTable( $tableName ) { |
238 | $tableSchemas = CargoUtils::getTableSchemas( [ $tableName ] ); |
239 | return count( $tableSchemas[$tableName]->mFieldDescriptions ); |
240 | } |
241 | |
242 | private function getTableLinkedToView( $tableName, $isReplacementTable ) { |
243 | $viewURL = SpecialPage::getTitleFor( 'CargoTables' )->getFullURL() . "/$tableName"; |
244 | if ( $isReplacementTable ) { |
245 | $viewURL .= strpos( $viewURL, '?' ) ? '&' : '?'; |
246 | $viewURL .= "_replacement"; |
247 | } |
248 | |
249 | $displayText = |
250 | $isReplacementTable ? $this->msg( 'cargo-cargotables-replacementlink' ) : $tableName; |
251 | |
252 | return Html::element( 'a', [ 'href' => $viewURL ], $displayText ); |
253 | } |
254 | |
255 | public function getActionButton( $action, $target ) { |
256 | // a button is a clickable link, its target being a table action |
257 | $actionList = self::$actionList; |
258 | $displayIcon = $actionList[$action]['ooui-icon']; |
259 | $displayTitle = $this->msg( $actionList[$action]['ooui-title'] ); |
260 | $element = new OOUI\ButtonWidget( [ |
261 | 'icon' => $displayIcon, |
262 | 'title' => $displayTitle, |
263 | 'href' => $target, |
264 | ] ); |
265 | |
266 | return $element->toString(); |
267 | } |
268 | |
269 | private function getActionIcon( $action ) { |
270 | // an icon is just a static icon, no link. these are used in headings. |
271 | $actionList = self::$actionList; |
272 | $displayIcon = $actionList[$action]['ooui-icon']; |
273 | $displayTitle = $this->msg( $actionList[$action]['ooui-title'] ); |
274 | $element = new OOUI\IconWidget( [ |
275 | 'icon' => $displayIcon, |
276 | 'title' => $displayTitle, |
277 | ] ); |
278 | |
279 | return $element->toString(); |
280 | } |
281 | |
282 | private function setAllowedActions() { |
283 | // initialize needed ooui stuff |
284 | $this->getOutput()->enableOOUI(); |
285 | $this->getOutput()->addModuleStyles( [ 'oojs-ui.styles.icons-interactions' ] ); |
286 | $this->getOutput()->addModuleStyles( [ 'oojs-ui.styles.icons-moderation' ] ); |
287 | OOUI\Element::setDefaultDir( 'ltr' ); |
288 | |
289 | // add display information for all actions that a user is able to perform |
290 | // if the parent param is set, then the action belongs to another column |
291 | // and will NOT cause creation of a new column |
292 | $user = $this->getUser(); |
293 | |
294 | $allowedActions = []; |
295 | if ( $user->isAllowed( 'runcargoqueries' ) ) { |
296 | $allowedActions['drilldown'] = [ |
297 | "ooui-icon" => "funnel", |
298 | "ooui-title" => "cargo-cargotables-action-drilldown", |
299 | ]; |
300 | } |
301 | |
302 | // recreatecargodata allows both recreating and switching in replacements |
303 | if ( $user->isAllowed( 'recreatecargodata' ) ) { |
304 | $allowedActions['recreate'] = |
305 | [ "ooui-icon" => "reload", "ooui-title" => "cargo-cargotables-action-recreate" ]; |
306 | $allowedActions['switchReplacement'] = |
307 | [ |
308 | "ooui-icon" => "check", |
309 | "ooui-title" => "cargo-cargotables-action-switchreplacement", |
310 | "parent" => "recreate", |
311 | ]; |
312 | // Should this have a separate permission? |
313 | $allowedActions['create'] = |
314 | [ |
315 | "ooui-icon" => "add", |
316 | "ooui-title" => "create", |
317 | "parent" => "recreate", |
318 | ]; |
319 | } |
320 | |
321 | // deletecargodata allows deleting live tables & their replacements |
322 | // these cases are handled separately so they can use separate icons |
323 | if ( $user->isAllowed( 'deletecargodata' ) ) { |
324 | $allowedActions['delete'] = |
325 | [ "ooui-icon" => "trash", "ooui-title" => "cargo-cargotables-action-delete" ]; |
326 | $allowedActions['deleteReplacement'] = |
327 | [ |
328 | "ooui-icon" => "cancel", |
329 | "ooui-title" => "cargo-cargotables-action-deletereplacement", |
330 | "parent" => "delete", |
331 | ]; |
332 | } |
333 | |
334 | // allow opportunity for adding additional actions & display info |
335 | $this->getHookContainer()->run( 'CargoTablesSetAllowedActions', [ $this, &$allowedActions ] ); |
336 | self::$actionList = $allowedActions; |
337 | } |
338 | |
339 | private function deriveListOfColumnsFromUserAllowedActions() { |
340 | $columns = []; |
341 | foreach ( self::$actionList as $action => $actionInfo ) { |
342 | if ( array_key_exists( "parent", $actionInfo ) ) { |
343 | continue; |
344 | } |
345 | $columns[] = $action; |
346 | } |
347 | |
348 | return $columns; |
349 | } |
350 | |
351 | private function getActionLinksForTable( $tableName, $isReplacementTable, $hasReplacementTable, $isSpecialTable = false ) { |
352 | $user = $this->getUser(); |
353 | |
354 | $canBeRecreated = |
355 | !$isReplacementTable && !$hasReplacementTable && |
356 | ( $isSpecialTable || array_key_exists( $tableName, $this->templatesThatDeclareTables ) ); |
357 | |
358 | $actionLinks = []; |
359 | |
360 | if ( array_key_exists( 'drilldown', self::$actionList ) ) { |
361 | $drilldownPage = CargoUtils::getSpecialPage( 'Drilldown' ); |
362 | $drilldownURL = $drilldownPage->getPageTitle()->getLocalURL() . '/' . $tableName; |
363 | $drilldownURL .= strpos( $drilldownURL, '?' ) ? '&' : '?'; |
364 | if ( $isReplacementTable ) { |
365 | $drilldownURL .= "_replacement"; |
366 | } else { |
367 | $drilldownURL .= "_single"; |
368 | } |
369 | $actionLinks['drilldown'] = $this->getActionButton( 'drilldown', $drilldownURL ); |
370 | } |
371 | |
372 | // Recreate permission governs both recreating and switching |
373 | if ( array_key_exists( 'recreate', self::$actionList ) ) { |
374 | // It's a bit odd to include the "Recreate data" link, since |
375 | // it's an action for the template and not the table (if a |
376 | // template defines two tables, this will recreate both of |
377 | // them), but for standard setups, this makes things more |
378 | // convenient. |
379 | if ( $canBeRecreated ) { |
380 | if ( $isSpecialTable ) { |
381 | $recreateURL = |
382 | SpecialPage::getTitleFor( 'RecreateCargoData' )->getFullURL() . "/$tableName"; |
383 | $actionLinks['recreate'] = $this->getActionButton( 'recreate', $recreateURL ); |
384 | } else { |
385 | $templateID = $this->templatesThatDeclareTables[$tableName][0]; |
386 | $templateTitle = Title::newFromID( $templateID ); |
387 | if ( $templateTitle !== null ) { |
388 | $recreateDataURL = $templateTitle->getLocalURL( [ 'action' => 'recreatedata' ] ); |
389 | $actionLinks['recreate'] = $this->getActionButton( 'recreate', $recreateDataURL ); |
390 | } |
391 | } |
392 | } elseif ( $isReplacementTable ) { |
393 | // switch will be in the same column as recreate |
394 | $switchURL = |
395 | SpecialPage::getTitleFor( 'SwitchCargoTable' )->getFullURL() . "/$tableName"; |
396 | $actionLinks['recreate'] = |
397 | $this->getActionButton( 'switchReplacement', $switchURL ); |
398 | } |
399 | } |
400 | |
401 | if ( array_key_exists( 'delete', self::$actionList ) ) { |
402 | $deleteTableURL = |
403 | SpecialPage::getTitleFor( 'DeleteCargoTable' )->getLocalURL() . "/$tableName"; |
404 | $deleteAction = "delete"; |
405 | if ( $isReplacementTable ) { |
406 | $deleteTableURL .= strpos( $deleteTableURL, '?' ) ? '&' : '?'; |
407 | $deleteTableURL .= "_replacement"; |
408 | $deleteAction = "deleteReplacement"; |
409 | } |
410 | $actionLinks['delete'] = $this->getActionButton( $deleteAction, $deleteTableURL ); |
411 | } |
412 | |
413 | $this->getHookContainer()->run( 'CargoTablesSetActionLinks', [ |
414 | $this, |
415 | &$actionLinks, |
416 | $tableName, |
417 | $isReplacementTable, |
418 | $hasReplacementTable, |
419 | $this->templatesThatDeclareTables, |
420 | $this->templatesThatAttachToTables, |
421 | self::$actionList, |
422 | $user |
423 | ] ); |
424 | |
425 | return $actionLinks; |
426 | } |
427 | |
428 | private function tableTemplatesText( $tableName ) { |
429 | $linkRenderer = $this->getLinkRenderer(); |
430 | |
431 | // "Declared by" text |
432 | if ( !array_key_exists( $tableName, $this->templatesThatDeclareTables ) ) { |
433 | $declaringTemplatesText = $this->msg( 'cargo-cargotables-notdeclared' )->text(); |
434 | } else { |
435 | $templatesThatDeclareThisTable = $this->templatesThatDeclareTables[$tableName]; |
436 | $templateLinks = []; |
437 | foreach ( $templatesThatDeclareThisTable as $templateID ) { |
438 | $templateTitle = Title::newFromID( $templateID ); |
439 | $templateLinks[] = CargoUtils::makeLink( $linkRenderer, $templateTitle ); |
440 | } |
441 | $declaringTemplatesText = |
442 | Html::rawElement( 'span', [ "class" => "cargo-tablelist-template-declaring" ], |
443 | implode( ', ', $templateLinks ) ); |
444 | } |
445 | |
446 | // "Attached by" text |
447 | if ( array_key_exists( $tableName, $this->templatesThatAttachToTables ) ) { |
448 | $templatesThatAttachToThisTable = $this->templatesThatAttachToTables[$tableName]; |
449 | } else { |
450 | $templatesThatAttachToThisTable = []; |
451 | } |
452 | |
453 | if ( count( $templatesThatAttachToThisTable ) == 0 ) { |
454 | return $declaringTemplatesText; |
455 | } |
456 | |
457 | $templateLinks = []; |
458 | foreach ( $templatesThatAttachToThisTable as $templateID ) { |
459 | $templateTitle = Title::newFromID( $templateID ); |
460 | $templateLinks[] = CargoUtils::makeLink( $linkRenderer, $templateTitle ); |
461 | } |
462 | $attachingTemplatesText = |
463 | Html::rawElement( 'span', [ "class" => "cargo-tablelist-template-attaching" ], |
464 | implode( ', ', $templateLinks ) ); |
465 | |
466 | return "$declaringTemplatesText, $attachingTemplatesText"; |
467 | } |
468 | |
469 | /** |
470 | * Returns HTML for a bulleted list of Cargo tables, with various |
471 | * links and information for each one. |
472 | */ |
473 | private function displayListOfTables() { |
474 | global $wgCargoTablesPrioritizeReplacements; |
475 | $text = ''; |
476 | |
477 | $this->setAllowedActions(); |
478 | |
479 | $listOfColumns = $this->deriveListOfColumnsFromUserAllowedActions(); |
480 | |
481 | // Show a note if there are currently Cargo populate-data jobs |
482 | // that haven't been run, to make troubleshooting easier. |
483 | $group = MediaWikiServices::getInstance()->getJobQueueGroup(); |
484 | // The following line would have made more sense to call, but |
485 | // it seems to return true if there are *any* jobs in the |
486 | // queue - a bug in MediaWiki? |
487 | // if ( $group->queuesHaveJobs( 'cargoPopulateTable' ) ) { |
488 | if ( in_array( 'cargoPopulateTable', $group->getQueuesWithJobs() ) ) { |
489 | $text .= Html::warningBox( |
490 | $this->msg( 'cargo-cargotables-beingpopulated' )->parse() |
491 | ); |
492 | } |
493 | |
494 | $cdb = CargoUtils::getDB(); |
495 | $tableNames = CargoUtils::getTables(); |
496 | |
497 | // Move the "special" tables into a separate array. |
498 | $existingSpecialTables = []; |
499 | foreach ( $tableNames as $tableIndex => $tableName ) { |
500 | if ( substr( $tableName, 0, 1 ) === '_' ) { |
501 | unset( $tableNames[$tableIndex] ); |
502 | $existingSpecialTables[] = $tableName; |
503 | } |
504 | } |
505 | |
506 | // reorder table list so tables with replacements are first, |
507 | // but only if the preference is set to do so |
508 | if ( $wgCargoTablesPrioritizeReplacements ) { |
509 | foreach ( $tableNames as $tableIndex => $tableName ) { |
510 | $possibleReplacementTable = $tableName . '__NEXT'; |
511 | if ( $cdb->tableExists( $possibleReplacementTable, __METHOD__ ) ) { |
512 | unset( $tableNames[$tableIndex] ); |
513 | array_unshift( $tableNames, $tableName ); |
514 | } |
515 | } |
516 | } |
517 | |
518 | $text .= Html::rawElement( 'p', null, $this->msg( 'cargo-cargotables-tablelist' ) |
519 | ->numParams( count( $tableNames ) ) |
520 | ->escaped() ) . "\n"; |
521 | |
522 | $headerText = Html::element( 'th', null, $this->msg( "cargo-cargotables-header-table" ) ); |
523 | $headerText .= Html::element( 'th', null, |
524 | $this->msg( "cargo-cargotables-header-rowcount" ) ); |
525 | $headerText .= Html::element( 'th', [ 'class' => 'cargotables-columncount' ], |
526 | $this->msg( "cargo-cargotables-header-columncount" ) ); |
527 | |
528 | foreach ( $listOfColumns as $action ) { |
529 | $headerText .= Html::rawElement( 'th', null, $this->getActionIcon( $action ) ); |
530 | } |
531 | |
532 | $headerText .= Html::element( 'th', null, |
533 | $this->msg( "cargo-cargotables-header-templates" ) ); |
534 | |
535 | $wikitableText = Html::rawElement( 'tr', null, $headerText ); |
536 | |
537 | foreach ( $tableNames as $tableName ) { |
538 | |
539 | $tableLink = $this->getTableLinkedToView( $tableName, false ); |
540 | |
541 | $rowText = ""; |
542 | if ( !CargoUtils::tableFullyExists( $tableName ) ) { |
543 | continue; |
544 | } |
545 | |
546 | $possibleReplacementTable = $tableName . '__NEXT'; |
547 | $hasReplacementTable = CargoUtils::tableFullyExists( $possibleReplacementTable ); |
548 | $actionLinks = $this->getActionLinksForTable( $tableName, false, $hasReplacementTable ); |
549 | |
550 | $numRowsText = $this->displayNumRowsForTable( $cdb, $tableName ); |
551 | $numColumnsText = $this->displayNumColumnsForTable( $tableName ); |
552 | $templatesText = $this->tableTemplatesText( $tableName ); |
553 | |
554 | $rowText .= Html::rawElement( 'td', [ 'class' => 'cargo-tablelist-tablename' ], |
555 | $tableLink ); |
556 | $rowText .= Html::element( 'td', [ 'class' => 'cargo-tablelist-numrows' ], |
557 | $numRowsText ); |
558 | $rowText .= Html::element( 'td', [ 'class' => 'cargo-tablelist-numcolumns' ], |
559 | $numColumnsText ); |
560 | |
561 | $this->displayActionLinks( $listOfColumns, $actionLinks, $rowText ); |
562 | |
563 | if ( !$hasReplacementTable ) { |
564 | $rowText .= Html::rawElement( 'td', null, $templatesText ); |
565 | $wikitableText .= Html::rawElement( 'tr', null, $rowText ); |
566 | continue; |
567 | } |
568 | |
569 | // if there's a replacement table, the template links need to span 2 rows |
570 | $rowText .= Html::rawElement( 'td', [ 'rowspan' => 2 ], $templatesText ); |
571 | $wikitableText .= Html::rawElement( 'tr', null, $rowText ); |
572 | |
573 | $replacementRowText = ''; |
574 | $tableLink = $this->getTableLinkedToView( $tableName, true ); |
575 | |
576 | $numRowsText = $this->displayNumRowsForTable( $cdb, $tableName . '__NEXT' ); |
577 | $numColumnsText = $this->displayNumColumnsForTable( $tableName . '__NEXT' ); |
578 | $actionLinks = $this->getActionLinksForTable( $tableName, true, false ); |
579 | |
580 | $replacementRowText .= Html::rawElement( 'td', |
581 | [ 'class' => 'cargo-tablelist-tablename' ], $tableLink ); |
582 | $replacementRowText .= Html::element( 'td', [ 'class' => 'cargo-tablelist-numrows' ], |
583 | $numRowsText ); |
584 | $replacementRowText .= Html::element( 'td', [ 'class' => 'cargo-tablelist-numcolumns' ], |
585 | $numColumnsText ); |
586 | |
587 | $this->displayActionLinks( $listOfColumns, $actionLinks, $replacementRowText ); |
588 | |
589 | $wikitableText .= Html::rawElement( 'tr', |
590 | [ 'class' => 'cargo-tablelist-replacement-row' ], $replacementRowText ); |
591 | } |
592 | $text .= Html::rawElement( 'table', [ 'class' => 'mw-datatable cargo-tablelist' ], |
593 | $wikitableText ); |
594 | |
595 | // Now display the table for the special Cargo tables. |
596 | $text .= '<br />'; |
597 | $text .= Html::element( 'p', null, $this->msg( 'cargo-cargotables-specialtables' )->escaped() ); |
598 | $specialTableNames = CargoUtils::specialTableNames(); |
599 | $headerText = Html::element( 'th', null, $this->msg( "cargo-cargotables-header-table" ) ); |
600 | $headerText .= Html::element( 'th', null, |
601 | $this->msg( "cargo-cargotables-header-rowcount" )->escaped() ); |
602 | $headerText .= Html::element( 'th', [ 'class' => 'cargotables-columncount' ], |
603 | $this->msg( "cargo-cargotables-header-columncount" )->escaped() ); |
604 | |
605 | $invalidActionsForSpecialTables = [ 'edit' ]; |
606 | foreach ( $listOfColumns as $i => $action ) { |
607 | if ( in_array( $action, $invalidActionsForSpecialTables ) ) { |
608 | unset( $listOfColumns[$i] ); |
609 | continue; |
610 | } |
611 | $headerText .= Html::rawElement( 'th', null, $this->getActionIcon( $action ) ); |
612 | } |
613 | $wikitableText = Html::rawElement( 'tr', null, $headerText ); |
614 | |
615 | foreach ( $specialTableNames as $specialTableName ) { |
616 | $rowText = ''; |
617 | $tableExists = in_array( $specialTableName, $existingSpecialTables ); |
618 | if ( $tableExists ) { |
619 | $possibleReplacementTable = $specialTableName . '__NEXT'; |
620 | $hasReplacementTable = CargoUtils::tableFullyExists( $possibleReplacementTable ); |
621 | $actionLinks = $this->getActionLinksForTable( $specialTableName, false, $hasReplacementTable, true ); |
622 | } else { |
623 | $hasReplacementTable = false; |
624 | $actionLinks = []; |
625 | if ( array_key_exists( 'recreate', self::$actionList ) ) { |
626 | $recreateURL = |
627 | SpecialPage::getTitleFor( 'RecreateCargoData' )->getFullURL() . "/$specialTableName"; |
628 | $actionLinks['recreate'] = $this->getActionButton( 'create', $recreateURL ); |
629 | } |
630 | } |
631 | foreach ( $actionLinks as $action => $actionLink ) { |
632 | if ( in_array( $action, $invalidActionsForSpecialTables ) ) { |
633 | unset( $actionLinks[$action] ); |
634 | } |
635 | } |
636 | if ( $tableExists ) { |
637 | $tableLink = $this->getTableLinkedToView( $specialTableName, false ); |
638 | } else { |
639 | $tableLink = $specialTableName; |
640 | } |
641 | $rowText .= Html::rawElement( 'td', [ 'class' => 'cargo-tablelist-tablename' ], |
642 | $tableLink ); |
643 | if ( $tableExists ) { |
644 | $numRowsText = $this->displayNumRowsForTable( $cdb, $specialTableName ); |
645 | $numColumnsText = $this->displayNumColumnsForTable( $specialTableName ); |
646 | } else { |
647 | $numRowsText = ''; |
648 | $numColumnsText = ''; |
649 | } |
650 | $rowText .= Html::element( 'td', [ 'class' => 'cargo-tablelist-numrows' ], |
651 | $numRowsText ); |
652 | |
653 | $rowText .= Html::element( 'td', [ 'class' => 'cargo-tablelist-numcolumns' ], |
654 | $numColumnsText ); |
655 | |
656 | $this->displayActionLinks( $listOfColumns, $actionLinks, $rowText ); |
657 | $wikitableText .= Html::rawElement( 'tr', null, $rowText ); |
658 | |
659 | if ( !$hasReplacementTable ) { |
660 | continue; |
661 | } |
662 | |
663 | // if there's a replacement table, the template links need to span 2 rows |
664 | $replacementRowText = ''; |
665 | $tableLink = $this->getTableLinkedToView( $specialTableName, true ); |
666 | |
667 | $numRowsText = $this->displayNumRowsForTable( $cdb, $specialTableName . '__NEXT' ); |
668 | $numColumnsText = $this->displayNumColumnsForTable( $specialTableName . '__NEXT' ); |
669 | $actionLinks = $this->getActionLinksForTable( $specialTableName, true, false ); |
670 | |
671 | $replacementRowText .= Html::rawElement( 'td', |
672 | [ 'class' => 'cargo-tablelist-tablename' ], $tableLink ); |
673 | $replacementRowText .= Html::element( 'td', [ 'class' => 'cargo-tablelist-numrows' ], |
674 | $numRowsText ); |
675 | $replacementRowText .= Html::element( 'td', [ 'class' => 'cargo-tablelist-numcolumns' ], |
676 | $numColumnsText ); |
677 | |
678 | $this->displayActionLinks( $listOfColumns, $actionLinks, $replacementRowText ); |
679 | |
680 | $wikitableText .= Html::rawElement( 'tr', |
681 | [ 'class' => 'cargo-tablelist-replacement-row' ], $replacementRowText ); |
682 | } |
683 | |
684 | $text .= Html::rawElement( 'table', [ |
685 | 'class' => 'mw-datatable cargo-tablelist', |
686 | 'style' => 'min-width: auto;' |
687 | ], |
688 | $wikitableText ); |
689 | |
690 | return $text; |
691 | } |
692 | |
693 | private function displayActionLinks( $listOfColumns, $actionLinks, &$rowText ) { |
694 | foreach ( $listOfColumns as $action ) { |
695 | if ( array_key_exists( $action, $actionLinks ) ) { |
696 | $rowText .= Html::rawElement( 'td', [ "class" => "cargo-tablelist-actionbutton" ], |
697 | $actionLinks[$action] ); |
698 | } else { |
699 | $rowText .= Html::rawElement( 'td', null, '' ); |
700 | } |
701 | } |
702 | } |
703 | |
704 | protected function getGroupName() { |
705 | return 'cargo'; |
706 | } |
707 | } |