Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 167 |
|
0.00% |
0 / 12 |
CRAP | |
0.00% |
0 / 1 |
CargoExhibitFormat | |
0.00% |
0 / 167 |
|
0.00% |
0 / 12 |
2450 | |
0.00% |
0 / 1 |
allowedParameters | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
prependDot | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
createMap | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
20 | |||
createTimeline | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
72 | |||
createTabular | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
12 | |||
createFacets | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
6 | |||
createSearch | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
createLens | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
30 | |||
queryAndDisplay | |
0.00% |
0 / 52 |
|
0.00% |
0 / 1 |
156 | |||
automateViews | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
12 | |||
getCoordinatesFields | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
20 | |||
getDateFields | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
30 |
1 | <?php |
2 | /** |
3 | * Adds Exhibit format to Cargo queries. |
4 | * |
5 | * @author @lmorillas |
6 | */ |
7 | |
8 | class CargoExhibitFormat extends CargoDeferredFormat { |
9 | /** @var array */ |
10 | private $displayParams; |
11 | /** @var array */ |
12 | private $views; |
13 | |
14 | public static function allowedParameters() { |
15 | return [ |
16 | 'view' => [ 'values' => [ 'map', 'tabular', 'timeline' ] ], |
17 | 'facets' => [ 'type' => 'string' ], |
18 | 'datalabel' => [ 'type' => 'string' ], |
19 | 'end' => [ 'type' => 'string' ], |
20 | 'color' => [ 'type' => 'string' ], |
21 | 'topunit' => [ 'values' => [ 'millisecond', 'second', 'minute', 'hour', 'day', 'week', 'month', 'year', 'decade', 'century', 'millennium' ] ], |
22 | 'toppx' => [ 'type' => 'int' ], |
23 | 'bottompx' => [ 'type' => 'int' ] |
24 | ]; |
25 | } |
26 | |
27 | /** |
28 | * @param string $p |
29 | * @return string |
30 | */ |
31 | private function prependDot( $p ) { |
32 | return '.' . trim( $p ); |
33 | } |
34 | |
35 | private function createMap( $sqlQueries ) { |
36 | $maps_script = '<link rel="exhibit-extension" href="//api.simile-widgets.org/exhibit/HEAD/extensions/map/map-extension.js"/>'; |
37 | $this->mOutput->addHeadItem( $maps_script, $maps_script ); |
38 | |
39 | if ( !array_key_exists( "latlng", $this->displayParams ) ) { |
40 | $coordFields = $this->getCoordinatesFields( $sqlQueries ); |
41 | if ( count( $coordFields ) > 0 ) { |
42 | $this->displayParams['latlng'] = $coordFields[0]; |
43 | } |
44 | } |
45 | |
46 | $attrs = [ |
47 | 'data-ex-role' => 'view', |
48 | 'data-ex-view-class' => "Map", |
49 | 'data-ex-latlng' => $this->prependDot( $this->displayParams['latlng'] ), |
50 | 'data-ex-autoposition' => "true", |
51 | ]; |
52 | |
53 | if ( array_key_exists( "color", $this->displayParams ) ) { |
54 | $attrs["data-ex-color-key"] = $this->prependDot( $this->displayParams['color'] ); |
55 | } |
56 | |
57 | return Html::element( 'div', $attrs ); |
58 | } |
59 | |
60 | private function createTimeline( $sqlQueries ) { |
61 | $timeline_script = '<link rel="exhibit-extension" href="//api.simile-widgets.org/exhibit/HEAD/extensions/time/time-extension.js"/>'; |
62 | $this->mOutput->addHeadItem( $timeline_script, $timeline_script ); |
63 | |
64 | // div |
65 | $attrs = []; |
66 | $attrs['data-ex-role'] = 'view'; |
67 | $attrs["data-ex-view-class"] = "Timeline"; |
68 | |
69 | if ( !array_key_exists( "start", $this->displayParams ) ) { |
70 | $dateFields = $this->getDateFields( $sqlQueries ); |
71 | if ( count( $dateFields ) > 0 ) { |
72 | $this->displayParams['start'] = $dateFields[0]; |
73 | } |
74 | } |
75 | |
76 | $attrs["data-ex-start"] = $this->prependDot( $this->displayParams['start'] ); |
77 | |
78 | if ( array_key_exists( "end", $this->displayParams ) ) { |
79 | $attrs["data-ex-end"] = $this->prependDot( $this->displayParams['end'] ); |
80 | } |
81 | if ( array_key_exists( "color", $this->displayParams ) ) { |
82 | $attrs["data-ex-color-key"] = $this->prependDot( $this->displayParams['color'] ); |
83 | } |
84 | if ( array_key_exists( "topunit", $this->displayParams ) ) { |
85 | $attrs["data-ex-top-band-unit"] = $this->displayParams['topunit']; |
86 | } |
87 | if ( array_key_exists( "toppx", $this->displayParams ) ) { |
88 | $attrs["data-ex-top-band-pixels-per-unit"] = $this->displayParams['toppx']; |
89 | } |
90 | if ( array_key_exists( "bottompx", $this->displayParams ) ) { |
91 | $attrs["data-ex-bottom-band-pixels-per-unit"] = $this->displayParams['bottompx']; |
92 | } |
93 | return Html::element( 'div', $attrs ); |
94 | } |
95 | |
96 | /** |
97 | * @param string[] $fieldList |
98 | * @return string HTML |
99 | */ |
100 | private function createTabular( $fieldList ) { |
101 | $columnsList = []; |
102 | foreach ( $fieldList as $field ) { |
103 | if ( strpos( $field, '__' ) == false ) { |
104 | $columnsList[] = $field; |
105 | } |
106 | } |
107 | |
108 | $attrs = [ |
109 | 'data-ex-role' => 'view', |
110 | 'data-ex-view-class' => 'Tabular', |
111 | 'data-ex-paginate' => "true", |
112 | 'data-ex-table-styler' => "tableStyler", |
113 | |
114 | 'data-ex-columns' => implode( ',', |
115 | array_map( "CargoExhibitFormat::prependDot", $columnsList ) ), |
116 | |
117 | 'data-ex-column-labels' => implode( ',', array_map( "ucfirst", $columnsList ) ) |
118 | ]; |
119 | |
120 | return Html::element( 'div', $attrs ); |
121 | } |
122 | |
123 | private function createFacets( $facets ) { |
124 | // Explode facets and create the div for each of them. |
125 | $text = $this->createSearch(); |
126 | foreach ( $facets as $f ) { |
127 | $attrs = [ |
128 | 'data-ex-role' => "facet", |
129 | 'data-ex-collapsible' => "true", |
130 | 'data-ex-expression' => '.' . $f, |
131 | 'data-ex-show-missing' => 'false', |
132 | 'data-ex-facet-label' => ucfirst( $f ), |
133 | 'style' => "float: left; width: 24%; margin: 0 1% 0 0;" |
134 | ]; |
135 | $text .= Html::element( 'div', $attrs ); |
136 | } |
137 | return Html::rawElement( 'div', [ "class" => "facets", "style" => "overflow: hidden; width: 100%;" ], $text ); |
138 | } |
139 | |
140 | /** |
141 | * @return string |
142 | */ |
143 | private function createSearch() { |
144 | $attrs = [ |
145 | 'data-ex-role' => "exhibit-facet", |
146 | 'data-ex-facet-class' => "TextSearch", |
147 | 'data-ex-facet-label' => wfMessage( 'search' )->text(), |
148 | 'style' => "float: left; width: 24%; margin: 0 1% 0 0;" |
149 | ]; |
150 | return Html::element( 'div', $attrs ); |
151 | } |
152 | |
153 | private function createLens( $fieldList ) { |
154 | $lensBody = '<caption><strong data-ex-content=".label"></strong></caption>'; |
155 | foreach ( $fieldList as $field ) { |
156 | if ( $field != "label" && strpos( $field, '__' ) === false && |
157 | strpos( $field, ' ' ) === false ) { |
158 | $th = "<strong>" . ucfirst( $field ) . "</strong>"; |
159 | $lensBody .= "<tr data-ex-if-exists=\".$field\"><td>$th</td><td data-ex-content=\".$field\"></td></tr>"; |
160 | } |
161 | } |
162 | $tableAttrs = [ |
163 | 'data-ex-role' => 'lens', |
164 | 'class' => 'cargoTable', |
165 | 'style' => "display: none; width: 100%;" |
166 | ]; |
167 | return Html::rawElement( 'table', $tableAttrs, $lensBody ); |
168 | } |
169 | |
170 | /** |
171 | * @param CargoSQLQuery[] $sqlQueries |
172 | * @param array $displayParams |
173 | * @param array|null $querySpecificParams |
174 | * @return string HTML |
175 | * @throws MWException |
176 | */ |
177 | public function queryAndDisplay( $sqlQueries, $displayParams, $querySpecificParams = null ) { |
178 | global $cgScriptPath; |
179 | |
180 | $this->mOutput->addModules( [ 'ext.cargo.exhibit' ] ); |
181 | $this->mOutput->addModuleStyles( [ 'ext.cargo.main' ] ); |
182 | |
183 | $exhibit_busy = $cgScriptPath . "/resources/images/loading.gif"; |
184 | // The "loading" message is just alt-text, so it doesn't really |
185 | // matter that it's hardcoded in English. |
186 | $preViewsText = '<img id="loading_exhibit" src="' . $exhibit_busy . '" alt="Loading Exhibit" style="display: none;" >'; |
187 | |
188 | $field_list = []; |
189 | foreach ( $sqlQueries as $sqlQuery ) { |
190 | foreach ( $sqlQuery->mAliasedFieldNames as $alias => $fieldName ) { |
191 | $field_list[] = $alias; |
192 | } |
193 | } |
194 | |
195 | $csv_properties = ''; |
196 | if ( !in_array( "label", $field_list ) ) { |
197 | // first field will be label! |
198 | $field_list[0] = 'label'; |
199 | $csv_properties = 'data-ex-properties="' . implode( ',', $field_list ) . '"'; |
200 | } |
201 | |
202 | $queryParams = $this->sqlQueriesToQueryParams( $sqlQueries ); |
203 | $queryParams['format'] = 'csv'; |
204 | |
205 | $ce = SpecialPage::getTitleFor( 'CargoExport' ); |
206 | $dataurl = htmlentities( $ce->getFullURL( $queryParams ) ); |
207 | |
208 | // Data imported as csv |
209 | $datalink = "<link href=\"$dataurl\" type=\"text/csv\" rel=\"exhibit/data\" data-ex-has-column-titles=\"true\" $csv_properties />"; |
210 | |
211 | $this->mOutput->addHeadItem( $datalink, $datalink ); |
212 | |
213 | $this->displayParams = $displayParams; |
214 | |
215 | // lens |
216 | $preViewsText .= $this->createLens( $field_list ); |
217 | |
218 | // Facets |
219 | if ( array_key_exists( 'facets', $displayParams ) ) { |
220 | $facets = array_map( 'trim', explode( ',', $displayParams['facets'] ) ); |
221 | $preViewsText .= $this->createFacets( $facets ); |
222 | } else { |
223 | $preViewsText .= $this->createFacets( array_slice( $field_list, 0, 3 ) ); |
224 | } |
225 | |
226 | if ( array_key_exists( 'datalabel', $displayParams ) ) { |
227 | $datalabel = trim( $displayParams['datalabel'] ); |
228 | // What is this used for? |
229 | $dataplural = $datalabel . 's'; |
230 | $data_label_link = <<<EOLABEL |
231 | <link href="#cargoExhibit" type="text/html" rel="exhibit/data" |
232 | data-ex-item-type="Item" data-ex-label="$datalabel" data-ex-plural-label="$dataplural" /> |
233 | EOLABEL; |
234 | $this->mOutput->addHeadItem( $data_label_link, $data_label_link ); |
235 | } |
236 | |
237 | // View |
238 | $this->views = []; |
239 | |
240 | if ( array_key_exists( 'view', $displayParams ) ) { |
241 | $this->views = array_map( 'ucfirst', array_map( 'trim', explode( ',', $displayParams['view'] ) ) ); |
242 | } else { |
243 | $this->automateViews( $sqlQueries ); |
244 | } |
245 | |
246 | $viewsText = ""; |
247 | foreach ( $this->views as $view ) { |
248 | switch ( $view ) { |
249 | case "Timeline": |
250 | $viewsText .= $this->createTimeline( $sqlQueries ); |
251 | break; |
252 | case "Map": |
253 | $viewsText .= $this->createMap( $sqlQueries ); |
254 | break; |
255 | case "Tabular": |
256 | $viewsText .= $this->createTabular( $field_list ); |
257 | } |
258 | } |
259 | |
260 | if ( count( $this->views ) > 1 ) { |
261 | $viewsText = Html::rawElement( 'div', |
262 | [ 'data-ex-role' => "viewPanel" ], |
263 | $viewsText ); |
264 | } |
265 | |
266 | return $preViewsText . '<div id="cargoExhibit">' . $viewsText . '</div>'; |
267 | } |
268 | |
269 | /** |
270 | * Initializes $this->views[] |
271 | */ |
272 | private function automateViews( $sqlQueries ) { |
273 | // map ? |
274 | $coordFields = $this->getCoordinatesFields( $sqlQueries ); |
275 | if ( count( $coordFields ) > 0 ) { |
276 | $this->views[] = 'Map'; |
277 | $this->displayParams['latlng'] = $coordFields[0]; |
278 | } |
279 | |
280 | // timeline ? |
281 | $dateFields = $this->getDateFields( $sqlQueries ); |
282 | if ( count( $dateFields ) > 0 ) { |
283 | $this->views[] = 'Timeline'; |
284 | $this->displayParams['start'] = $dateFields[0]; |
285 | } |
286 | |
287 | $this->views[] = 'Tabular'; |
288 | } |
289 | |
290 | private function getCoordinatesFields( $sqlQueries ) { |
291 | $coordinatesFields = []; |
292 | |
293 | foreach ( $sqlQueries as $query ) { |
294 | $fieldDescriptions = $query->mFieldDescriptions; |
295 | foreach ( $fieldDescriptions as $field => $description ) { |
296 | if ( $description->mType == 'Coordinates' ) { |
297 | $coordinatesFields[] = $field; |
298 | } |
299 | } |
300 | } |
301 | return $coordinatesFields; |
302 | } |
303 | |
304 | private function getDateFields( $sqlQueries ) { |
305 | $dateFields = []; |
306 | |
307 | foreach ( $sqlQueries as $query ) { |
308 | $fieldDescriptions = $query->mFieldDescriptions; |
309 | foreach ( $fieldDescriptions as $field => $description ) { |
310 | if ( $description->mType == 'Date' || $description->mType == 'Datetime' ) { |
311 | $dateFields[] = $field; |
312 | } |
313 | } |
314 | } |
315 | return $dateFields; |
316 | } |
317 | } |