3 * A Class for handling vCalendar & vCard data.
5 * When parsed the underlying structure is roughly as follows:
7 * vComponent( array(vComponent), array(vProperty) )
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
17 include_once('vObject.php');
18 //include_once('HeapLines.php');
19 include_once('vProperty.php');
21 class vComponent
extends vObject
{
29 private $propertyLocation;
31 const KEYBEGIN
= 'BEGIN:';
32 const KEYBEGINLENGTH
= 6;
33 const KEYEND
= "END:";
34 const KEYENDLENGTH
= 4;
37 public static $PREPARSED = false;
39 function __construct($propstring=null, &$refData=null){
40 parent
::__construct($master);
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);
54 //$this->initFromText($text);
58 // if(isset($this->iterator)){
59 // $this->parseFrom($this->iterator);
65 function initFromIterator(&$iterator, $begin = -1){
66 $this->iterator
= &$iterator;
68 //$this->seekBegin = $this->iterator->key();
72 $iterator = $this->iterator
;
74 $line = $iterator->current();
75 $seek = $iterator->key();
77 $posStart = strpos(strtoupper($line), vComponent
::KEYBEGIN
);
78 if($posStart !== false && $posStart == 0){
79 if(!isset($this->type
)){
80 $this->seekBegin
= $seek;
82 $this->type
= strtoupper(substr($line, vComponent
::KEYBEGINLENGTH
));
86 $posEnd = strpos(strtoupper($line), vComponent
::KEYEND
);
87 if($posEnd !== false && $posEnd == 0){
88 $thisEnd = substr($line, vComponent
::KEYENDLENGTH
);
89 if($thisEnd == $this->type
){
90 $this->seekEnd
= $seek;
92 $len = strlen($this->type
);
93 $last = $this->type
[$len-1];
95 $this->type
= strtoupper(substr($this->type
, 0, $len-1));
101 //$this->properties[] = new vProperty(null, $iterator, $seek);
109 } while($iterator->valid());
110 //$this->parseFrom($iterator);
114 public function getIterator(){
115 return $this->iterator
;
118 function initFromText(&$plainText){
119 $plain2 = $this->UnwrapComponent($plainText);
121 //$file = fopen('data.out.tmp', 'w');
122 //$plain3 = preg_replace('{\r?\n}', '\r\n', $plain2 );
123 //fwrite($file, $plain2);
125 //$lines = &explode(PHP_EOL, $plain2);
126 // $arrayData = new ArrayObject($lines);
127 // $this->iterator = &$arrayData->getIterator();
128 // $this->initFromIterator($this->iterator, 0);
131 // unset($arrayData);
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();
138 //$this->initFromIterator($this->iterator);
139 //$this->iterator = new HeapLines($plain);
141 //$this->initFromIterator(new HeapLines($plain), 0);
142 $this->parseFrom($this->iterator
);
147 if(isset($this->iterator
) && isset($this->seekBegin
)){
148 $this->iterator
->seek($this->seekBegin
);
154 * fill arrays with components and properties if they are empty.
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
161 * @see GetComponents(), ComponentsCount(), GetProperties(), PropertiesCount()
164 if((!isset($this->properties
) ||
!isset($this->components
)) && $this->isValid()){
165 unset($this->properties
);
166 unset($this->components
);
169 $this->parseFrom($this->iterator
);
175 if(isset($this->components
)){
176 foreach($this->components
as $comp){
181 if($this->isValid()){
182 unset($this->properties
);
183 unset($this->components
);
190 function parseFrom(&$iterator){
193 $begin = $iterator->key();
195 //$count = $lines->count();
198 $line = $iterator->current();
199 //$line = substr($current, 0, strlen($current) -1);
200 $end = $iterator->key();
202 $pos = strpos(strtoupper($line), vComponent
::KEYBEGIN
);
204 if($pos !== false && $pos == 0) {
205 $type = strtoupper(substr($line, vComponent
::KEYBEGINLENGTH
));
207 if($typelen !== 0 && strncmp($this->type
, $type, $typelen) !== 0){
208 $this->components
[] = new vComponent(null, $iterator);
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"){
217 $this->type
= substr($type, 0, $typelen);
223 //$iterator->offsetUnset($end);
224 //$iterator->seek($begin);
229 $pos = strpos(strtoupper($line), vComponent
::KEYEND
);
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();
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);
247 // $prstart = $lines->getSwheretartLineOnHeap();
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>";
259 // $iterator->next();
262 // $iterator->offsetUnset($end);
264 } while($iterator->valid() && ( !isset($this->seekEnd
) ||
$this->seekEnd
> $end) );
265 //$lines->getEndLineOnHeap();
276 public function ComponentCount(){
278 return isset($this->components
) ?
count($this->components
) : 0;
285 public function propertiesCount(){
287 return isset($this->properties
) ?
count($this->properties
) : 0;
292 * @return null - whet is position out of range
294 public function getComponentAt($position){
296 if($this->ComponentCount() > $position){
297 return $this->components
[$position];
303 function getPropertyAt($position){
305 if($this->propertiesCount() > $position){
306 return $this->properties
[$position];
315 * Return the type of component which this is
323 * Set the type of component which this is
325 function SetType( $type ) {
326 if ( $this->isValid() ) {
329 $this->type
= strtoupper($type);
335 * Collect an array of all parameters of our properties which are the specified type
336 * Mainly used for collecting the full variety of references TZIDs
338 function CollectParameterValues( $parameter_name ) {
341 if(isset($this->components
)){
342 foreach( $this->components
AS $k => $v ) {
343 $also = $v->CollectParameterValues($parameter_name);
344 $values = array_merge( $values, $also );
347 if(isset($this->properties
)){
348 foreach( $this->properties
AS $k => $v ) {
349 $also = $v->GetParameterValue($parameter_name);
350 if ( isset($also) && $also != "" ) {
351 // dbg_error_log( 'vComponent', "::CollectParameterValues(%s) : Found '%s'", $parameter_name, $also);
362 * Return the first instance of a property of this name
364 function GetProperty( $type ) {
366 foreach( $this->properties
AS $k => $v ) {
367 if ( is_object($v) && $v->Name() == $type ) {
370 else if ( !is_object($v) ) {
371 debug_error_log("ERROR", 'vComponent::GetProperty(): Trying to get %s on %s which is not an object!', $type, $v );
374 /** So we can call methods on the result of this, make sure we always return a vProperty of some kind */
379 * Return the value of the first instance of a property of this name, or null
381 function GetPValue( $type ) {
383 $p = $this->GetProperty($type);
384 if ( isset($p) ) return $p->Value();
390 * Get all properties, or the properties matching a particular type, or matching an
391 * array associating property names with true values: array( 'PROPERTY' => true, 'PROPERTY2' => true )
393 function GetProperties( $type = null ) {
395 // the properties in base are with name
396 // it was setted in parseFrom(&interator)
397 if(!isset($this->properties
)){
401 $properties = array();
402 $testtypes = (gettype($type) == 'string' ?
array( $type => true ) : $type );
403 foreach( $this->properties
AS $k => $v ) {
404 if ( $type == null ||
(isset($testtypes[$v->Name()]) && $testtypes[$v->Name()]) ) {
412 * Return an array of properties matching the specified path
414 * @return array An array of vProperty within the tree which match the path given, in the form
415 * [/]COMPONENT[/...]/PROPERTY in a syntax kind of similar to our poor man's XML queries. We
416 * also allow COMPONENT and PROPERTY to be !COMPONENT and !PROPERTY for ++fun.
418 * @note At some point post PHP4 this could be re-done with an iterator, which should be more efficient for common use cases.
420 function GetPropertiesByPath( $path ) {
421 $properties = array();
422 dbg_error_log( 'vComponent', "GetPropertiesByPath: Querying within '%s' for path '%s'", $this->type
, $path );
423 if ( !preg_match( '#(/?)(!?)([^/]+)(/?.*)$#', $path, $matches ) ) return $properties;
425 $anchored = ($matches[1] == '/');
426 $inverted = ($matches[2] == '!');
427 $ourtest = $matches[3];
428 $therest = $matches[4];
429 dbg_error_log( 'vComponent', "GetPropertiesByPath: Matches: %s -- %s -- %s -- %s\n", $matches[1], $matches[2], $matches[3], $matches[4] );
430 if ( $ourtest == '*' ||
(($ourtest == $this->type
) !== $inverted) && $therest != '' ) {
431 if ( preg_match( '#^/(!?)([^/]+)$#', $therest, $matches ) ) {
432 $normmatch = ($matches[1] =='');
433 $proptest = $matches[2];
435 $thisproperties = $this->GetProperties();
436 if(isset($thisproperties) && count($thisproperties) > 0){
437 foreach( $thisproperties AS $k => $v ) {
438 if ( $proptest == '*' ||
(($v->Name() == $proptest) === $normmatch ) ) {
447 * There is more to the path, so we recurse into that sub-part
449 foreach( $this->GetComponents() AS $k => $v ) {
450 $properties = array_merge( $properties, $v->GetPropertiesByPath($therest) );
457 * Our input $path was not rooted, so we recurse further
459 foreach( $this->GetComponents() AS $k => $v ) {
460 $properties = array_merge( $properties, $v->GetPropertiesByPath($path) );
463 dbg_error_log('vComponent', "GetPropertiesByPath: Found %d within '%s' for path '%s'\n", count($properties), $this->type
, $path );
468 * Clear all properties, or the properties matching a particular type
469 * @param string|array $type The type of property - omit for all properties - or an
470 * array associating property names with true values: array( 'PROPERTY' => true, 'PROPERTY2' => true )
472 function ClearProperties( $type = null ) {
474 if($this->isValid()){
478 if ( $type != null ) {
479 $testtypes = (gettype($type) == 'string' ?
array( $type => true ) : $type );
480 // First remove all the existing ones of that type
481 foreach( $this->properties
AS $k => $v ) {
482 if ( isset($testtypes[$v->Name()]) && $testtypes[$v->Name()] ) {
483 unset($this->properties
[$k]);
487 $this->properties
= array_values($this->properties
);
491 $this->properties
= array();
496 * Set all properties, or the ones matching a particular type
498 function SetProperties( $new_properties, $type = null ) {
500 $this->ClearProperties($type);
501 foreach( $new_properties AS $k => $v ) {
502 $this->properties
[] = $v;
507 * Adds a new property
509 * @param vProperty $new_property The new property to append to the set, or a string with the name
510 * @param string $value The value of the new property (default: param 1 is an vProperty with everything
511 * @param array $parameters The key/value parameter pairs (default: none, or param 1 is an vProperty with everything)
513 function AddProperty( $new_property, $value = null, $parameters = null ) {
515 if ( isset($value) && gettype($new_property) == 'string' ) {
516 $new_prop = new vProperty('', $this->master
);
517 $new_prop->Name($new_property);
518 $new_prop->Value($value);
519 if ( $parameters != null ) {
520 $new_prop->Parameters($parameters);
522 // dbg_error_log('vComponent'," Adding new property '%s'", $new_prop->Render() );
523 $this->properties
[] = $new_prop;
525 else if ( $new_property instanceof vProperty
) {
526 $this->properties
[] = $new_property;
527 $new_property->setMaster($this->master
);
530 if($this->isValid()){
536 * Get all sub-components, or at least get those matching a type, or failling to match,
537 * should the second parameter be set to false. Component types may be a string or an array
538 * associating property names with true values: array( 'TYPE' => true, 'TYPE2' => true )
540 * @param mixed $type The type(s) to match (default: All)
541 * @param boolean $normal_match Set to false to invert the match (default: true)
542 * @return array an array of the sub-components
544 function GetComponents( $type = null, $normal_match = true ) {
546 $components = isset($this->components
) ?
$this->components
: array();
548 if ( $type != null ) {
549 //$components = $this->components;
550 $testtypes = (gettype($type) == 'string' ?
array( $type => true ) : $type );
551 foreach( $components AS $k => $v ) {
552 // printf( "Type: %s, %s, %s\n", $v->GetType(),
553 // ($normal_match && isset($testtypes[$v->GetType()]) && $testtypes[$v->GetType()] ? 'true':'false'),
554 // ( !$normal_match && (!isset($testtypes[$v->GetType()]) || !$testtypes[$v->GetType()]) ? 'true':'false')
556 if ( !($normal_match && isset($testtypes[$v->GetType()]) && $testtypes[$v->GetType()] )
557 && !( !$normal_match && (!isset($testtypes[$v->GetType()]) ||
!$testtypes[$v->GetType()])) ) {
558 unset($components[$k]);
561 $components = array_values($components);
563 // print_r($components);
569 * Clear all components, or the components matching a particular type
570 * @param string $type The type of component - omit for all components
572 function ClearComponents( $type = null ) {
573 if($this->isValid()){
578 if ( $type != null && !empty($this->components
)) {
579 $testtypes = (gettype($type) == 'string' ?
array( $type => true ) : $type );
580 // First remove all the existing ones of that type
581 foreach( $this->components
AS $k => $v ) {
582 $this->components
[$k]->ClearComponents($testtypes);
583 if ( isset($testtypes[$v->GetType()]) && $testtypes[$v->GetType()] ) {
584 unset($this->components
[$k]);
585 if ( $this->isValid()) {
593 $this->components
= array();
594 if ( $this->isValid()) {
599 return $this->isValid();
603 * Sets some or all sub-components of the component to the supplied new components
605 * @param array of vComponent $new_components The new components to replace the existing ones
606 * @param string $type The type of components to be replaced. Defaults to null, which means all components will be replaced.
608 function SetComponents( $new_component, $type = null ) {
610 if ( $this->isValid()) {
613 if ( empty($type) ) {
614 $this->components
= $new_component;
618 $this->ClearComponents($type);
619 foreach( $new_component AS $k => $v ) {
620 $this->components
[] = $v;
625 * Adds a new subcomponent
627 * @param vComponent $new_component The new component to append to the set
629 public function AddComponent( $new_component ) {
631 if ( is_array($new_component) && count($new_component) == 0 ) return;
633 if ( $this->isValid()) {
638 if ( is_array($new_component) ) {
639 foreach( $new_component AS $k => $v ) {
640 $this->components
[] = $v;
641 if ( !method_exists($v,'setMaster') ) fatal('Component to be added must be a vComponent');
642 $v->setMaster($this->master
);
646 if ( !method_exists($new_component,'setMaster') ) fatal('Component to be added must be a vComponent');
647 $new_component->setMaster($this->master
);
648 $this->components
[] = $new_component;
651 catch( Exception
$e ) {
658 * Mask components, removing any that are not of the types in the list
659 * @param array $keep An array of component types to be kept
660 * @param boolean $recursive (default true) Whether to recursively MaskComponents on the ones we find
662 function MaskComponents( $keep, $recursive = true ) {
664 if(!isset($this->components
)){
668 foreach( $this->components
AS $k => $v ) {
669 if ( !isset($keep[$v->GetType()]) ) {
670 unset($this->components
[$k]);
671 if ( $this->isValid()) {
675 else if ( $recursive ) {
676 $v->MaskComponents($keep);
682 * Mask properties, removing any that are not in the list
683 * @param array $keep An array of property names to be kept
684 * @param array $component_list An array of component types to check within
686 function MaskProperties( $keep, $component_list=null ) {
688 if ( !isset($component_list) ||
isset($component_list[$this->type
]) ) {
689 foreach( $this->properties
AS $k => $v ) {
690 if ( !isset($keep[$v->Name()]) ||
!$keep[$v->Name()] ) {
691 unset($this->properties
[$k]);
692 if ( $this->isValid()) {
698 if(isset($this->components
)){
699 foreach( $this->components
AS $k => $v ) {
700 $v->MaskProperties($keep, $component_list);
707 * This imposes the (CRLF + linear space) wrapping specified in RFC2445. According
708 * to RFC2445 we should always end with CRLF but the CalDAV spec says that normalising
709 * XML parsers often muck with it and may remove the CR. We output RFC2445 compliance.
711 * In order to preserve pre-existing wrapping in the component, we split the incoming
712 * string on line breaks before running wordwrap over each component of that.
714 function WrapComponent( $content ) {
715 $strs = preg_split( "/\r?\n/", $content );
717 foreach ($strs as $str) {
718 // print "Before: >>$str<<, len(".strlen($str).")\n";
719 $wrapped_bit = (strlen($str) == 72 ?
$str : preg_replace( '/(.{72})/u', '$1'."\r\n ", $str )) .self
::VEOL
;
720 // print "After: >>$wrapped_bit<<\n";
721 $wrapped .= $wrapped_bit;
727 * This unescapes the (CRLF + linear space) wrapping specified in RFC2445. According
728 * to RFC2445 we should always end with CRLF but the CalDAV spec says that normalising
729 * XML parsers often muck with it and may remove the CR. We accept either case.
731 function UnwrapComponent( &$content ) {
732 return preg_replace('/\r?\n[ \t]/', '', $content );
737 * Render vComponent without wrap lines
738 * @param null $restricted_properties
739 * @param bool $force_rendering
742 protected function RenderWithoutWrap($restricted_properties = null, $force_rendering = false){
743 $unrolledComponents = isset($this->components
);
744 $rendered = vComponent
::KEYBEGIN
. $this->type
. self
::VEOL
;
747 if($this->isValid()){
748 $rendered .= $this->RenderWithoutWrapFromIterator($unrolledComponents);
750 $rendered .= $this->RenderWithoutWrapFromObjects();
753 if($unrolledComponents){
755 foreach($this->components
as $component){
756 //$component->explode();
758 $component_render = $component->RenderWithoutWrap();
759 if(strlen($component_render) > 0){
760 $rendered .= $component_render . self
::VEOL
;
763 //$component->close();
768 return $rendered . vComponent
::KEYEND
. $this->type
;
772 * Let render property by property
775 protected function RenderWithoutWrapFromObjects(){
777 if(isset($this->properties
)){
778 foreach( $this->properties
AS $k => $v ) {
779 if ( method_exists($v, 'Render') ) {
780 $forebug = $v->Render() . self
::VEOL
;
781 $rendered .= $forebug;
790 * take source data in Iterator and recreate to string
791 * @param boolean $unroledComponents - have any components
792 * @return string - rendered object
794 protected function RenderWithoutWrapFromIterator($unrolledComponents){
799 if(isset($this->type
)){
800 $lentype = strlen($this->type
);
803 $iterator = $this->iterator
;
806 $line = $iterator->current() . self
::VEOL
;
807 $seek = $iterator->key();
809 $posStart = strpos($line, vComponent
::KEYBEGIN
);
810 if($posStart !== false && $posStart == 0){
811 $type = substr($line, vComponent
::KEYBEGINLENGTH
);
812 if(!isset($this->type
)){
813 //$this->seekBegin = $seek;
815 $lentype = strlen($this->type
);
816 } else if(strncmp($type, $this->type
, $lentype) != 0){
817 // dont render line which is owned
818 // by inner commponent -> inner component *BEGIN*
819 if($unrolledComponents){
827 $posEnd = strpos($line, vComponent
::KEYEND
);
828 if($posEnd !== false && $posEnd == 0){
829 $thisEnd = substr($line, vComponent
::KEYENDLENGTH
);
830 if(strncmp($thisEnd, $this->type
, $lentype) == 0){
831 // Current object end
832 $this->seekEnd
= $seek;
835 }else if($unrolledComponents){
836 // dont render line which is owned
837 // by inner commponent -> inner component *END*
843 } else if($inInnerObject === 0 ||
!$unrolledComponents){
848 } while($iterator->valid() && ( !isset($this->seekEnd
) ||
$this->seekEnd
> $seek));
857 * render object to string with wraped lines
858 * @param null $restricted_properties
859 * @param bool $force_rendering
860 * @return string - rendered object
862 function Render($restricted_properties = null, $force_rendering = false){
863 return $this->WrapComponent($this->RenderWithoutWrap($restricted_properties, $force_rendering));
864 //return $this->InternalRender($restricted_properties, $force_rendering);
869 if(isset($this->components
)){
870 foreach($this->components
as $comp){
871 if(!$comp->isValid()){
883 * Test a PROP-FILTER or COMP-FILTER and return a true/false
884 * COMP-FILTER (is-defined | is-not-defined | (time-range?, prop-filter*, comp-filter*))
885 * PROP-FILTER (is-defined | is-not-defined | ((time-range | text-match)?, param-filter*))
887 * @param array $filter An array of XMLElement defining the filter
889 * @return boolean Whether or not this vComponent passes the test
891 function TestFilter( $filters ) {
892 foreach( $filters AS $k => $v ) {
893 $tag = $v->GetNSTag();
894 // dbg_error_log( 'vCalendar', ":TestFilter: '%s' ", $tag );
896 case 'urn:ietf:params:xml:ns:caldav:is-defined':
897 case 'urn:ietf:params:xml:ns:carddav:is-defined':
898 if ( count($this->properties
) == 0 && count($this->components
) == 0 ) return false;
901 case 'urn:ietf:params:xml:ns:caldav:is-not-defined':
902 case 'urn:ietf:params:xml:ns:carddav:is-not-defined':
903 if ( count($this->properties
) > 0 ||
count($this->components
) > 0 ) return false;
906 case 'urn:ietf:params:xml:ns:caldav:comp-filter':
907 case 'urn:ietf:params:xml:ns:carddav:comp-filter':
908 $subcomponents = $this->GetComponents($v->GetAttribute('name'));
909 $subfilter = $v->GetContent();
910 // dbg_error_log( 'vCalendar', ":TestFilter: Found '%d' (of %d) subs of type '%s'",
911 // count($subcomponents), count($this->components), $v->GetAttribute('name') );
912 $subtag = $subfilter[0]->GetNSTag();
913 if ( $subtag == 'urn:ietf:params:xml:ns:caldav:is-not-defined'
914 ||
$subtag == 'urn:ietf:params:xml:ns:carddav:is-not-defined' ) {
915 if ( count($properties) > 0 ) {
916 // dbg_error_log( 'vComponent', ":TestFilter: Wanted none => false" );
920 else if ( count($subcomponents) == 0 ) {
921 if ( $subtag == 'urn:ietf:params:xml:ns:caldav:is-defined'
922 ||
$subtag == 'urn:ietf:params:xml:ns:carddav:is-defined' ) {
923 // dbg_error_log( 'vComponent', ":TestFilter: Wanted some => false" );
927 // dbg_error_log( 'vCalendar', ":TestFilter: Wanted something from missing sub-components => false" );
928 $negate = $subfilter[0]->GetAttribute("negate-condition");
929 if ( empty($negate) ||
strtolower($negate) != 'yes' ) return false;
933 foreach( $subcomponents AS $kk => $subcomponent ) {
934 if ( ! $subcomponent->TestFilter($subfilter) ) return false;
939 case 'urn:ietf:params:xml:ns:carddav:prop-filter':
940 case 'urn:ietf:params:xml:ns:caldav:prop-filter':
941 $subfilter = $v->GetContent();
942 $properties = $this->GetProperties($v->GetAttribute("name"));
943 dbg_error_log( 'vCalendar', ":TestFilter: Found '%d' props of type '%s'", count($properties), $v->GetAttribute('name') );
944 $subtag = $subfilter[0]->GetNSTag();
945 if ( $subtag == 'urn:ietf:params:xml:ns:caldav:is-not-defined'
946 ||
$subtag == 'urn:ietf:params:xml:ns:carddav:is-not-defined' ) {
947 if ( count($properties) > 0 ) {
948 // dbg_error_log( 'vCalendar', ":TestFilter: Wanted none => false" );
952 else if ( count($properties) == 0 ) {
953 if ( $subtag == 'urn:ietf:params:xml:ns:caldav:is-defined'
954 ||
$subtag == 'urn:ietf:params:xml:ns:carddav:is-defined' ) {
955 // dbg_error_log( 'vCalendar', ":TestFilter: Wanted some => false" );
959 // dbg_error_log( 'vCalendar', ":TestFilter: Wanted '%s' from missing sub-properties => false", $subtag );
960 $negate = $subfilter[0]->GetAttribute("negate-condition");
961 if ( empty($negate) ||
strtolower($negate) != 'yes' ) return false;
965 foreach( $properties AS $kk => $property ) {
966 if ( !$property->TestFilter($subfilter) ) return false;