Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
71.84% |
74 / 103 |
|
50.00% |
1 / 2 |
CRAP | |
0.00% |
0 / 1 |
CargoQuery | |
71.84% |
74 / 103 |
|
50.00% |
1 / 2 |
70.23 | |
0.00% |
0 / 1 |
run | |
71.00% |
71 / 100 |
|
0.00% |
0 / 1 |
67.61 | |||
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 | } elseif ( $key == 'intro' || $key == 'outro' || $key == 'default' ) { |
72 | $displayParams[$key] = $value; |
73 | } else { |
74 | // We'll assume it's going to the formatter. |
75 | $displayParams[$key] = htmlspecialchars( $value ); |
76 | } |
77 | } |
78 | // Special handling. |
79 | if ( $format == 'dynamic table' && $orderByStr != null ) { |
80 | $displayParams['order by'] = $orderByStr; |
81 | } |
82 | |
83 | try { |
84 | $sqlQuery = CargoSQLQuery::newFromValues( $tablesStr, $fieldsStr, $whereStr, $joinOnStr, |
85 | $groupByStr, $havingStr, $orderByStr, $limitStr, $offsetStr ); |
86 | // If this is a non-grouped query, make a 2nd query just |
87 | // for _pageID (since the original query won't always |
88 | // have a _pageID field) in order to populate the |
89 | // cargo_backlinks table. |
90 | // Also remove the limit from this 2nd query so that it |
91 | // can include all results. |
92 | // Fetch results title only if "cargo_backlinks" table exists |
93 | $dbr = CargoUtils::getMainDBForRead(); |
94 | if ( !$wgCargoIgnoreBacklinks && !$sqlQuery->isAggregating() && $dbr->tableExists( 'cargo_backlinks', __METHOD__ ) ) { |
95 | $newFieldsStr = $fieldsStr; |
96 | // $fieldsToCollectForPageIDs allows us to |
97 | // collect all those special fields' values in |
98 | // the results |
99 | $fieldsToCollectForPageIDs = []; |
100 | foreach ( $sqlQuery->mAliasedTableNames as $alias => $table ) { |
101 | // Ignore helper tables. |
102 | if ( strpos( $table, '__' ) !== false ) { |
103 | continue; |
104 | } |
105 | $fieldFullName = "cargo_backlink_page_id_$alias"; |
106 | $fieldsToCollectForPageIDs[] = $fieldFullName; |
107 | $newFieldsStr = "$alias._pageID=$fieldFullName, " . $newFieldsStr; |
108 | } |
109 | $sqlQueryJustForResultsTitle = CargoSQLQuery::newFromValues( |
110 | $tablesStr, $newFieldsStr, $whereStr, $joinOnStr, |
111 | $groupByStr, $havingStr, $orderByStr, '', $offsetStr |
112 | ); |
113 | $queryResultsJustForResultsTitle = $sqlQueryJustForResultsTitle->run(); |
114 | } |
115 | } catch ( Exception $e ) { |
116 | return CargoUtils::formatError( $e->getMessage() ); |
117 | } |
118 | |
119 | $pageIDsForBacklinks = []; |
120 | if ( isset( $queryResultsJustForResultsTitle ) ) { |
121 | // Collect all special _pageID entries. |
122 | foreach ( $fieldsToCollectForPageIDs as $fieldToCollectForPageIds ) { |
123 | $pageIDsForBacklinks = array_merge( $pageIDsForBacklinks, array_column( $queryResultsJustForResultsTitle, $fieldToCollectForPageIds ) ); |
124 | } |
125 | $pageIDsForBacklinks = array_unique( $pageIDsForBacklinks ); |
126 | } |
127 | |
128 | $queryDisplayer = CargoQueryDisplayer::newFromSQLQuery( $sqlQuery ); |
129 | $queryDisplayer->mFormat = $format; |
130 | $queryDisplayer->mDisplayParams = $displayParams; |
131 | $queryDisplayer->mParser = $parser; |
132 | $formatter = $queryDisplayer->getFormatter( $parser->getOutput(), $parser ); |
133 | |
134 | // Let the format run the query itself, if it wants to. |
135 | if ( $formatter->isDeferred() ) { |
136 | // @TODO - fix this inefficiency. Right now a |
137 | // CargoSQLQuery object is constructed three times for |
138 | // deferred formats: the first two times here and the |
139 | // 3rd by Special:CargoExport. It's the first |
140 | // construction that involves a bunch of text |
141 | // processing, and is unneeded. |
142 | // However, this first CargoSQLQuery is passed to |
143 | // the CargoQueryDisplayer, which in turn uses it |
144 | // to figure out the formatting class, so that we |
145 | // know whether it is a deferred class or not. The |
146 | // class is based in part on the set of fields in the |
147 | // query, so in theory (though not in practice), |
148 | // whether or not it's deferred could depend on the |
149 | // fields in the query, making the first 'Query |
150 | // necessary. There has to be some better way, though. |
151 | $sqlQuery = CargoSQLQuery::newFromValues( $tablesStr, $fieldsStr, $whereStr, $joinOnStr, |
152 | $groupByStr, $havingStr, $orderByStr, $limitStr, $offsetStr ); |
153 | $text = $formatter->queryAndDisplay( [ $sqlQuery ], $displayParams ); |
154 | self::setBackLinks( $parser, $pageIDsForBacklinks ); |
155 | return [ $text, 'noparse' => true, 'isHTML' => true ]; |
156 | } |
157 | |
158 | // If the query limit was set to 0, no need to run the query - |
159 | // all we need to do is show the "more results" link, then exit. |
160 | if ( $sqlQuery->mQueryLimit == 0 ) { |
161 | $text = $queryDisplayer->viewMoreResultsLink( true ); |
162 | return [ $text, 'noparse' => true, 'isHTML' => true ]; |
163 | } |
164 | |
165 | try { |
166 | $queryResults = $sqlQuery->run(); |
167 | } catch ( Exception $e ) { |
168 | return CargoUtils::formatError( $e->getMessage() ); |
169 | } |
170 | |
171 | // Finally, do the display. |
172 | $text = $queryDisplayer->displayQueryResults( $formatter, $queryResults ); |
173 | // If there are no results, then - given that we already know |
174 | // that the limit was not set to 0 - we just need to display an |
175 | // automatic message, so there's no need for special parsing. |
176 | if ( count( $queryResults ) == 0 ) { |
177 | return $text; |
178 | } |
179 | // No errors? Let's save our reverse links. |
180 | self::setBackLinks( $parser, $pageIDsForBacklinks ); |
181 | |
182 | // The 'template' format gets special parsing, because |
183 | // it can be used to display a larger component, like a table, |
184 | // which means that everything needs to be parsed together |
185 | // instead of one instance at a time. Also, the template will |
186 | // contain wikitext, not HTML. |
187 | $displayHTML = ( !$noHTML && $format != 'template' ); |
188 | |
189 | // If there are (seemingly) more results than what we showed, |
190 | // show a "View more" link that links to Special:ViewData. |
191 | if ( count( $queryResults ) == $sqlQuery->mQueryLimit ) { |
192 | $text .= $queryDisplayer->viewMoreResultsLink( $displayHTML ); |
193 | } |
194 | |
195 | if ( $displayHTML ) { |
196 | return [ $text, 'noparse' => true, 'isHTML' => true ]; |
197 | } else { |
198 | return [ $text, 'noparse' => false ]; |
199 | } |
200 | } |
201 | |
202 | /** |
203 | * Store the list of page IDs referenced by this query in the parser output. |
204 | * @param Parser $parser |
205 | * @param int[] $backlinkPageIds List of referenced page IDs to store. |
206 | */ |
207 | private static function setBacklinks( Parser $parser, array $backlinkPageIds ): void { |
208 | $parserOutput = $parser->getOutput(); |
209 | |
210 | foreach ( $backlinkPageIds as $pageId ) { |
211 | $parserOutput->appendExtensionData( CargoBackLinks::BACKLINKS_DATA_KEY, $pageId ); |
212 | } |
213 | } |
214 | |
215 | } |