Location: PHPKode > scripts > IMC Objects > imc-objects/class.imcComponent.php
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------+
// | PHP version 4                                                        |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2003 The PHP Group                                |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.0 of the PHP license,       |
// | that is bundled with this package in the file LICENSE, and is        |
// | available through the world-wide-web at                              |
// | http://www.php.net/license/2_02.txt.                                 |
// | If you did not receive a copy of the PHP license and are unable to   |
// | obtain it through the world-wide-web, please send a note to          |
// | hide@address.com so we can mail you a copy immediately.               |
// +----------------------------------------------------------------------+
// | Authors: Andreas Haberstroh <hide@address.com>                      |
// +----------------------------------------------------------------------+
//
// $Id$

class	imcComponent {

    /**
    *   List of property objects for this component.
    *   @var array
    */
	var	$properties;

    /**
    *   Name of this component.
    *   @var string
    */
    var $componentName;

    /**
    *   List of subcomponent objects for this component
    *   @var array
    */
    var $components;

    /**
    *   UID to Sequence index for components.
    *   @var array
    */
    var $uid_hash;


    /**
    *   Default constructor.
    */
    function    imcComponent() {

	    $this->properties = NULL;
        $this->components = NULL;
        $this->uid_hash = NULL;
    }

    /**
    *   Recursively writes a component's properties and subcomponents to disk.
    *
    *   @param  filehandle  File to write the components to.
    */
    function    writeFile($fh) {
        
        if( $this->getComponentName() ) {

            // every component has a beginning
            fwrite( $fh, 'BEGIN:'.$this->getComponentName()."\r\n" );

            // walk thru all the properties and write them out.
            if( is_array($this->properties) ) {

                // walk the properties. $key == property name                
                foreach( $this->properties as $key=>$propertyArray ) {

                    // every property MAY have multiple sequences
                    foreach( $propertyArray as $propertyObject ) {

                        // we build an array for easy imploding
                        $property = array($key);

                        // get the encoded value of the property and see if it is REAL
                        $propertyValue = rtrim($propertyObject->getEncodedValue());
                        if( !strlen($propertyValue) )
                            continue;

                        // build the TYPE= portion
                        $type_array = $propertyObject->getParamType();
                        if( is_array($type_array) ) {
                            $property[] = "TYPE=".implode(",", $type_array);
                        }

                        /// build the VALUE= portion
                        $value_array = $propertyObject->getParamValue();
                        if( is_array($value_array) ) {
                            foreach( $value_array as $value ) {
                                $property[] = "VALUE=".$value;
                            }
                        }

                        // build the ENCODING portion
                        $encoding = $propertyObject->getParamEncoding();
                        if( strlen($encoding) ) {
                            $property[] = "ENCODING=".$encoding;
                        }

                        /// now, build the WHOLE line of data
                        $line = implode(";", $property).":$propertyValue";

                        /*
                            The encoded values are based on the actual VALUE not the whole line.
                            The entire line may not be longer than 75.
                            So, in this case, we implement line folding.
                        */
                        if( strlen($line) > 75 ) {
                            $len = 72;
                            if( $encoding == 'QUOTED-PRINTABLE' ) {
                                $len--;
                                $replace = "=\r\n"; 
                            } else {
                                $replace = "\r\n"; 
                            }

                            // strip out anything that got inserted by the encoders.
                            $line = str_replace($replace, "", $line);
                        
                            $line = rtrim(chunk_split($line, $len, "$replace\t"));
                        }

                        // lastly, write the line of data
                        fwrite($fh, "$line\r\n" );
                    }
                }
            }
        }


        // do we have subcomponents?
        if( is_array($this->components) ) {

            // walk the subcomponents and call their writers as well
            foreach( $this->components as $key=>$componentArray )  {

                foreach( $componentArray as $object ) {
                    // in case SOME how this is what we think it is.
                    if( is_a($object,'imcComponent') )
                        $object->writeFile($fh);
                }
            }
        }

        if( $this->getComponentName() ) {
            // laslty, write the END: marker
            fwrite( $fh, 'END:'.$this->getComponentName()."\r\n" );
        }
    }

