Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 432 |
|
0.00% |
0 / 16 |
CRAP | |
0.00% |
0 / 1 |
CargoExport | |
0.00% |
0 / 432 |
|
0.00% |
0 / 16 |
17556 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 73 |
|
0.00% |
0 / 1 |
506 | |||
displayCalendarData | |
0.00% |
0 / 65 |
|
0.00% |
0 / 1 |
420 | |||
displayGanttData | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getGanttJSONData | |
0.00% |
0 / 45 |
|
0.00% |
0 / 1 |
156 | |||
displayBPMNData | |
0.00% |
0 / 90 |
|
0.00% |
0 / 1 |
702 | |||
displayTimelineData | |
0.00% |
0 / 30 |
|
0.00% |
0 / 1 |
72 | |||
displayNVD3ChartData | |
0.00% |
0 / 38 |
|
0.00% |
0 / 1 |
132 | |||
parseWikitextInQueryResults | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
displayCSVData | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
56 | |||
displayExcelData | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
42 | |||
displayJSONData | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
72 | |||
displayBibtexData | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
displayIcalendarData | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
displayFeedData | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
outputFile | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | /** |
3 | * Displays the results of a Cargo query in one of several possible |
4 | * structured data formats - in some cases for use by an Ajax-based |
5 | * display format. |
6 | * |
7 | * @author Yaron Koren |
8 | * @ingroup Cargo |
9 | */ |
10 | |
11 | class CargoExport extends UnlistedSpecialPage { |
12 | |
13 | /** |
14 | * Constructor |
15 | */ |
16 | public function __construct() { |
17 | parent::__construct( 'CargoExport' ); |
18 | } |
19 | |
20 | public function execute( $query ) { |
21 | $this->getOutput()->disable(); |
22 | $req = $this->getRequest(); |
23 | |
24 | // If no value has been set for 'tables', or 'table', just |
25 | // display a blank screen. |
26 | $tableArray = $req->getArray( 'tables' ); |
27 | if ( $tableArray == null ) { |
28 | $tableArray = $req->getArray( 'table' ); |
29 | } |
30 | if ( $tableArray == null ) { |
31 | return; |
32 | } |
33 | $fieldsArray = $req->getArray( 'fields' ); |
34 | $whereArray = $req->getArray( 'where' ); |
35 | $joinOnArray = $req->getArray( 'join_on' ); |
36 | $groupByArray = $req->getArray( 'group_by' ); |
37 | $havingArray = $req->getArray( 'having' ); |
38 | $orderByArray = $req->getArray( 'order_by' ); |
39 | $limitArray = $req->getArray( 'limit' ); |
40 | $offsetArray = $req->getArray( 'offset' ); |
41 | |
42 | $sqlQueries = []; |
43 | foreach ( $tableArray as $i => $table ) { |
44 | $fields = $fieldsArray[$i] ?? null; |
45 | $where = $whereArray[$i] ?? null; |
46 | $joinOn = $joinOnArray[$i] ?? null; |
47 | $groupBy = $groupByArray[$i] ?? null; |
48 | $having = $havingArray[$i] ?? null; |
49 | $orderBy = $orderByArray[$i] ?? null; |
50 | $limit = $limitArray[$i] ?? null; |
51 | $offset = $offsetArray[$i] ?? null; |
52 | $sqlQueries[] = CargoSQLQuery::newFromValues( $table, |
53 | $fields, $where, $joinOn, $groupBy, $having, |
54 | $orderBy, $limit, $offset ); |
55 | } |
56 | |
57 | $format = $req->getVal( 'format' ); |
58 | |
59 | try { |
60 | if ( $format == 'fullcalendar' ) { |
61 | $this->displayCalendarData( $sqlQueries ); |
62 | } elseif ( $format == 'timeline' ) { |
63 | $this->displayTimelineData( $sqlQueries ); |
64 | } elseif ( $format == 'gantt' ) { |
65 | $this->displayGanttData( $sqlQueries ); |
66 | } elseif ( $format == 'bpmn' ) { |
67 | $this->displayBPMNData( $sqlQueries ); |
68 | } elseif ( $format == 'nvd3chart' ) { |
69 | $this->displayNVD3ChartData( $sqlQueries ); |
70 | } elseif ( $format == 'csv' ) { |
71 | $delimiter = $req->getVal( 'delimiter' ); |
72 | if ( $delimiter == '' ) { |
73 | $delimiter = ','; |
74 | } elseif ( $delimiter == '\t' ) { |
75 | $delimiter = "\t"; |
76 | } |
77 | $filename = $req->getVal( 'filename' ); |
78 | if ( $filename == '' ) { |
79 | $filename = 'results.csv'; |
80 | } |
81 | $parseValues = $req->getCheck( 'parse_values' ); |
82 | $this->displayCSVData( $sqlQueries, $delimiter, $filename, $parseValues ); |
83 | } elseif ( $format == 'excel' ) { |
84 | $filename = $req->getVal( 'filename' ); |
85 | if ( $filename == '' ) { |
86 | $filename = 'results.xlsx'; |
87 | } |
88 | $parseValues = $req->getCheck( 'parse_values' ); |
89 | $this->displayExcelData( $sqlQueries, $filename, $parseValues ); |
90 | } elseif ( $format == 'json' ) { |
91 | $parseValues = $req->getCheck( 'parse_values' ); |
92 | $this->displayJSONData( $sqlQueries, $parseValues ); |
93 | } elseif ( $format == 'bibtex' ) { |
94 | $defaultEntryType = $req->getVal( 'default_entry_type' ); |
95 | if ( $defaultEntryType == '' ) { |
96 | $defaultEntryType = 'article'; |
97 | } |
98 | $this->displayBibtexData( $sqlQueries, $defaultEntryType ); |
99 | } elseif ( $format === 'icalendar' ) { |
100 | $this->displayIcalendarData( $sqlQueries ); |
101 | } elseif ( $format === 'feed' ) { |
102 | $this->displayFeedData( $sqlQueries ); |
103 | } else { |
104 | // Let other extensions display the data if they have defined their own "deferred" |
105 | // formats. This is an unusual hook in that functions that use it have to return false; |
106 | // otherwise the error message will be displayed. |
107 | $result = $this->getHookContainer()->run( 'CargoDisplayExportData', [ $format, $sqlQueries, $req ] ); |
108 | if ( $result ) { |
109 | print $this->msg( "cargo-query-missingformat" )->parse(); |
110 | } |
111 | } |
112 | } catch ( Exception $e ) { |
113 | print $e->getMessage(); |
114 | } |
115 | } |
116 | |
117 | /** |
118 | * Used for calendar format |
119 | */ |
120 | private function displayCalendarData( $sqlQueries ) { |
121 | $req = $this->getRequest(); |
122 | |
123 | $colorArray = $req->getArray( 'color' ); |
124 | $textColorArray = $req->getArray( 'text_color' ); |
125 | |
126 | $datesLowerLimit = $req->getVal( 'start' ); |
127 | $datesUpperLimit = $req->getVal( 'end' ); |
128 | |
129 | $displayedArray = []; |
130 | foreach ( $sqlQueries as $i => $sqlQuery ) { |
131 | list( $startDateField, $endDateField ) = $sqlQuery->getMainStartAndEndDateFields(); |
132 | |
133 | $where = $sqlQuery->mWhereStr; |
134 | if ( $where != '' ) { |
135 | $where .= " AND "; |
136 | } |
137 | $where .= "("; |
138 | foreach ( $sqlQuery->mDateFieldPairs as $j => $dateFieldPair ) { |
139 | if ( $j > 0 ) { |
140 | $where .= " OR "; |
141 | } |
142 | $startDateFieldName = $dateFieldPair['start'][0]; |
143 | if ( array_key_exists( 'end', $dateFieldPair ) ) { |
144 | $endDateFieldName = $dateFieldPair['end'][0]; |
145 | } else { |
146 | $endDateFieldName = $startDateFieldName; |
147 | } |
148 | $where .= "($endDateFieldName > '$datesLowerLimit' AND $startDateFieldName < '$datesUpperLimit')"; |
149 | } |
150 | $where .= ")"; |
151 | $sqlQuery->mWhereStr = $where; |
152 | |
153 | $queryResults = $sqlQuery->run(); |
154 | |
155 | foreach ( $queryResults as $queryResult ) { |
156 | if ( array_key_exists( 'name', $queryResult ) ) { |
157 | $eventTitle = $queryResult['name']; |
158 | } else { |
159 | $eventTitle = reset( $queryResult ); |
160 | } |
161 | // The FullCalendar JS library will HTML-encode |
162 | // titles, so avoid a double-encoding. |
163 | $eventTitle = html_entity_decode( $eventTitle ); |
164 | if ( array_key_exists( 'color', $queryResult ) ) { |
165 | $eventColor = $queryResult['color']; |
166 | } elseif ( $colorArray != null && array_key_exists( $i, $colorArray ) ) { |
167 | $eventColor = $colorArray[$i]; |
168 | } else { |
169 | $eventColor = null; |
170 | } |
171 | if ( array_key_exists( 'text color', $queryResult ) ) { |
172 | $eventTextColor = $queryResult['text color']; |
173 | } elseif ( $textColorArray != null && array_key_exists( $i, $textColorArray ) ) { |
174 | $eventTextColor = $textColorArray[$i]; |
175 | } else { |
176 | $eventTextColor = null; |
177 | } |
178 | $eventStart = $queryResult[$startDateField]; |
179 | $eventEnd = ( $endDateField !== null ) ? $queryResult[$endDateField] : null; |
180 | if ( array_key_exists( 'description', $queryResult ) ) { |
181 | $eventDescription = $queryResult['description']; |
182 | } else { |
183 | $eventDescription = null; |
184 | } |
185 | |
186 | $startDatePrecisionField = $startDateField . '__precision'; |
187 | // There might not be a precision field, if, |
188 | // for instance, the date field is an SQL |
189 | // function. Ideally we would figure out |
190 | // the right precision, but for now just |
191 | // go with "DATE_ONLY" - seems safe. |
192 | if ( array_key_exists( $startDatePrecisionField, $queryResult ) ) { |
193 | $startDatePrecision = $queryResult[$startDatePrecisionField]; |
194 | } else { |
195 | $startDatePrecision = CargoStore::DATE_ONLY; |
196 | } |
197 | $curEvent = [ |
198 | // Get first field for the title - not |
199 | // necessarily the page name. |
200 | 'title' => $eventTitle, |
201 | 'start' => $eventStart, |
202 | 'end' => $eventEnd, |
203 | 'color' => $eventColor, |
204 | 'textColor' => $eventTextColor, |
205 | 'description' => $eventDescription |
206 | ]; |
207 | if ( array_key_exists( '_pageName', $queryResult ) ) { |
208 | $title = Title::newFromText( $queryResult['_pageName'] ); |
209 | $curEvent['url'] = $title->getLocalURL(); |
210 | } elseif ( array_key_exists( 'link', $queryResult ) ) { |
211 | $title = Title::newFromText( $queryResult['link'] ); |
212 | $curEvent['url'] = $title->getLocalURL(); |
213 | } |
214 | if ( $startDatePrecision != CargoStore::DATE_AND_TIME ) { |
215 | $curEvent['allDay'] = true; |
216 | } |
217 | $displayedArray[] = $curEvent; |
218 | } |
219 | } |
220 | |
221 | print json_encode( $displayedArray ); |
222 | } |
223 | |
224 | /** |
225 | * Used for gantt format |
226 | */ |
227 | private function displayGanttData( $sqlQueries ) { |
228 | print self::getGanttJSONData( $sqlQueries ); |
229 | } |
230 | |
231 | public static function getGanttJSONData( $sqlQueries ) { |
232 | $displayedArray['data'] = []; |
233 | $displayedArray['links'] = []; |
234 | foreach ( $sqlQueries as $sqlQuery ) { |
235 | list( $startDateField, $endDateField ) = $sqlQuery->getMainStartAndEndDateFields(); |
236 | |
237 | $queryResults = $sqlQuery->run(); |
238 | $n = 1; |
239 | foreach ( $queryResults as $queryResult ) { |
240 | if ( array_key_exists( 'name', $queryResult ) ) { |
241 | $eventTitle = $queryResult['name']; |
242 | } else { |
243 | $eventTitle = reset( $queryResult ); |
244 | } |
245 | if ( array_key_exists( '_pageID', $queryResult ) ) { |
246 | $eventID = $queryResult['_pageID']; |
247 | } else { |
248 | $eventID = $n; |
249 | $n++; |
250 | } |
251 | |
252 | if ( !isset( $queryResult[$startDateField] ) ) { |
253 | continue; |
254 | } |
255 | $eventStart = $queryResult[$startDateField]; |
256 | $eventEnd = ( $endDateField !== null && isset( $queryResult[$endDateField] ) ) ? $queryResult[$endDateField] : null; |
257 | if ( array_key_exists( 'duration', $queryResult ) ) { |
258 | $eventDuration = $queryResult['duration']; |
259 | } else { |
260 | $eventDuration = 1; |
261 | } |
262 | if ( array_key_exists( 'target', $queryResult ) ) { |
263 | $target = $queryResult['target']; |
264 | } else { |
265 | $target = null; |
266 | } |
267 | |
268 | $data = [ |
269 | 'id' => $eventID, |
270 | 'text' => $eventTitle, |
271 | 'start_date' => $eventStart, |
272 | 'end_date' => $eventEnd, |
273 | 'duration' => $eventDuration |
274 | ]; |
275 | $links = [ |
276 | 'id' => $eventID, |
277 | 'source' => $eventID, |
278 | 'target' => $target, |
279 | 'type' => "0" |
280 | ]; |
281 | array_push( $displayedArray['data'], $data ); |
282 | array_push( $displayedArray['links'], $links ); |
283 | } |
284 | for ( $t = 0; $t < count( $displayedArray['links'] ); $t++ ) { |
285 | if ( $displayedArray['links'][$t]['target'] != null ) { |
286 | $temp = $displayedArray['links'][$t]['target']; |
287 | $key = array_search( $temp, array_column( $displayedArray['data'], 'text' ) ); |
288 | $displayedArray['links'][$t]['target'] = $displayedArray['links'][$key]['id']; |
289 | } |
290 | } |
291 | } |
292 | return json_encode( $displayedArray ); |
293 | } |
294 | |
295 | /** |
296 | * Used for bpmn format |
297 | */ |
298 | private function displayBPMNData( $sqlQueries ) { |
299 | $sequenceFlows = []; |
300 | $elements = []; |
301 | $t = 1; |
302 | foreach ( $sqlQueries as $sqlQuery ) { |
303 | $queryResults = $sqlQuery->run(); |
304 | foreach ( $queryResults as $queryResult ) { |
305 | if ( array_key_exists( 'name', $queryResult ) ) { |
306 | $name = $queryResult['name']; |
307 | } else { |
308 | $name = reset( $queryResult ); |
309 | } |
310 | if ( array_key_exists( 'label', $queryResult ) ) { |
311 | $label = $queryResult['label']; |
312 | } else { |
313 | $label = ""; |
314 | } |
315 | if ( array_key_exists( 'type', $queryResult ) ) { |
316 | $eventType = $queryResult['type']; |
317 | } else { |
318 | continue; |
319 | } |
320 | if ( array_key_exists( 'sources', $queryResult ) ) { |
321 | $source = $queryResult['sources']; |
322 | } else { |
323 | $source = ""; |
324 | } |
325 | if ( array_key_exists( 'flowLabels', $queryResult ) ) { |
326 | $flowLabels = $queryResult['flowLabels']; |
327 | } else { |
328 | $flowLabels = ""; |
329 | } |
330 | if ( array_key_exists( 'linked', $queryResult ) ) { |
331 | $linkedpage = $queryResult['linked']; |
332 | } else { |
333 | $linkedpage = ""; |
334 | } |
335 | |
336 | $curEvent = [ |
337 | 'name' => $name, |
338 | 'label' => $label, |
339 | 'type' => $eventType, |
340 | 'source' => $source, |
341 | 'linkedpage' => $linkedpage, |
342 | 'flowLabels' => $flowLabels |
343 | ]; |
344 | |
345 | if ( str_contains( $curEvent['type'], 'Event' ) ) { |
346 | $curEvent['height'] = "36"; |
347 | $curEvent['width'] = "36"; |
348 | } elseif ( str_contains( $curEvent['type'], 'Gateway' ) ) { |
349 | $curEvent['height'] = "50"; |
350 | $curEvent['width'] = "50"; |
351 | } else { |
352 | $curEvent['height'] = "80"; |
353 | $curEvent['width'] = "100"; |
354 | } |
355 | $curEvent['id'] = $curEvent['type'] . $t; |
356 | $t++; |
357 | array_push( $elements, $curEvent ); |
358 | } |
359 | } |
360 | |
361 | header( 'Content-Type: text/xml' ); |
362 | $XML = '<?xml version="1.0" encoding="UTF-8"?>'; |
363 | // Needed to restore highlighting in vi - <? |
364 | $XML .= '<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" |
365 | xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" |
366 | xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" |
367 | id="Definitions_18vnora" |
368 | targetNamespace="http://bpmn.io/schema/bpmn" |
369 | exporter="bpmn-js (https://demo.bpmn.io)" |
370 | exporterVersion="4.0.3"> |
371 | <bpmn:process id="Process_1" isExecutable="false">'; |
372 | |
373 | // XML for BPMN Process |
374 | foreach ( $elements as $task ) { |
375 | if ( is_array( $task ) && $task['type'] != "" ) { |
376 | $XML .= '<bpmn:' . $task[ 'type' ] . ' id="' . $task['id']; |
377 | if ( $task['name'] != "" ) { |
378 | if ( $task['label'] != "" ) { |
379 | $XML .= '" name="' . $task['label']; |
380 | } else { |
381 | $XML .= '" name="' . $task['name']; |
382 | } |
383 | } |
384 | if ( $task[ 'linkedpage' ] != "" ) { |
385 | $XML .= ' [[' . $task['linkedpage'] . ']]'; |
386 | } |
387 | $XML .= '"></bpmn:' . $task['type'] . '>'; |
388 | } |
389 | } |
390 | foreach ( $elements as $element ) { |
391 | if ( !array_key_exists( 'source', $element ) ) { |
392 | continue; |
393 | } |
394 | $sources = explode( ", ", $element['source'] ); |
395 | $labels = explode( ", ", $element['flowLabels'] ); |
396 | if ( count( $sources ) == 1 ) { |
397 | $sourceElementName = $element['source']; |
398 | $key = array_search( $sourceElementName, array_column( $elements, 'name' ) ); |
399 | if ( $key === false ) { |
400 | continue; |
401 | } |
402 | $sequenceFlows[] = [ |
403 | 'type' => 'sequenceFlow', |
404 | 'source' => $elements[$key]['id'], |
405 | 'target' => $element['id'], |
406 | 'name' => $element['flowLabels'], |
407 | 'id' => 'sequenceFlow' . ( count( $sequenceFlows ) + 1 ) |
408 | ]; |
409 | } else { |
410 | foreach ( $sources as $sourceNum => $sourceElementName ) { |
411 | $key = array_search( $sourceElementName, array_column( $elements, 'name' ) ); |
412 | if ( $key === false ) { |
413 | continue; |
414 | } |
415 | $sequenceFlows[] = [ |
416 | 'type' => 'sequenceFlow', |
417 | 'source' => $elements[$key]['id'], |
418 | 'target' => $element['id'], |
419 | 'name' => $labels[$sourceNum], |
420 | 'id' => 'sequenceFlow' . ( count( $sequenceFlows ) + 1 ) |
421 | ]; |
422 | } |
423 | } |
424 | } |
425 | foreach ( $sequenceFlows as $task ) { |
426 | if ( is_array( $task ) && $task['type'] == "sequenceFlow" ) { |
427 | $XML .= '<bpmn:sequenceFlow id="' . $task['id'] . '" sourceRef="' . $task['source'] . '" targetRef="' . $task['target'] . '" name="' . $task['name'] . '"/>'; |
428 | } |
429 | } |
430 | $XML .= '</bpmn:process></bpmn:definitions>'; |
431 | print $XML; |
432 | } |
433 | |
434 | private function displayTimelineData( $sqlQueries ) { |
435 | $displayedArray = []; |
436 | foreach ( $sqlQueries as $sqlQuery ) { |
437 | list( $startDateField, $endDateField ) = $sqlQuery->getMainStartAndEndDateFields(); |
438 | |
439 | $queryResults = $sqlQuery->run(); |
440 | |
441 | foreach ( $queryResults as $queryResult ) { |
442 | $eventDescription = ''; |
443 | |
444 | if ( array_key_exists( 'name', $queryResult ) ) { |
445 | $eventTitle = $queryResult['name']; |
446 | } else { |
447 | // Get first field for the 'title' - not |
448 | // necessarily the page name. |
449 | $eventTitle = reset( $queryResult ); |
450 | } |
451 | |
452 | if ( !isset( $queryResult[$startDateField] ) ) { |
453 | continue; |
454 | } |
455 | $startDateValue = $queryResult[$startDateField]; |
456 | if ( $endDateField !== null && isset( $queryResult[$endDateField] ) ) { |
457 | $endDateValue = $queryResult[$endDateField]; |
458 | } else { |
459 | $endDateValue = $startDateValue; |
460 | } |
461 | |
462 | $eventDisplayDetails = [ |
463 | 'title' => $eventTitle, |
464 | 'description' => $eventDescription, |
465 | 'start' => $startDateValue, |
466 | 'end' => $endDateValue |
467 | ]; |
468 | |
469 | // If we have the name of the page on which |
470 | // the event is defined, link to that - |
471 | // otherwise, don't link to anything. |
472 | // (In most cases, the _pageName field will |
473 | // also be the title of the event.) |
474 | if ( array_key_exists( '_pageName', $queryResult ) ) { |
475 | $title = Title::newFromText( $queryResult['_pageName'] ); |
476 | $eventDisplayDetails['link'] = $title->getFullURL(); |
477 | } |
478 | $displayedArray[] = $eventDisplayDetails; |
479 | } |
480 | } |
481 | // Sort by date, ascending. |
482 | usort( $displayedArray, static function ( $a, $b ) { |
483 | return $a['start'] <=> $b['start']; |
484 | } ); |
485 | |
486 | $displayedArray = [ 'events' => $displayedArray ]; |
487 | print json_encode( $displayedArray, JSON_HEX_TAG | JSON_HEX_QUOT ); |
488 | } |
489 | |
490 | private function displayNVD3ChartData( $sqlQueries ) { |
491 | // We'll only use the first query, if there's more than one. |
492 | $sqlQuery = $sqlQueries[0]; |
493 | $queryResults = $sqlQuery->run(); |
494 | |
495 | // Handle date precision fields, which come alongside date fields. |
496 | foreach ( $queryResults as $i => $curRow ) { |
497 | foreach ( $curRow as $fieldName => $value ) { |
498 | if ( strpos( $fieldName, '__precision' ) == false ) { |
499 | continue; |
500 | } |
501 | $dateField = str_replace( '__precision', '', $fieldName ); |
502 | if ( !array_key_exists( $dateField, $curRow ) ) { |
503 | continue; |
504 | } |
505 | $origDateValue = $curRow[$dateField]; |
506 | // Years by themselves lead to a display |
507 | // problem, for some reason, so add a space. |
508 | $queryResults[$i][$dateField] = CargoQueryDisplayer::formatDateFieldValue( $origDateValue, $value, 'Date' ) . ' '; |
509 | unset( $queryResults[$i][$fieldName] ); |
510 | } |
511 | } |
512 | |
513 | // @TODO - this array needs to be longer. |
514 | $colorsArray = [ '#60BD68', '#FAA43A', '#5DA6DA', '#CC333F' ]; |
515 | |
516 | // Initialize everything, using the field names. |
517 | $firstRow = reset( $queryResults ); |
518 | $displayedArray = []; |
519 | $fieldNum = 0; |
520 | foreach ( $firstRow as $fieldName => $value ) { |
521 | if ( $fieldNum > 0 ) { |
522 | $curSeries = [ |
523 | 'key' => $fieldName, |
524 | 'color' => $colorsArray[$fieldNum - 1], |
525 | 'values' => [] |
526 | ]; |
527 | $displayedArray[] = $curSeries; |
528 | } |
529 | $fieldNum++; |
530 | } |
531 | |
532 | foreach ( $queryResults as $queryResult ) { |
533 | $fieldNum = 0; |
534 | foreach ( $queryResult as $value ) { |
535 | if ( $fieldNum == 0 ) { |
536 | $labelName = $value; |
537 | if ( trim( $value ) == '' ) { |
538 | // Display blank labels as "None". |
539 | $labelName = $this->msg( 'powersearch-togglenone' )->text(); |
540 | } |
541 | } else { |
542 | $displayedArray[$fieldNum - 1]['values'][] = [ |
543 | 'label' => $labelName, |
544 | 'value' => $value |
545 | ]; |
546 | } |
547 | $fieldNum++; |
548 | } |
549 | } |
550 | |
551 | print json_encode( $displayedArray, JSON_NUMERIC_CHECK | JSON_HEX_TAG ); |
552 | } |
553 | |
554 | /** |
555 | * Turn all wikitext into HTML in a set of query results. |
556 | */ |
557 | private function parseWikitextInQueryResults( $queryResults ) { |
558 | $parsedQueryResults = []; |
559 | foreach ( $queryResults as $rowNum => $rowValues ) { |
560 | $parsedQueryResults[$rowNum] = []; |
561 | foreach ( $rowValues as $colName => $value ) { |
562 | $parsedQueryResults[$rowNum][$colName] = CargoUtils::smartParse( $value, null ); |
563 | } |
564 | } |
565 | return $parsedQueryResults; |
566 | } |
567 | |
568 | private function displayCSVData( $sqlQueries, $delimiter, $filename, $parseValues ) { |
569 | header( "Content-Type: text/csv" ); |
570 | header( "Content-Disposition: attachment; filename=$filename" ); |
571 | |
572 | $queryResultsArray = []; |
573 | $allHeaders = []; |
574 | foreach ( $sqlQueries as $sqlQuery ) { |
575 | $queryResults = $sqlQuery->run(); |
576 | if ( $parseValues ) { |
577 | $queryResults = $this->parseWikitextInQueryResults( $queryResults ); |
578 | } |
579 | $allHeaders = array_merge( $allHeaders, array_keys( reset( $queryResults ) ) ); |
580 | $queryResultsArray[] = $queryResults; |
581 | } |
582 | |
583 | // Remove duplicates from headers array. |
584 | $allHeaders = array_unique( $allHeaders ); |
585 | |
586 | $out = fopen( 'php://output', 'w' ); |
587 | |
588 | // Display header row. |
589 | fputcsv( $out, $allHeaders, $delimiter ); |
590 | |
591 | // Display the data. |
592 | foreach ( $queryResultsArray as $queryResults ) { |
593 | foreach ( $queryResults as $queryResultRow ) { |
594 | // Put in a blank if this row doesn't contain |
595 | // a certain column (this will only happen |
596 | // for compound queries). |
597 | $displayedRow = []; |
598 | foreach ( $allHeaders as $header ) { |
599 | if ( array_key_exists( $header, $queryResultRow ) ) { |
600 | $displayedRow[$header] = $queryResultRow[$header]; |
601 | } else { |
602 | $displayedRow[$header] = null; |
603 | } |
604 | } |
605 | fputcsv( $out, $displayedRow, $delimiter ); |
606 | } |
607 | } |
608 | fclose( $out ); |
609 | } |
610 | |
611 | private function displayExcelData( $sqlQueries, $filename, $parseValues ) { |
612 | // We'll only use the first query, if there's more than one. |
613 | $sqlQuery = $sqlQueries[0]; |
614 | $queryResults = $sqlQuery->run(); |
615 | if ( $parseValues ) { |
616 | $queryResults = $this->parseWikitextInQueryResults( $queryResults ); |
617 | } |
618 | |
619 | if ( class_exists( 'PhpOffice\PhpSpreadsheet\Spreadsheet' ) ) { |
620 | $file = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); |
621 | } elseif ( class_exists( 'PHPExcel' ) ) { |
622 | $file = new PHPExcel(); |
623 | } else { |
624 | die( "Error: Either the PHPExcel or the PhpSpreadsheet library must be installed for this format to work." ); |
625 | } |
626 | $file->setActiveSheetIndex( 0 ); |
627 | |
628 | // Create array with header row and query results. |
629 | $header[] = array_keys( reset( $queryResults ) ); |
630 | $rows = array_merge( $header, $queryResults ); |
631 | |
632 | $file->getActiveSheet()->fromArray( $rows, null, 'A1' ); |
633 | header( "Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ); |
634 | header( "Content-Disposition: attachment;filename=$filename" ); |
635 | header( "Cache-Control: max-age=0" ); |
636 | |
637 | if ( class_exists( 'PhpOffice\PhpSpreadsheet\Spreadsheet' ) ) { |
638 | $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter( $file, 'Xlsx' ); |
639 | } elseif ( class_exists( 'PHPExcel' ) ) { |
640 | $writer = PHPExcel_IOFactory::createWriter( $file, 'Excel2007' ); |
641 | } |
642 | |
643 | $writer->save( 'php://output' ); |
644 | } |
645 | |
646 | private function displayJSONData( $sqlQueries, $parseValues ) { |
647 | $allQueryResults = []; |
648 | foreach ( $sqlQueries as $sqlQuery ) { |
649 | $queryResults = $sqlQuery->run(); |
650 | if ( $parseValues ) { |
651 | $queryResults = $this->parseWikitextInQueryResults( $queryResults ); |
652 | } |
653 | |
654 | // Turn "List" fields into arrays. |
655 | foreach ( $sqlQuery->mFieldDescriptions as $alias => $fieldDescription ) { |
656 | if ( $fieldDescription->mIsList ) { |
657 | $delimiter = $fieldDescription->getDelimiter(); |
658 | for ( $i = 0; $i < count( $queryResults ); $i++ ) { |
659 | $curValue = $queryResults[$i][$alias]; |
660 | if ( !is_array( $curValue ) ) { |
661 | $queryResults[$i][$alias] = explode( $delimiter, $curValue ); |
662 | } |
663 | } |
664 | } |
665 | } |
666 | |
667 | $allQueryResults = array_merge( $allQueryResults, $queryResults ); |
668 | } |
669 | |
670 | if ( $parseValues ) { |
671 | $jsonOptions = JSON_PRETTY_PRINT; |
672 | } else { |
673 | $jsonOptions = JSON_NUMERIC_CHECK | JSON_HEX_TAG | JSON_PRETTY_PRINT; |
674 | } |
675 | $json = json_encode( $allQueryResults, $jsonOptions ); |
676 | $this->outputFile( 'application/json', 'export', 'json', $json ); |
677 | } |
678 | |
679 | private function displayBibtexData( $sqlQueries, $defaultEntryType ) { |
680 | $text = ''; |
681 | foreach ( $sqlQueries as $sqlQuery ) { |
682 | $queryResults = $sqlQuery->run(); |
683 | $text .= CargoBibtexFormat::generateBibtexEntries( $queryResults, |
684 | $sqlQuery->mFieldDescriptions, |
685 | [ 'default entry type' => $defaultEntryType ] ); |
686 | } |
687 | $this->outputFile( 'text/plain', 'results.bib', 'bib', $text ); |
688 | } |
689 | |
690 | /** |
691 | * Output in the icalendar format. |
692 | * |
693 | * @param CargoSQLQuery[] $sqlQueries |
694 | */ |
695 | private function displayIcalendarData( $sqlQueries ) { |
696 | $req = $this->getRequest(); |
697 | $format = new CargoICalendarFormat( $this->getOutput() ); |
698 | $calendar = $format->getCalendar( $req, $sqlQueries ); |
699 | |
700 | $filename = $req->getText( 'filename', 'export.ics' ); |
701 | $this->outputFile( 'text/calendar', $filename, 'ics', $calendar ); |
702 | } |
703 | |
704 | /** |
705 | * Output an RSS or Atom feed. |
706 | * |
707 | * @param CargoSQLQuery[] $sqlQueries |
708 | */ |
709 | private function displayFeedData( $sqlQueries ) { |
710 | $format = new CargoFeedFormat( $this->getOutput() ); |
711 | $format->outputFeed( $this->getRequest(), $sqlQueries ); |
712 | } |
713 | |
714 | /** |
715 | * Output a file, with a normalized name and appropriate HTTP headers. |
716 | * |
717 | * @param string $contentType The MIME type of the file. |
718 | * @param string $filename The filename. It doesn't matter if it has the extension or not. |
719 | * @param string $fileExtension The file extension, without a leading dot. |
720 | * @param string $data The file contents. |
721 | * @param string $disposition Either 'inline' (the default) or 'attachment'. |
722 | */ |
723 | private function outputFile( $contentType, $filename, $fileExtension, $data, $disposition = 'inline' ) { |
724 | // Clean the filename and make sure it has the correct extension. |
725 | $filenameTitle = Title::newFromText( wfStripIllegalFilenameChars( $filename ) ); |
726 | $filename = $filenameTitle->getDBkey(); |
727 | if ( substr( $filename, -strlen( '.' . $fileExtension ) ) !== '.' . $fileExtension ) { |
728 | $filename .= '.' . $fileExtension; |
729 | } |
730 | |
731 | $disposition = in_array( $disposition, [ 'inline', 'attachment' ] ) ? $disposition : 'inline'; |
732 | header( 'Content-Type: ' . $contentType ); |
733 | header( 'Content-Disposition: ' . $disposition . ';filename=' . $filename ); |
734 | file_put_contents( 'php://output', $data ); |
735 | } |
736 | } |