Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 123
0.00% covered (danger)
0.00%
0 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
CargoPageValues
0.00% covered (danger)
0.00%
0 / 123
0.00% covered (danger)
0.00%
0 / 8
1406
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 58
0.00% covered (danger)
0.00%
0 / 1
240
 getTableLink
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 getInfoForAllFields
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
 getRowsForPageInTable
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
42
 printRow
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
 printTable
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 isListed
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Displays an interface to let users recreate data via the Cargo
4 * extension.
5 *
6 * @author Yaron Koren
7 * @ingroup Cargo
8 */
9
10class CargoPageValues extends IncludableSpecialPage {
11    public $mTitle;
12
13    public function __construct( $title = null ) {
14        parent::__construct( 'PageValues' );
15
16        $this->mTitle = $title;
17    }
18
19    public function execute( $subpage = null ) {
20        if ( $subpage ) {
21            // Allow inclusion with e.g. {{Special:PageValues/Book}}
22            $this->mTitle = Title::newFromText( $subpage );
23        }
24
25        // If no title, or a nonexistent title, was set, just exit out.
26        // @TODO - display an error message.
27        if ( $this->mTitle == null || !$this->mTitle->exists() ) {
28            return true;
29        }
30
31        $out = $this->getOutput();
32
33        $this->setHeaders();
34
35        $pageName = $this->mTitle->getPrefixedText();
36        $out->setPageTitle( $this->msg( 'cargo-pagevaluesfor', $pageName )->text() );
37
38        $text = '';
39
40        $dbr = CargoUtils::getMainDBForRead();
41
42        $tableNames = [];
43        // There is an exception check later on when we query for rows, so it's safe not to
44        // check for existence yet
45        $tableNames[] = '_pageData';
46        $tableNames[] = '_fileData';
47
48        $res = $dbr->select(
49            'cargo_pages', 'table_name',
50            [ 'page_id' => $this->mTitle->getArticleID() ]
51        );
52        foreach ( $res as $row ) {
53            $tableNames[] = $row->table_name;
54        }
55
56        $toc = Linker::tocIndent();
57        $tocLength = 0;
58
59        foreach ( $tableNames as $tableName ) {
60            try {
61                $queryResults = $this->getRowsForPageInTable( $tableName );
62            } catch ( Exception $e ) {
63                // Most likely this is because the _pageData
64                // table doesn't exist.
65                continue;
66            }
67            $numRowsOnPage = count( $queryResults );
68
69            // Hide _fileData if it's empty - we do this only for _fileData,
70            // as another table having 0 rows can indicate an error, and we'd
71            // like to preserve that information for debugging purposes.
72            if ( $numRowsOnPage === 0 && $tableName === '_fileData' ) {
73                continue;
74            }
75
76            $tableLink = $this->getTableLink( $tableName );
77
78            $tableSectionHeader = $this->msg( 'cargo-pagevalues-tablevalues', $tableLink )->text();
79            $tableSectionTocDisplay = $this->msg( 'cargo-pagevalues-tablevalues', $tableName )->escaped();
80            $tableSectionAnchor = $this->msg( 'cargo-pagevalues-tablevalues', $tableName )->escaped();
81            $tableSectionAnchor = Sanitizer::escapeIdForAttribute( $tableSectionAnchor );
82
83            // We construct the table of contents at the same time
84            // as the main text.
85            $toc .= Linker::tocLine( $tableSectionAnchor, $tableSectionTocDisplay,
86                $this->getLanguage()->formatNum( ++$tocLength ), 1 ) . Linker::tocLineEnd();
87
88            $h2 = Html::rawElement( 'h2', null,
89                Html::rawElement( 'span', [ 'class' => 'mw-headline', 'id' => $tableSectionAnchor ], $tableSectionHeader ) );
90
91            $text .= Html::rawElement( 'div', [ 'class' => 'cargo-pagevalues-tableinfo' ],
92                $h2 . $this->msg( "cargo-pagevalues-tableinfo-numrows", $numRowsOnPage )
93            );
94
95            foreach ( $queryResults as $rowValues ) {
96                $tableContents = '';
97                $fieldInfo = $this->getInfoForAllFields( $tableName );
98                $anyFieldHasAllowedValues = false;
99                foreach ( $fieldInfo as $info ) {
100                    if ( $info['allowed values'] !== '' ) {
101                        $anyFieldHasAllowedValues = true;
102                    }
103                }
104                foreach ( $rowValues as $field => $value ) {
105                    // @HACK - this check should ideally
106                    // be done earlier.
107                    if ( strpos( $field, '__precision' ) !== false ) {
108                        continue;
109                    }
110                    $tableContents .= $this->printRow( $field, $value, $fieldInfo[$field], $anyFieldHasAllowedValues );
111                }
112                $text .= $this->printTable( $tableContents, $anyFieldHasAllowedValues );
113            }
114        }
115
116        // Show table of contents only if there are enough sections.
117        if ( count( $tableNames ) >= 3 ) {
118            $toc = Linker::tocList( $toc );
119            $out->addHTML( $toc );
120        }
121
122        $out->addHTML( $text );
123        $out->addModules( 'ext.cargo.pagevalues' );
124
125        return true;
126    }
127
128    private function getTableLink( $tableName ) {
129        $originalTableName = str_replace( '__NEXT', '', $tableName );
130        $isReplacementTable = substr( $tableName, -6 ) == '__NEXT';
131        $viewURL = SpecialPage::getTitleFor( 'CargoTables' )->getFullURL() . "/$originalTableName";
132        if ( $isReplacementTable ) {
133            $viewURL .= strpos( $viewURL, '?' ) ? '&' : '?';
134            $viewURL .= "_replacement";
135        }
136
137        return Html::element( 'a', [ 'href' => $viewURL ], $tableName );
138    }
139
140    /**
141     * Used to get the information about field type and the list
142     * of allowed values (if any) of all fields of a table.
143     *
144     * @param string $tableName
145     */
146    private function getInfoForAllFields( $tableName ) {
147        $tableSchemas = CargoUtils::getTableSchemas( [ $tableName ] );
148        if ( $tableName == '_pageData' ) {
149            CargoUtils::addGlobalFieldsToSchema( $tableSchemas[$tableName] );
150        }
151        $fieldDescriptions = $tableSchemas[$tableName]->mFieldDescriptions;
152        $fieldInfo = [];
153        foreach ( $fieldDescriptions as $fieldName => $fieldDescription ) {
154            $fieldInfo[$fieldName]['field type'] = $fieldDescription->prettyPrintType();
155            if ( is_array( $fieldDescription->mAllowedValues ) ) {
156                $fieldInfo[$fieldName]['allowed values'] = implode( ' &middot; ', $fieldDescription->mAllowedValues );
157            } else {
158                $fieldInfo[$fieldName]['allowed values'] = '';
159            }
160        }
161        return $fieldInfo;
162    }
163
164    public function getRowsForPageInTable( $tableName ) {
165        $cdb = CargoUtils::getDB();
166
167        $sqlQuery = new CargoSQLQuery();
168        $sqlQuery->mAliasedTableNames = [ $tableName => $tableName ];
169
170        $tableSchemas = CargoUtils::getTableSchemas( [ $tableName ] );
171
172        if ( $tableName == '_pageData' ) {
173            CargoUtils::addGlobalFieldsToSchema( $tableSchemas[$tableName] );
174        }
175
176        $sqlQuery->mTableSchemas = $tableSchemas;
177
178        $aliasedFieldNames = [];
179        foreach ( $tableSchemas[$tableName]->mFieldDescriptions as $fieldName => $fieldDescription ) {
180            if ( $fieldDescription->mIsHidden ) {
181                // @TODO - do some custom formatting
182            }
183
184            // $fieldAlias = str_replace( '_', ' ', $fieldName );
185            $fieldAlias = $fieldName;
186
187            if ( $fieldDescription->mIsList ) {
188                $aliasedFieldNames[$fieldAlias] = $fieldName . '__full';
189            } elseif ( $fieldDescription->mType == 'Coordinates' ) {
190                $aliasedFieldNames[$fieldAlias] = $fieldName . '__full';
191            } else {
192                $aliasedFieldNames[$fieldAlias] = $fieldName;
193            }
194        }
195
196        $sqlQuery->mAliasedFieldNames = $aliasedFieldNames;
197        $sqlQuery->mOrigAliasedFieldNames = $aliasedFieldNames;
198        $sqlQuery->setDescriptionsAndTableNamesForFields();
199        $sqlQuery->handleDateFields();
200        $sqlQuery->mWhereStr = $cdb->addIdentifierQuotes( '_pageID' ) . " = " .
201            $this->mTitle->getArticleID();
202
203        $queryResults = $sqlQuery->run();
204        $queryDisplayer = CargoQueryDisplayer::newFromSQLQuery( $sqlQuery );
205        $formattedQueryResults = $queryDisplayer->getFormattedQueryResults( $queryResults );
206        return $formattedQueryResults;
207    }
208
209    /**
210     * Based on MediaWiki's InfoAction::addRow()
211     */
212    public function printRow( $name, $value, $fieldInfo, $fieldHasAnyAllowedValues ) {
213        if ( $name == '_fullText' && strlen( $value ) > 300 ) {
214            $value = substr( $value, 0, 300 ) . ' ...';
215        }
216        $text = Html::element( 'td', [ 'class' => 'cargo-pagevalues-table-field' ], $name ) .
217            Html::rawElement( 'td', [ 'class' => 'cargo-pagevalues-table-type' ], $fieldInfo['field type'] );
218        if ( $fieldHasAnyAllowedValues ) {
219            $allowedValuesText = $fieldInfo['allowed values'];
220            // Count "middot" as only one character, not eight, when counting the string length.
221            $allowedValuesDisplayText = str_replace( '&middot;', '.', $allowedValuesText );
222            if ( strlen( $allowedValuesDisplayText ) > 25 ) {
223                $allowedValuesText = '<span class="cargoMinimizedText">' . $fieldInfo['allowed values'] . '</span>';
224            }
225            $text .= Html::rawElement( 'td', [ 'class' => 'cargo-pagevalues-table-allowedvalues' ], $allowedValuesText );
226        }
227        $text .= Html::rawElement( 'td', [ 'class' => 'cargo-pagevalues-table-value' ], $value );
228
229        return Html::rawElement( 'tr', [], $text );
230    }
231
232    /**
233     * Based on MediaWiki's InfoAction::addTable()
234     */
235    public function printTable( $tableContents, $anyFieldHasAllowedValues ) {
236        $headerRow = '<tr><th>Field</th><th>' . $this->msg( 'cargo-field-type' )->text() . '</th>';
237        if ( $anyFieldHasAllowedValues ) {
238            $headerRow .= '<th>' . $this->msg( 'cargo-allowed-values' )->text() . '</th>';
239        }
240        $headerRow .= '<th>Value</th></tr>';
241        return Html::rawElement( 'table', [ 'class' => 'wikitable mw-page-info' ],
242            $headerRow . $tableContents ) . "\n";
243    }
244
245    /**
246     * Don't list this in Special:SpecialPages.
247     */
248    public function isListed() {
249        return false;
250    }
251}