    /**
    *   Builds a tree of components from a IMC based file.
    *
    *   @param  filehandle  File handle which stores a imc based file
    */
    function    readFile($fh) {

        // in case we've set something BEFORE reading a file
	    $this->properties = NULL;
        $this->components = NULL;
        $this->uid_hash = NULL;

        $folded = '';
        while( !feof($fh) ) {

            $raw_line = fgets($fh);
            $line = ltrim(str_replace( "\r\n", "", $raw_line));

            // Nothing to be read
            if ( !strlen($line) )
                continue;

            // find the first instance of ':' on the line.  The part
            // to the left of the colon is the type and parameters;
            // the part to the right of the colon is the value data.
            $pos = strpos($line, ':');

            //  This may be a continuation of a previous parameter!
            if( $pos === false  ) {
                $folded .= $line;
                continue;
            } else {

                // we're dealing with a folding line
                $this->getProperty($typedef, $propertyObject, $lastPropertyIndex );

                // $right should be undamaged, so we can concat the folded data, 
                //  and re-decode the value
                $propertyObject->setEncodedValue($right.str_replace("\t", "", $folded));
                $this->setProperty($typedef, $propertyObject, $lastPropertyIndex );

                // dumped folded scratch space for the next time we have folding.
                $folded = '';
            }
            
            // get the left and right portions
            $left = trim(substr($line, 0, $pos));
            $right = trim(substr($line, $pos+1, strlen($line)));

            switch( $left ) {

            case 'BEGIN':
                $this->_componentFactory($right, $componentObject);
                
                $componentObject->readFile($fh);

                $this->setComponent($right, $componentObject);
                break;

            case 'END':
                return;

            default:
                $typedef = $this->_getTypeDef($left);
                $params = $this->_getParams($left);

                $this->_propertyFactory($typedef, $propertyObject);

                $propertyObject->setParamType($params['TYPE']);
                $propertyObject->setParamValue($params['VALUE']);
                $propertyObject->setParamEncoding($params['ENCODING'][0]);
                $propertyObject->setEncodedValue($right);

                $lastPropertyIndex = $this->setProperty($typedef, $propertyObject);
            }
        }
    }

    // {{{ setComponentName()
    /**
    *   Sets the name of this component.
    *
    *   @param  string  Component name
    *   @see    getComponentName
    */
    function    setComponentName($name) {

        $this->componentName = $name;
    }

    // {{{ getComponentName()
    /**
    *   Gets the name of this component.
    *
    *   @return string  Component name
    *   @see    getComponentName
    */
    function    getComponentName() {

        return $this->componentName;
    }

    // {{{ removeComponent()
    /**
    *   Removes a component from the list.
    *
    *   @param  string  Name of the component type to delete
    *   @param  integer Sequence index of the component to delete
    */
    function    removeComponent($name, $seq) {

        // get a copy of this element real quick
        $this->getComponent($name, &$object, $seq);

        $left = array_slice($this->components[$name], 0, $seq);
        $right = array_slice($this->components[$name], $seq+1);

        $this->components[$name] = array_merge($left,$right);
    }

    // {{{ setComponent()
    /**
    *   Sets a component object into the list.
    *
    *   @param  string  Name of the component type to set
    *   @param  object  Component object to add
    *   @param  integer Sequence to add this object at. -1 means add
    *   @return integer Sequence number where this object was added
    */
    function    setComponent($name, $object, $seq = - 1) {

        if( $seq == -1 )
        {
            $this->components[$name][] = $object;
            $seq = Count($this->components[$name]) - 1;
        }
        else
            $this->components[$name][$seq] = $object;

        $this->uid_hash[$object->getUID()] = $seq;
        return $seq;
    }

    // {{{ getComponent()
    /**
    *   Gets a component object from the list.
    *
    *   @param  string  Name of the component type to get
    *   @param  object  Copy of an instance of the component
    *   @param  integer Sequence from which to retrieve this object from. -1 means add
    *   @return integer Sequence number where this object was added
    */
    function    getComponent($name, &$object, $seq = -1 ) {

        if( is_array($this->components[$name]) )
        {
            if( $seq == -1 )
            {
                $count = Count($this->components[$name]);
                $seq = $count - 1;
            }
        }

        $object = $this->components[$name][$seq];
        if( !is_object($object) )
        {
            $this->_componentFactory($name, $object);
            $this->setComponent($name, $object, $seq);
        }
    }

    // {{{ getComponentCount
    /**
    *   Gets the number of components of a certain type
    *
    *   @param  string  Name of the component to check
    */
    function    getComponentCount($name) {

        return Count($this->components[$name]);
    }

    // {{{ getComponentUIDs
    /**
    *   Gets the array of UID's for each of the components.
    *
    *   @return array   UID hash list
    */
    function    getComponentUIDs() {

        return $this->uid_hash;
    }

    // {{{ removeComponentByUID
    /**
    *   Removes a component by UID reference
    *
    *   @param  string  Name of the component
    *   @param  string  UID of the component to remove
    */
    function    removeComponentByUID($name, $uid ) {

        $this->removeComponent($name, $this->uid_hash[$uid] );
    }

    // {{{ getComponentByUID
    /**
    *   Gets a component by UID reference
    *
    *   @param  string  Name of the component
    *   @param  object  Component Object to return
    *   @param  string  UID of the component to retrieve
    */
    function    getComponentByUID($name, &$object, $uid ) {

        return $this->getComponent($name, &$object, $this->uid_hash[$uid] );
    }

    // {{{ setComponentByUID
    /**
    *   Sets a component by UID reference
    *
    *   @param  string  Name of the component
    *   @param  object  Component Object to set
    *   @param  string  UID of the component to set
    */
    function    setComponentByUID($name, $object, $uid ) {

        return $this->setComponent($name, $object, $this->uid_hash[$uid] );
    }

    // {{{ setProperty()
    /**
    *   Sets a property object into the list.
    *
    *   @param  string  Name of the property type to set
    *   @param  object  Property object to add
    *   @param  integer Sequence to add this object at. -1 means add
    *   @return integer Sequence number where this object was added
    */
    function    setProperty($name, $object, $seq = - 1) {

        if( $seq == -1 )
        {
            $this->properties[$name][] = $object;
            $seq = Count($this->properties[$name]) - 1;
        }
        else
            $this->properties[$name][$seq] = $object;

        return $seq;
    }

    // {{{ getProperty()
    /**
    *   Gets a property object from the list.
    *
    *   @param  string  Name of the property type to get
    *   @param  object  Copy of an instance of the property
    *   @param  integer Sequence from which to retrieve this object from. -1 means add
    *   @return integer Sequence number where this object was added
    */
    function    getProperty($name, &$object, $seq = -1 ) {

        if( is_array($this->properties[$name]) )
        {
            if( $seq == -1 )
            {
                $count = Count($this->properties[$name]);
                $seq = $count - 1;
            }
        }

        $object = $this->properties[$name][$seq];
        if( !is_object($object) )
        {
            $this->_propertyFactory($name, $object);
            $this->setProperty($name, $object, $seq);
        }
    }

    // {{{ getPropertyCount
    /**
    *   Gets the number of propertys of a certain type
    *
    *   @param  string  Name of the property to check
    */
    function    getPropertyCount($name) {

        return Count($this->properties[$name]);
    }

    /**
    *   Finds a property sequence number by a TYPE array
    *
    *   @param  string  Property to search for
    *   @param  array   Array of types to match against
    *   @return integer Returns the sequence number or -1 if not found
    */
    function    _findPropertySequenceByTypeArray($name, $type ) {

        sort($type);
        $total = Count($type);
        for( $i = 0; $i < Count($this->properties[$name]); $i++ )
        {
            $c = 0;
            $object = $this->properties[$name][$i];
            
            if( is_array($object->getParamType()) )
            {
                $c = 0;
                foreach( $type as $key=>$val ) {
                    if( in_array($val, $object->getParamType()) )
                        $c++;
                }

                if( $c == $total ) 
                    return $i;
            }
        }

        return -1;
    }

    /**
    *   Finds a property sequence number by a TYPE string
    *
    *   @param  string  Property to search for
    *   @param  string  Single string type to match against
    *   @return integer Returns the sequence number or -1 if not found
    */
    function    _findPropertySequenceByTypeValue($name, $type ) {

        for( $i = 0; $i < Count($this->properties[$name]); $i++ )
        {
            $object = $this->properties[$name][$i];
            if( is_array($object->getParamType()) )
            {
                if( in_array($type, $object->getParamType()) )
                    return $i;
            }
        }

        return -1;
    }

    /**
    *   Finds a property the first available property
    *
    *   @param  string  Property to search for
    *   @return integer Returns the 0 or -1 if not found
    */
    function    _findPropertySequenceFirstAvail($name) {

        if( Count($this->properties[$name]) )
            return 0;
        else
            return -1;
    }

    // {{{ findPropertySequenceByType
    /**
    *   Finds a property sequence number. The search type can
    *   be a single string type, an array of types or the first available
    *
    *   @param  string  Property to search for
    *   @param  mixed   String or array of types to match against. 
    *                   -1 finds the first available
    *   @return integer Returns the sequence number or -1 if not found
    */
    function    findPropertySequenceByType($name, $type) {

        if( is_array($type) ) {
            return $this->_findPropertySequenceByTypeArray($name, $type);
        }
        else if( $type=='-1' ) {
            return $this->_findPropertySequenceFirstAvail($name);
        } else {
            return $this->_findPropertySequenceByTypeValue($name, $type);
        }
    }

