Completed
Push — master ( 61c879...ee88cb )
by mw
06:12
created

iCalendarFileExportPrinter::handleParameters()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 5
cts 5
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 2
crap 1
1
<?php
2
3
namespace SRF\iCalendar;
4
5
use SRF\iCalendar\IcalTimezoneFormatter;
6
use SMWQueryResult  as QueryResult;
7
use SMWQueryProcessor as QueryProcessor;
8
use SMWQuery as Query;
9
use SMWExportPrinter as FileExportPrinter;
10
use SMWTimeValue as TimeValue;
11
use SMWDataValueFactory as DataValueFactory;
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
	public function getFileName( QueryResult $queryResult ) {
74
75
		if ( $this->title != '' ) {
76
			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 ) {
0 ignored issues
show
Coding Style introduced by
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...
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 3
	private function getIcalForItem( array $row ) {
225 3
		$result = '';
226
227 3
		$subject = $row[0]->getResultSubject(); // get the object
228 3
		$subject = DataValueFactory::getInstance()->newDataValueByItem( $subject, null );
229
230
		$params = [
231 3
			'summary' => $subject->getShortWikiText()
232
		];
233
234 3
		$from = null;
235 3
		$to = null;
236 3
		foreach ( $row as /* SMWResultArray */ $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': case 'end':
245 2
					if ( $req->getTypeID() == '_dat' ) {
246 2
						$dataValue = $field->getNextDataValue();
247
248 2
						if ( $dataValue === false ) {
249 2
							unset( $params[$label] );
250
						} else {
251 2
							$params[$label] = $this->parsedate( $dataValue, $label == 'end' );
252
253 2
							$timestamp = strtotime( $params[$label] );
254 2
							if ( $from === null || $timestamp < $from )
255 2
								$from = $timestamp;
256 2
							if ( $to === null || $timestamp > $to )
257 2
								$to = $timestamp;
258
						}
259
					}
260 2
					break;
261 3
				case 'location': case 'description': case 'summary':
262 2
					$value = $field->getNextDataValue();
263 2
					if ( $value !== false ) {
264 2
						$params[$label] = $value->getShortWikiText();
265
					}
266 3
					break;
267
			}
268
		}
269
270 3
		$this->icalTimezoneFormatter->calcTransitions( $from, $to );
271
272 3
		$title = $subject->getTitle();
273 3
		$timestamp = WikiPage::factory( $title )->getTimestamp();
274 3
		$url = $title->getFullURL();
275
276 3
		$result .= "BEGIN:VEVENT\r\n";
277 3
		$result .= "SUMMARY:" . $this->escape( $params['summary'] ) . "\r\n";
278 3
		$result .= "URL:$url\r\n";
279 3
		$result .= "UID:$url\r\n";
280
281 3
		if ( array_key_exists( 'start', $params ) ) {
282 2
			$result .= "DTSTART:" . $params['start'] . "\r\n";
283
		}
284
285 3
		if ( array_key_exists( 'end', $params ) ) {
286 2
			$result .= "DTEND:" . $params['end'] . "\r\n";
287
		}
288
289 3
		if ( array_key_exists( 'location', $params ) ) {
290 2
			$result .= "LOCATION:" . $this->escape( $params['location'] ) . "\r\n";
291
		}
292
293 3
		if ( array_key_exists( 'description', $params ) ) {
294 2
			$result .= "DESCRIPTION:" . $this->escape( $params['description'] ) . "\r\n";
295
		}
296
297 3
		$t = strtotime( str_replace( 'T', ' ', $timestamp ) );
298 3
		$result .= "DTSTAMP:" . date( "Ymd", $t ) . "T" . date( "His", $t ) . "\r\n";
299 3
		$result .= "SEQUENCE:" . $title->getLatestRevID() . "\r\n";
300 3
		$result .= "END:VEVENT\r\n";
301
302 3
		return $result;
303
	}
304
305
	/**
306
	 * Extract a date string formatted for iCalendar from a SMWTimeValue object.
307
	 */
308 2
	private function parsedate( TimeValue $dv, $isend = false ) {
309 2
		$year = $dv->getYear();
310
311
		 // ISO range is limited to four digits
312 2
		if ( ( $year > 9999 ) || ( $year < -9998 ) ) {
313
			 return '';
314
		}
315
316 2
		$year = number_format( $year, 0, '.', '' );
317 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...
318
319
		// increment by one day, compute date to cover leap years etc.
320 2
		if ( ( $time == false ) && ( $isend ) ) {
321
			$dv = DataValueFactoryg::getInstance()->newDataValueByType( '_dat', $dv->getWikiValue() . 'T00:00:00-24:00' );
322
		}
323
324 2
		$month = $dv->getMonth();
325
326 2
		if ( strlen( $month ) == 1 ) {
327 2
			$month = '0' . $month;
328
		}
329
330 2
		$day = $dv->getDay();
331
332 2
		if ( strlen( $day ) == 1 ) {
333 2
			$day = '0' . $day;
334
		}
335
336 2
		$result = $year . $month . $day;
337
338 2
		if ( $time != false ) {
339 2
			$result .= "T$time";
340
		}
341
342 2
		return $result;
343
	}
344
345
	/**
346
	 * Implements esaping of special characters for iCalendar properties of type
347
	 * TEXT. This is defined in RFC2445 Section 4.3.11.
348
	 */
349 3
	private function escape( $text ) {
350
		// Note that \\ is a PHP escaped single \ here
351 3
		return str_replace( [ "\\", "\n", ";", "," ], [ "\\\\", "\\n", "\\;", "\\," ], $text );
352
	}
353
354
}
355