Handle htmlspecialchars() differences between PHP < 5.4 vs >= 5.4
[awl.git] / inc / vComponent.php
bloba516653fa08d94f25d2b809314d272c8324c32a9
1 <?php
2 /**
3 * A Class for handling vCalendar & vCard data.
5 * When parsed the underlying structure is roughly as follows:
7 * vComponent( array(vComponent), array(vProperty) )
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
17 include_once('vObject.php');
18 //include_once('HeapLines.php');
19 include_once('vProperty.php');
21 class vComponent extends vObject{
23 private $components;
24 private $properties;
25 private $type;
26 private $iterator;
27 private $seekBegin;
28 private $seekEnd;
29 private $propertyLocation;
31 const KEYBEGIN = 'BEGIN:';
32 const KEYBEGINLENGTH = 6;
33 const KEYEND = "END:";
34 const KEYENDLENGTH = 4;
35 const VEOL = "\r\n";
37 public static $PREPARSED = false;
39 function __construct($propstring=null, &$refData=null){
40 parent::__construct($master);
42 unset($this->type);
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);
52 } else {
53 //$text = '';
54 //$this->initFromText($text);
58 // if(isset($this->iterator)){
59 // $this->parseFrom($this->iterator);
60 // }
65 function initFromIterator(&$iterator, $begin = -1){
66 $this->iterator = &$iterator;
68 //$this->seekBegin = $this->iterator->key();
72 $iterator = $this->iterator;
73 do {
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));
84 } else {
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;
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));
97 break;
100 } else {
101 //$this->properties[] = new vProperty(null, $iterator, $seek);
108 $iterator->next();
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);
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);
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);
141 //$this->initFromIterator(new HeapLines($plain), 0);
142 $this->parseFrom($this->iterator);
146 function rewind(){
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()
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);
173 function close(){
175 if(isset($this->components)){
176 foreach($this->components as $comp){
177 $comp->close();
181 if($this->isValid()){
182 unset($this->properties);
183 unset($this->components);
190 function parseFrom(&$iterator){
193 $begin = $iterator->key();
194 $typelen = 0;
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);
203 $callnext = true;
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);
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;
223 //$iterator->offsetUnset($end);
224 //$iterator->seek($begin);
225 //$callnext = false;
228 } else {
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();
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>";
258 // if($callnext){
259 // $iterator->next();
260 // }
261 //if($callnext)
262 // $iterator->offsetUnset($end);
263 $iterator->next();
264 } while($iterator->valid() && ( !isset($this->seekEnd) || $this->seekEnd > $end) );
265 //$lines->getEndLineOnHeap();
273 * count of component
274 * @return int
276 public function ComponentCount(){
277 $this->explode();
278 return isset($this->components) ? count($this->components) : 0;
282 * count of component
283 * @return int
285 public function propertiesCount(){
286 $this->explode();
287 return isset($this->properties) ? count($this->properties) : 0;
291 * @param $position
292 * @return null - whet is position out of range
294 public function getComponentAt($position){
295 $this->explode();
296 if($this->ComponentCount() > $position){
297 return $this->components[$position];
298 } else {
299 return null;
303 function getPropertyAt($position){
304 $this->explode();
305 if($this->propertiesCount() > $position){
306 return $this->properties[$position];
307 } else {
308 return null;
315 * Return the type of component which this is
317 function GetType() {
318 return $this->type;
323 * Set the type of component which this is
325 function SetType( $type ) {
326 if ( $this->isValid() ) {
327 $this->invalidate();
329 $this->type = strtoupper($type);
330 return $this->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 ) {
339 $this->explode();
340 $values = array();
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);
352 $values[$also] = 1;
357 return $values;
362 * Return the first instance of a property of this name
364 function GetProperty( $type ) {
365 $this->explode();
366 foreach( $this->properties AS $k => $v ) {
367 if ( is_object($v) && $v->Name() == $type ) {
368 return $v;
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 */
375 return null;
379 * Return the value of the first instance of a property of this name, or null
381 function GetPValue( $type ) {
382 $this->explode();
383 $p = $this->GetProperty($type);
384 if ( isset($p) ) return $p->Value();
385 return null;
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)){
398 $this->explode();
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()]) ) {
405 $properties[] = $v;
408 return $properties;
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 ) ) {
439 $properties[] = $v;
445 else {
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) );
455 if ( ! $anchored ) {
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 );
464 return $properties;
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 ) {
473 $this->explode();
474 if($this->isValid()){
475 $this->invalidate();
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);
489 else {
491 $this->properties = array();
496 * Set all properties, or the ones matching a particular type
498 function SetProperties( $new_properties, $type = null ) {
499 $this->explode();
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 ) {
514 $this->explode();
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()){
531 $this->invalidate();
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 ) {
545 $this->explode();
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')
555 // );
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);
564 return $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()){
574 $this->explode();
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()) {
586 $this->invalidate();
592 else {
593 $this->components = array();
594 if ( $this->isValid()) {
595 $this->invalidate();
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 ) {
609 $this->explode();
610 if ( $this->isValid()) {
611 $this->invalidate();
613 if ( empty($type) ) {
614 $this->components = $new_component;
615 return;
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 ) {
630 $this->explode();
631 if ( is_array($new_component) && count($new_component) == 0 ) return;
633 if ( $this->isValid()) {
634 $this->invalidate();
637 try {
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);
645 else {
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 ) {
652 fatal();
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 ) {
663 $this->explode();
664 if(!isset($this->components)){
665 return ;
668 foreach( $this->components AS $k => $v ) {
669 if ( !isset($keep[$v->GetType()]) ) {
670 unset($this->components[$k]);
671 if ( $this->isValid()) {
672 $this->invalidate();
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 ) {
687 $this->explode();
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()) {
693 $this->invalidate();
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 );
716 $wrapped = "";
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;
723 return $wrapped;
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
740 * @return string
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);
749 } else {
750 $rendered .= $this->RenderWithoutWrapFromObjects();
753 if($unrolledComponents){
754 //$count = 0;
755 foreach($this->components as $component){
756 //$component->explode();
757 //$count++;
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
773 * @return string
775 protected function RenderWithoutWrapFromObjects(){
776 $rendered = '';
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;
786 return $rendered;
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){
795 $this->rewind();
796 $rendered = '';
797 $lentype = 0;
799 if(isset($this->type)){
800 $lentype = strlen($this->type);
803 $iterator = $this->iterator;
804 $inInnerObject = 0;
805 do {
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;
814 $this->type = $type;
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){
820 $inInnerObject++;
821 } else {
822 $rendered .= $line ;
825 } else {
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;
833 //$iterator->next();
834 break;
835 }else if($unrolledComponents){
836 // dont render line which is owned
837 // by inner commponent -> inner component *END*
838 $inInnerObject--;
839 } else {
840 $rendered .= $line;
843 } else if($inInnerObject === 0 || !$unrolledComponents){
844 $rendered .= $line;
847 $iterator->next();
848 } while($iterator->valid() && ( !isset($this->seekEnd) || $this->seekEnd > $seek));
851 return $rendered;
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);
867 function isValid(){
868 if($this->valid){
869 if(isset($this->components)){
870 foreach($this->components as $comp){
871 if(!$comp->isValid()){
872 return false;
877 return true;
879 return false;
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 );
895 switch( $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;
899 break;
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;
904 break;
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" );
917 return 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" );
924 return false;
926 else {
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;
932 else {
933 foreach( $subcomponents AS $kk => $subcomponent ) {
934 if ( ! $subcomponent->TestFilter($subfilter) ) return false;
937 break;
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" );
949 return 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" );
956 return false;
958 else {
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;
964 else {
965 foreach( $properties AS $kk => $property ) {
966 if ( !$property->TestFilter($subfilter) ) return false;
969 break;
972 return true;