Completed
Push — master ( b9f247...ba3c73 )
by mw
12:20
created

iCalendarFileExportPrinter   A

Complexity

Total Complexity 39

Size/Duplication

Total Lines 306
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 81.89%

Importance

Changes 0
Metric Value
wmc 39
lcom 1
cbo 4
dl 0
loc 306
ccs 95
cts 116
cp 0.8189
rs 9.28
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A getName() 0 3 1
A getMimeType() 0 3 1
A getFileName() 0 8 2
A getQueryMode() 0 3 2
A getParamDefinitions() 0 20 1
A handleParameters() 0 6 1
A getResultText() 0 8 2
A getIcal() 0 38 5
A getIcalLink() 0 30 5
F getIcalForItem() 0 86 18
A escape() 0 4 1
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
	 * @var DateParser
46
	 */
47
	private $dateParser;
48
49
	/**
50
	 * @see ResultPrinter::getName
51
	 *
52
	 * @since 1.8
53
	 *
54
	 * {@inheritDoc}
55
	 */
56
	public function getName() {
57
		return wfMessage( 'srf_printername_icalendar' )->text();
58
	}
59
60
	/**
61
	 * @see FileExportPrinter::getMimeType
62
	 *
63
	 * @since 1.8
64
	 *
65
	 * {@inheritDoc}
66
	 */
67
	public function getMimeType( QueryResult $queryResult ) {
68
		return 'text/calendar';
69
	}
70
71
	/**
72
	 * @see FileExportPrinter::getFileName
73
	 *
74
	 * @since 1.8
75
	 *
76
	 * {@inheritDoc}
77
	 */
78 3
	public function getFileName( QueryResult $queryResult ) {
79
80 3
		if ( $this->title != '' ) {
81 3
			return str_replace( ' ', '_', $this->title ) . '.ics';
82
		}
83
84
		return 'iCalendar.ics';
85
	}
86
87
	/**
88
	 * @see FileExportPrinter::getQueryMode
89
	 *
90
	 * @since 1.8
91
	 *
92
	 * {@inheritDoc}
93
	 */
94 3
	public function getQueryMode( $context ) {
95 3
		return ( $context == QueryProcessor::SPECIAL_PAGE ) ? Query::MODE_INSTANCES : Query::MODE_NONE;
96
	}
97
98
	/**
99
	 * @see ResultPrinter::getParamDefinitions
100
	 *
101
	 * @since 1.8
102
	 *
103
	 * {@inheritDoc}
104
	 */
105 3
	public function getParamDefinitions( array $definitions ) {
106 3
		$params = parent::getParamDefinitions( $definitions );
107
108 3
		$params['title'] = [
109
			'default' => '',
110
			'message' => 'srf_paramdesc_icalendartitle',
111
		];
112
113 3
		$params['description'] = [
114
			'default' => '',
115
			'message' => 'srf_paramdesc_icalendardescription',
116
		];
117
118 3
		$params['timezone'] = [
119
			'default' => '',
120
			'message' => 'srf-paramdesc-icalendar-timezone',
121
		];
122
123 3
		return $params;
124
	}
125
126
	/**
127
	 * @see ResultPrinter::handleParameters
128
	 *
129
	 * {@inheritDoc}
130
	 */
131 3
	protected function handleParameters( array $params, $outputMode ) {
132 3
		parent::handleParameters( $params, $outputMode );
133
134 3
		$this->title = trim( $params['title'] );
135 3
		$this->description = trim( $params['description'] );
136 3
	}
137
138
	/**
139
	 * @see ResultPrinter::getResultText
140
	 *
141
	 * {@inheritDoc}
142
	 */
143 3
	protected function getResultText( QueryResult $res, $outputMode ) {
144
145 3
		if ( $outputMode == SMW_OUTPUT_FILE ) {
146 3
			return $this->getIcal( $res );
147
		}
148
149
		return $this->getIcalLink( $res, $outputMode );
150
	}
151
152
	/**
153
	 * Returns the query result in iCal.
154
	 */
155 3
	private function getIcal( QueryResult $res ) {
156
157 3
		$this->dateParser = new DateParser();
158
159 3
		$this->icalTimezoneFormatter = new IcalTimezoneFormatter();
160
161 3
		$this->icalTimezoneFormatter->setLocalTimezones(
162 3
			isset( $this->params['timezone'] ) ? $this->params['timezone'] : []
163
		);
164
165 3
		$result = '';
166
167 3
		if ( $this->title == '' ) {
168 3
			$this->title = $GLOBALS['wgSitename'];
169
		}
170
171 3
		$result .= "BEGIN:VCALENDAR\r\n";
172 3
		$result .= "PRODID:-//SMW Project//Semantic Result Formats\r\n";
173 3
		$result .= "VERSION:2.0\r\n";
174 3
		$result .= "METHOD:PUBLISH\r\n";
175 3
		$result .= "X-WR-CALNAME:" . $this->title . "\r\n";
176
177 3
		if ( $this->description !== '' ) {
178 3
			$result .= "X-WR-CALDESC:" . $this->description . "\r\n";
179
		}
180
181 3
		$events = '';
182
183 3
		while ( $row = $res->getNext() ) {
184 3
			$events .= $this->getIcalForItem( $row );
185
		}
186
187 3
		$result .= $this->icalTimezoneFormatter->getTransitions();
188 3
		$result .= $events;
189 3
		$result .= "END:VCALENDAR\r\n";
190
191 3
		return $result;
192
	}
