1: <?php
2: /**
3: * A Class for handling vCalendar & vCard data.
4: *
5: * When parsed the underlying structure is roughly as follows:
6: *
7: * vComponent( array(vComponent), array(vProperty) )
8: *
9: * @package awl
10: * @subpackage vComponent
11: * @author Milan Medlik <milan@morphoss.com>
12: * @copyright Morphoss Ltd <http://www.morphoss.com/>
13: * @license http://gnu.org/copyleft/lgpl.html GNU LGPL v2 or later
14: *
15: */
16:
17: include_once('vObject.php');
18: //include_once('HeapLines.php');
19: include_once('vProperty.php');
20:
21: class vComponent extends vObject{
22:
23: private $components;
24: private $properties;
25: private $type;
26: private $iterator;
27: private $seekBegin;
28: private $seekEnd;
29: private $propertyLocation;
30:
31: const KEYBEGIN = 'BEGIN:';
32: const KEYBEGINLENGTH = 6;
33: const KEYEND = "END:";
34: const KEYENDLENGTH = 4;
35: const VEOL = "\r\n";
36:
37: public static $PREPARSED = false;
38:
39: function __construct($propstring=null, &$refData=null){
40: parent::__construct($master);
41:
42: unset($this->type);
43:
44: if(isset($propstring) && gettype($propstring) == 'string'){
45: $this->initFromText($propstring);
46: } else if(isset($refData)){
47: if(gettype($refData) == 'string'){
48: $this->initFromText($refData);
49: } else if(gettype($refData) == 'object') {
50: $this->initFromIterator($refData);
51: }
52: } else {
53: //$text = '';
54: //$this->initFromText($text);
55: }
56:
57:
58: // if(isset($this->iterator)){
59: // $this->parseFrom($this->iterator);
60: // }
61:
62:
63: }
64:
65: function initFromIterator(&$iterator, $begin = -1){
66: $this->iterator = &$iterator;
67:
68: //$this->seekBegin = $this->iterator->key();
69:
70:
71:
72: $iterator = $this->iterator;
73: do {
74: $line = $iterator->current();
75: $seek = $iterator->key();
76:
77: $posStart = strpos(strtoupper($line), vComponent::KEYBEGIN);
78: if($posStart !== false && $posStart == 0){
79: if(!isset($this->type)){
80: $this->seekBegin = $seek;
81:
82: $this->type = strtoupper(substr($line, vComponent::KEYBEGINLENGTH));
83: }
84: } else {
85:
86: $posEnd = strpos(strtoupper($line), vComponent::KEYEND);
87: if($posEnd !== false && $posEnd == 0){
88: $thisEnd = strtoupper(substr($line, vComponent::KEYENDLENGTH));
89: if($thisEnd == $this->type){
90: $this->seekEnd = $seek;
91: //$iterator->next();
92: $len = strlen($this->type);
93: $last = $this->type[$len-1];
94: if($last == "\r"){
95: $this->type = strtoupper(substr($this->type, 0, $len-1));
96: }
97: break;
98: }
99:
100: } else {
101: //$this->properties[] = new vProperty(null, $iterator, $seek);
102: }
103: }
104:
105:
106:
107:
108: $iterator->next();
109: } while($iterator->valid());
110: //$this->parseFrom($iterator);
111:
112: }
113:
114: public function getIterator(){
115: return $this->iterator;
116: }
117:
118: function initFromText(&$plainText){
119: $plain2 = $this->UnwrapComponent($plainText);
120:
121: //$file = fopen('data.out.tmp', 'w');
122: //$plain3 = preg_replace('{\r?\n}', '\r\n', $plain2 );
123: //fwrite($file, $plain2);
124: //fclose($file);
125: //$lines = &explode(PHP_EOL, $plain2);
126: // $arrayData = new ArrayObject($lines);
127: // $this->iterator = &$arrayData->getIterator();
128: // $this->initFromIterator($this->iterator, 0);
129: // unset($plain);
130: // unset($iterator);
131: // unset($arrayData);
132: // unset($lines);
133:
134: // Note that we can't use PHP_EOL here, since the line splitting should handle *either* of CR, CRLF or LF line endings.
135: $arrayOfLines = new ArrayObject(preg_split('{\r?\n}', $plain2));
136: $this->iterator = $arrayOfLines->getIterator();
137: unset($plain2);
138: //$this->initFromIterator($this->iterator);
139: //$this->iterator = new HeapLines($plain);
140:
141: //$this->initFromIterator(new HeapLines($plain), 0);
142: $this->parseFrom($this->iterator);
143:
144: }
145:
146: function rewind(){
147: if(isset($this->iterator) && isset($this->seekBegin)){
148: $this->iterator->seek($this->seekBegin);
149: }
150: }
151:
152:
153: /**
154: * fill arrays with components and properties if they are empty.
155: *
156: * basicaly the object are just pointer to memory with input data
157: * (iterator with seek address on start and end)
158: * but when you want get information of any components
159: * or properties is necessary call this function first
160: *
161: * @see GetComponents(), ComponentsCount(), GetProperties(), PropertiesCount()
162: */
163: function explode(){
164: if(!isset($this->properties) && !isset($this->components) && $this->isValid()){
165: unset($this->properties);
166: unset($this->components);
167: unset($this->type);
168: $this->rewind();
169: $this->parseFrom($this->iterator);
170: }
171: }
172:
173: function close(){
174:
175: if(isset($this->components)){
176: foreach($this->components as $comp){
177: $comp->close();
178: }
179: }
180:
181: if($this->isValid()){
182: unset($this->properties);
183: unset($this->components);
184: }
185:
186:
187:
188: }
189:
190: function parseFrom(&$iterator){
191:
192:
193: $begin = $iterator->key();
194: $typelen = 0;
195: //$count = $lines->count();
196:
197: do{
198: $line = $iterator->current();
199: //$line = substr($current, 0, strlen($current) -1);
200: $end = $iterator->key();
201:
202: $pos = strpos(strtoupper($line), vComponent::KEYBEGIN);
203: $callnext = true;
204: if($pos !== false && $pos == 0) {
205: $type = strtoupper(substr($line, vComponent::KEYBEGINLENGTH));
206:
207: if($typelen !== 0 && strncmp($this->type, $type, $typelen) !== 0){
208: $this->components[] = new vComponent(null, $iterator);
209: $callnext = false;
210: } else {
211: // in special cases when is "\r" on end remove it
212: // We should probably throw an error if we get here, because the
213: // thing that splits stuff should not be giving us this.
214: $typelen = strlen($type);
215: if($type[$typelen-1] == "\r"){
216: $typelen--;
217: $this->type = substr($type, 0, $typelen);
218: } else {
219: $this->type = $type;
220: }
221:
222:
223: //$iterator->offsetUnset($end);
224: //$iterator->seek($begin);
225: //$callnext = false;
226: }
227:
228: } else {
229: $pos = strpos(strtoupper($line), vComponent::KEYEND);
230:
231: if($pos !== false && $pos == 0) {
232: $this->seekBegin = $begin;
233: $this->seekEnd = $end;
234: //$iterator->offsetUnset($end);
235: //$iterator->seek($end-2);
236: //$line2 = $iterator->current();
237: //$this->seekEnd = $iterator->key();
238:
239: //$callnext = false;
240: //$newheap = $lines->createLineHeapFrom($start, $end);
241: //$testfistline = $newheap->substr(0);
242: //echo "end:" . $this->key . "[$start, $end]<br>";
243: //$lines->nextLine();
244: //$iterator->offsetUnset($end);
245: return;
246: } else {
247: // $prstart = $lines->getSwheretartLineOnHeap();
248: // $prend =
249: //$this->properties[] = new vProperty("AHOJ");
250: $parameters = preg_split( '(:|;)', $line);
251: $possiblename = strtoupper(array_shift( $parameters ));
252: $this->properties[] = new vProperty($possiblename, $this->master, $iterator, $end);
253: //echo $this->key . ' property line' . "[$prstart,$prend]<br>";
254:
255: }
256: }
257:
258: // if($callnext){
259: // $iterator->next();
260: // }
261: //if($callnext)
262: // $iterator->offsetUnset($end);
263: if($iterator->valid())
264: $iterator->next();
265:
266: } while($iterator->valid() && ( !isset($this->seekEnd) || $this->seekEnd > $end ) );
267: //$lines->getEndLineOnHeap();
268:
269:
270: }
271:
272:
273:
274: /**
275: * count of component
276: * @return int
277: */
278: public function ComponentCount(){
279: $this->explode();
280: return isset($this->components) ? count($this->components) : 0;
281: }
282:
283: /**
284: * count of component
285: * @return int
286: */
287: public function propertiesCount(){
288: $this->explode();
289: return isset($this->properties) ? count($this->properties) : 0;
290: }
291:
292: /**
293: * @param $position
294: * @return null - whet is position out of range
295: */
296: public function getComponentAt($position){
297: $this->explode();
298: if($this->ComponentCount() > $position){
299: return $this->components[$position];
300: } else {
301: return null;
302: }
303: }
304:
305: function getPropertyAt($position){
306: $this->explode();
307: if($this->propertiesCount() > $position){
308: return $this->properties[$position];
309: } else {
310: return null;
311: }
312:
313: }
314:
315: function clearPropertyAt($position) {
316: $this->explode();
317: if($this->isValid()){
318: $this->invalidate();
319: }
320:
321: $i=0; // property index/position is relative to current vComponent
322: foreach( $this->properties AS $k => $v ) {
323: if ( $i == $position ) {
324: unset($this->properties[$k]);
325: }
326: $i++;
327: }
328: $this->properties = array_values($this->properties);
329: }
330:
331:
332: /**
333: * Return the type of component which this is
334: */
335: function GetType() {
336: return $this->type;
337: }
338:
339:
340: /**
341: * Set the type of component which this is
342: */
343: function SetType( $type ) {
344: if ( $this->isValid() ) {
345: $this->invalidate();
346: };
347: $this->type = strtoupper($type);
348: return $this->type;
349: }
350:
351:
352: /**
353: * Collect an array of all parameters of our properties which are the specified type
354: * Mainly used for collecting the full variety of references TZIDs
355: */
356: function CollectParameterValues( $parameter_name ) {
357: $this->explode();
358: $values = array();
359: if(isset($this->components)){
360: foreach( $this->components AS $k => $v ) {
361: $also = $v->CollectParameterValues($parameter_name);
362: $values = array_merge( $values, $also );
363: }
364: }
365: if(isset($this->properties)){
366: foreach( $this->properties AS $k => $v ) {
367: $also = $v->GetParameterValue($parameter_name);
368: if ( isset($also) && $also != "" ) {
369: // dbg_error_log( 'vComponent', "::CollectParameterValues(%s) : Found '%s'", $parameter_name, $also);
370: $values[$also] = 1;
371: }
372: }
373: }
374:
375: return $values;
376: }
377:
378:
379: /**
380: * Return the first instance of a property of this name
381: */
382: function GetProperty( $type ) {
383: $this->explode();
384: foreach( $this->properties AS $k => $v ) {
385: if ( is_object($v) && $v->Name() == $type ) {
386: return $v;
387: }
388: else if ( !is_object($v) ) {
389: dbg_error_log("ERROR", 'vComponent::GetProperty(): Trying to get %s on %s which is not an object!', $type, $v );
390: }
391: }
392: /** So we can call methods on the result of this, make sure we always return a vProperty of some kind */
393: return null;
394: }
395:
396: /**
397: * Return the value of the first instance of a property of this name, or null
398: */
399: function GetPValue( $type ) {
400: $this->explode();
401: $p = $this->GetProperty($type);
402: if ( isset($p) ) return $p->Value();
403: return null;
404: }
405:
406:
407: /**
408: * Get all properties, or the properties matching a particular type, or matching an
409: * array associating property names with true values: array( 'PROPERTY' => true, 'PROPERTY2' => true )
410: */
411: function GetProperties( $type = null ) {
412: // the properties in base are with name
413: // it was setted in parseFrom(&interator)
414: if(!isset($this->properties)){
415: $this->explode();
416: }
417: $properties = array();
418: $testtypes = (gettype($type) == 'string' ? array( $type => true ) : $type );
419: foreach( $this->properties AS $k => $v ) {
420: $name = $v->Name(); //get Property name (string)
421: $name = preg_replace( '/^.*[.]/', '', $name ); //for grouped properties we remove prefix itemX.
422: if ( $type == null || (isset($testtypes[$name]) && $testtypes[$name])) {
423: $properties[] = $v;
424: }
425: }
426: return $properties;
427: }
428:
429: /**
430: * Return an array of properties matching the specified path
431: *
432: * @return array An array of vProperty within the tree which match the path given, in the form
433: * [/]COMPONENT[/...]/PROPERTY in a syntax kind of similar to our poor man's XML queries. We
434: * also allow COMPONENT and PROPERTY to be !COMPONENT and !PROPERTY for ++fun.
435: *
436: * @note At some point post PHP4 this could be re-done with an iterator, which should be more efficient for common use cases.
437: */
438: function GetPropertiesByPath( $path ) {
439: $properties = array();
440: dbg_error_log( 'vComponent', "GetPropertiesByPath: Querying within '%s' for path '%s'", $this->type, $path );
441: if ( !preg_match( '#(/?)(!?)([^/]+)(/?.*)$#', $path, $matches ) ) return $properties;
442:
443: $anchored = ($matches[1] == '/');
444: $inverted = ($matches[2] == '!');
445: $ourtest = $matches[3];
446: $therest = $matches[4];
447: dbg_error_log( 'vComponent', "GetPropertiesByPath: Matches: %s -- %s -- %s -- %s\n", $matches[1], $matches[2], $matches[3], $matches[4] );
448: if ( $ourtest == '*' || (($ourtest == $this->type) !== $inverted) && $therest != '' ) {
449: if ( preg_match( '#^/(!?)([^/]+)$#', $therest, $matches ) ) {
450: $normmatch = ($matches[1] =='');
451: $proptest = $matches[2];
452:
453: $thisproperties = $this->GetProperties();
454: if(isset($thisproperties) && count($thisproperties) > 0){
455: foreach( $thisproperties AS $k => $v ) {
456: if ( $proptest == '*' || (($v->Name() == $proptest) === $normmatch ) ) {
457: $properties[] = $v;
458: }
459: }
460: }
461:
462: }
463: else {
464: /**
465: * There is more to the path, so we recurse into that sub-part
466: */
467: foreach( $this->GetComponents() AS $k => $v ) {
468: $properties = array_merge( $properties, $v->GetPropertiesByPath($therest) );
469: }
470: }
471: }
472:
473: if ( ! $anchored ) {
474: /**
475: * Our input $path was not rooted, so we recurse further
476: */
477: foreach( $this->GetComponents() AS $k => $v ) {
478: $properties = array_merge( $properties, $v->GetPropertiesByPath($path) );
479: }
480: }
481: dbg_error_log('vComponent', "GetPropertiesByPath: Found %d within '%s' for path '%s'\n", count($properties), $this->type, $path );
482: return $properties;
483: }
484:
485: /**
486: * Clear all properties, or the properties matching a particular type
487: * @param string|array $type The type of property - omit for all properties - or an
488: * array associating property names with true values: array( 'PROPERTY' => true, 'PROPERTY2' => true )
489: */
490: function ClearProperties( $type = null ) {
491: $this->explode();
492: if($this->isValid()){
493: $this->invalidate();
494: }
495:
496: if ( $type != null ) {
497: $testtypes = (gettype($type) == 'string' ? array( $type => true ) : $type );
498: // First remove all the existing ones of that type
499: foreach( $this->properties AS $k => $v ) {
500: if ( isset($testtypes[$v->Name()]) && $testtypes[$v->Name()] ) {
501: unset($this->properties[$k]);
502:
503: }
504: }
505: $this->properties = array_values($this->properties);
506: }
507: else {
508:
509: $this->properties = array();
510: }
511: }
512:
513: /**
514: * Set all properties, or the ones matching a particular type
515: */
516: function SetProperties( $new_properties, $type = null ) {
517: $this->explode();
518: $this->ClearProperties($type);
519: foreach( $new_properties AS $k => $v ) {
520: $this->properties[] = $v;
521: }
522: }
523:
524: /**
525: * Adds a new property
526: *
527: * @param vProperty $new_property The new property to append to the set, or a string with the name
528: * @param string $value The value of the new property (default: param 1 is an vProperty with everything
529: * @param array $parameters The key/value parameter pairs (default: none, or param 1 is an vProperty with everything)
530: */
531: function AddProperty( $new_property, $value = null, $parameters = null ) {
532: $this->explode();
533: if ( isset($value) && gettype($new_property) == 'string' ) {
534: $new_prop = new vProperty('', $this->master);
535: $new_prop->Name($new_property);
536: $new_prop->Value($value);
537: if ( $parameters != null ) {
538: $new_prop->Parameters($parameters);
539: }
540: // dbg_error_log('vComponent'," Adding new property '%s'", $new_prop->Render() );
541: $this->properties[] = $new_prop;
542: }
543: else if ( $new_property instanceof vProperty ) {
544: $this->properties[] = $new_property;
545: $new_property->setMaster($this->master);
546: }
547:
548: if($this->isValid()){
549: $this->invalidate();
550: }
551: }
552:
553: /**
554: * Get all sub-components, or at least get those matching a type, or failling to match,
555: * should the second parameter be set to false. Component types may be a string or an array
556: * associating property names with true values: array( 'TYPE' => true, 'TYPE2' => true )
557: *
558: * @param mixed $type The type(s) to match (default: All)
559: * @param boolean $normal_match Set to false to invert the match (default: true)
560: * @return array an array of the sub-components
561: */
562: function GetComponents( $type = null, $normal_match = true ) {
563: $this->explode();
564: $components = isset($this->components) ? $this->components : array();
565:
566: if ( $type != null ) {
567: //$components = $this->components;
568: $testtypes = (gettype($type) == 'string' ? array( $type => true ) : $type );
569: foreach( $components AS $k => $v ) {
570: // printf( "Type: %s, %s, %s\n", $v->GetType(),
571: // ($normal_match && isset($testtypes[$v->GetType()]) && $testtypes[$v->GetType()] ? 'true':'false'),
572: // ( !$normal_match && (!isset($testtypes[$v->GetType()]) || !$testtypes[$v->GetType()]) ? 'true':'false')
573: // );
574: if ( !($normal_match && isset($testtypes[$v->GetType()]) && $testtypes[$v->GetType()] )
575: && !( !$normal_match && (!isset($testtypes[$v->GetType()]) || !$testtypes[$v->GetType()])) ) {
576: unset($components[$k]);
577: }
578: }
579: $components = array_values($components);
580: }
581: // print_r($components);
582: return $components;
583: }
584:
585:
586: /**
587: * Clear all components, or the components matching a particular type
588: * @param string $type The type of component - omit for all components
589: */
590: function ClearComponents( $type = null ) {
591: if($this->isValid()){
592: $this->explode();
593: }
594:
595:
596: if ( $type != null && !empty($this->components)) {
597: $testtypes = (gettype($type) == 'string' ? array( $type => true ) : $type );
598: // First remove all the existing ones of that type
599: foreach( $this->components AS $k => $v ) {
600: $this->components[$k]->ClearComponents($testtypes);
601: if ( isset($testtypes[$v->GetType()]) && $testtypes[$v->GetType()] ) {
602: unset($this->components[$k]);
603: if ( $this->isValid()) {
604: $this->invalidate();
605: }
606: }
607:
608: }
609: }
610: else {
611: $this->components = array();
612: if ( $this->isValid()) {
613: $this->invalidate();
614: }
615: }
616:
617: return $this->isValid();
618: }
619:
620: /**
621: * Sets some or all sub-components of the component to the supplied new components
622: *
623: * @param array of vComponent $new_components The new components to replace the existing ones
624: * @param string $type The type of components to be replaced. Defaults to null, which means all components will be replaced.
625: */
626: function SetComponents( $new_component, $type = null ) {
627: $this->explode();
628: if ( $this->isValid()) {
629: $this->invalidate();
630: }
631: if ( empty($type) ) {
632: $this->components = $new_component;
633: return;
634: }
635:
636: $this->ClearComponents($type);
637: foreach( $new_component AS $k => $v ) {
638: $this->components[] = $v;
639: }
640: }
641:
642: /**
643: * Adds a new subcomponent
644: *
645: * @param vComponent $new_component The new component to append to the set
646: */
647: public function AddComponent( $new_component ) {
648: $this->explode();
649: if ( is_array($new_component) && count($new_component) == 0 ) return;
650:
651: if ( $this->isValid()) {
652: $this->invalidate();
653: }
654:
655: try {
656: if ( is_array($new_component) ) {
657: foreach( $new_component AS $k => $v ) {
658: $this->components[] = $v;
659: if ( !method_exists($v,'setMaster') ) fatal('Component to be added must be a vComponent');
660: $v->setMaster($this->master);
661: }
662: }
663: else {
664: if ( !method_exists($new_component,'setMaster') ) fatal('Component to be added must be a vComponent');
665: $new_component->setMaster($this->master);
666: $this->components[] = $new_component;
667: }
668: }
669: catch( Exception $e ) {
670: fatal();
671: }
672: }
673:
674:
675: /**
676: * Mask components, removing any that are not of the types in the list
677: * @param array $keep An array of component types to be kept
678: * @param boolean $recursive (default true) Whether to recursively MaskComponents on the ones we find
679: */
680: function MaskComponents( $keep, $recursive = true ) {
681: $this->explode();
682: if(!isset($this->components)){
683: return ;
684: }
685:
686: foreach( $this->components AS $k => $v ) {
687: if ( !isset($keep[$v->GetType()]) ) {
688: unset($this->components[$k]);
689: if ( $this->isValid()) {
690: $this->invalidate();
691: }
692: }
693: else if ( $recursive ) {
694: $v->MaskComponents($keep);
695: }
696: }
697: }
698:
699: /**
700: * Mask properties, removing any that are not in the list
701: * @param array $keep An array of property names to be kept
702: * @param array $component_list An array of component types to check within
703: */
704: function MaskProperties( $keep, $component_list=null ) {
705: $this->explode();
706: if ( !isset($component_list) || isset($component_list[$this->type]) ) {
707: foreach( $this->properties AS $k => $v ) {
708: if ( !isset($keep[$v->Name()]) || !$keep[$v->Name()] ) {
709: unset($this->properties[$k]);
710: if ( $this->isValid()) {
711: $this->invalidate();
712: }
713: }
714: }
715: }
716: if(isset($this->components)){
717: foreach( $this->components AS $k => $v ) {
718: $v->MaskProperties($keep, $component_list);
719: }
720: }
721:
722: }
723:
724: /**
725: * This imposes the (CRLF + linear space) wrapping specified in RFC2445. According
726: * to RFC2445 we should always end with CRLF but the CalDAV spec says that normalising
727: * XML parsers often muck with it and may remove the CR. We output RFC2445 compliance.
728: *
729: * In order to preserve pre-existing wrapping in the component, we split the incoming
730: * string on line breaks before running wordwrap over each component of that.
731: */
732: function WrapComponent( $content ) {
733: $strs = preg_split( "/\r?\n/", $content );
734: $wrapped = "";
735: foreach ($strs as $str) {
736: // print "Before: >>$str<<, len(".strlen($str).")\n";
737: $wrapped_bit = (strlen($str) == 72 ? $str : preg_replace( '/(.{72})/u', '$1'."\r\n ", $str )) .self::VEOL;
738: // print "After: >>$wrapped_bit<<\n";
739: $wrapped .= $wrapped_bit;
740: }
741: return $wrapped;
742: }
743:
744: /**
745: * This unescapes the (CRLF + linear space) wrapping specified in RFC2445. According
746: * to RFC2445 we should always end with CRLF but the CalDAV spec says that normalising
747: * XML parsers often muck with it and may remove the CR. We accept either case.
748: */
749: function UnwrapComponent( &$content ) {
750: return preg_replace('/\r?\n[ \t]/', '', $content );
751: }
752:
753:
754: /**
755: * Render vComponent without wrap lines
756: * @param null $restricted_properties
757: * @param bool $force_rendering
758: * @return string
759: */
760: protected function RenderWithoutWrap($restricted_properties = null, $force_rendering = false){
761: $unrolledComponents = isset($this->components);
762: $rendered = vComponent::KEYBEGIN . $this->type . self::VEOL;
763:
764:
765: if($this->isValid()){
766: $rendered .= $this->RenderWithoutWrapFromIterator($unrolledComponents);
767: } else {
768: $rendered .= $this->RenderWithoutWrapFromObjects();
769: }
770:
771: if($unrolledComponents){
772: //$count = 0;
773: foreach($this->components as $component){
774: //$component->explode();
775: //$count++;
776: $component_render = $component->RenderWithoutWrap( null, $force_rendering );
777: if(strlen($component_render) > 0){
778: $rendered .= $component_render . self::VEOL;
779: }
780:
781: //$component->close();
782:
783: }
784: }
785:
786: return $rendered . vComponent::KEYEND . $this->type;
787: }
788:
789: /**
790: * Let render property by property
791: * @return string
792: */
793: protected function RenderWithoutWrapFromObjects(){
794: $rendered = '';
795: if(isset($this->properties)){
796: foreach( $this->properties AS $k => $v ) {
797: if ( method_exists($v, 'Render') ) {
798: $forebug = $v->Render() . self::VEOL;
799: $rendered .= $forebug;
800: }
801: }
802: }
803:
804: return $rendered;
805: }
806:
807: /**
808: * take source data in Iterator and recreate to string
809: * @param boolean $unroledComponents - have any components
810: * @return string - rendered object
811: */
812: protected function RenderWithoutWrapFromIterator($unrolledComponents){
813: $this->rewind();
814: $rendered = '';
815: $lentype = 0;
816:
817: if(isset($this->type)){
818: $lentype = strlen($this->type);
819: }
820:
821: $iterator = $this->iterator;
822: $inInnerObject = 0;
823: do {
824: $line = $iterator->current() . self::VEOL;
825: $seek = $iterator->key();
826:
827: $posStart = strpos($line, vComponent::KEYBEGIN);
828: if($posStart !== false && $posStart == 0){
829: $type = substr($line, vComponent::KEYBEGINLENGTH);
830: if(!isset($this->type)){
831: //$this->seekBegin = $seek;
832: $this->type = $type;
833: $lentype = strlen($this->type);
834: } else if(strncmp($type, $this->type, $lentype) != 0){
835: // dont render line which is owned
836: // by inner commponent -> inner component *BEGIN*
837: if($unrolledComponents){
838: $inInnerObject++;
839: } else {
840: $rendered .= $line ;
841: }
842: }
843: } else {
844:
845: $posEnd = strpos($line, vComponent::KEYEND);
846: if($posEnd !== false && $posEnd == 0){
847: $thisEnd = substr($line, vComponent::KEYENDLENGTH);
848: if(strncmp($thisEnd, $this->type, $lentype) == 0){
849: // Current object end
850: $this->seekEnd = $seek;
851: //$iterator->next();
852: break;
853: }else if($unrolledComponents){
854: // dont render line which is owned
855: // by inner commponent -> inner component *END*
856: $inInnerObject--;
857: } else {
858: $rendered .= $line;
859: }
860:
861: } else if($inInnerObject === 0 || !$unrolledComponents){
862: $rendered .= $line;
863: }
864: }
865: $iterator->next();
866: } while($iterator->valid() && ( !isset($this->seekEnd) || $this->seekEnd > $seek));
867:
868:
869: return $rendered;
870:
871: }
872:
873:
874: /**
875: * render object to string with wraped lines
876: * @param null $restricted_properties
877: * @param bool $force_rendering
878: * @return string - rendered object
879: */
880: function Render($restricted_properties = null, $force_rendering = false){
881: return $this->WrapComponent($this->RenderWithoutWrap($restricted_properties, $force_rendering));
882: //return $this->InternalRender($restricted_properties, $force_rendering);
883: }
884:
885: function isValid(){
886: if($this->valid){
887: if(isset($this->components)){
888: foreach($this->components as $comp){
889: if(!$comp->isValid()){
890: return false;
891: }
892: }
893: }
894:
895: return true;
896: }
897: return false;
898: }
899:
900: /**
901: * Test a PROP-FILTER or COMP-FILTER and return a true/false
902: * COMP-FILTER (is-defined | is-not-defined | (time-range?, prop-filter*, comp-filter*))
903: * PROP-FILTER (is-defined | is-not-defined | ((time-range | text-match)?, param-filter*))
904: *
905: * @param array $filter An array of XMLElement defining the filter
906: *
907: * @return boolean Whether or not this vComponent passes the test
908: */
909: function TestFilter( $filters ) {
910: foreach( $filters AS $k => $v ) {
911: $tag = $v->GetNSTag();
912: // dbg_error_log( 'vCalendar', ":TestFilter: '%s' ", $tag );
913: switch( $tag ) {
914: case 'urn:ietf:params:xml:ns:caldav:is-defined':
915: case 'urn:ietf:params:xml:ns:carddav:is-defined':
916: if ( count($this->properties) == 0 && count($this->components) == 0 ) return false;
917: break;
918:
919: case 'urn:ietf:params:xml:ns:caldav:is-not-defined':
920: case 'urn:ietf:params:xml:ns:carddav:is-not-defined':
921: if ( count($this->properties) > 0 || count($this->components) > 0 ) return false;
922: break;
923:
924: case 'urn:ietf:params:xml:ns:caldav:comp-filter':
925: case 'urn:ietf:params:xml:ns:carddav:comp-filter':
926: $subcomponents = $this->GetComponents($v->GetAttribute('name'));
927: $subfilter = $v->GetContent();
928: // dbg_error_log( 'vCalendar', ":TestFilter: Found '%d' (of %d) subs of type '%s'",
929: // count($subcomponents), count($this->components), $v->GetAttribute('name') );
930: $subtag = $subfilter[0]->GetNSTag();
931: if ( $subtag == 'urn:ietf:params:xml:ns:caldav:is-not-defined'
932: || $subtag == 'urn:ietf:params:xml:ns:carddav:is-not-defined' ) {
933: if ( count($properties) > 0 ) {
934: // dbg_error_log( 'vComponent', ":TestFilter: Wanted none => false" );
935: return false;
936: }
937: }
938: else if ( count($subcomponents) == 0 ) {
939: if ( $subtag == 'urn:ietf:params:xml:ns:caldav:is-defined'
940: || $subtag == 'urn:ietf:params:xml:ns:carddav:is-defined' ) {
941: // dbg_error_log( 'vComponent', ":TestFilter: Wanted some => false" );
942: return false;
943: }
944: else {
945: // dbg_error_log( 'vCalendar', ":TestFilter: Wanted something from missing sub-components => false" );
946: $negate = $subfilter[0]->GetAttribute("negate-condition");
947: if ( empty($negate) || strtolower($negate) != 'yes' ) return false;
948: }
949: }
950: else {
951: foreach( $subcomponents AS $kk => $subcomponent ) {
952: if ( ! $subcomponent->TestFilter($subfilter) ) return false;
953: }
954: }
955: break;
956:
957: case 'urn:ietf:params:xml:ns:carddav:prop-filter':
958: case 'urn:ietf:params:xml:ns:caldav:prop-filter':
959: $subfilter = $v->GetContent();
960: $properties = $this->GetProperties($v->GetAttribute("name"));
961: dbg_error_log( 'vCalendar', ":TestFilter: Found '%d' props of type '%s'", count($properties), $v->GetAttribute('name') );
962: $subtag = $subfilter[0]->GetNSTag();
963: if ( $subtag == 'urn:ietf:params:xml:ns:caldav:is-not-defined'
964: || $subtag == 'urn:ietf:params:xml:ns:carddav:is-not-defined' ) {
965: if ( count($properties) > 0 ) {
966: // dbg_error_log( 'vCalendar', ":TestFilter: Wanted none => false" );
967: return false;
968: }
969: }
970: else if ( count($properties) == 0 ) {
971: if ( $subtag == 'urn:ietf:params:xml:ns:caldav:is-defined'
972: || $subtag == 'urn:ietf:params:xml:ns:carddav:is-defined' ) {
973: // dbg_error_log( 'vCalendar', ":TestFilter: Wanted some => false" );
974: return false;
975: }
976: else {
977: // dbg_error_log( 'vCalendar', ":TestFilter: Wanted '%s' from missing sub-properties => false", $subtag );
978: $negate = $subfilter[0]->GetAttribute("negate-condition");
979: if ( empty($negate) || strtolower($negate) != 'yes' ) return false;
980: }
981: }
982: else {
983: foreach( $properties AS $kk => $property ) {
984: if ( !$property->TestFilter($subfilter) ) return false;
985: }
986: }
987: break;
988: }
989: }
990: return true;
991: }
992:
993: }
994:
995:
996: