Completed
Push — master ( 6dc7d8...407c40 )
by Karsten
15:45
created

src/iCalendar/iCalendarFileExportPrinter.php (1 issue)

Upgrade to new PHP Analysis Engine

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
getIcal uses the super-global variable $GLOBALS which is generally not recommended.

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:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
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