    /**
    *   Component factory to build the named component. 
    *   Used by the readFile function
    *
    *   @param  string  Name of the component to build
    *   @param  object  Component Object to return
    */
    function    _componentFactory($componentName, &$componentObject) {

        switch( $componentName ) {

        case 'VCARD':
            $componentObject = new imc_vCard;
            break;

        case 'VCALENDAR':
            $componentObject = new imc_vCalendar;
            break;

        case 'VEVENT':
            $componentObject = new imc_vEvent;
            break;

        default:
            $componentObject = new imcComponent;
            $componentObject->setComponentName($componentName);
            break;
        }

        
    }

    /**
    *   Property factory to build the named property
    *   Used by the readFile function
    *
    *   @param  string  Name of the property to build
    *   @param  object  Property Object to return
    */
    function    _propertyFactory( $typedef, &$propertyObject ) {

        $propertyObject = new imcProperty();
    }

   /**
    * Splits a string into an array at semicolons.  Honors backslash-
    * escaped semicolons (i.e., splits at ';' not '\;').
    * 
    * Borrowed from File_IMC on PEAR, written by 
    * Marshall Roch <hide@address.com> and Paul Jones <hide@address.com>. 
    *
    * @param string $text The string to split into an array.
    * @param bool $convertSingle If splitting the string results in a
    * single array element, return a string instead of a one-element
    * array.
    * @return string|array An array of values, or a single string.
    * 
    * @access public
    */
    function splitBySemi($text, $convertSingle = false) {

        // we use these double-backs (\\) because they get get converted
        // to single-backs (\) by preg_split.  the quad-backs (\\\\) end
        // up as as double-backs (\\), which is what preg_split requires
        // to indicate a single backslash (\). what a mess.
        $regex = '(?<!\\\\)(\;)';
        $tmp = preg_split("/$regex/i", $text);
        
        // if there is only one array-element and $convertSingle is
        // true, then return only the value of that one array element
        // (instead of returning the array).
        if ($convertSingle && count($tmp) == 1) {
            return $tmp[0];
        } else {
            return $tmp;
        }
    }
    
    
   /**
    * Splits a string into an array at commas.  Honors backslash-
    * escaped commas (i.e., splits at ',' not '\,').
    * 
    * Borrowed from File_IMC on PEAR, written by 
    * Marshall Roch <hide@address.com> and Paul Jones <hide@address.com>. 
    *
    * @param string $text The string to split into an array.
    * @param bool $convertSingle If splitting the string results in a
    * single array element, return a string instead of a one-element
    * array.
    * @return string|array An array of values, or a single string.
    * 
    * @access public
    */
    function splitByComma($text, $convertSingle = false) {

        // we use these double-backs (\\) because they get get converted
        // to single-backs (\) by preg_split.  the quad-backs (\\\\) end
        // up as as double-backs (\\), which is what preg_split requires
        // to indicate a single backslash (\). ye gods, how ugly.
        $regex = '(?<!\\\\)(\,)';
        $tmp = preg_split("/$regex/i", $text);
        
        // if there is only one array-element and $convertSingle is
        // true, then return only the value of that one array element
        // (instead of returning the array).
        if ($convertSingle && count($tmp) == 1) {
            return $tmp[0];
        } else {
            return $tmp;
        }
    }


   /**
    * Takes a line and extracts the Type-Definition for the line.
    *
    * Borrowed from File_IMC on PEAR, written by 
    * Marshall Roch <hide@address.com> and Paul Jones <hide@address.com>. 
    *
    * @param string A left-part (before-the-colon part) from a line.
    * @return string The type definition for the line.
    * 
    * @access private
    */
    function _getTypeDef($text) {

        // split the text by semicolons
        $split = $this->splitBySemi($text);
        
        // only return first element (the typedef)
        return $split[0];
    }
    
