These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace SRF\iCalendar; |
||
4 | |||
5 | use SMWDataValueFactory as DataValueFactory; |
||
6 | use SMWExportPrinter as FileExportPrinter; |
||
7 | use SMWQuery as Query; |
||
8 | use SMWQueryProcessor as QueryProcessor; |
||
9 | use SMWQueryResult as QueryResult; |
||
10 | use SMWTimeValue as TimeValue; |
||
11 | use WikiPage; |
||
12 | |||
13 | /** |
||
14 | * Printer class for iCalendar exports |
||
15 | * |
||
16 | * @see https://en.wikipedia.org/wiki/ICalendar |
||
17 | * @see https://tools.ietf.org/html/rfc5545 |
||
18 | * |
||
19 | * @license GNU GPL v2+ |
||
20 | * @since 1.5 |
||
21 | * |
||
22 | * @author Markus Krötzsch |
||
23 | * @author Denny Vrandecic |
||
24 | * @author Jeroen De Dauw |
||
25 | */ |
||
26 | class iCalendarFileExportPrinter extends FileExportPrinter { |
||
27 | |||
28 | /** |
||
29 | * @var string |
||
30 | */ |
||
31 | private $title; |
||
32 | |||
33 | /** |
||
34 | * @var string |
||
35 | */ |
||
36 | private $description; |
||
37 | |||
38 | /** |
||
39 | * @var IcalTimezoneFormatter |
||
40 | */ |
||
41 | private $icalTimezoneFormatter; |
||
42 | |||
43 | /** |
||
44 | * @see ResultPrinter::getName |
||
45 | * |
||
46 | * @since 1.8 |
||
47 | * |
||
48 | * {@inheritDoc} |
||
49 | */ |
||
50 | public function getName() { |
||
51 | return wfMessage( 'srf_printername_icalendar' )->text(); |
||
52 | } |
||
53 | |||
54 | /** |
||
55 | * @see FileExportPrinter::getMimeType |
||
56 | * |
||
57 | * @since 1.8 |
||
58 | * |
||
59 | * {@inheritDoc} |
||
60 | */ |
||
61 | public function getMimeType( QueryResult $queryResult ) { |
||
62 | return 'text/calendar'; |
||
63 | } |
||
64 | |||
65 | /** |
||
66 | * @see FileExportPrinter::getFileName |
||
67 | * |
||
68 | * @since 1.8 |
||
69 | * |
||
70 | * {@inheritDoc} |
||
71 | */ |
||
72 | 3 | public function getFileName( QueryResult $queryResult ) { |
|
73 | |||
74 | 3 | if ( $this->title != '' ) { |
|
75 | 3 | return str_replace( ' ', '_', $this->title ) . '.ics'; |
|
76 | } |
||
77 | |||
78 | return 'iCalendar.ics'; |
||
79 | } |
||
80 | |||
81 | /** |
||
82 | * @see FileExportPrinter::getQueryMode |
||
83 | * |
||
84 | * @since 1.8 |
||
85 | * |
||
86 | * {@inheritDoc} |
||
87 | */ |
||
88 | 3 | public function getQueryMode( $context ) { |
|
89 | 3 | return ( $context == QueryProcessor::SPECIAL_PAGE ) ? Query::MODE_INSTANCES : Query::MODE_NONE; |
|
90 | } |
||
91 | |||
92 | /** |
||
93 | * @see ResultPrinter::getParamDefinitions |
||
94 | * |
||
95 | * @since 1.8 |
||
96 | * |
||
97 | * {@inheritDoc} |
||
98 | */ |
||
99 | 3 | public function getParamDefinitions( array $definitions ) { |
|
100 | 3 | $params = parent::getParamDefinitions( $definitions ); |
|
101 | |||
102 | 3 | $params['title'] = [ |
|
103 | 'default' => '', |
||
104 | 'message' => 'srf_paramdesc_icalendartitle', |
||
105 | ]; |
||
106 | |||
107 | 3 | $params['description'] = [ |
|
108 | 'default' => '', |
||
109 | 'message' => 'srf_paramdesc_icalendardescription', |
||
110 | ]; |
||
111 | |||
112 | 3 | $params['timezone'] = [ |
|
113 | 'default' => '', |
||
114 | 'message' => 'srf-paramdesc-icalendar-timezone', |
||
115 | ]; |
||
116 | |||
117 | 3 | return $params; |
|
118 | } |
||
119 | |||
120 | /** |
||
121 | * @see ResultPrinter::handleParameters |
||
122 | * |
||
123 | * {@inheritDoc} |
||
124 | */ |
||
125 | 3 | protected function handleParameters( array $params, $outputMode ) { |
|
126 | 3 | parent::handleParameters( $params, $outputMode ); |
|
127 | |||
128 | 3 | $this->title = trim( $params['title'] ); |
|
129 | 3 | $this->description = trim( $params['description'] ); |
|
130 | 3 | } |
|
131 | |||
132 | /** |
||
133 | * @see ResultPrinter::getResultText |
||
134 | * |
||
135 | * {@inheritDoc} |
||
136 | */ |
||
137 | 3 | protected function getResultText( QueryResult $res, $outputMode ) { |
|
138 | |||
139 | 3 | if ( $outputMode == SMW_OUTPUT_FILE ) { |
|
140 | 3 | return $this->getIcal( $res ); |
|
141 | } |
||
142 | |||
143 | return $this->getIcalLink( $res, $outputMode ); |
||
144 | } |
||
145 | |||
146 | /** |
||
147 | * Returns the query result in iCal. |
||
148 | */ |
||
149 | 3 | private function getIcal( QueryResult $res ) { |
|
0 ignored issues
–
show
|
|||
150 | |||
151 | 3 | $this->icalTimezoneFormatter = new IcalTimezoneFormatter(); |
|
152 | |||
153 | 3 | $this->icalTimezoneFormatter->setLocalTimezones( |
|
154 | 3 | isset( $this->params['timezone'] ) ? $this->params['timezone'] : [] |
|
155 | ); |
||
156 | |||
157 | 3 | $result = ''; |
|
158 | |||
159 | 3 | if ( $this->title == '' ) { |
|
160 | 3 | $this->title = $GLOBALS['wgSitename']; |
|
161 | } |
||
162 | |||
163 | 3 | $result .= "BEGIN:VCALENDAR\r\n"; |
|
164 | 3 | $result .= "PRODID:-//SMW Project//Semantic Result Formats\r\n"; |
|
165 | 3 | $result .= "VERSION:2.0\r\n"; |
|
166 | 3 | $result .= "METHOD:PUBLISH\r\n"; |
|
167 | 3 | $result .= "X-WR-CALNAME:" . $this->title . "\r\n"; |
|
168 | |||
169 | 3 | if ( $this->description !== '' ) { |
|
170 | 3 | $result .= "X-WR-CALDESC:" . $this->description . "\r\n"; |
|
171 | } |
||
172 | |||
173 | 3 | $events = ''; |
|
174 | |||
175 | 3 | while ( $row = $res->getNext() ) { |
|
176 | 3 | $events .= $this->getIcalForItem( $row ); |
|
177 | } |
||
178 | |||
179 | 3 | $result .= $this->icalTimezoneFormatter->getTransitions(); |
|
180 | 3 | $result .= $events; |
|
181 | 3 | $result .= "END:VCALENDAR\r\n"; |
|
182 | |||
183 | 3 | return $result; |
|
184 | } |
||
185 | |||
186 | /** |
||
187 | * Returns html for a link to a query that returns the iCal. |
||
188 | */ |
||
189 | private function getIcalLink( QueryResult $res, $outputMode ) { |
||
190 | |||
191 | if ( $this->getSearchLabel( $outputMode ) ) { |
||
192 | $label = $this->getSearchLabel( $outputMode ); |
||
193 | } else { |
||
194 | $label = wfMessage( 'srf_icalendar_link' )->inContentLanguage()->text(); |
||
195 | } |
||
196 | |||
197 | $link = $res->getQueryLink( $label ); |
||
198 | $link->setParameter( 'icalendar', 'format' ); |
||
199 | |||
200 | if ( $this->title !== '' ) { |
||
201 | $link->setParameter( $this->title, 'title' ); |
||
202 | } |
||
203 | |||
204 | if ( $this->description !== '' ) { |
||
205 | $link->setParameter( $this->description, 'description' ); |
||
206 | } |
||
207 | |||
208 | if ( array_key_exists( 'limit', $this->params ) ) { |
||
209 | $link->setParameter( $this->params['limit'], 'limit' ); |
||
210 | } else { // use a reasonable default limit |
||
211 | $link->setParameter( 20, 'limit' ); |
||
212 | } |
||
213 | |||
214 | // yes, our code can be viewed as HTML if requested, no more parsing needed |
||
215 | $this->isHTML = ( $outputMode == SMW_OUTPUT_HTML ); |
||
216 | |||
217 | return $link->getText( $outputMode, $this->mLinker ); |
||
218 | } |
||
219 | |||
220 | /** |
||
221 | * Returns the iCal for a single item. |
||
222 | */ |
||
223 | 3 | private function getIcalForItem( array $row ) { |
|
224 | 3 | $result = ''; |
|
225 | |||
226 | 3 | $subject = $row[0]->getResultSubject(); // get the object |
|
227 | 3 | $subject = DataValueFactory::getInstance()->newDataValueByItem( $subject, null ); |
|
228 | |||
229 | $params = [ |
||
230 | 3 | 'summary' => $subject->getShortWikiText() |
|
231 | ]; |
||
232 | |||
233 | 3 | $from = null; |
|
234 | 3 | $to = null; |
|
235 | 3 | foreach ( $row as /* SMWResultArray */ |
|
236 | $field ) { |
||
237 | // later we may add more things like a generic |
||
238 | // mechanism to add whatever you want :) |
||
239 | // could include funny things like geo, description etc. though |
||
240 | 3 | $req = $field->getPrintRequest(); |
|
241 | 3 | $label = strtolower( $req->getLabel() ); |
|
242 | |||
243 | switch ( $label ) { |
||
244 | 3 | case 'start': |
|
245 | 3 | case 'end': |
|
246 | 2 | if ( $req->getTypeID() == '_dat' ) { |
|
247 | 2 | $dataValue = $field->getNextDataValue(); |
|
248 | |||
249 | 2 | if ( $dataValue === false ) { |
|
250 | 2 | unset( $params[$label] ); |
|
251 | } else { |
||
252 | 2 | $params[$label] = $this->parsedate( $dataValue, $label == 'end' ); |
|
253 | |||
254 | 2 | $timestamp = strtotime( $params[$label] ); |
|
255 | 2 | if ( $from === null || $timestamp < $from ) { |
|
256 | 2 | $from = $timestamp; |
|
257 | } |
||
258 | 2 | if ( $to === null || $timestamp > $to ) { |
|
259 | 2 | $to = $timestamp; |
|
260 | } |
||
261 | } |
||
262 | } |
||
263 | 2 | break; |
|
264 | 3 | case 'location': |
|
265 | 3 | case 'description': |
|
266 | 3 | case 'summary': |
|
267 | 2 | $value = $field->getNextDataValue(); |
|
268 | 2 | if ( $value !== false ) { |
|
269 | 2 | $params[$label] = $value->getShortWikiText(); |
|
270 | } |
||
271 | 3 | break; |
|
272 | } |
||
273 | } |
||
274 | |||
275 | 3 | $this->icalTimezoneFormatter->calcTransitions( $from, $to ); |
|
276 | |||
277 | 3 | $title = $subject->getTitle(); |
|
278 | 3 | $timestamp = WikiPage::factory( $title )->getTimestamp(); |
|
279 | 3 | $url = $title->getFullURL(); |
|
280 | |||
281 | 3 | $result .= "BEGIN:VEVENT\r\n"; |
|
282 | 3 | $result .= "SUMMARY:" . $this->escape( $params['summary'] ) . "\r\n"; |
|
283 | 3 | $result .= "URL:$url\r\n"; |
|
284 | 3 | $result .= "UID:$url\r\n"; |
|
285 | |||
286 | 3 | if ( array_key_exists( 'start', $params ) ) { |
|
287 | 2 | $result .= "DTSTART:" . $params['start'] . "\r\n"; |
|
288 | } |
||
289 | |||
290 | 3 | if ( array_key_exists( 'end', $params ) ) { |
|
291 | 2 | $result .= "DTEND:" . $params['end'] . "\r\n"; |
|
292 | } |
||
293 | |||
294 | 3 | if ( array_key_exists( 'location', $params ) ) { |
|
295 | 2 | $result .= "LOCATION:" . $this->escape( $params['location'] ) . "\r\n"; |
|
296 | } |
||
297 | |||
298 | 3 | if ( array_key_exists( 'description', $params ) ) { |
|
299 | 2 | $result .= "DESCRIPTION:" . $this->escape( $params['description'] ) . "\r\n"; |
|
300 | } |
||
301 | |||
302 | 3 | $t = strtotime( str_replace( 'T', ' ', $timestamp ) ); |
|
303 | 3 | $result .= "DTSTAMP:" . date( "Ymd", $t ) . "T" . date( "His", $t ) . "\r\n"; |
|
304 | 3 | $result .= "SEQUENCE:" . $title->getLatestRevID() . "\r\n"; |
|
305 | 3 | $result .= "END:VEVENT\r\n"; |
|
306 | |||
307 | 3 | return $result; |
|
308 | } |
||
309 | |||
310 | /** |
||
311 | * Extract a date string formatted for iCalendar from a SMWTimeValue object. |
||
312 | */ |
||
313 | 2 | private function parsedate( TimeValue $dv, $isend = false ) { |
|
314 | 2 | $year = $dv->getYear(); |
|
315 | |||
316 | // ISO range is limited to four digits |
||
317 | 2 | if ( ( $year > 9999 ) || ( $year < -9998 ) ) { |
|
318 | return ''; |
||
319 | } |
||
320 | |||
321 | 2 | $year = number_format( $year, 0, '.', '' ); |
|
322 | 2 | $time = str_replace( ':', '', $dv->getTimeString( false ) ); |
|
323 | |||
324 | // increment by one day, compute date to cover leap years etc. |
||
325 | 2 | if ( ( $time == false ) && ( $isend ) ) { |
|
326 | $dv = DataValueFactoryg::getInstance()->newDataValueByType( |
||
327 | '_dat', |
||
328 | $dv->getWikiValue() . 'T00:00:00-24:00' |
||
329 | ); |
||
330 | } |
||
331 | |||
332 | 2 | $month = $dv->getMonth(); |
|
333 | |||
334 | 2 | if ( strlen( $month ) == 1 ) { |
|
335 | 2 | $month = '0' . $month; |
|
336 | } |
||
337 | |||
338 | 2 | $day = $dv->getDay(); |
|
339 | |||
340 | 2 | if ( strlen( $day ) == 1 ) { |
|
341 | 2 | $day = '0' . $day; |
|
342 | } |
||
343 | |||
344 | 2 | $result = $year . $month . $day; |
|
345 | |||
346 | 2 | if ( $time != false ) { |
|
347 | 2 | $result .= "T$time"; |
|
348 | } |
||
349 | |||
350 | 2 | return $result; |
|
351 | } |
||
352 | |||
353 | /** |
||
354 | * Implements esaping of special characters for iCalendar properties of type |
||
355 | * TEXT. This is defined in RFC2445 Section 4.3.11. |
||
356 | */ |
||
357 | 3 | private function escape( $text ) { |
|
358 | // Note that \\ is a PHP escaped single \ here |
||
359 | 3 | return str_replace( [ "\\", "\n", ";", "," ], [ "\\\\", "\\n", "\\;", "\\," ], $text ); |
|
360 | } |
||
361 | |||
362 | } |
||
363 |
Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable: