Andrew's Web Libraries (AWL)
Loading...
Searching...
No Matches
iCalendar.php
1<?php
2
3require_once('XMLElement.php');
4require_once('AwlQuery.php');
5
15class iCalProp {
25 var $name;
26
32 var $parameters;
33
39 var $content;
40
46 var $rendered;
47
58 function __construct( $propstring = null ) {
59 $this->name = "";
60 $this->content = "";
61 $this->parameters = array();
62 unset($this->rendered);
63 if ( $propstring != null && gettype($propstring) == 'string' ) {
64 $this->ParseFrom($propstring);
65 }
66 }
67
68
77 function ParseFrom( $propstring ) {
78 $this->rendered = (strlen($propstring) < 72 ? $propstring : null); // Only pre-rendered if we didn't unescape it
79
80 // Unescape newlines
81 $unescaped = preg_replace('{\\\\[nN]}', "\n", $propstring);
82
83 /*
84 * Split propname with params from propvalue. Searches for the first unquoted COLON.
85 *
86 * RFC5545 3.2
87 *
88 * Property parameter values that contain the COLON, SEMICOLON, or COMMA
89 * character separators MUST be specified as quoted-string text values.
90 * Property parameter values MUST NOT contain the DQUOTE character.
91 */
92 $split = $this->SplitQuoted($unescaped, ':', 2);
93 if (count($split) != 2) {
94 // Bad things happended...
95 dbg_error_log('ERROR', "iCalendar::ParseFrom(): Couldn't parse property from string: `%s`, skipping", $unescaped);
96 return;
97 }
98 list($prop, $value) = $split;
99
100 // Unescape ESCAPED-CHAR
101 $this->content = preg_replace( "/\\\\([,;:\"\\\\])/", '$1', $value);
102
103 // Split property name and parameters
104 $parameters = $this->SplitQuoted($prop, ';');
105 $this->name = array_shift($parameters);
106 $this->parameters = array();
107 foreach ($parameters AS $k => $v) {
108 $pos = strpos($v, '=');
109 $name = substr($v, 0, $pos);
110 $value = substr($v, $pos + 1);
111 $this->parameters[$name] = preg_replace('/^"(.+)"$/', '$1', $value); // Removes DQUOTE on demand
112 }
113// dbg_error_log('iCalendar', " iCalProp::ParseFrom found '%s' = '%s' with %d parameters", $this->name, substr($this->content,0,200), count($this->parameters) );
114 }
115
124 function SplitQuoted($str, $sep = ',', $limit = 0) {
125 $result = array();
126 $cursor = 0;
127 $inquote = false;
128 $num = 0;
129 for($i = 0, $len = strlen($str); $i < $len; ++$i) {
130 $ch = $str[$i];
131 if ($ch == '"') {
132 $inquote = !$inquote;
133 }
134 if (!$inquote && $ch == $sep) {
135 //var_dump("Found sep `$sep` - Splitting from $cursor to $i from $len.");
136 // If we reached the maximal number of splits, we cut till the end and stop here.
137 ++$num;
138 if ($limit > 0 && $num == $limit) {
139 $result[] = substr($str, $cursor);
140 break;
141 }
142 $result[] = substr($str, $cursor, $i - $cursor);
143 $cursor = $i + 1;
144 }
145 // Add rest of string on end reached
146 if ($i + 1 == $len) {
147 //var_dump("Reached end - Splitting from $cursor to $len.");
148 $result[] = substr($str, $cursor);
149 }
150 }
151
152 return $result;
153 }
154
162 function Name( $newname = null ) {
163 if ( $newname != null ) {
164 $this->name = $newname;
165 if ( isset($this->rendered) ) unset($this->rendered);
166// dbg_error_log('iCalendar', " iCalProp::Name(%s)", $this->name );
167 }
168 return $this->name;
169 }
170
171
179 function Value( $newvalue = null ) {
180 if ( $newvalue != null ) {
181 $this->content = $newvalue;
182 if ( isset($this->rendered) ) unset($this->rendered);
183 }
184 return $this->content;
185 }
186
187
195 function Parameters( $newparams = null ) {
196 if ( $newparams != null ) {
197 $this->parameters = $newparams;
198 if ( isset($this->rendered) ) unset($this->rendered);
199 }
200 return $this->parameters;
201 }
202
203
211 function TextMatch( $search ) {
212 if ( isset($this->content) ) {
213 return (stristr( $this->content, $search ) !== false);
214 }
215 return false;
216 }
217
218
226 function GetParameterValue( $name ) {
227 if ( isset($this->parameters[$name]) ) return $this->parameters[$name];
228 }
229
237 function SetParameterValue( $name, $value ) {
238 if ( isset($this->rendered) ) unset($this->rendered);
239 $this->parameters[$name] = $value;
240 }
241
246 function RenderParameters() {
247 $rendered = "";
248 foreach( $this->parameters AS $k => $v ) {
249 $escaped = preg_replace( "/([;:])/", '\\\\$1', $v);
250 $rendered .= sprintf( ";%s=%s", $k, $escaped );
251 }
252 return $rendered;
253 }
254
255
259 function Render() {
260 // If we still have the string it was parsed in from, it hasn't been screwed with
261 // and we can just return that without modification.
262 if ( isset($this->rendered) ) return $this->rendered;
263
264 $property = preg_replace( '/[;].*$/', '', $this->name );
265 $escaped = $this->content;
266 switch( $property ) {
268 case 'ATTACH': case 'GEO': case 'PERCENT-COMPLETE': case 'PRIORITY':
269 case 'DURATION': case 'FREEBUSY': case 'TZOFFSETFROM': case 'TZOFFSETTO':
270 case 'TZURL': case 'ATTENDEE': case 'ORGANIZER': case 'RECURRENCE-ID':
271 case 'URL': case 'EXRULE': case 'SEQUENCE': case 'CREATED':
272 case 'RRULE': case 'REPEAT': case 'TRIGGER':
273 break;
274
275 case 'COMPLETED': case 'DTEND':
276 case 'DUE': case 'DTSTART':
277 case 'DTSTAMP': case 'LAST-MODIFIED':
278 case 'CREATED': case 'EXDATE':
279 case 'RDATE':
280 if ( isset($this->parameters['VALUE']) && $this->parameters['VALUE'] == 'DATE' ) {
281 $escaped = substr( $escaped, 0, 8);
282 }
283 break;
284
286 default:
287 $escaped = str_replace( '\\', '\\\\', $escaped);
288 $escaped = preg_replace( '/\r?\n/', '\\n', $escaped);
289 $escaped = preg_replace( "/([,;])/", '\\\\$1', $escaped);
290 }
291 $property = sprintf( "%s%s:", $this->name, $this->RenderParameters() );
292 if ( (strlen($property) + strlen($escaped)) <= 72 ) {
293 $this->rendered = $property . $escaped;
294 }
295 else if ( (strlen($property) + strlen($escaped)) > 72 && (strlen($property) < 72) && (strlen($escaped) < 72) ) {
296 $this->rendered = $property . "\r\n " . $escaped;
297 }
298 else {
299 $this->rendered = preg_replace( '/(.{72})/u', '$1'."\r\n ", $property . $escaped );
300 }
301 return $this->rendered;
302 }
303
304}
305
306
326 var $type;
327
333 var $properties;
334
340 var $components;
341
347 var $rendered;
348
354 function __construct( $content = null ) {
355 $this->type = "";
356 $this->properties = array();
357 $this->components = array();
358 $this->rendered = "";
359 if ( $content != null && (gettype($content) == 'string' || gettype($content) == 'array') ) {
360 $this->ParseFrom($content);
361 }
362 }
363
364
369 function VCalendar( $extra_properties = null ) {
370 $this->SetType('VCALENDAR');
371 $this->AddProperty('PRODID', '-//davical.org//NONSGML AWL Calendar//EN');
372 $this->AddProperty('VERSION', '2.0');
373 $this->AddProperty('CALSCALE', 'GREGORIAN');
374 if ( is_array($extra_properties) ) {
375 foreach( $extra_properties AS $k => $v ) {
376 $this->AddProperty($k,$v);
377 }
378 }
379 }
380
385 function CollectParameterValues( $parameter_name ) {
386 $values = array();
387 foreach( $this->components AS $k => $v ) {
388 $also = $v->CollectParameterValues($parameter_name);
389 $values = array_merge( $values, $also );
390 }
391 foreach( $this->properties AS $k => $v ) {
392 $also = $v->GetParameterValue($parameter_name);
393 if ( isset($also) && $also != "" ) {
394// dbg_error_log( 'iCalendar', "::CollectParameterValues(%s) : Found '%s'", $parameter_name, $also);
395 $values[$also] = 1;
396 }
397 }
398 return $values;
399 }
400
401
406 function ParseFrom( $content ) {
407 $this->rendered = $content;
408 $content = $this->UnwrapComponent($content);
409
410 $type = false;
411 $subtype = false;
412 $finish = null;
413 $subfinish = null;
414
415 $length = strlen($content);
416 $linefrom = 0;
417 while( $linefrom < $length ) {
418 $lineto = strpos( $content, "\n", $linefrom );
419 if ( $lineto === false ) {
420 $lineto = strpos( $content, "\r", $linefrom );
421 }
422 if ( $lineto > 0 ) {
423 $line = substr( $content, $linefrom, $lineto - $linefrom);
424 $linefrom = $lineto + 1;
425 }
426 else {
427 $line = substr( $content, $linefrom );
428 $linefrom = $length;
429 }
430 if ( preg_match('/^\s*$/', $line ) ) continue;
431 $line = rtrim( $line, "\r\n" );
432// dbg_error_log( 'iCalendar', "::ParseFrom: Parsing line: $line");
433
434 if ( $type === false ) {
435 if ( preg_match( '/^BEGIN:(.+)$/', $line, $matches ) ) {
436 // We have found the start of the main component
437 $type = $matches[1];
438 $finish = "END:$type";
439 $this->type = $type;
440 dbg_error_log( 'iCalendar', "::ParseFrom: Start component of type '%s'", $type);
441 }
442 else {
443 dbg_error_log( 'iCalendar', "::ParseFrom: Ignoring crap before start of component: $line");
444 // unset($lines[$k]); // The content has crap before the start
445 if ( $line != "" ) $this->rendered = null;
446 }
447 }
448 else if ( $type == null ) {
449 dbg_error_log( 'iCalendar', "::ParseFrom: Ignoring crap after end of component");
450 if ( $line != "" ) $this->rendered = null;
451 }
452 else if ( $line == $finish ) {
453 dbg_error_log( 'iCalendar', "::ParseFrom: End of component");
454 $type = null; // We have reached the end of our component
455 }
456 else {
457 if ( $subtype === false && preg_match( '/^BEGIN:(.+)$/', $line, $matches ) ) {
458 // We have found the start of a sub-component
459 $subtype = $matches[1];
460 $subfinish = "END:$subtype";
461 $subcomponent = $line . "\r\n";
462 dbg_error_log( 'iCalendar', "::ParseFrom: Found a subcomponent '%s'", $subtype);
463 }
464 else if ( $subtype ) {
465 // We are inside a sub-component
466 $subcomponent .= $this->WrapComponent($line);
467 if ( $line == $subfinish ) {
468 dbg_error_log( 'iCalendar', "::ParseFrom: End of subcomponent '%s'", $subtype);
469 // We have found the end of a sub-component
470 $this->components[] = new iCalComponent($subcomponent);
471 $subtype = false;
472 }
473// else
474// dbg_error_log( 'iCalendar', "::ParseFrom: Inside a subcomponent '%s'", $subtype );
475 }
476 else {
477// dbg_error_log( 'iCalendar', "::ParseFrom: Parse property of component");
478 // It must be a normal property line within a component.
479 $this->properties[] = new iCalProp($line);
480 }
481 }
482 }
483 }
484
485
491 function UnwrapComponent( $content ) {
492 return preg_replace('/\r?\n[ \t]/', '', $content );
493 }
494
503 function WrapComponent( $content ) {
504 $strs = preg_split( "/\r?\n/", $content );
505 $wrapped = "";
506 foreach ($strs as $str) {
507 $wrapped .= preg_replace( '/(.{72})/u', '$1'."\r\n ", $str ) ."\r\n";
508 }
509 return $wrapped;
510 }
511
515 function GetType() {
516 return $this->type;
517 }
518
519
523 function SetType( $type ) {
524 if ( isset($this->rendered) ) unset($this->rendered);
525 $this->type = $type;
526 return $this->type;
527 }
528
529
533 function GetProperties( $type = null ) {
534 $properties = array();
535 foreach( $this->properties AS $k => $v ) {
536 if ( $type == null || $v->Name() == $type ) {
537 $properties[$k] = $v;
538 }
539 }
540 return $properties;
541 }
542
543
551 function GetPValue( $type ) {
552 foreach( $this->properties AS $k => $v ) {
553 if ( $v->Name() == $type ) return $v->Value();
554 }
555 return null;
556 }
557
558
567 function GetPParamValue( $type, $parameter_name ) {
568 foreach( $this->properties AS $k => $v ) {
569 if ( $v->Name() == $type ) return $v->GetParameterValue($parameter_name);
570 }
571 return null;
572 }
573
574
579 function ClearProperties( $type = null ) {
580 if ( $type != null ) {
581 // First remove all the existing ones of that type
582 foreach( $this->properties AS $k => $v ) {
583 if ( $v->Name() == $type ) {
584 unset($this->properties[$k]);
585 if ( isset($this->rendered) ) unset($this->rendered);
586 }
587 }
588 $this->properties = array_values($this->properties);
589 }
590 else {
591 if ( isset($this->rendered) ) unset($this->rendered);
592 $this->properties = array();
593 }
594 }
595
596
600 function SetProperties( $new_properties, $type = null ) {
601 if ( isset($this->rendered) && count($new_properties) > 0 ) unset($this->rendered);
602 $this->ClearProperties($type);
603 foreach( $new_properties AS $k => $v ) {
604 $this->AddProperty($v);
605 }
606 }
607
608
616 function AddProperty( $new_property, $value = null, $parameters = null ) {
617 if ( isset($this->rendered) ) unset($this->rendered);
618 if ( isset($value) && gettype($new_property) == 'string' ) {
619 $new_prop = new iCalProp();
620 $new_prop->Name($new_property);
621 $new_prop->Value($value);
622 if ( $parameters != null ) $new_prop->Parameters($parameters);
623 dbg_error_log('iCalendar'," Adding new property '%s'", $new_prop->Render() );
624 $this->properties[] = $new_prop;
625 }
626 else if ( gettype($new_property) ) {
627 $this->properties[] = $new_property;
628 }
629 }
630
631
636 function &FirstNonTimezone( $type = null ) {
637 foreach( $this->components AS $k => $v ) {
638 if ( $v->GetType() != 'VTIMEZONE' ) return $this->components[$k];
639 }
640 $result = false;
641 return $result;
642 }
643
644
651 function IsOrganizer( $email ) {
652 if ( !preg_match( '#^mailto:#', $email ) ) $email = 'mailto:'.$email;
653 $props = $this->GetPropertiesByPath('!VTIMEZONE/ORGANIZER');
654 foreach( $props AS $k => $prop ) {
655 if ( $prop->Value() == $email ) return true;
656 }
657 return false;
658 }
659
660
667 function IsAttendee( $email ) {
668 if ( !preg_match( '#^mailto:#', $email ) ) $email = 'mailto:'.$email;
669 if ( $this->IsOrganizer($email) ) return true;
670 $props = $this->GetPropertiesByPath('!VTIMEZONE/ATTENDEE');
671 foreach( $props AS $k => $prop ) {
672 if ( $prop->Value() == $email ) return true;
673 }
674 return false;
675 }
676
677
686 function GetComponents( $type = null, $normal_match = true ) {
687 $components = $this->components;
688 if ( $type != null ) {
689 foreach( $components AS $k => $v ) {
690 if ( ($v->GetType() != $type) === $normal_match ) {
691 unset($components[$k]);
692 }
693 }
694 $components = array_values($components);
695 }
696 return $components;
697 }
698
699
704 function ClearComponents( $type = null ) {
705 if ( $type != null ) {
706 // First remove all the existing ones of that type
707 foreach( $this->components AS $k => $v ) {
708 if ( $v->GetType() == $type ) {
709 unset($this->components[$k]);
710 if ( isset($this->rendered) ) unset($this->rendered);
711 }
712 else {
713 if ( ! $this->components[$k]->ClearComponents($type) ) {
714 if ( isset($this->rendered) ) unset($this->rendered);
715 }
716 }
717 }
718 return isset($this->rendered);
719 }
720 else {
721 if ( isset($this->rendered) ) unset($this->rendered);
722 $this->components = array();
723 }
724 }
725
726
733 function SetComponents( $new_component, $type = null ) {
734 if ( isset($this->rendered) ) unset($this->rendered);
735 if ( count($new_component) > 0 ) $this->ClearComponents($type);
736 foreach( $new_component AS $k => $v ) {
737 $this->components[] = $v;
738 }
739 }
740
741
747 function AddComponent( $new_component ) {
748 if ( is_array($new_component) && count($new_component) == 0 ) return;
749 if ( isset($this->rendered) ) unset($this->rendered);
750 if ( is_array($new_component) ) {
751 foreach( $new_component AS $k => $v ) {
752 $this->components[] = $v;
753 }
754 }
755 else {
756 $this->components[] = $new_component;
757 }
758 }
759
760
765 function MaskComponents( $keep ) {
766 foreach( $this->components AS $k => $v ) {
767 if ( ! in_array( $v->GetType(), $keep ) ) {
768 unset($this->components[$k]);
769 if ( isset($this->rendered) ) unset($this->rendered);
770 }
771 else {
772 $v->MaskComponents($keep);
773 }
774 }
775 }
776
777
783 function MaskProperties( $keep, $component_list=null ) {
784 foreach( $this->components AS $k => $v ) {
785 $v->MaskProperties($keep, $component_list);
786 }
787
788 if ( !isset($component_list) || in_array($this->GetType(), $component_list) ) {
789 foreach( $this->properties AS $k => $v ) {
790 if ( ! in_array( $v->name, $keep ) ) {
791 unset($this->properties[$k]);
792 if ( isset($this->rendered) ) unset($this->rendered);
793 }
794 }
795 }
796 }
797
798
804 function CloneConfidential() {
805 $confidential = clone($this);
806 $keep_properties = array( 'DTSTAMP', 'DTSTART', 'RRULE', 'DURATION', 'DTEND', 'DUE', 'UID', 'CLASS', 'TRANSP', 'CREATED', 'LAST-MODIFIED' );
807 $resource_components = array( 'VEVENT', 'VTODO', 'VJOURNAL' );
808 $confidential->MaskComponents(array( 'VTIMEZONE', 'STANDARD', 'DAYLIGHT', 'VEVENT', 'VTODO', 'VJOURNAL' ));
809 $confidential->MaskProperties($keep_properties, $resource_components );
810
811 if ( isset($confidential->rendered) )
812 unset($confidential->rendered); // we need to re-render the whole object
813
814 if ( in_array( $confidential->GetType(), $resource_components ) ) {
815 $confidential->AddProperty( 'SUMMARY', translate('Busy') );
816 }
817 foreach( $confidential->components AS $k => $v ) {
818 if ( in_array( $v->GetType(), $resource_components ) ) {
819 $v->AddProperty( 'SUMMARY', translate('Busy') );
820 }
821 }
822
823 return $confidential;
824 }
825
834 function RenderWithoutWrap($restricted_properties = null){
835 // substr - remove new line of end, because new line
836 // are handled in vComponent::RenderWithoutWrap
837 return substr($this->Render($restricted_properties), 0 , -2);
838 }
839
840
844 function Render( $restricted_properties = null) {
845
846 $unrestricted = (!isset($restricted_properties) || count($restricted_properties) == 0);
847
848 if ( isset($this->rendered) && $unrestricted )
849 return $this->rendered;
850
851 $rendered = "BEGIN:$this->type\r\n";
852 foreach( $this->properties AS $k => $v ) {
853 if ( method_exists($v, 'Render') ) {
854 if ( $unrestricted || isset($restricted_properties[$v]) ) $rendered .= $v->Render() . "\r\n";
855 }
856 }
857 foreach( $this->components AS $v ) { $rendered .= $v->Render(); }
858 $rendered .= "END:$this->type\r\n";
859
860 $rendered = preg_replace('{(?<!\r)\n}', "\r\n", $rendered);
861 if ( $unrestricted ) $this->rendered = $rendered;
862
863 return $rendered;
864 }
865
866
876 function GetPropertiesByPath( $path ) {
877 $properties = array();
878 dbg_error_log( 'iCalendar', "GetPropertiesByPath: Querying within '%s' for path '%s'", $this->type, $path );
879 if ( !preg_match( '#(/?)(!?)([^/]+)(/?.*)$#', $path, $matches ) ) return $properties;
880
881 $adrift = ($matches[1] == '');
882 $normal = ($matches[2] == '');
883 $ourtest = $matches[3];
884 $therest = $matches[4];
885 dbg_error_log( 'iCalendar', "GetPropertiesByPath: Matches: %s -- %s -- %s -- %s\n", $matches[1], $matches[2], $matches[3], $matches[4] );
886 if ( $ourtest == '*' || (($ourtest == $this->type) === $normal) && $therest != '' ) {
887 if ( preg_match( '#^/(!?)([^/]+)$#', $therest, $matches ) ) {
888 $normmatch = ($matches[1] =='');
889 $proptest = $matches[2];
890 foreach( $this->properties AS $k => $v ) {
891 if ( $proptest == '*' || (($v->Name() == $proptest) === $normmatch ) ) {
892 $properties[] = $v;
893 }
894 }
895 }
896 else {
900 foreach( $this->components AS $k => $v ) {
901 $properties = array_merge( $properties, $v->GetPropertiesByPath($therest) );
902 }
903 }
904 }
905
906 if ( $adrift ) {
910 foreach( $this->components AS $k => $v ) {
911 $properties = array_merge( $properties, $v->GetPropertiesByPath($path) );
912 }
913 }
914 dbg_error_log('iCalendar', "GetPropertiesByPath: Found %d within '%s' for path '%s'\n", count($properties), $this->type, $path );
915 return $properties;
916 }
917
918}
IsAttendee( $email)
VCalendar( $extra_properties=null)
ParseFrom( $content)
SetType( $type)
CollectParameterValues( $parameter_name)
WrapComponent( $content)
IsOrganizer( $email)
AddProperty( $new_property, $value=null, $parameters=null)
MaskProperties( $keep, $component_list=null)
ClearComponents( $type=null)
__construct( $content=null)
SetComponents( $new_component, $type=null)
MaskComponents( $keep)
UnwrapComponent( $content)
& FirstNonTimezone( $type=null)
RenderWithoutWrap($restricted_properties=null)
GetPParamValue( $type, $parameter_name)
GetProperties( $type=null)
Render( $restricted_properties=null)
ClearProperties( $type=null)
SetProperties( $new_properties, $type=null)
GetPropertiesByPath( $path)
GetPValue( $type)
AddComponent( $new_component)
GetComponents( $type=null, $normal_match=true)
__construct( $propstring=null)
Definition iCalendar.php:58
SetParameterValue( $name, $value)
Value( $newvalue=null)
SplitQuoted($str, $sep=',', $limit=0)
Parameters( $newparams=null)
ParseFrom( $propstring)
Definition iCalendar.php:77
Name( $newname=null)
TextMatch( $search)
GetParameterValue( $name)
RenderParameters()