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