IcalIterator::unread_line()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * EGroupware: Iterator for iCal files
4
 *
5
 * @link http://www.egroupware.org
6
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
7
 * @package api
8
 * @subpackage caldav
9
 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
10
 * @copyright (c) 2010-16 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
11
 * @version $Id$
12
 */
13
14
namespace EGroupware\Api\CalDAV;
15
16
use EGroupware\Api;
17
use Horde_Icalendar;
18
19
// required for tests at the end of this file (run if file called directly)
20
if (isset($_SERVER['SCRIPT_FILENAME']) && $_SERVER['SCRIPT_FILENAME'] == __FILE__)
21
{
22
	$GLOBALS['egw_info'] = array(
23
		'flags' => array(
24
			'currentapp' => 'login',
25
			'nonavbar'   => 'true',
26
		),
27
	);
28
	include('../../../header.inc.php');
29
}
30
31
/**
32
 * Iterator for iCal files
33
 *
34
 * try {
35
 * 		$ical_file = fopen($path,'r');
36
 * 		$ical_it = new Api\CalDAV\IcalIterator($ical_file,'VCALENDAR');
37
 * }
38
 * catch (Exception $e)
39
 * {
40
 * 		// could not open $path or no valid iCal file
41
 * }
42
 * foreach($ical_it as $vevent)
43
 * {
44
 * 		// do something with $vevent
45
 * }
46
 * fclose($ical_file)
47
 */
48
class IcalIterator extends Horde_Icalendar implements \Iterator
49
{
50
	/**
51
	 * File we work on
52
	 *
53
	 * @var resource
54
	 */
55
	protected $ical_file;
56
57
	/**
58
	 * Base name of container, eg. 'VCALENDAR', as passed to the constructor
59
	 *
60
	 * @var string
61
	 */
62
	protected $base;
63
64
	/**
65
	 * Does ical_file contain a container: BEGIN:$base ... END:$base
66
	 *
67
	 * @var boolean
68
	 */
69
	protected $container;
70
71
	/**
72
	 * Charset passed to the constructor
73
	 *
74
	 * @var string
75
	 */
76
	protected $charset;
77
78
	/**
79
	 * Current component, as it get's returned by current() method
80
	 *
81
	 * @var Horde_Icalendar
82
	 */
83
	protected $component;
84
85
	/**
86
	 * Callback to call with component in current() method, if returning false, item get's ignored
87
	 *
88
	 * @var callback
89
	 */
90
	protected $callback;
91
92
	/**
93
	 * Further parameters for the callback, 1. parameter is component
94
	 *
95
	 * @var array
96
	 */
97
	protected $callback_params = array();
98
99
	/**
100
	 * Constructor
101
	 *
102
	 * @param string|resource $ical_file file opened for reading or string
103
	 * @param string $base ='VCALENDAR' container
104
	 * @param string $charset =null
105
	 * @param callback $callback =null callback to call with component in current() method, if returning false, item get's ignored
106
	 * @param array $callback_params =array() further parameters for the callback, 1. parameter is component
107
	 * @param boolean $add_container =false true, add container / $this as last parameter to callback
108
	 */
109
	public function __construct($ical_file,$base='VCALENDAR',$charset=null,$callback=null,array $callback_params=array(), $add_container=false)
110
	{
111
		// call parent constructor
112
		parent::__construct();
113
114
		$this->base = $base;
115
		$this->charset = $charset;
116
		if (is_callable($callback))
117
		{
118
			$this->callback = $callback;
119
			if ($add_container) $callback_params[] = $this;
120
			$this->callback_params = $callback_params;
121
		}
122
		if (is_string($ical_file))
123
		{
124
			$this->ical_file = fopen('php://temp', 'w+');
0 ignored issues
show
Documentation Bug introduced by
It seems like fopen('php://temp', 'w+') can also be of type false. However, the property $ical_file is declared as type resource. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
125
			fwrite($this->ical_file, $ical_file);
126
			fseek($this->ical_file, 0, SEEK_SET);
127
		}
128
		else
129
		{
130
			$this->ical_file = $ical_file;
131
		}
132
		if (!is_resource($this->ical_file))
133
		{
134
			throw new Api\Exception\WrongParameter(__METHOD__.'($ical_file, ...) NO resource! $ical_file='.substr(array2string($ical_file),0,100));
135
		}
136
	}
137
138
	/**
139
	 * Stack with not yet processed lines
140
	 *
141
	 * @var string
142
	 */
143
	protected $unread_lines = array();
144
145
	/**
146
	 * Read and return one line from file (or line-buffer)
147
	 *
148
	 * We do NOT handle folding, that's done by Horde_Icalendar and not necessary for us as BEGIN: or END: component is never folded
149
	 *
150
	 * @return string|boolean string with line or false if end-of-file or end-of-container reached
151
	 */
152
	protected function read_line()
153
	{
154
		if ($this->unread_lines)
155
		{
156
			$line = array_shift($this->unread_lines);
157
		}
158
		elseif(feof($this->ical_file))
159
		{
160
			$line = false;
161
		}
162
		else
163
		{
164
			$line = ltrim(fgets($this->ical_file), "\xef\xbb\xbf");
165
		}
166
		// check if end of container reached
167
		if ($this->container && $line && substr($line,0,4+strlen($this->base)) === 'END:'.$this->base)
168
		{
169
			$this->unread_line($line);	// put back end-of-container, to continue to return false
170
			$line = false;
171
		}
172
		//error_log(__METHOD__."() returning ".($line === false ? 'FALSE' : "'$line'"));
173
174
		return $line;
175
	}
176
177
	/**
178
	 * Take back on line, already read with read_line
179
	 *
180
	 * @param string $line
181
	 */
182
	protected function unread_line($line)
183
	{
184
		//error_log(__METHOD__."('$line')");
185
		array_unshift($this->unread_lines,$line);
186
	}
187
188
	/**
189
	 * Return the current element
190
	 *
191
	 * @return Horde_Icalendar or whatever a given callback returns
192
	 */
193
	public function current()
194
	{
195
		//error_log(__METHOD__."() returning a ".gettype($this->component));
196
		if ($this->callback)
197
		{
198
			$ret = is_a($this->component,'Horde_Icalendar');
199
			do {
200
				if ($ret === false) $this->next();
201
				if (!is_a($this->component,'Horde_Icalendar')) return false;
202
				$params = $this->callback_params;
203
				array_unshift($params,$this->component);
204
			}
205
			while(($ret = call_user_func_array($this->callback,$params)) === false);
206
207
			return $ret;
208
		}
209
		return $this->component;
210
	}
211
212
	/**
213
	 * Return the key of the current element
214
	 *
215
	 * @return int|string
216
	 */
217
	public function key()
218
	{
219
		//error_log(__METHOD__."() returning ".$this->component->getAttribute('UID'));
220
		return $this->component ? $this->component->getAttribute('UID') : false;
221
	}
222
223
	/**
224
	 * Move forward to next component (called after each foreach loop)
225
	 */
226
	public function next()
227
	{
228
		unset($this->component);
229
230
		while (($line = $this->read_line()) && substr($line,0,6) !== 'BEGIN:')
231
		{
232
			error_log("'".$line."'");
0 ignored issues
show
Bug introduced by
Are you sure $line of type string|true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

232
			error_log("'"./** @scrutinizer ignore-type */ $line."'");
Loading history...
233
			// ignore it
234
		}
235
		if ($line === false)	// end-of-file or end-of-container
236
		{
237
			$this->component = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type Horde_Icalendar of property $component.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
238
			return;
239
		}
240
		$type = substr(trim($line),6);
241
242
		//error_log(__METHOD__."() found $type component");
243
244
		$data = $line;
245
		while (($line = $this->read_line()) && substr($line,0,4+strlen($type)) !== 'END:'.$type)
246
		{
247
			$data .= $line;
248
		}
249
		$data .= $line;
250
251
		$this->component = Horde_Icalendar::newComponent($type, $this);
252
		//error_log(__METHOD__."() this->component = Horde_Icalendar::newComponent('$type', \$this) = ".array2string($this->component));
253
        if ($this->component === false)
0 ignored issues
show
introduced by
The condition $this->component === false is always false.
Loading history...
254
        {
255
        	error_log(__METHOD__."() Horde_Icalendar::newComponent('$type', \$this) returned FALSE");
256
        	return;
257
            //return PEAR::raiseError("Unable to create object for type $type");
258
        }
259
		if ($this->charset && $this->charset != 'utf-8')
260
		{
261
			$data = Api\Translation::convert($data, $this->charset, 'utf-8');
262
		}
263
		//error_log(__METHOD__."() about to call parsevCalendar('".substr($data,0,100)."...','$type')");
264
		$this->component->parsevCalendar($data, $type);
265
266
		// VTIMEZONE components are NOT returned, they are only processed internally
267
		if ($type == 'VTIMEZONE')
268
		{
269
			$this->addComponent($this->component);
270
			// calling ourself recursive, to set next non-VTIMEZONE component
271
			$this->next();
272
		}
273
	}
274
275
	/**
276
	 * Rewind the Iterator to the first element (called at beginning of foreach loop)
277
	 */
278
	public function rewind()
279
	{
280
		@fseek($this->ical_file,0,SEEK_SET);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for fseek(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

280
		/** @scrutinizer ignore-unhandled */ @fseek($this->ical_file,0,SEEK_SET);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
281
282
		// advance to begin of container
283
		while(($line = $this->read_line()) && substr($line,0,6+strlen($this->base)) !== 'BEGIN:'.$this->base)
284
		{
285
286
		}
287
		// if no container start found --> use whole file (rewind) and set container marker
288
		if (!($this->container = $line !== false))
289
		{
290
			fseek($this->ical_file,0,SEEK_SET);
291
		}
292
		//error_log(__METHOD__."() $this->base container ".($this->container ? 'found' : 'NOT found'));
293
294
		$data = $line;
295
		// advance to first component
296
		while (($line = $this->read_line()) && substr($line,0,6) !== 'BEGIN:')
297
		{
298
			$matches = null;
299
			if (preg_match('/^VERSION:(\d\.\d)\s*$/ism', $line, $matches))
300
			{
301
				// define the version asap
302
				$this->setAttribute('VERSION', $matches[1]);
303
			}
304
			$data .= $line;
305
		}
306
		// fake end of container to get it parsed by Horde code
307
		if ($this->container)
308
		{
309
			$data .= "END:$this->base\n";
310
			//error_log(__METHOD__."() about to call this->parsevCalendar('$data','$this->base','$this->charset')");
311
			$this->parsevCalendar($data,$this->base,$this->charset);
312
		}
313
		if ($line) $this->unread_line($line);
314
315
		// advance to first element
316
		$this->next();
317
	}
318
319
	/**
320
	 * Checks if current position is valid
321
	 *
322
	 * @return boolean
323
	 */
324
	public function valid ()
325
	{
326
		//error_log(__METHOD__."() returning ".(is_a($this->component,'Horde_Icalendar') ? 'TRUE' : 'FALSE').' get_class($this->component)='.get_class($this->component));
327
		return is_a($this->component,'Horde_Icalendar');
328
	}
329
}
330
331
// some tests run if file called directly
332
if (isset($_SERVER['SCRIPT_FILENAME']) && $_SERVER['SCRIPT_FILENAME'] == __FILE__)
333
{
334
	$ical_file = 'BEGIN:VCALENDAR
335
PRODID:-//Microsoft Corporation//Outlook 12.0 MIMEDIR//EN
336
VERSION:2.0
337
METHOD:PUBLISH
338
X-CALSTART:19980101T000000
339
X-WR-RELCALID:{0000002E-1BB2-8F0F-1203-47B98FEEF211}
340
X-WR-CALNAME:Fxlxcxtxsxxxxxxxxxxxxx
341
X-PRIMARY-CALENDAR:TRUE
342
X-OWNER;CN="Fxlxcxtxsxxxxxxx":mailto:xexixixax.xuxaxa@xxxxxxxxxxxxxxx-berli
343
 n.de
344
X-MS-OLK-WKHRSTART;TZID="Westeuropäische Normalzeit":080000
345
X-MS-OLK-WKHREND;TZID="Westeuropäische Normalzeit":170000
346
X-MS-OLK-WKHRDAYS:MO,TU,WE,TH,FR
347
BEGIN:VTIMEZONE
348
TZID:Westeuropäische Normalzeit
349
BEGIN:STANDARD
350
DTSTART:16011028T030000
351
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
352
TZOFFSETFROM:+0200
353
TZOFFSETTO:+0100
354
END:STANDARD
355
BEGIN:DAYLIGHT
356
DTSTART:16010325T020000
357
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
358
TZOFFSETFROM:+0100
359
TZOFFSETTO:+0200
360
END:DAYLIGHT
361
END:VTIMEZONE
362
BEGIN:VEVENT
363
ATTENDEE;CN=Vorstand;RSVP=TRUE:mailto:[email protected]
364
ATTENDEE;CN=\'[email protected]\';RSVP=TRUE:mailto:[email protected]
365
ATTENDEE;CN=Pressestelle;RSVP=TRUE:mailto:xrxsxexxexlx@xxxxxxxxxxxxxxx-berl
366
 in.de
367
ATTENDEE;CN="Dxuxe Nxcxxlxxx";ROLE=OPT-PARTICIPANT;RSVP=TRUE:mailto:xjxkx.x
368
 [email protected]
369
ATTENDEE;CN="Mxxxaxx Sxxäxxr";ROLE=OPT-PARTICIPANT;RSVP=TRUE:mailto:xixhxe
370
 [email protected]
371
CLASS:PUBLIC
372
CREATED:20100408T232652Z
373
DESCRIPTION:\n
374
DTEND;TZID="Westeuropäische Normalzeit":20100414T210000
375
DTSTAMP:20100406T125856Z
376
DTSTART;TZID="Westeuropäische Normalzeit":20100414T190000
377
LAST-MODIFIED:20100408T232653Z
378
LOCATION:Axtx Fxuxrxaxhx\, Axex-Sxrxnxxxxxxxxxxxxxx
379
ORGANIZER;CN="Exixaxexhxxxxxxxxxxxräxx":mailto:xxx.xxxxxxxxxxx@xxxxxxxxxxx
380
 xxxx-berlin.de
381
PRIORITY:5
382
RECURRENCE-ID;TZID="Westeuropäische Normalzeit":20100414T190000
383
SEQUENCE:0
384
SUMMARY;LANGUAGE=de:Aktualisiert: LA  - mit Ramona
385
TRANSP:OPAQUE
386
UID:040000008200E00074C5B7101A82E00800000000D0AFE96CB462CA01000000000000000
387
 01000000019F8AF4D13C91844AA9CE63190D3408D
388
X-ALT-DESC;FMTTYPE=text/html:<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//E
389
 N">\n<HTML>\n<HEAD>\n<META NAME="Generator" CONTENT="MS Exchange Server ve
390
 rsion 08.00.0681.000">\n<TITLE></TITLE>\n</HEAD>\n<BODY>\n<!-- Converted f
391
 rom text/rtf format -->\n<BR>\n\n</BODY>\n</HTML>
392
X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE
393
X-MICROSOFT-CDO-IMPORTANCE:1
394
X-MICROSOFT-DISALLOW-COUNTER:FALSE
395
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
396
X-MS-OLK-APPTSEQTIME:20091111T085039Z
397
X-MS-OLK-AUTOSTARTCHECK:FALSE
398
X-MS-OLK-CONFTYPE:0
399
END:VEVENT
400
BEGIN:VEVENT
401
CLASS:PUBLIC
402
CREATED:20100331T125400Z
403
DTEND:20100409T110000Z
404
DTSTAMP:20100409T123209Z
405
DTSTART:20100409T080000Z
406
LAST-MODIFIED:20100331T125400Z
407
PRIORITY:5
408
SEQUENCE:0
409
SUMMARY;LANGUAGE=de:Marissa
410
TRANSP:OPAQUE
411
UID:AAAAAEyulq85HfZCtWDOITo5tZQHABE65KS0gg5Fu6X1g2z9eWUAAAAA3BAAABE65KS0gg5
412
 Fu6X1g2z9eWUAAAIQ6D0AAA==
413
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
414
X-MICROSOFT-CDO-IMPORTANCE:1
415
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
416
X-MS-OLK-AUTOSTARTCHECK:FALSE
417
X-MS-OLK-CONFTYPE:0
418
END:VEVENT
419
BEGIN:VEVENT
420
CLASS:PUBLIC
421
CREATED:20100331T124848Z
422
DTEND;VALUE=DATE:20100415
423
DTSTAMP:20100409T123209Z
424
DTSTART;VALUE=DATE:20100414
425
LAST-MODIFIED:20100331T124907Z
426
PRIORITY:5
427
SEQUENCE:0
428
SUMMARY;LANGUAGE=de:MELANIE wieder da
429
TRANSP:TRANSPARENT
430
UID:AAAAAEyulq85HfZCtWDOITo5tZQHABE65KS0gg5Fu6X1g2z9eWUAAAAA3BAAABE65KS0gg5
431
 Fu6X1g2z9eWUAAAIQ6DsAAA==
432
X-MICROSOFT-CDO-BUSYSTATUS:FREE
433
X-MICROSOFT-CDO-IMPORTANCE:1
434
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
435
X-MS-OLK-AUTOSTARTCHECK:FALSE
436
X-MS-OLK-CONFTYPE:0
437
END:VEVENT
438
END:VCALENDAR
439
';
440
	echo $GLOBALS['egw']->framework->header();
441
	//$ical_file = fopen('/tmp/KalenderFelicitasKubala.ics');
442
	if (!is_resource($ical_file)) echo "<pre>$ical_file</pre>\n";
0 ignored issues
show
introduced by
The condition is_resource($ical_file) is always false.
Loading history...
443
	//$calendar_ical = new calendar_ical();
444
	//$calendar_ical->setSupportedFields('file');
445
	$ical_it = new IcalIterator($ical_file);//,'VCALENDAR','iso-8859-1',array($calendar_ical,'_ical2egw_callback'),array('Europe/Berlin'));
446
	foreach($ical_it as $uid => $vevent)
447
	{
448
		echo "$uid<pre>".print_r($vevent->toHash(), true)."</pre>\n";
449
	}
450
	if (is_resource($ical_file)) fclose($ical_file);
0 ignored issues
show
introduced by
The condition is_resource($ical_file) is always false.
Loading history...
451
}