193
194
	/**
195
	 * Returns html for a link to a query that returns the iCal.
196
	 */
197
	private function getIcalLink( QueryResult $res, $outputMode ) {
198
199
		if ( $this->getSearchLabel( $outputMode ) ) {
200
			$label = $this->getSearchLabel( $outputMode );
201
		} else {
202
			$label = wfMessage( 'srf_icalendar_link' )->inContentLanguage()->text();
203
		}
204
205
		$link = $res->getQueryLink( $label );
206
		$link->setParameter( 'icalendar', 'format' );
207
208
		if ( $this->title !== '' ) {
209
			$link->setParameter( $this->title, 'title' );
210
		}
211
212
		if ( $this->description !== '' ) {
213
			$link->setParameter( $this->description, 'description' );
214
		}
215
216
		if ( array_key_exists( 'limit', $this->params ) ) {
217
			$link->setParameter( $this->params['limit'], 'limit' );
218
		} else { // use a reasonable default limit
219
			$link->setParameter( 20, 'limit' );
220
		}
221
222
		// yes, our code can be viewed as HTML if requested, no more parsing needed
223
		$this->isHTML = ( $outputMode == SMW_OUTPUT_HTML );
224
225
		return $link->getText( $outputMode, $this->mLinker );
226
	}
227
228
	/**
229
	 * Returns the iCal for a single item.
230
	 *
231
	 * @param ResultArray[] $row
232
	 *
233
	 * @return string
234
	 * @throws \MWException
235
	 */
236 3
	private function getIcalForItem( array $row ) {
237 3
		$result = '';
238
239 3
		$subjectDI = $row[0]->getResultSubject(); // get the object
240 3
		$subjectDV = DataValueFactory::getInstance()->newDataValueByItem( $subjectDI, null );
241
242
		$params = [
243 3
			'summary' => $subjectDV->getShortWikiText()
244
		];
245
246 3
		$from = null;
247 3
		$to = null;
248 3
		foreach ( $row as /* SMWResultArray */
249
				  $field ) {
250
			// later we may add more things like a generic
251
			// mechanism to add whatever you want :)
252
			// could include funny things like geo, description etc. though
253 3
			$req = $field->getPrintRequest();
254 3
			$label = strtolower( $req->getLabel() );
255
256 3
			switch ( $label ) {
257 3
				case 'start':
258 3
				case 'end':
259 2
					if ( $req->getTypeID() == '_dat' ) {
260 2
						$dataValue = $field->getNextDataValue();
261
262 2
						if ( $dataValue === false ) {
263 2
							unset( $params[$label] );
264
						} else {
265 2
							$params[$label] = $this->dateParser->parseDate( $dataValue, $label == 'end' );
266
267 2
							$timestamp = strtotime( $params[$label] );
268 2
							if ( $from === null || $timestamp < $from ) {
269 2
								$from = $timestamp;
270
							}
271 2
							if ( $to === null || $timestamp > $to ) {
272 2
								$to = $timestamp;
273
							}
274
						}
275
					}
276 2
					break;
277 3
				case 'location':
278 3
				case 'description':
279 3
				case 'summary':
280 2
					$value = $field->getNextDataValue();
281 2
					if ( $value !== false ) {
282 2
						$params[$label] = $value->getShortWikiText();
283
					}
284 2
					break;
285
			}
286
		}
287
288 3
		$this->icalTimezoneFormatter->calcTransitions( $from, $to );
289
290 3
		$title = $subjectDI->getTitle();
291 3
		$timestamp = WikiPage::factory( $title )->getTimestamp();
292 3
		$url = $title->getFullURL();
293
294 3
		$result .= "BEGIN:VEVENT\r\n";
295 3
		$result .= "SUMMARY:" . $this->escape( $params['summary'] ) . "\r\n";
296 3
		$result .= "URL:$url\r\n";
297 3
		$result .= "UID:$url\r\n";
298
299 3
		if ( array_key_exists( 'start', $params ) ) {
300 2
			$result .= "DTSTART:" . $params['start'] . "\r\n";
301
		}
302
303 3
		if ( array_key_exists( 'end', $params ) ) {
304 2
			$result .= "DTEND:" . $params['end'] . "\r\n";
305
		}
306
307 3
		if ( array_key_exists( 'location', $params ) ) {
308 2
			$result .= "LOCATION:" . $this->escape( $params['location'] ) . "\r\n";
309
		}
310
311 3
		if ( array_key_exists( 'description', $params ) ) {
312 2
			$result .= "DESCRIPTION:" . $this->escape( $params['description'] ) . "\r\n";
313
		}
314
315 3
		$t = strtotime( str_replace( 'T', ' ', $timestamp ) );
316 3
		$result .= "DTSTAMP:" . date( "Ymd", $t ) . "T" . date( "His", $t ) . "\r\n";
317 3
		$result .= "SEQUENCE:" . $title->getLatestRevID() . "\r\n";
318 3
		$result .= "END:VEVENT\r\n";
319
320 3
		return $result;
321
	}
322
323
	/**
324
	 * Implements esaping of special characters for iCalendar properties of type
325
	 * TEXT. This is defined in RFC2445 Section 4.3.11.
326
	 */
327 3
	private function escape( $text ) {
328
		// Note that \\ is a PHP escaped single \ here
329 3
		return str_replace( [ "\\", "\n", ";", "," ], [ "\\\\", "\\n", "\\;", "\\," ], $text );
330
	}
331
332
}
333