Overview

Packages

  • awl
    • AuthPlugin
    • AwlDatabase
    • Browser
    • classEditor
    • DataEntry
    • DataUpdate
    • EMail
    • iCalendar
    • MenuSet
    • PgQuery
    • Session
    • Translation
    • User
    • Utilities
    • Validation
    • vCalendar
    • vComponent
    • XMLDocument
    • XMLElement
  • None

Classes

  • AuthPlugin
  • AwlCache
  • AwlDatabase
  • AwlDBDialect
  • AwlQuery
  • AwlUpgrader
  • Browser
  • BrowserColumn
  • DBRecord
  • Editor
  • EditorField
  • EMail
  • EntryField
  • EntryForm
  • iCalComponent
  • iCalendar
  • iCalProp
  • MenuOption
  • MenuSet
  • Multipart
  • PgQuery
  • Session
  • SinglePart
  • User
  • Validation
  • vCalendar
  • vComponent
  • vObject
  • vProperty
  • XMLDocument
  • XMLElement

Functions

  • _awl_connect_configured_database
  • _CompareMenuSequence
  • auth_external
  • auth_other_awl
  • awl_replace_sql_args
  • awl_set_locale
  • awl_version
  • BuildXMLTree
  • check_by_regex
  • check_temporary_passwords
  • clean_string
  • connect_configured_database
  • dbg_error_log
  • dbg_log_array
  • define_byte_mappings
  • deprecated
  • duration
  • fatal
  • force_utf8
  • get_fields
  • getCacheInstance
  • gzdecode
  • i18n
  • init_gettext
  • olson_from_tzstring
  • param_to_global
  • qpg
  • quoted_printable_encode
  • replace_uri_params
  • session_salted_md5
  • session_salted_sha1
  • session_simple_md5
  • session_validate_password
  • sql_from_object
  • sql_from_post
  • trace_bug
  • translate
  • uuid
  • Overview
  • Package
  • Class
  1:   2:   3:   4:   5:   6:   7:   8:   9:  10:  11:  12:  13:  14:  15:  16:  17:  18:  19:  20:  21:  22:  23:  24:  25:  26:  27:  28:  29:  30:  31:  32:  33:  34:  35:  36:  37:  38:  39:  40:  41:  42:  43:  44:  45:  46:  47:  48:  49:  50:  51:  52:  53:  54:  55:  56:  57:  58:  59:  60:  61:  62:  63:  64:  65:  66:  67:  68:  69:  70:  71:  72:  73:  74:  75:  76:  77:  78:  79:  80:  81:  82:  83:  84:  85:  86:  87:  88:  89:  90:  91:  92:  93:  94:  95:  96:  97:  98:  99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370: 371: 372: 373: 374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384: 385: 386: 387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398: 399: 400: 401: 402: 403: 404: 405: 406: 407: 408: 409: 410: 411: 412: 413: 414: 415: 416: 417: 418: 419: 420: 421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440: 441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451: 452: 453: 454: 455: 456: 457: 458: 459: 460: 461: 462: 463: 464: 465: 466: 467: 468: 469: 470: 471: 472: 473: 474: 475: 476: 477: 478: 479: 480: 481: 482: 483: 484: 485: 486: 487: 488: 489: 490: 491: 492: 493: 494: 495: 496: 497: 498: 499: 500: 501: 502: 503: 504: 505: 506: 507: 508: 509: 510: 511: 512: 513: 514: 515: 516: 517: 518: 519: 520: 521: 522: 523: 524: 525: 526: 527: 528: 529: 530: 531: 532: 533: 534: 535: 536: 537: 538: 539: 540: 541: 542: 543: 544: 545: 546: 547: 548: 549: 550: 551: 552: 553: 554: 555: 556: 557: 558: 559: 560: 561: 562: 563: 564: 565: 566: 567: 568: 569: 570: 571: 572: 573: 574: 575: 576: 577: 578: 579: 580: 581: 582: 583: 584: 585: 586: 587: 588: 589: 590: 591: 592: 593: 594: 595: 596: 597: 598: 599: 600: 601: 602: 603: 604: 605: 606: 607: 608: 609: 610: 611: 612: 613: 614: 615: 616: 617: 618: 619: 620: 621: 622: 623: 624: 625: 626: 627: 628: 629: 630: 631: 632: 633: 634: 635: 636: 637: 638: 639: 640: 641: 642: 643: 644: 645: 646: 647: 648: 649: 650: 651: 652: 653: 654: 655: 656: 657: 658: 659: 660: 661: 662: 663: 664: 665: 666: 667: 668: 669: 670: 671: 672: 673: 674: 675: 676: 677: 678: 679: 680: 681: 682: 683: 684: 685: 686: 687: 688: 689: 690: 691: 692: 693: 694: 695: 696: 697: 698: 699: 700: 701: 702: 703: 704: 705: 706: 707: 708: 709: 710: 711: 712: 713: 714: 715: 716: 717: 718: 719: 720: 721: 722: 723: 724: 725: 726: 727: 728: 729: 730: 731: 732: 733: 734: 735: 736: 737: 738: 739: 740: 741: 742: 743: 744: 745: 746: 747: 748: 749: 750: 751: 752: 753: 754: 755: 756: 757: 758: 759: 760: 761: 762: 763: 764: 765: 766: 767: 768: 769: 770: 771: 772: 773: 774: 775: 776: 777: 778: 779: 780: 781: 782: 783: 784: 785: 786: 787: 788: 789: 790: 791: 792: 793: 794: 795: 796: 797: 798: 799: 800: 801: 802: 803: 804: 805: 806: 807: 808: 809: 810: 811: 812: 813: 814: 815: 816: 817: 818: 819: 820: 821: 822: 823: 824: 825: 826: 827: 828: 829: 830: 831: 832: 833: 834: 835: 836: 837: 838: 839: 840: 841: 842: 843: 844: 845: 846: 847: 848: 849: 850: 851: 852: 853: 854: 855: 856: 857: 858: 859: 860: 861: 862: 863: 864: 865: 866: 867: 868: 869: 870: 871: 872: 873: 874: 875: 876: 877: 878: 879: 880: 881: 882: 883: 884: 885: 886: 887: 888: 889: 890: 891: 892: 893: 894: 895: 896: 897: 898: 899: 900: 901: 902: 903: 904: 905: 906: 907: 908: 909: 910: 911: 912: 913: 914: 915: 916: 917: 918: 919: 920: 921: 922: 923: 924: 925: 926: 927: 928: 929: 930: 931: 932: 933: 934: 935: 936: 937: 938: 939: 940: 941: 942: 943: 944: 945: 946: 947: 948: 949: 950: 951: 952: 953: 954: 955: 956: 957: 958: 959: 960: 961: 962: 963: 964: 965: 966: 967: 968: 969: 970: 971: 972: 973: 974: 975: 976: 977: 978: 979: 980: 981: 982: 983: 984: 985: 986: 987: 988: 989: 990: 991: 992: 993: 994: 995: 996: 
