Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
73.27% |
74 / 101 |
|
50.00% |
1 / 2 |
CRAP | |
0.00% |
0 / 1 |
CargoQuery | |
73.27% |
74 / 101 |
|
50.00% |
1 / 2 |
58.40 | |
0.00% |
0 / 1 |
run | |
72.45% |
71 / 98 |
|
0.00% |
0 / 1 |
55.77 | |||
setBacklinks | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * CargoQuery - class for the #cargo_query parser function. |
4 | * |
5 | * @author Yaron Koren |
6 | * @ingroup Cargo |
7 | */ |
8 | |
9 | class CargoQuery { |
10 | |
11 | /** |
12 | * Handles the #cargo_query parser function - calls a query on the |
13 | * Cargo data stored in the database. |
14 | * |
15 | * @param Parser $parser |
16 | * @return string|array Error message string, or an array holding output text and format flags |
17 | */ |
18 | public static function run( $parser ) { |
19 | global $wgCargoIgnoreBacklinks; |
20 | |
21 | $params = func_get_args(); |
22 | array_shift( $params ); // we already know the $parser... |
23 | |
24 | $tablesStr = null; |
25 | $fieldsStr = null; |
26 | $whereStr = null; |
27 | $joinOnStr = null; |
28 | $groupByStr = null; |
29 | $havingStr = null; |
30 | $orderByStr = null; |
31 | $limitStr = null; |
32 | $offsetStr = null; |
33 | $noHTML = false; |
34 | $format = 'auto'; // default |
35 | $displayParams = []; |
36 | |
37 | foreach ( $params as $param ) { |
38 | $parts = explode( '=', $param, 2 ); |
39 | |
40 | if ( count( $parts ) == 1 ) { |
41 | if ( $param == 'no html' ) { |
42 | $noHTML = true; |
43 | } |
44 | continue; |
45 | } |
46 | if ( count( $parts ) > 2 ) { |
47 | continue; |
48 | } |
49 | $key = trim( $parts[0] ); |
50 | $value = trim( $parts[1] ); |
51 | if ( $key == 'tables' || $key == 'table' ) { |
52 | $tablesStr = $value; |
53 | } elseif ( $key == 'fields' ) { |
54 | $fieldsStr = $value; |
55 | } elseif ( $key == 'where' ) { |
56 | $whereStr = $value; |
57 | } elseif ( $key == 'join on' ) { |
58 | $joinOnStr = $value; |
59 | } elseif ( $key == 'group by' ) { |
60 | $groupByStr = $value; |
61 | } elseif ( $key == 'having' ) { |
62 | $havingStr = $value; |
63 | } elseif ( $key == 'order by' ) { |
64 | $orderByStr = $value; |
65 | } elseif ( $key == 'limit' ) { |
66 | $limitStr = $value; |
67 | } elseif ( $key == 'offset' ) { |
68 | $offsetStr = $value; |
69 | } elseif ( $key == 'format' ) { |
70 | $format = $value; |
71 | } else { |
72 | // We'll assume it's going to the formatter. |
73 | $displayParams[$key] = $value; |
74 | } |
75 | } |
76 | // Special handling. |
77 | if ( $format == 'dynamic table' && $orderByStr != null ) { |
78 | $displayParams['order by'] = $orderByStr; |
79 | } |
80 | |
81 | try { |
82 | $sqlQuery = CargoSQLQuery::newFromValues( $tablesStr, $fieldsStr, $whereStr, $joinOnStr, |
83 | $groupByStr, $havingStr, $orderByStr, $limitStr, $offsetStr ); |
84 | // If this is a non-grouped query, make a 2nd query just |
85 | // for _pageID (since the original query won't always |
86 | // have a _pageID field) in order to populate the |
87 | // cargo_backlinks table. |
88 | // Also remove the limit from this 2nd query so that it |
89 | // can include all results. |
90 | // Fetch results title only if "cargo_backlinks" table exists |
91 | $dbr = CargoUtils::getMainDBForRead(); |
92 | if ( !$wgCargoIgnoreBacklinks && !$sqlQuery->isAggregating() && $dbr->tableExists( 'cargo_backlinks' ) ) { |
93 | $newFieldsStr = $fieldsStr; |
94 | // $fieldsToCollectForPageIDs allows us to |
95 | // collect all those special fields' values in |
96 | // the results |
97 | $fieldsToCollectForPageIDs = []; |
98 | foreach ( $sqlQuery->mAliasedTableNames as $alias => $table ) { |
99 | // Ignore helper tables. |
100 | if ( strpos( $table, '__' ) !== false ) { |
101 | continue; |
102 | } |
103 | $fieldFullName = "cargo_backlink_page_id_$alias"; |
104 | $fieldsToCollectForPageIDs[] = $fieldFullName; |
105 | $newFieldsStr = "$alias._pageID=$fieldFullName, " . $newFieldsStr; |
106 | } |
107 | $sqlQueryJustForResultsTitle = CargoSQLQuery::newFromValues( |
108 | $tablesStr, $newFieldsStr, $whereStr, $joinOnStr, |
109 | $groupByStr, $havingStr, $orderByStr, '', $offsetStr |
110 | ); |
111 | $queryResultsJustForResultsTitle = $sqlQueryJustForResultsTitle->run(); |
112 | } |
113 | } catch ( Exception $e ) { |
114 | return CargoUtils::formatError( $e->getMessage() ); |
115 | } |
116 | |
117 | $pageIDsForBacklinks = []; |
118 | if ( isset( $queryResultsJustForResultsTitle ) ) { |
119 | // Collect all special _pageID entries. |
120 | foreach ( $fieldsToCollectForPageIDs as $fieldToCollectForPageIds ) { |
121 | $pageIDsForBacklinks = array_merge( $pageIDsForBacklinks, array_column( $queryResultsJustForResultsTitle, $fieldToCollectForPageIds ) ); |
122 | } |
123 | $pageIDsForBacklinks = array_unique( $pageIDsForBacklinks ); |
124 | } |
125 | |
126 | $queryDisplayer = CargoQueryDisplayer::newFromSQLQuery( $sqlQuery ); |
127 | $queryDisplayer->mFormat = $format; |
128 | $queryDisplayer->mDisplayParams = $displayParams; |
129 | $queryDisplayer->mParser = $parser; |
130 | $formatter = $queryDisplayer->getFormatter( $parser->getOutput(), $parser ); |
131 | |
132 | // Let the format run the query itself, if it wants to. |
133 | if ( $formatter->isDeferred() ) { |
134 | // @TODO - fix this inefficiency. Right now a |
135 | // CargoSQLQuery object is constructed three times for |
136 | // deferred formats: the first two times here and the |
137 | // 3rd by Special:CargoExport. It's the first |
138 | // construction that involves a bunch of text |
139 | // processing, and is unneeded. |
140 | // However, this first CargoSQLQuery is passed to |
141 | // the CargoQueryDisplayer, which in turn uses it |
142 | // to figure out the formatting class, so that we |
143 | // know whether it is a deferred class or not. The |
144 | // class is based in part on the set of fields in the |
145 | // query, so in theory (though not in practice), |
146 | // whether or not it's deferred could depend on the |
147 | // fields in the query, making the first 'Query |
148 | // necessary. There has to be some better way, though. |
149 | $sqlQuery = CargoSQLQuery::newFromValues( $tablesStr, $fieldsStr, $whereStr, $joinOnStr, |
150 | $groupByStr, $havingStr, $orderByStr, $limitStr, $offsetStr ); |
151 | $text = $formatter->queryAndDisplay( [ $sqlQuery ], $displayParams ); |
152 | self::setBackLinks( $parser, $pageIDsForBacklinks ); |
153 | return [ $text, 'noparse' => true, 'isHTML' => true ]; |
154 | } |
155 | |
156 | // If the query limit was set to 0, no need to run the query - |
157 | // all we need to do is show the "more results" link, then exit. |
158 | if ( $sqlQuery->mQueryLimit == 0 ) { |
159 | $text = $queryDisplayer->viewMoreResultsLink( true ); |
160 | return [ $text, 'noparse' => true, 'isHTML' => true ]; |
161 | } |
162 | |
163 | try { |
164 | $queryResults = $sqlQuery->run(); |
165 | } catch ( Exception $e ) { |
166 | return CargoUtils::formatError( $e->getMessage() ); |
167 | } |
168 | |
169 | // Finally, do the display. |
170 | $text = $queryDisplayer->displayQueryResults( $formatter, $queryResults ); |
171 | // If there are no results, then - given that we already know |
172 | // that the limit was not set to 0 - we just need to display an |
173 | // automatic message, so there's no need for special parsing. |
174 | if ( count( $queryResults ) == 0 ) { |
175 | return $text; |
176 | } |
177 | // No errors? Let's save our reverse links. |
178 | self::setBackLinks( $parser, $pageIDsForBacklinks ); |
179 | |
180 | // The 'template' format gets special parsing, because |
181 | // it can be used to display a larger component, like a table, |
182 | // which means that everything needs to be parsed together |
183 | // instead of one instance at a time. Also, the template will |
184 | // contain wikitext, not HTML. |
185 | $displayHTML = ( !$noHTML && $format != 'template' ); |
186 | |
187 | // If there are (seemingly) more results than what we showed, |
188 | // show a "View more" link that links to Special:ViewData. |
189 | if ( count( $queryResults ) == $sqlQuery->mQueryLimit ) { |
190 | $text .= $queryDisplayer->viewMoreResultsLink( $displayHTML ); |
191 | } |
192 | |
193 | if ( $displayHTML ) { |
194 | return [ $text, 'noparse' => true, 'isHTML' => true ]; |
195 | } else { |
196 | return [ $text, 'noparse' => false ]; |
197 | } |
198 | } |
199 | |
200 | /** |
201 | * Store the list of page IDs referenced by this query in the parser output. |
202 | * @param Parser $parser |
203 | * @param int[] $backlinkPageIds List of referenced page IDs to store. |
204 | */ |
205 | private static function setBacklinks( Parser $parser, array $backlinkPageIds ): void { |
206 | $parserOutput = $parser->getOutput(); |
207 | |
208 | foreach ( $backlinkPageIds as $pageId ) { |
209 | $parserOutput->appendExtensionData( CargoBackLinks::BACKLINKS_DATA_KEY, $pageId ); |
210 | } |
211 | } |
212 | |
213 | } |