Completed
Push — master ( d3d832...67979d )
by mw
57:02 queued 55:38
created

iCalendarFileExportPrinter::getFileName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

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

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
329
330
		// increment by one day, compute date to cover leap years etc.
331 2
		if ( ( $time == false ) && ( $isend ) ) {
332
			$dv = DataValueFactoryg::getInstance()->newDataValueByType(
333
				'_dat',
334
				$dv->getWikiValue() . 'T00:00:00-24:00'
335
			);
336
		}
337
338 2
		$month = $dv->getMonth();
339
340 2
		if ( strlen( $month ) == 1 ) {
341 2
			$month = '0' . $month;
342
		}
343
344 2
		$day = $dv->getDay();
345
346 2
		if ( strlen( $day ) == 1 ) {
347 2
			$day = '0' . $day;
348
		}
349
350 2
		$result = $year . $month . $day;
351
352 2
		if ( $time != false ) {
353 2
			$result .= "T$time";
354
		}
355
356 2
		return $result;
357
	}
358
359
	/**
360
	 * Implements esaping of special characters for iCalendar properties of type
361
	 * TEXT. This is defined in RFC2445 Section 4.3.11.
362
	 */
363 3
	private function escape( $text ) {
364
		// Note that \\ is a PHP escaped single \ here
365 3
		return str_replace( [ "\\", "\n", ";", "," ], [ "\\\\", "\\n", "\\;", "\\," ], $text );
366
	}
367
368
}
369