<?php
/**
 * A Class for handling vCalendar & vCard data.
 *
 * When parsed the underlying structure is roughly as follows:
 *
 *   vComponent( array(vComponent), array(vProperty) )
 *
 * @package awl
 * @subpackage vComponent
 * @author Milan Medlik <milan@morphoss.com>
 * @copyright Morphoss Ltd <http://www.morphoss.com/>
 * @license   http://gnu.org/copyleft/lgpl.html GNU LGPL v2 or later
 *
 */

    include_once('vObject.php');
    //include_once('HeapLines.php');
    include_once('vProperty.php');

    class vComponent extends vObject{

        private $components;
        private $properties;
        private $type;
        private $iterator;
        private $seekBegin;
        private $seekEnd;
        private $propertyLocation;

        const KEYBEGIN = 'BEGIN:';
        const KEYBEGINLENGTH = 6;
        const KEYEND = "END:";
        const KEYENDLENGTH = 4;
        const VEOL = "\r\n";

        public static $PREPARSED = false;

        function __construct($propstring=null, &$refData=null){
            parent::__construct($master);

            unset($this->type);

            if(isset($propstring) && gettype($propstring) == 'string'){
                $this->initFromText($propstring);
            } else if(isset($refData)){
                if(gettype($refData) == 'string'){
                    $this->initFromText($refData);
                } else if(gettype($refData) == 'object') {
                    $this->initFromIterator($refData);
                }
            } else {
                //$text = '';
                //$this->initFromText($text);
            }


//            if(isset($this->iterator)){
//                $this->parseFrom($this->iterator);
//            }


        }

        function initFromIterator(&$iterator, $begin = -1){
            $this->iterator = &$iterator;

            //$this->seekBegin = $this->iterator->key();



            $iterator = $this->iterator;
            do {
                $line = $iterator->current();
                $seek = $iterator->key();

                $posStart = strpos(strtoupper($line), vComponent::KEYBEGIN);
                if($posStart !== false && $posStart == 0){
                    if(!isset($this->type)){
                        $this->seekBegin = $seek;

                        $this->type = strtoupper(substr($line, vComponent::KEYBEGINLENGTH));
                    }
                } else {

                    $posEnd = strpos(strtoupper($line), vComponent::KEYEND);
                    if($posEnd !== false && $posEnd == 0){
                        $thisEnd = substr($line, vComponent::KEYENDLENGTH);
                        if($thisEnd == $this->type){
                            $this->seekEnd = $seek;
                            //$iterator->next();
                            $len = strlen($this->type);
                            $last = $this->type[$len-1];
                            if($last == "\r"){
                                $this->type = strtoupper(substr($this->type, 0, $len-1));
                            }
                            break;
                        }

                    } else {
                        //$this->properties[] = new vProperty(null, $iterator, $seek);
                    }
                }




                $iterator->next();
            } while($iterator->valid());
            //$this->parseFrom($iterator);

        }

        public function getIterator(){
            return $this->iterator;
        }

        function initFromText(&$plainText){
            $plain2 = $this->UnwrapComponent($plainText);

            //$file = fopen('data.out.tmp', 'w');
            //$plain3 = preg_replace('{\r?\n}', '\r\n', $plain2 );
            //fwrite($file, $plain2);
            //fclose($file);
            //$lines = &explode(PHP_EOL, $plain2);
//            $arrayData = new ArrayObject($lines);
//            $this->iterator = &$arrayData->getIterator();
//            $this->initFromIterator($this->iterator, 0);
//            unset($plain);
//            unset($iterator);
//            unset($arrayData);
//            unset($lines);

            // Note that we can't use PHP_EOL here, since the line splitting should handle *either* of CR, CRLF or LF line endings.
            $arrayOfLines = new ArrayObject(preg_split('{\r?\n}', $plain2));
            $this->iterator = $arrayOfLines->getIterator();
            unset($plain2);
            //$this->initFromIterator($this->iterator);
            //$this->iterator = new HeapLines($plain);

            //$this->initFromIterator(new HeapLines($plain), 0);
            $this->parseFrom($this->iterator);

        }

        function rewind(){
            if(isset($this->iterator) && isset($this->seekBegin)){
                $this->iterator->seek($this->seekBegin);
            }
        }


        /**
         * fill arrays with components and properties if they are empty.
         *
         * basicaly the object are just pointer to memory with input data
         * (iterator with seek address on start and end)
         * but when you want get information of any components
         * or properties is necessary call this function first
         *
         * @see GetComponents(), ComponentsCount(), GetProperties(), PropertiesCount()
         */
        function explode(){
            if(!isset($this->properties) && !isset($this->components) && $this->isValid()){
                unset($this->properties);
                unset($this->components);
                unset($this->type);
                $this->rewind();
                $this->parseFrom($this->iterator);
            }
        }

        function close(){

            if(isset($this->components)){
                foreach($this->components as $comp){
                    $comp->close();
                }
            }

            if($this->isValid()){
                unset($this->properties);
                unset($this->components);
            }



        }

        function parseFrom(&$iterator){


            $begin = $iterator->key();
            $typelen = 0;
            //$count = $lines->count();

            do{
                $line = $iterator->current();
                //$line = substr($current, 0, strlen($current) -1);
                $end = $iterator->key();

                $pos = strpos(strtoupper($line), vComponent::KEYBEGIN);
                $callnext = true;
                if($pos !== false && $pos == 0) {
                    $type = strtoupper(substr($line, vComponent::KEYBEGINLENGTH));

                    if($typelen !== 0 && strncmp($this->type, $type, $typelen) !== 0){
                        $this->components[] = new vComponent(null, $iterator);
                        $callnext = false;
                    } else {
                        // in special cases when is "\r" on end remove it
                        // We should probably throw an error if we get here, because the
                        // thing that splits stuff should not be giving us this.
                        $typelen = strlen($type);
                        if($type[$typelen-1] == "\r"){
                            $typelen--;
                            $this->type = substr($type, 0, $typelen);
                        } else {
                            $this->type = $type;
                        }


                        //$iterator->offsetUnset($end);
                        //$iterator->seek($begin);
                        //$callnext = false;
                    }

                } else {
                    $pos = strpos(strtoupper($line), vComponent::KEYEND);

                    if($pos !== false && $pos == 0) {
                        $this->seekBegin = $begin;
                        $this->seekEnd = $end;
                        //$iterator->offsetUnset($end);
                        //$iterator->seek($end-2);
                        //$line2 = $iterator->current();
                        //$this->seekEnd = $iterator->key();

                        //$callnext = false;
                        //$newheap = $lines->createLineHeapFrom($start, $end);
                        //$testfistline = $newheap->substr(0);
                        //echo "end:" . $this->key . "[$start, $end]<br>";
                        //$lines->nextLine();
                        //$iterator->offsetUnset($end);
                        return;
                    } else {
//                    $prstart = $lines->getSwheretartLineOnHeap();
//                    $prend =
//$this->properties[] = new vProperty("AHOJ");
                        $parameters = preg_split( '(:|;)', $line);
                        $possiblename = strtoupper(array_shift( $parameters ));
                        $this->properties[] = new vProperty($possiblename, $this->master, $iterator, $end);
                        //echo $this->key . ' property line' . "[$prstart,$prend]<br>";

                    }
                }

//                if($callnext){
//                    $iterator->next();
//                }
                //if($callnext)
                //    $iterator->offsetUnset($end);
                if($iterator->valid())
                    $iterator->next();

            } while($iterator->valid() && ( !isset($this->seekEnd) || $this->seekEnd > $end ) );
            //$lines->getEndLineOnHeap();


        }



        /**
         * count of component
         * @return int
         */
        public function ComponentCount(){
            $this->explode();
            return isset($this->components) ? count($this->components) : 0;
        }

        /**
         * count of component
         * @return int
         */
        public function propertiesCount(){
            $this->explode();
            return isset($this->properties) ? count($this->properties) : 0;
        }

        /**
         * @param $position
         * @return null - whet is position out of range
         */
        public function getComponentAt($position){
            $this->explode();
            if($this->ComponentCount() > $position){
                return $this->components[$position];
            } else {
                return null;
            }
        }

        function getPropertyAt($position){
            $this->explode();
            if($this->propertiesCount() > $position){
                return $this->properties[$position];
            } else {
                return null;
            }

        }

        function clearPropertyAt($position) {
            $this->explode();
            if($this->isValid()){
                $this->invalidate();
            }

            $i=0;  // property index/position is relative to current vComponent
            foreach( $this->properties AS $k => $v ) {
                if ( $i == $position ) {
                    unset($this->properties[$k]);
                }
                $i++;
            }
            $this->properties = array_values($this->properties);
        }


        /**
         * Return the type of component which this is
         */
        function GetType() {
            return $this->type;
        }


        /**
         * Set the type of component which this is
         */
        function SetType( $type ) {
            if ( $this->isValid() ) {
                $this->invalidate();
            };
            $this->type = strtoupper($type);
            return $this->type;
        }


        /**
         * Collect an array of all parameters of our properties which are the specified type
         * Mainly used for collecting the full variety of references TZIDs
         */
        function CollectParameterValues( $parameter_name ) {
            $this->explode();
            $values = array();
            if(isset($this->components)){
                foreach( $this->components AS $k => $v ) {
                    $also = $v->CollectParameterValues($parameter_name);
                    $values = array_merge( $values, $also );
                }
            }
            if(isset($this->properties)){
                foreach( $this->properties AS $k => $v ) {
                    $also = $v->GetParameterValue($parameter_name);
                    if ( isset($also) && $also != "" ) {
//        dbg_error_log( 'vComponent', "::CollectParameterValues(%s) : Found '%s'", $parameter_name, $also);
                        $values[$also] = 1;
                    }
                }
            }

            return $values;
        }


        /**
         * Return the first instance of a property of this name
         */
        function GetProperty( $type ) {
            $this->explode();
            foreach( $this->properties AS $k => $v ) {
                if ( is_object($v) && $v->Name() == $type ) {
                    return $v;
                }
                else if ( !is_object($v) ) {
                    dbg_error_log("ERROR", 'vComponent::GetProperty(): Trying to get %s on %s which is not an object!', $type, $v );
                }
            }
            /** So we can call methods on the result of this, make sure we always return a vProperty of some kind */
            return null;
        }

        /**
         * Return the value of the first instance of a property of this name, or null
         */
        function GetPValue( $type ) {
            $this->explode();
            $p = $this->GetProperty($type);
            if ( isset($p) ) return $p->Value();
            return null;
        }


        /**
         * Get all properties, or the properties matching a particular type, or matching an
         * array associating property names with true values: array( 'PROPERTY' => true, 'PROPERTY2' => true )
         */
        function GetProperties( $type = null ) {
            // the properties in base are with name
            // it was setted in parseFrom(&interator)
            if(!isset($this->properties)){
                $this->explode();
            }
            $properties = array();
            $testtypes = (gettype($type) == 'string' ? array( $type => true ) : $type );
            foreach( $this->properties AS $k => $v ) {
                $name = $v->Name(); //get Property name (string)
                $name = preg_replace( '/^.*[.]/', '', $name ); //for grouped properties we remove prefix itemX.
                if ( $type == null || (isset($testtypes[$name]) && $testtypes[$name])) {
                    $properties[] = $v;
                }
            }
            return $properties;
        }

        /**
         * Return an array of properties matching the specified path
         *
         * @return array An array of vProperty within the tree which match the path given, in the form
         *  [/]COMPONENT[/...]/PROPERTY in a syntax kind of similar to our poor man's XML queries. We
         *  also allow COMPONENT and PROPERTY to be !COMPONENT and !PROPERTY for ++fun.
         *
         * @note At some point post PHP4 this could be re-done with an iterator, which should be more efficient for common use cases.
         */
        function GetPropertiesByPath( $path ) {
            $properties = array();
            dbg_error_log( 'vComponent', "GetPropertiesByPath: Querying within '%s' for path '%s'", $this->type, $path );
            if ( !preg_match( '#(/?)(!?)([^/]+)(/?.*)$#', $path, $matches ) ) return $properties;

            $anchored = ($matches[1] == '/');
            $inverted = ($matches[2] == '!');
            $ourtest = $matches[3];
            $therest = $matches[4];
            dbg_error_log( 'vComponent', "GetPropertiesByPath: Matches: %s -- %s -- %s -- %s\n", $matches[1], $matches[2], $matches[3], $matches[4] );
            if ( $ourtest == '*' || (($ourtest == $this->type) !== $inverted) && $therest != '' ) {
                if ( preg_match( '#^/(!?)([^/]+)$#', $therest, $matches ) ) {
                    $normmatch = ($matches[1] =='');
                    $proptest  = $matches[2];

                    $thisproperties = $this->GetProperties();
                    if(isset($thisproperties) && count($thisproperties) > 0){
                        foreach( $thisproperties AS $k => $v ) {
                            if ( $proptest == '*' || (($v->Name() == $proptest) === $normmatch ) ) {
                                $properties[] = $v;
                            }
                        }
                    }

                }
                else {
                    /**
                     * There is more to the path, so we recurse into that sub-part
                     */
                    foreach( $this->GetComponents() AS $k => $v ) {
                        $properties = array_merge( $properties, $v->GetPropertiesByPath($therest) );
                    }
                }
            }

            if ( ! $anchored ) {
                /**
                 * Our input $path was not rooted, so we recurse further
                 */
                foreach( $this->GetComponents() AS $k => $v ) {
                    $properties = array_merge( $properties, $v->GetPropertiesByPath($path) );
                }
            }
            dbg_error_log('vComponent', "GetPropertiesByPath: Found %d within '%s' for path '%s'\n", count($properties), $this->type, $path );
            return $properties;
        }

        /**
         * Clear all properties, or the properties matching a particular type
         * @param string|array $type The type of property - omit for all properties - or an
         * array associating property names with true values: array( 'PROPERTY' => true, 'PROPERTY2' => true )
         */
        function ClearProperties( $type = null ) {
            $this->explode();
            if($this->isValid()){
                $this->invalidate();
            }

            if ( $type != null ) {
                $testtypes = (gettype($type) == 'string' ? array( $type => true ) : $type );
                // First remove all the existing ones of that type
                foreach( $this->properties AS $k => $v ) {
                    if ( isset($testtypes[$v->Name()]) && $testtypes[$v->Name()] ) {
                        unset($this->properties[$k]);

                    }
                }
                $this->properties = array_values($this->properties);
            }
            else {

                $this->properties = array();
            }
        }

        /**
         * Set all properties, or the ones matching a particular type
         */
        function SetProperties( $new_properties, $type = null ) {
            $this->explode();
            $this->ClearProperties($type);
            foreach( $new_properties AS $k => $v ) {
                $this->properties[] = $v;
            }
        }

        /**
         * Adds a new property
         *
         * @param vProperty $new_property The new property to append to the set, or a string with the name
         * @param string $value The value of the new property (default: param 1 is an vProperty with everything
         * @param array $parameters The key/value parameter pairs (default: none, or param 1 is an vProperty with everything)
         */
        function AddProperty( $new_property, $value = null, $parameters = null ) {
            $this->explode();
            if ( isset($value) && gettype($new_property) == 'string' ) {
                $new_prop = new vProperty('', $this->master);
                $new_prop->Name($new_property);
                $new_prop->Value($value);
                if ( $parameters != null ) {
                    $new_prop->Parameters($parameters);
                }
//      dbg_error_log('vComponent'," Adding new property '%s'", $new_prop->Render() );
                $this->properties[] = $new_prop;
            }
            else if ( $new_property instanceof vProperty ) {
                $this->properties[] = $new_property;
                $new_property->setMaster($this->master);
            }

            if($this->isValid()){
                $this->invalidate();
            }
        }

        /**
         * Get all sub-components, or at least get those matching a type, or failling to match,
         * should the second parameter be set to false. Component types may be a string or an array
         * associating property names with true values: array( 'TYPE' => true, 'TYPE2' => true )
         *
         * @param mixed $type The type(s) to match (default: All)
         * @param boolean $normal_match Set to false to invert the match (default: true)
         * @return array an array of the sub-components
         */
        function GetComponents( $type = null, $normal_match = true ) {
            $this->explode();
            $components = isset($this->components) ? $this->components : array();

            if ( $type != null ) {
                //$components = $this->components;
                $testtypes = (gettype($type) == 'string' ? array( $type => true ) : $type );
                foreach( $components AS $k => $v ) {
//        printf( "Type: %s, %s, %s\n", $v->GetType(),
//                 ($normal_match && isset($testtypes[$v->GetType()]) && $testtypes[$v->GetType()] ? 'true':'false'),
//                 ( !$normal_match && (!isset($testtypes[$v->GetType()]) || !$testtypes[$v->GetType()]) ? 'true':'false')
//               );
                    if ( !($normal_match && isset($testtypes[$v->GetType()]) && $testtypes[$v->GetType()] )
                        && !( !$normal_match && (!isset($testtypes[$v->GetType()]) || !$testtypes[$v->GetType()])) ) {
                        unset($components[$k]);
                    }
                }
                $components = array_values($components);
            }
//    print_r($components);
            return $components;
        }


        /**
         * Clear all components, or the components matching a particular type
         * @param string $type The type of component - omit for all components
         */
        function ClearComponents( $type = null ) {
            if($this->isValid()){
                $this->explode();
            }


            if ( $type != null && !empty($this->components)) {
                $testtypes = (gettype($type) == 'string' ? array( $type => true ) : $type );
                // First remove all the existing ones of that type
                foreach( $this->components AS $k => $v ) {
                    $this->components[$k]->ClearComponents($testtypes);
                    if ( isset($testtypes[$v->GetType()]) && $testtypes[$v->GetType()] ) {
                        unset($this->components[$k]);
                        if ( $this->isValid()) {
                            $this->invalidate();
                        }
                    }

                }
            }
            else {
                $this->components = array();
                if ( $this->isValid()) {
                    $this->invalidate();
                }
            }

            return $this->isValid();
        }

        /**
         * Sets some or all sub-components of the component to the supplied new components
         *
         * @param array of vComponent $new_components The new components to replace the existing ones
         * @param string $type The type of components to be replaced.  Defaults to null, which means all components will be replaced.
         */
        function SetComponents( $new_component, $type = null ) {
            $this->explode();
            if ( $this->isValid()) {
                $this->invalidate();
            }
            if ( empty($type) ) {
                $this->components = $new_component;
                return;
            }

            $this->ClearComponents($type);
            foreach( $new_component AS $k => $v ) {
                $this->components[] = $v;
            }
        }

        /**
         * Adds a new subcomponent
         *
         * @param vComponent $new_component The new component to append to the set
         */
        public function AddComponent( $new_component ) {
            $this->explode();
            if ( is_array($new_component) && count($new_component) == 0 ) return;

            if ( $this->isValid()) {
                $this->invalidate();
            }

            try {
                if ( is_array($new_component) ) {
                    foreach( $new_component AS $k => $v ) {
                        $this->components[] = $v;
                        if ( !method_exists($v,'setMaster') ) fatal('Component to be added must be a vComponent');
                        $v->setMaster($this->master);
                    }
                }
                else {
                    if ( !method_exists($new_component,'setMaster') ) fatal('Component to be added must be a vComponent');
                    $new_component->setMaster($this->master);
                    $this->components[] = $new_component;
                }
            }
            catch( Exception $e ) {
                fatal();
            }
        }


        /**
         * Mask components, removing any that are not of the types in the list
         * @param array $keep An array of component types to be kept
         * @param boolean $recursive (default true) Whether to recursively MaskComponents on the ones we find
         */
        function MaskComponents( $keep, $recursive = true ) {
            $this->explode();
            if(!isset($this->components)){
                return ;
            }

            foreach( $this->components AS $k => $v ) {
                if ( !isset($keep[$v->GetType()]) ) {
                    unset($this->components[$k]);
                    if ( $this->isValid()) {
                        $this->invalidate();
                    }
                }
                else if ( $recursive ) {
                    $v->MaskComponents($keep);
                }
            }
        }

        /**
         * Mask properties, removing any that are not in the list
         * @param array $keep An array of property names to be kept
         * @param array $component_list An array of component types to check within
         */
        function MaskProperties( $keep, $component_list=null ) {
            $this->explode();
            if ( !isset($component_list) || isset($component_list[$this->type]) ) {
                foreach( $this->properties AS $k => $v ) {
                    if ( !isset($keep[$v->Name()]) || !$keep[$v->Name()] ) {
                        unset($this->properties[$k]);
                        if ( $this->isValid()) {
                            $this->invalidate();
                        }
                    }
                }
            }
            if(isset($this->components)){
                foreach( $this->components AS $k => $v ) {
                    $v->MaskProperties($keep, $component_list);
                }
            }

        }

        /**
         * This imposes the (CRLF + linear space) wrapping specified in RFC2445. According
         * to RFC2445 we should always end with CRLF but the CalDAV spec says that normalising
         * XML parsers often muck with it and may remove the CR.  We output RFC2445 compliance.
         *
         * In order to preserve pre-existing wrapping in the component, we split the incoming
         * string on line breaks before running wordwrap over each component of that.
         */
        function WrapComponent( $content ) {
            $strs = preg_split( "/\r?\n/", $content );
            $wrapped = "";
            foreach ($strs as $str) {
//                print "Before: >>$str<<, len(".strlen($str).")\n";
                $wrapped_bit = (strlen($str) == 72 ? $str : preg_replace( '/(.{72})/u', '$1'."\r\n ", $str )) .self::VEOL;
//                print "After: >>$wrapped_bit<<\n";
                $wrapped .= $wrapped_bit;
            }
            return $wrapped;
        }

        /**
         * This unescapes the (CRLF + linear space) wrapping specified in RFC2445. According
         * to RFC2445 we should always end with CRLF but the CalDAV spec says that normalising
         * XML parsers often muck with it and may remove the CR.  We accept either case.
         */
        function UnwrapComponent( &$content ) {
            return preg_replace('/\r?\n[ \t]/', '', $content );
        }


        /**
         * Render vComponent without wrap lines
         * @param null $restricted_properties
         * @param bool $force_rendering
         * @return string
         */
        protected function RenderWithoutWrap($restricted_properties = null, $force_rendering = false){
            $unrolledComponents = isset($this->components);
            $rendered = vComponent::KEYBEGIN . $this->type . self::VEOL;


            if($this->isValid()){
                $rendered .= $this->RenderWithoutWrapFromIterator($unrolledComponents);
            } else {
                $rendered .= $this->RenderWithoutWrapFromObjects();
            }

            if($unrolledComponents){
                //$count = 0;
                foreach($this->components as $component){
                    //$component->explode();
                    //$count++;
                    $component_render = $component->RenderWithoutWrap( null, $force_rendering );
                    if(strlen($component_render) > 0){
                        $rendered .= $component_render . self::VEOL;
                    }

                    //$component->close();

                }
            }

            return $rendered . vComponent::KEYEND . $this->type;
        }

        /**
         * Let render property by property
         * @return string
         */
        protected function RenderWithoutWrapFromObjects(){
            $rendered = '';
            if(isset($this->properties)){
                foreach( $this->properties AS $k => $v ) {
                    if ( method_exists($v, 'Render') ) {
                        $forebug = $v->Render() . self::VEOL;
                        $rendered .= $forebug;
                    }
                }
            }

            return $rendered;
        }

        /**
         * take source data in Iterator and recreate to string
         * @param boolean $unroledComponents - have any components
         * @return string - rendered object
         */
        protected function RenderWithoutWrapFromIterator($unrolledComponents){
            $this->rewind();
            $rendered = '';
            $lentype = 0;

            if(isset($this->type)){
                $lentype = strlen($this->type);
            }

            $iterator = $this->iterator;
            $inInnerObject = 0;
            do {
                $line = $iterator->current() . self::VEOL;
                $seek = $iterator->key();

                $posStart = strpos($line, vComponent::KEYBEGIN);
                if($posStart !== false && $posStart == 0){
                    $type = substr($line, vComponent::KEYBEGINLENGTH);
                    if(!isset($this->type)){
                        //$this->seekBegin = $seek;
                        $this->type = $type;
                        $lentype = strlen($this->type);
                    } else if(strncmp($type, $this->type, $lentype) != 0){
                        // dont render line which is owned
                        // by inner commponent -> inner component *BEGIN*
                        if($unrolledComponents){
                            $inInnerObject++;
                        } else {
                            $rendered .= $line ;
                        }
                    }
                } else {

                    $posEnd = strpos($line, vComponent::KEYEND);
                    if($posEnd !== false && $posEnd == 0){
                        $thisEnd = substr($line, vComponent::KEYENDLENGTH);
                        if(strncmp($thisEnd, $this->type, $lentype) == 0){
                            // Current object end
                            $this->seekEnd = $seek;
                            //$iterator->next();
                            break;
                        }else if($unrolledComponents){
                            // dont render line which is owned
                            // by inner commponent -> inner component *END*
                            $inInnerObject--;
                        } else {
                            $rendered .= $line;
                        }

                    } else if($inInnerObject === 0 || !$unrolledComponents){
                        $rendered .= $line;
                    }
                }
                $iterator->next();
            } while($iterator->valid() && ( !isset($this->seekEnd) || $this->seekEnd > $seek));


            return $rendered;

        }


        /**
         * render object to string with wraped lines
         * @param null $restricted_properties
         * @param bool $force_rendering
         * @return string - rendered object
         */
        function Render($restricted_properties = null, $force_rendering = false){
            return $this->WrapComponent($this->RenderWithoutWrap($restricted_properties, $force_rendering));
            //return $this->InternalRender($restricted_properties, $force_rendering);
        }

        function isValid(){
            if($this->valid){
                if(isset($this->components)){
                    foreach($this->components as $comp){
                        if(!$comp->isValid()){
                            return false;
                        }
                    }
                }

                return true;
            }
            return false;
        }

 /**
   * Test a PROP-FILTER or COMP-FILTER and return a true/false
   * COMP-FILTER (is-defined | is-not-defined | (time-range?, prop-filter*, comp-filter*))
   * PROP-FILTER (is-defined | is-not-defined | ((time-range | text-match)?, param-filter*))
   *
   * @param array $filter An array of XMLElement defining the filter
   *
   * @return boolean Whether or not this vComponent passes the test
   */
  function TestFilter( $filters ) {
    foreach( $filters AS $k => $v ) {
      $tag = $v->GetNSTag();
//      dbg_error_log( 'vCalendar', ":TestFilter: '%s' ", $tag );
      switch( $tag ) {
        case 'urn:ietf:params:xml:ns:caldav:is-defined':
        case 'urn:ietf:params:xml:ns:carddav:is-defined':
          if ( count($this->properties) == 0 && count($this->components) == 0 ) return false;
          break;
        
        case 'urn:ietf:params:xml:ns:caldav:is-not-defined':
        case 'urn:ietf:params:xml:ns:carddav:is-not-defined':
          if ( count($this->properties) > 0 || count($this->components) > 0 ) return false;
          break;

        case 'urn:ietf:params:xml:ns:caldav:comp-filter':
        case 'urn:ietf:params:xml:ns:carddav:comp-filter':
          $subcomponents = $this->GetComponents($v->GetAttribute('name'));
          $subfilter = $v->GetContent();
//          dbg_error_log( 'vCalendar', ":TestFilter: Found '%d' (of %d) subs of type '%s'",
//                       count($subcomponents), count($this->components), $v->GetAttribute('name') );
          $subtag = $subfilter[0]->GetNSTag(); 
          if ( $subtag == 'urn:ietf:params:xml:ns:caldav:is-not-defined'
                     || $subtag == 'urn:ietf:params:xml:ns:carddav:is-not-defined' ) {
            if ( count($properties) > 0 ) {
//              dbg_error_log( 'vComponent', ":TestFilter: Wanted none => false" );
              return false;
            }
          }
          else if ( count($subcomponents) == 0 ) {
            if ( $subtag == 'urn:ietf:params:xml:ns:caldav:is-defined'
                     || $subtag == 'urn:ietf:params:xml:ns:carddav:is-defined' ) {
//              dbg_error_log( 'vComponent', ":TestFilter: Wanted some => false" );
              return false;
            }
            else {
//              dbg_error_log( 'vCalendar', ":TestFilter: Wanted something from missing sub-components => false" );
              $negate = $subfilter[0]->GetAttribute("negate-condition");
              if ( empty($negate) || strtolower($negate) != 'yes' ) return false;
            }
          }
          else {
            foreach( $subcomponents AS $kk => $subcomponent ) {
              if ( ! $subcomponent->TestFilter($subfilter) ) return false;
            }
          }
          break;

        case 'urn:ietf:params:xml:ns:carddav:prop-filter':
        case 'urn:ietf:params:xml:ns:caldav:prop-filter':
          $subfilter = $v->GetContent();
          $properties = $this->GetProperties($v->GetAttribute("name"));
          dbg_error_log( 'vCalendar', ":TestFilter: Found '%d' props of type '%s'", count($properties), $v->GetAttribute('name') );
          $subtag = $subfilter[0]->GetNSTag();
          if ( $subtag == 'urn:ietf:params:xml:ns:caldav:is-not-defined'
                     || $subtag == 'urn:ietf:params:xml:ns:carddav:is-not-defined' ) {
            if ( count($properties) > 0 ) {
//              dbg_error_log( 'vCalendar', ":TestFilter: Wanted none => false" );
              return false;
            }
          }
          else if ( count($properties) == 0 ) {
            if ( $subtag == 'urn:ietf:params:xml:ns:caldav:is-defined'
                         || $subtag == 'urn:ietf:params:xml:ns:carddav:is-defined' ) {
//              dbg_error_log( 'vCalendar', ":TestFilter: Wanted some => false" );
              return false;
            }
            else {
//              dbg_error_log( 'vCalendar', ":TestFilter: Wanted '%s' from missing sub-properties => false", $subtag );
              $negate = $subfilter[0]->GetAttribute("negate-condition");
              if ( empty($negate) || strtolower($negate) != 'yes' ) return false;
            }
          }
          else {
            foreach( $properties AS $kk => $property ) {
              if ( !$property->TestFilter($subfilter) ) return false;
            }
          }
          break;
      }
    }
    return true;
  }

    }


API documentation generated by ApiGen