   /**
    * Finds the Type-Definition parameters for a line.
    * 
    * Borrowed from File_IMC on PEAR, written by 
    * Marshall Roch <hide@address.com> and Paul Jones <hide@address.com>. 
    *
    * @param string $text The left-part (before-the-colon part) of a line.
    * @return array An array of parameters.
    * 
    * @access private
    */
    function _getParams($text) {

        // split the text by semicolons into an array
        $split = $this->splitBySemi($text);
        
        // drop the first element of the array (the type-definition)
        array_shift($split);
        
        // set up an array to retain the parameters, if any
        $params = array();
        
        // loop through each parameter.  the params may be in the format...
        // "TYPE=type1,type2,type3"
        //    ...or...
        // "TYPE=type1;TYPE=type2;TYPE=type3"
        foreach ($split as $full) {
            
            // split the full parameter at the equal sign so we can tell
            // the parameter name from the parameter value
            $tmp = explode("=", $full);
            
            // the key is the left portion of the parameter (before
            // '='). if in 2.1 format, the key may in fact be the
            // parameter value, not the parameter name.
            $key = strtoupper(trim($tmp[0]));
            
            // get the parameter name by checking to see if it's in
            // vCard 2.1 or 3.0 format.
            $name = $this->_getParamName($key);
            
            // list of all parameter values
            $listall = trim($tmp[1]);
            
            // if there is a value-list for this parameter, they are
            // separated by commas, so split them out too.
            $list = $this->splitByComma($listall);
            
            // now loop through each value in the parameter and retain
            // it.  if the value is blank, that means it's a 2.1-style
            // param, and the key itself is the value.
            foreach ($list as $val) {
                if (trim($val) != '') {
                    // 3.0 formatted parameter
                    $params[$name][] = trim($val);
                } else {
                    // 2.1 formatted parameter
                    $params[$name][] = $key;
                }
            }
            
            // if, after all this, there are no parameter values for the
            // parameter name, retain no info about the parameter (saves
            // ram and checking-time later).
            if (count($params[$name]) == 0) {
                unset($params[$name]);
            }
        }
        
        // return the parameters array.
        return $params;
    }

   /**
    * Returns the parameter name for parameters given without names.
    *
    * The vCard 2.1 specification allows parameter values without a
    * name. The parameter name is then determined from the unique
    * parameter value.
    * 
    * Shamelessly lifted from Frank Hellwig <hide@address.com> and his
    * vCard PHP project <http://vcardphp.sourceforge.net>.
    * 
    * @param string $value The first element in a parameter name-value
    * pair.
    * @return string The proper parameter name (TYPE, ENCODING, or
    * VALUE).
    * 
    * @access private
    */
    function _getParamName($value) {

        static $types = array (
            'DOM', 'INTL', 'POSTAL', 'PARCEL','HOME', 'WORK',
            'PREF', 'VOICE', 'FAX', 'MSG', 'CELL', 'PAGER',
            'BBS', 'MODEM', 'CAR', 'ISDN', 'VIDEO',
            'AOL', 'APPLELINK', 'ATTMAIL', 'CIS', 'EWORLD',
            'INTERNET', 'IBMMAIL', 'MCIMAIL',
            'POWERSHARE', 'PRODIGY', 'TLX', 'X400',
            'GIF', 'CGM', 'WMF', 'BMP', 'MET', 'PMB', 'DIB',
            'PICT', 'TIFF', 'PDF', 'PS', 'JPEG', 'QTIME',
            'MPEG', 'MPEG2', 'AVI',
            'WAVE', 'AIFF', 'PCM',
            'X509', 'PGP'
        );
        
        // CONTENT-ID added by pmj
        static $values = array (
            'INLINE', 'URL', 'CID', 'CONTENT-ID'
        );
        
        // 8BIT added by pmj
        static $encodings = array (
            '7BIT', '8BIT', 'QUOTED-PRINTABLE', 'BASE64'
        );
        
        // changed by pmj to the following so that the name defaults to
        // whatever the original value was.  Frank Hellwig's original
        // code was "$name = 'UNKNOWN'".
        $name = $value;
        
        if (in_array($value, $types)) {
            $name = 'TYPE';
        } elseif (in_array($value, $values)) {
            $name = 'VALUE';
        } elseif (in_array($value, $encodings)) {
            $name = 'ENCODING';
        }
        
        return $name;
    }

    // {{{ setUID
    /**
    *   Sets the Unique Identifier for a component.
    *
    *   @param  string  UID to set
    *   @see    getUID
    */
    function    setUID($uid) {

        $this->getProperty('UID', $propertyObject, 0);
        $propertyObject->setValue($uid);
        $this->setProperty('UID', $propertyObject, 0);
    }

    // {{{ getUID
    /**
    *   Gets the Unique Identifier for a component.
    *
    *   @return string  UID to set
    *   @see    getUID
    */
    function    getUID() {

        $this->getProperty('UID', $propertyObject, 0);
        return $propertyObject->getValue();
    }
}
?>
Return current item: IMC Objects