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