gunnar: server/php-kolab/Kolab_Freebusy/Freebusy domxml-php4-to-php5.php, NONE, 1.1 freebusy.class.php, NONE, 1.1 freebusycache.class.php, NONE, 1.1 freebusycollector.class.php, NONE, 1.1 freebusyimapcache.class.php, NONE, 1.1 freebusyldap.class.php, NONE, 1.1 freebusyldap_dummy.class.php, NONE, 1.1 misc.php, NONE, 1.1 recurrence.class.php, NONE, 1.1

cvs at kolab.org cvs at kolab.org
Tue Aug 7 17:45:58 CEST 2007


Author: gunnar

Update of /kolabrepository/server/php-kolab/Kolab_Freebusy/Freebusy
In directory doto:/tmp/cvs-serv31660/Kolab_Freebusy/Freebusy

Added Files:
	domxml-php4-to-php5.php freebusy.class.php 
	freebusycache.class.php freebusycollector.class.php 
	freebusyimapcache.class.php freebusyldap.class.php 
	freebusyldap_dummy.class.php misc.php recurrence.class.php 
Log Message:
A first PEAR package derived from kolab-resource-handlers/freebusy. Not yet completed.

--- NEW FILE: domxml-php4-to-php5.php ---
<?php
/*
 Requires PHP5, uses built-in DOM extension.
 To be used in PHP4 scripts using DOMXML extension.
 Allows PHP4/DOMXML scripts to run on PHP5/DOM.
 (Optional: requires PHP5/XSL extension for domxml_xslt functions, and PHP>=5.1/libxml for error reports)

 Typical use:
 {
  if (version_compare(PHP_VERSION,'5','>='))
   require_once('domxml-php4-to-php5.php');
 }

 Version 1.17, 2007-06-05, http://alexandre.alapetite.net/doc-alex/domxml-php4-php5/

 ------------------------------------------------------------------
 Written by Alexandre Alapetite, http://alexandre.alapetite.net/cv/

 Copyright 2004-2007, Licence: Creative Commons "Attribution-ShareAlike 2.0 France" BY-SA (FR),
 http://creativecommons.org/licenses/by-sa/2.0/fr/
 http://alexandre.alapetite.net/divers/apropos/#by-sa
 - Attribution. You must give the original author credit
 - Share Alike. If you alter, transform, or build upon this work,
   you may distribute the resulting work only under a license identical to this one
   (Can be included in GPL/LGPL projects)
 - The French law is authoritative
 - Any of these conditions can be waived if you get permission from Alexandre Alapetite
 - Please send to Alexandre Alapetite the modifications you make,
   in order to improve this file for the benefit of everybody

 If you want to distribute this code, please do it as a link to:
 http://alexandre.alapetite.net/doc-alex/domxml-php4-php5/
*/

define('DOMXML_LOAD_PARSING',0);
define('DOMXML_LOAD_VALIDATING',1);
define('DOMXML_LOAD_RECOVERING',2);
define('DOMXML_LOAD_SUBSTITUTE_ENTITIES',4);
//define('DOMXML_LOAD_COMPLETE_ATTRS',8);
define('DOMXML_LOAD_DONT_KEEP_BLANKS',16);

function domxml_new_doc($version) {return new php4DOMDocument();}
function domxml_new_xmldoc($version) {return new php4DOMDocument();}
function domxml_open_file($filename,$mode=DOMXML_LOAD_PARSING,&$error=null)
{
 $dom=new php4DOMDocument($mode);
 $errorMode=(func_num_args()>2);
 if ($errorMode) libxml_use_internal_errors(true);
 if (!$dom->myDOMNode->load($filename)) $dom=null;
 if ($errorMode)
 {
  $error=array_map('_error_report',libxml_get_errors());
  libxml_clear_errors();
 }
 return $dom;
}
function domxml_open_mem($str,$mode=DOMXML_LOAD_PARSING,&$error=null)
{
 $dom=new php4DOMDocument($mode);
 $errorMode=(func_num_args()>2);
 if ($errorMode) libxml_use_internal_errors(true);
 if (!$dom->myDOMNode->loadXML($str)) $dom=null;
 if ($errorMode)
 {
  $error=array_map('_error_report',libxml_get_errors());
  libxml_clear_errors();
 }
 return $dom;
}
function html_doc($html_doc,$from_file=false)
{
 $dom=new php4DOMDocument();
 if ($from_file) $result=$dom->myDOMNode->loadHTMLFile($html_doc);
 else $result=$dom->myDOMNode->loadHTML($html_doc);
 return $result ? $dom : null;
}
function html_doc_file($filename) {return html_doc($filename,true);}
function xmldoc($str) {return domxml_open_mem($str);}
function xmldocfile($filename) {return domxml_open_file($filename);}
function xpath_eval($xpath_context,$eval_str,$contextnode=null) {return $xpath_context->xpath_eval($eval_str,$contextnode);}
function xpath_new_context($dom_document) {return new php4DOMXPath($dom_document);}
function xpath_register_ns($xpath_context,$prefix,$namespaceURI) {return $xpath_context->myDOMXPath->registerNamespace($prefix,$namespaceURI);}
function _error_report($error) {return array('errormessage'=>$error->message,'nodename'=>'','line'=>$error->line,'col'=>$error->column)+($error->file==''?array():array('directory'=>dirname($error->file),'file'=>basename($error->file)));}

class php4DOMAttr extends php4DOMNode
{
 function name() {return $this->myDOMNode->name;}
 function set_value($content) {return $this->myDOMNode->value=$content;}
 function specified() {return $this->myDOMNode->specified;}
 function value() {return $this->myDOMNode->value;}
}

class php4DOMDocument extends php4DOMNode
{
 function php4DOMDocument($mode=DOMXML_LOAD_PARSING)
 {
  $this->myDOMNode=new DOMDocument();
  $this->myOwnerDocument=$this;
  if ($mode & DOMXML_LOAD_VALIDATING) $dom->myDOMNode->validateOnParse=true;
  if ($mode & DOMXML_LOAD_RECOVERING) $dom->myDOMNode->recover=true;
  if ($mode & DOMXML_LOAD_SUBSTITUTE_ENTITIES) $dom->myDOMNode->substituteEntities=true;
  if ($mode & DOMXML_LOAD_DONT_KEEP_BLANKS) $dom->myDOMNode->preserveWhiteSpace=false;
 }
 function add_root($name)
 {
  if ($this->myDOMNode->hasChildNodes()) $this->myDOMNode->removeChild($this->myDOMNode->firstChild);
  return new php4DOMElement($this->myDOMNode->appendChild($this->myDOMNode->createElement($name)),$this->myOwnerDocument);
 }
 function create_attribute($name,$value)
 {
  $myAttr=$this->myDOMNode->createAttribute($name);
  $myAttr->value=$value;
  return new php4DOMAttr($myAttr,$this);
 }
 function create_cdata_section($content) {return new php4DOMNode($this->myDOMNode->createCDATASection($content),$this);}
 function create_comment($data) {return new php4DOMNode($this->myDOMNode->createComment($data),$this);}
 function create_element($name) {return new php4DOMElement($this->myDOMNode->createElement($name),$this);}
 function create_element_ns($uri,$name,$prefix=null)
 {
  if ($prefix==null) $prefix=$this->myDOMNode->lookupPrefix($uri);
  if (($prefix==null)&&($this->myDOMNode->hasChildNodes())&&(!$this->myDOMNode->firstChild->isDefaultNamespace($uri))) $prefix='a'.sprintf('%u',crc32($uri));
  return new php4DOMElement($this->myDOMNode->createElementNS($uri,$prefix==null ? $name : $prefix.':'.$name),$this);
 }
 function create_entity_reference($content) {return new php4DOMNode($this->myDOMNode->createEntityReference($content),$this);} //By Walter Ebert 2007-01-22
 function create_text_node($content) {return new php4DOMText($this->myDOMNode->createTextNode($content),$this);}
 function document_element() {return parent::_newDOMElement($this->myDOMNode->documentElement,$this);}
 function dump_file($filename,$compressionmode=false,$format=false)
 {
  $format0=$this->myDOMNode->formatOutput;
  $this->myDOMNode->formatOutput=$format;
  $res=$this->myDOMNode->save($filename);
  $this->myDOMNode->formatOutput=$format0;
  return $res;
 }
 function dump_mem($format=false,$encoding=false)
 {
  $format0=$this->myDOMNode->formatOutput;
  $this->myDOMNode->formatOutput=$format;
  $encoding0=$this->myDOMNode->encoding;
  if ($encoding) $this->myDOMNode->encoding=$encoding;
  $dump=$this->myDOMNode->saveXML();
  $this->myDOMNode->formatOutput=$format0;
  if ($encoding) $this->myDOMNode->encoding= $encoding0=='' ? 'UTF-8' : $encoding0; //UTF-8 is XML default encoding
  return $dump;
 }
 function free()
 {
  if ($this->myDOMNode->hasChildNodes()) $this->myDOMNode->removeChild($this->myDOMNode->firstChild);
  $this->myDOMNode=null;
  $this->myOwnerDocument=null;
 }
 function get_element_by_id($id) {return parent::_newDOMElement($this->myDOMNode->getElementById($id),$this);}
 function get_elements_by_tagname($name)
 {
  $myDOMNodeList=$this->myDOMNode->getElementsByTagName($name);
  $nodeSet=array();
  $i=0;
  if (isset($myDOMNodeList))
   while ($node=$myDOMNodeList->item($i++)) $nodeSet[]=new php4DOMElement($node,$this);
  return $nodeSet;
 }
 function html_dump_mem() {return $this->myDOMNode->saveHTML();}
 function root() {return parent::_newDOMElement($this->myDOMNode->documentElement,$this);}
 function xpath_new_context() {return new php4DOMXPath($this);}
}

class php4DOMElement extends php4DOMNode
{
 function add_namespace($uri,$prefix)
 {
  if ($this->myDOMNode->hasAttributeNS('http://www.w3.org/2000/xmlns/',$prefix)) return false;
  else
  {
   $this->myDOMNode->setAttributeNS('http://www.w3.org/2000/xmlns/','xmlns:'.$prefix,$uri); //By Daniel Walker 2006-09-08
   return true;
  }
 }
 function get_attribute($name) {return $this->myDOMNode->getAttribute($name);}
 function get_attribute_node($name) {return parent::_newDOMElement($this->myDOMNode->getAttributeNode($name),$this->myOwnerDocument);}
 function get_elements_by_tagname($name)
 {
  $myDOMNodeList=$this->myDOMNode->getElementsByTagName($name);
  $nodeSet=array();
  $i=0;
  if (isset($myDOMNodeList))
   while ($node=$myDOMNodeList->item($i++)) $nodeSet[]=new php4DOMElement($node,$this->myOwnerDocument);
  return $nodeSet;
 }
 function has_attribute($name) {return $this->myDOMNode->hasAttribute($name);}
 function remove_attribute($name) {return $this->myDOMNode->removeAttribute($name);}
 function set_attribute($name,$value)
 {
  //return $this->myDOMNode->setAttribute($name,$value); //Does not return a DomAttr
  $myAttr=$this->myDOMNode->ownerDocument->createAttribute($name);
  $myAttr->value=$value;
  $this->myDOMNode->setAttributeNode($myAttr);
  return new php4DOMAttr($myAttr,$this->myOwnerDocument);
 }
 function set_attribute_node($attr)
 {
  $this->myDOMNode->setAttributeNode($this->_importNode($attr));
  return $attr;
 }
 function set_name($name)
 {
  if ($this->myDOMNode->prefix=='') $newNode=$this->myDOMNode->ownerDocument->createElement($name);
  else $newNode=$this->myDOMNode->ownerDocument->createElementNS($this->myDOMNode->namespaceURI,$this->myDOMNode->prefix.':'.$name);
  $myDOMNodeList=$this->myDOMNode->attributes;
  $i=0;
  if (isset($myDOMNodeList))
   while ($node=$myDOMNodeList->item($i++))
    if ($node->namespaceURI=='') $newNode->setAttribute($node->name,$node->value);
    else $newNode->setAttributeNS($node->namespaceURI,$node->nodeName,$node->value);
  $myDOMNodeList=$this->myDOMNode->childNodes;
  if (isset($myDOMNodeList))
   while ($node=$myDOMNodeList->item(0)) $newNode->appendChild($node);
  $this->myDOMNode->parentNode->replaceChild($newNode,$this->myDOMNode);
  $this->myDOMNode=$newNode;
  return true;
 }
 function tagname() {return $this->myDOMNode->tagName;}
}

class php4DOMNode
{
 public $myDOMNode;
 public $myOwnerDocument;
 function php4DOMNode($aDomNode,$aOwnerDocument)
 {
  $this->myDOMNode=$aDomNode;
  $this->myOwnerDocument=$aOwnerDocument;
 }
 function __get($name)
 {
  switch ($name)
  {
   case 'type': return $this->myDOMNode->nodeType;
   case 'tagname': return $this->myDOMNode->tagName;
   case 'content': return $this->myDOMNode->textContent;
   case 'name': return $this->myDOMNode->name;
   case 'value': return $this->myDOMNode->value;
   default:
    $myErrors=debug_backtrace();
    trigger_error('Undefined property: '.get_class($this).'::$'.$name.' ['.$myErrors[0]['file'].':'.$myErrors[0]['line'].']',E_USER_NOTICE);
    return false;
  }
 }
 function add_child($newnode) {return append_child($newnode);}
 function add_namespace($uri,$prefix) {return false;}
 function append_child($newnode) {return self::_newDOMElement($this->myDOMNode->appendChild($this->_importNode($newnode)),$this->myOwnerDocument);}
 function append_sibling($newnode) {return self::_newDOMElement($this->myDOMNode->parentNode->appendChild($this->_importNode($newnode)),$this->myOwnerDocument);}
 function attributes()
 {
  $myDOMNodeList=$this->myDOMNode->attributes;
  $nodeSet=array();
  $i=0;
  if (isset($myDOMNodeList))
   while ($node=$myDOMNodeList->item($i++)) $nodeSet[]=new php4DOMAttr($node,$this->myOwnerDocument);
  return $nodeSet;
 }
 function child_nodes()
 {
  $myDOMNodeList=$this->myDOMNode->childNodes;
  $nodeSet=array();
  $i=0;
  if (isset($myDOMNodeList))
   while ($node=$myDOMNodeList->item($i++)) $nodeSet[]=self::_newDOMElement($node,$this->myOwnerDocument);
  return $nodeSet;
 }
 function children() {return $this->child_nodes();}
 function clone_node($deep=false) {return self::_newDOMElement($this->myDOMNode->cloneNode($deep),$this->myOwnerDocument);}
 //dump_node($node) should only be called on php4DOMDocument
 function dump_node($node=null) {return $node==null ? $this->myOwnerDocument->myDOMNode->saveXML($this->myDOMNode) : $this->myOwnerDocument->myDOMNode->saveXML($node->myDOMNode);}
 function first_child() {return self::_newDOMElement($this->myDOMNode->firstChild,$this->myOwnerDocument);}
 function get_content() {return $this->myDOMNode->textContent;}
 function has_attributes() {return $this->myDOMNode->hasAttributes();}
 function has_child_nodes() {return $this->myDOMNode->hasChildNodes();}
 function insert_before($newnode,$refnode) {return self::_newDOMElement($this->myDOMNode->insertBefore($this->_importNode($newnode),$refnode==null?null:$refnode->myDOMNode),$this->myOwnerDocument);}
 function is_blank_node() {return ($this->myDOMNode->nodeType===XML_TEXT_NODE)&&preg_match('%^\s*$%',$this->myDOMNode->nodeValue);}
 function last_child() {return self::_newDOMElement($this->myDOMNode->lastChild,$this->myOwnerDocument);}
 function new_child($name,$content)
 {
  $mySubNode=$this->myDOMNode->ownerDocument->createElement($name);
  $mySubNode->appendChild($this->myDOMNode->ownerDocument->createTextNode(html_entity_decode($content,ENT_QUOTES)));
  $this->myDOMNode->appendChild($mySubNode);
  return new php4DOMElement($mySubNode,$this->myOwnerDocument);
 }
 function next_sibling() {return self::_newDOMElement($this->myDOMNode->nextSibling,$this->myOwnerDocument);}
 function node_name() {return ($this->myDOMNode->nodeType===XML_ELEMENT_NODE) ? $this->myDOMNode->localName : $this->myDOMNode->nodeName;} //Avoid namespace prefix for DOMElement
 function node_type() {return $this->myDOMNode->nodeType;}
 function node_value() {return $this->myDOMNode->nodeValue;}
 function owner_document() {return $this->myOwnerDocument;}
 function parent_node() {return self::_newDOMElement($this->myDOMNode->parentNode,$this->myOwnerDocument);}
 function prefix() {return $this->myDOMNode->prefix;}
 function previous_sibling() {return self::_newDOMElement($this->myDOMNode->previousSibling,$this->myOwnerDocument);}
 function remove_child($oldchild) {return self::_newDOMElement($this->myDOMNode->removeChild($oldchild->myDOMNode),$this->myOwnerDocument);}
 function replace_child($newnode,$oldnode) {return self::_newDOMElement($this->myDOMNode->replaceChild($this->_importNode($newnode),$oldnode->myDOMNode),$this->myOwnerDocument);}
 function set_content($text) {return $this->myDOMNode->appendChild($this->myDOMNode->ownerDocument->createTextNode($text));}
 //function set_name($name) {return $this->myOwnerDocument->renameNode($this->myDOMNode,$this->myDOMNode->namespaceURI,$name);}
 function set_namespace($uri,$prefix=null)
 {//Contributions by Daniel Walker 2006-09-08
  $nsprefix=$this->myDOMNode->lookupPrefix($uri);
  if ($nsprefix==null)
  {
   $nsprefix= $prefix==null ? $nsprefix='a'.sprintf('%u',crc32($uri)) : $prefix;
   if ($this->myDOMNode->nodeType===XML_ATTRIBUTE_NODE)
   {
    if (($prefix!=null)&&$this->myDOMNode->ownerElement->hasAttributeNS('http://www.w3.org/2000/xmlns/',$nsprefix)&&
        ($this->myDOMNode->ownerElement->getAttributeNS('http://www.w3.org/2000/xmlns/',$nsprefix)!=$uri))
    {//Remove namespace
     $parent=$this->myDOMNode->ownerElement;
     $parent->removeAttributeNode($this->myDOMNode);
     $parent->setAttribute($this->myDOMNode->localName,$this->myDOMNode->nodeValue);
     $this->myDOMNode=$parent->getAttributeNode($this->myDOMNode->localName);
     return;
    }
    $this->myDOMNode->ownerElement->setAttributeNS('http://www.w3.org/2000/xmlns/','xmlns:'.$nsprefix,$uri);
   }
  }
  if ($this->myDOMNode->nodeType===XML_ATTRIBUTE_NODE)
  {
   $parent=$this->myDOMNode->ownerElement;
   $parent->removeAttributeNode($this->myDOMNode);
   $parent->setAttributeNS($uri,$nsprefix.':'.$this->myDOMNode->localName,$this->myDOMNode->nodeValue);
   $this->myDOMNode=$parent->getAttributeNodeNS($uri,$this->myDOMNode->localName);
  }
  elseif ($this->myDOMNode->nodeType===XML_ELEMENT_NODE)
  {
   $NewNode=$this->myDOMNode->ownerDocument->createElementNS($uri,$nsprefix.':'.$this->myDOMNode->localName);
   foreach ($this->myDOMNode->attributes as $n) $NewNode->appendChild($n->cloneNode(true));
   foreach ($this->myDOMNode->childNodes as $n) $NewNode->appendChild($n->cloneNode(true));
   $xpath=new DOMXPath($this->myDOMNode->ownerDocument);
   $myDOMNodeList=$xpath->query('namespace::*[name()!="xml"]',$this->myDOMNode); //Add old namespaces
   foreach ($myDOMNodeList as $n) $NewNode->setAttributeNS('http://www.w3.org/2000/xmlns/',$n->nodeName,$n->nodeValue); 
   $this->myDOMNode->parentNode->replaceChild($NewNode,$this->myDOMNode);
   $this->myDOMNode=$NewNode;
  }
 }
 function unlink_node()
 {
  if ($this->myDOMNode->parentNode!=null)
  {
   if ($this->myDOMNode->nodeType===XML_ATTRIBUTE_NODE) $this->myDOMNode->parentNode->removeAttributeNode($this->myDOMNode);
   else $this->myDOMNode->parentNode->removeChild($this->myDOMNode);
  }
 }
 protected function _importNode($newnode) {return $this->myOwnerDocument===$newnode->myOwnerDocument ? $newnode->myDOMNode : $this->myOwnerDocument->myDOMNode->importNode($newnode->myDOMNode,true);} //To import DOMNode from another DOMDocument
 static function _newDOMElement($aDOMNode,$aOwnerDocument)
 {//Check the PHP5 DOMNode before creating a new associated PHP4 DOMNode wrapper
  if ($aDOMNode==null) return null;
  switch ($aDOMNode->nodeType)
  {
   case XML_ELEMENT_NODE: return new php4DOMElement($aDOMNode,$aOwnerDocument);
   case XML_TEXT_NODE: return new php4DOMText($aDOMNode,$aOwnerDocument);
   case XML_ATTRIBUTE_NODE: return new php4DOMAttr($aDOMNode,$aOwnerDocument);
   default: return new php4DOMNode($aDOMNode,$aOwnerDocument);
  }
 }
}

class php4DOMText extends php4DOMNode
{
 function __get($name)
 {
  if ($name==='tagname') return '#text';
  else return parent::__get($name);
 }
 function tagname() {return '#text';}
}

if (!defined('XPATH_NODESET'))
{
 define('XPATH_UNDEFINED',0);
 define('XPATH_NODESET',1);
 /*define('XPATH_BOOLEAN',2);
 define('XPATH_NUMBER',3);
 define('XPATH_STRING',4);
 define('XPATH_POINT',5);
 define('XPATH_RANGE',6);
 define('XPATH_LOCATIONSET',7);
 define('XPATH_USERS',8);
 define('XPATH_XSLT_TREE',9);*/
}

class php4DOMNodelist
{//TODO: To be updated for PHP>=5.1 to allow XPath boolean expressions etc. DOMXPath->evaluate()
 private $myDOMNodelist;
 public $nodeset;
 public $type;
 function php4DOMNodelist($aDOMNodelist,$aOwnerDocument)
 {
  $this->myDOMNodelist=$aDOMNodelist;
  $this->nodeset=array();
  $i=0;
  if (isset($this->myDOMNodelist))
  {
   $this->type=XPATH_NODESET;
   while ($node=$this->myDOMNodelist->item($i++)) $this->nodeset[]=php4DOMNode::_newDOMElement($node,$aOwnerDocument);
  }
  else $this->type=XPATH_UNDEFINED;
 }
}

class php4DOMXPath
{
 public $myDOMXPath;
 private $myOwnerDocument;
 function php4DOMXPath($dom_document)
 {
  //TODO: If $dom_document is a DomElement, make that default $contextnode and modify XPath. Ex: '/test'
  $this->myOwnerDocument=$dom_document->myOwnerDocument;
  $this->myDOMXPath=new DOMXPath($this->myOwnerDocument->myDOMNode);
 }
 function xpath_eval($eval_str,$contextnode=null) {return isset($contextnode) ? new php4DOMNodelist($this->myDOMXPath->query($eval_str,$contextnode->myDOMNode),$this->myOwnerDocument) : new php4DOMNodelist($this->myDOMXPath->query($eval_str),$this->myOwnerDocument);}
 function xpath_register_ns($prefix,$namespaceURI) {return $this->myDOMXPath->registerNamespace($prefix,$namespaceURI);}
}

if (extension_loaded('xsl'))
{//See also: http://alexandre.alapetite.net/doc-alex/xslt-php4-php5/
 function domxml_xslt_stylesheet($xslstring) {return new php4DomXsltStylesheet(DOMDocument::loadXML($xslstring));}
 function domxml_xslt_stylesheet_doc($dom_document) {return new php4DomXsltStylesheet($dom_document);}
 function domxml_xslt_stylesheet_file($xslfile) {return new php4DomXsltStylesheet(DOMDocument::load($xslfile));}
 class php4DomXsltStylesheet
 {
  private $myxsltProcessor;
  function php4DomXsltStylesheet($dom_document)
  {
   $this->myxsltProcessor=new xsltProcessor();
   $this->myxsltProcessor->importStyleSheet($dom_document);
  }
  function process($dom_document,$xslt_parameters=array(),$param_is_xpath=false)
  {
   foreach ($xslt_parameters as $param=>$value) $this->myxsltProcessor->setParameter('',$param,$value);
   $myphp4DOMDocument=new php4DOMDocument();
   $myphp4DOMDocument->myDOMNode=$this->myxsltProcessor->transformToDoc($dom_document->myDOMNode);
   return $myphp4DOMDocument;
  }
  function result_dump_file($dom_document,$filename)
  {
   $html=$dom_document->myDOMNode->saveHTML();
   file_put_contents($filename,$html);
   return $html;
  }
  function result_dump_mem($dom_document) {return $dom_document->myDOMNode->saveHTML();}
 }
}
?>

--- NEW FILE: freebusy.class.php ---
<?php
/*
 *  Copyright (c) 2004-2006 Klaraelvdalens Datakonsult AB, Intra2net AG,
 *                            erfrakon Partnerschaftsgesellschaft
 *
 *    Written by Steffen Hansen <steffen at klaralvdalens-datakonsult.se>
 *               Thomas Jarosch <thomas.jarosch at intra2net.com>
 *               Martin Konold <martin.konold at erfrakon.de>
 *
 *  This  program is free  software; you can redistribute  it and/or
 *  modify it  under the terms of the GNU  General Public License as
 *  published by the  Free Software Foundation; either version 2, or
 *  (at your option) any later version.
 *
 *  This program is  distributed in the hope that it will be useful,
 *  but WITHOUT  ANY WARRANTY; without even the  implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 *  General Public License for more details.
 *
 *  You can view the  GNU General Public License, online, at the GNU
 *  Project's homepage; see <http://www.gnu.org/licenses/gpl.html>.
 */

 /*
    TODO: Looks like we don't get called for deleted folders.
          Best thing would be to make the clients make the call.
    	  As an alternative a update_pfbs(user) script which can be 
	  called regularily for all users also helps with incompatible
	  updates.
 */


require_once 'Kolab/Freebusy/freebusycache.class.php';
require_once 'Kolab/Freebusy/freebusyimapcache.class.php';
require_once 'Kolab/Freebusy/recurrence.class.php';

if (version_compare(PHP_VERSION,'5', '>='))
  require_once('Kolab/Freebusy/domxml-php4-to-php5.php');

class FreeBusyRecurrence extends Recurrence {
  function FreeBusyRecurrence( &$imapcache, &$imapuid, &$vfb, &$extra ) {
    $this->imapcache =& $imapcache;
    $this->imapuid =& $imapuid;
    $this->vfb =& $vfb;
    $this->extra =& $extra;
  }

  function setBusy( $start, $end, $duration ) {
    $this->imapcache->add_imap2fb($this->imapuid, $start, null, $duration, $this->extra);
  }

  var $imapcache;
  var $vfb;
  var $extra;
};

class FreeBusy {
  function FreeBusy( $cache_dir,
		     $owner_email,
                     $username,
		     $password, 
		     $imaphost,
		     $imapoptions,
		     $fbfuture=60,
		     $fbpast=0 ) {
    $this->cache_dir = $cache_dir;
    $this->owner_email = $owner_email;
    $this->username = $username;
    $this->password = $password;
    $this->imaphost = $imaphost;
    $this->imapoptions = $imapoptions;
    $this->fbfuture = $fbfuture;
    $this->fbpast   = $fbpast;
    $this->relevance = null;
  }

  function imapConnect() {
    $this->imap_serverstring = "{".$this->imaphost.":".$this->imapport.$this->imapoptions."}";
    $this->imap = imap_open( $this->imap_serverstring, $this->username, $this->password );

    return $this->imap;
  }

  function imapDisconnect() {
    return imap_close($this->imap);
  }

  function imapOpenMailbox($foldername = 'INBOX') {
    $this->foldername = $foldername;

    // Reset error stack
    imap_errors();

    $rc = imap_reopen($this->imap, $this->imap_serverstring . $this->foldername);
    // PHP only returns false for imap_reopen() if we use an HALF_OPEN connection. doh!
    if (imap_last_error() !== false)
       $rc = false;

    // $a = $this->imap->getAnnotation( '/vendor/kolab/folder-type', '*' );
    // myLog( "$folder has annotation: ".print_r($a,true), RM_LOG_DEBUG);
    
    return $rc;
  }

  function getACL() {
    $imap_acls = imap_getacl($this->imap, $this->foldername);

    $rtn = array();
    while (list($user, $rights) = each($imap_acls))
        $rtn[] = array("USER" => $user, "RIGHTS" => $rights);

    return $rtn;
  }

  function getRelevance() {
    // cached?
    if (isset($this->relevance))
        return $this->relevance;

    $val = imap_getannotation( $this->imap, $this->foldername, '/vendor/kolab/incidences-for', 'value.shared' );
    if( $val === false || empty($val) ) {
      myLog("No /vendor/kolab/incidences-for found for ".$this->foldername, RM_LOG_DEBUG);
      $this->relevance = "admins";
    } else {
      myLog("/vendor/kolab/incidences-for = ".print_r($val,true)." for ".$this->foldername, RM_LOG_DEBUG);
    }
    
    return $this->relevance;
  }

  function &generateFreeBusy($startstamp = NULL, $endstamp = NULL ) {
    require_once 'PEAR.php';
    require_once 'Horde/iCalendar.php';
    require_once 'Horde/MIME.php';
    require_once 'Horde/MIME/Message.php';
    require_once 'Horde/MIME/Structure.php';
  
    // Default the start date to today.
    if (is_null($startstamp)) {
      $month = date('n');
      $year = date('Y');
      $day = date('j');
    
      $startstamp = strtotime( '-'.$this->fbpast.' days', mktime(0, 0, 0, $month, $day, $year) );
    }
  
    // Default the end date to the start date + fbfuture.

    if (is_null($endstamp) || $endstamp < $startstamp) {
      $endstamp = strtotime( '+'.$this->fbfuture.' days', $startstamp );
    }

    myLog("Creating pfb from $startstamp to $endstamp", RM_LOG_DEBUG);
  
    // Create the new iCalendar.
    $vCal = &new Horde_iCalendar();
    $vCal->setAttribute('PRODID', '-//proko2//freebusy 1.0//EN');
    $vCal->setAttribute('METHOD', 'PUBLISH');
  
    // Create new vFreebusy.
    $vFb = &Horde_iCalendar::newComponent('vfreebusy', $vCal);
    $vFb->setAttribute('ORGANIZER', 'MAILTO:' . $this->username);
  
    $vFb->setAttribute('DTSTAMP', time());
    $vFb->setAttribute('DTSTART', $startstamp);
    $vFb->setAttribute('DTEND', $endstamp);
    // URL is not required, so out it goes...
    //$vFb->setAttribute('URL', 'http://' . $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI']);
  
    $status = imap_status_current($this->imap, SA_MESSAGES | SA_UIDVALIDITY | SA_UIDNEXT);

    if( $status->messages == 0 ) {
      //Outlook does not like the fake DTSTART and DTEND information 
      //see also: https://intevation.de/roundup/kolab/issue1422
      //$vFb->setAttribute('DTSTART', 0, array(), false );
      //$vFb->setAttribute('DTEND', 0, array(), false );
      $vFb->setAttribute('COMMENT', 'This is a dummy vfreebusy that indicates an empty calendar');
      /* It seems to be a bad idea to put bogus values in pfbs
       * so we accept that they are not completely in line 
       * with the rfc and take care of the problem when merging 
       * pfbs to ifbs later 
       */
      //$vFb->addBusyPeriod( 'BUSY', 0,0, null );
      $vCal->addComponent($vFb);
      $retval = array($vCal->exportvCalendar(),$vCal->exportvCalendar());
      return $retval;
    }
    myLog("Reading messagelist", RM_LOG_DEBUG);
    $getMessages_start = microtime_float();
//    $msglist = $this->imap->getMessagesList();
    //$msglist = &$this->imap->getMessages();
    myLog("FreeBusy::imap->getMessagesList() took ".(microtime_float()-$getMessages_start)." secs.", RM_LOG_DEBUG);
//   if( PEAR::isError( $msglist ) ) return array( $msglist, null);
//	foreach ($msglist as $msginfo) {

    // This only happens in php standalone mode and is needed
    // to make the debug log quiet
    if (!isset($_SERVER["SERVER_NAME"]))
        $_SERVER["SERVER_NAME"] = "localhost";

    $imapcache = new FreeBusyIMAPCache($this->cache_dir."/", $this->owner_email, $this->foldername);
    $imapcache->cache_load();

    $uids = imap_search($this->imap, "UNDELETED", SE_UID);

    if ($imapcache->check_folder_changed($status->uidvalidity, $status->uidnext, $this->getRelevance(), $uids)) {
        reset ($uids);
        while(list($key, $uid) = each($uids))
            if (!$imapcache->check_uid_exists($uid))
                $this->process_imap_message($imapcache, $uid, $startstamp, $endstamp);
    }

    // store cache and output free busy list
    $imapcache->cache_store();
    $imapcache->output_fb($vFb);

    $xvCal = $vCal;
    $xvCal->addComponent($vFb);
    $vCal->addComponent($this->clearExtra($vFb));

    // Generate the vCal file.
    $result = array( $vCal->exportvCalendar(), $xvCal->exportvCalendar() );
    return $result;
  }

  /********************** Private API below this line ********************/

  function process_imap_message(&$imapcache, &$imapuid, &$startstamp, &$endstamp)
  {
      myLog("Processing new message $imapuid", RM_LOG_DEBUG);
      $imapcache->add_empty_imap2fb($imapuid);

      $textmsg = imap_fetchheader($this->imap, $imapuid, FT_UID).imap_body($this->imap, $imapuid, FT_UID);

      $mimemsg = &MIME_Structure::parseTextMIMEMessage($textmsg);
    
      // Read in a Kolab event object, if one exists
      $parts = $mimemsg->contentTypeMap();
      $event = false;
      foreach ($parts as $mimeid => $conttype) {
	if ($conttype == 'application/x-vnd.kolab.event') {
	  $part = $mimemsg->getPart($mimeid);
	  $part->transferDecodeContents();
	  $event = $this->getEventHash($part->getContents());
	  if ($event === false) {
	    myLog("No x-vnd.kolab.event in ".$part->getContents(), RM_LOG_DEBUG);
	    continue;
	  }
	}
      }
    
      if ($event === false) {
	myLog("No x-vnd.kolab.events at all ", RM_LOG_DEBUG);
	return;
      }
      
      $uid = $event['uid'];

      if( array_key_exists( 'show-time-as', $event ) && 
	  strtolower(trim($event['show-time-as'])) == 'free' ) {
	return;
      }
    
      $summary = ($event['sensitivity'] == 'public' ? $event['summary'] : '');
    
      //myLog("Looking at message with uid=$uid and summary=$summary", RM_LOG_DEBUG);
    
      // Get the events initial start
      $initial_start = $event['start-date'];
      $initial_end = $event['end-date'];
      if( $event['allday'] ) {
	$initial_end = strtotime( '+1 day', $initial_end );
	myLog("Detected all-day event $uid", RM_LOG_DEBUG);
      }
      $extra = array( 'X-UID'     => base64_encode($uid) );
      if (!empty($summary)) {
	$extra['X-SUMMARY'] = base64_encode($summary);
      }
      if( !empty($event['scheduling-id']) ) {
	$extra['X-SID'] = base64_encode($event['scheduling-id']);
      }
      if( !empty($event['location'])) {
	$extra['X-LOCATION'] = base64_encode($event['location']);
      }

      if( array_key_exists( 'recurrence', $event ) ) {	
	myLog("Detected recurring event $uid", RM_LOG_DEBUG);
	$rec = $event['recurrence'];
	$recurrence =& new FreeBusyRecurrence( $imapcache, $imapuid, $vFb, $extra );
	$recurrence->setStartDate( $initial_start );
	$recurrence->setEndDate( $initial_end );
	$recurrence->setCycletype( $rec['cycle'] );
	if( isset($rec['type']) ) $recurrence->setType( $rec['type'] );
	if( isset($rec['interval']) ) $recurrence->setInterval( (int)$rec['interval'] );
	if( isset($rec['daynumber']) ) $recurrence->setDaynumber( $rec['daynumber'] );
	if( isset($rec['day']) ) $recurrence->setDay( $rec['day'] );
	if( isset($rec['month']) ) $recurrence->setMonth( $rec['month'] );
	if( isset($rec['exclusion'] ) ) $recurrence->setExclusionDates( $rec['exclusion'] );
	$rangetype = $rec['rangetype'];
	if( $rangetype == 'number' ) {
	  $range = (int)$rec['range'];
	} else if( $rangetype == 'date' ) {
	  $range = $this->parseDateTime( $rec['range'] );
	} else {
	  $range = false;
	}
	$recurrence->setRangetype( $rangetype );
	$recurrence->setRange( $range );
	$recurrence->expand( $startstamp, $endstamp );
      } else {
	// Normal event
    
	// Don't bother adding the initial event if it's outside our free/busy window
	if ($initial_start < $startstamp || $initial_end > $endstamp) {
	  return;
	}

        // $initial_start/* + FreeBusy::tzOffset($initial_start)
        $imapcache->add_imap2fb($imapuid, $initial_start, $initial_end, null, $extra);

      }
  }

  function tzOffset( $ts ) {
    $dstr = date('O',$ts);
    return 3600 * substr( $dstr, 0, 3) + 60 * substr( $dstr, 3, 2);
  }

  // static function to compute foldername for imap
  function imapFolderName( $user, $owner, $folder = false, $default_domain = false ) {
    $userdom = false;
    $ownerdom = false;
    if( ereg( '(.*)@(.*)', $user, $regs ) ) {
      // Regular user
      $user = $regs[1];
      $userdom  = $regs[2];
    } else {
      // Domainless user (ie. manager)
    }

    if( ereg( '(.*)@(.*)', $owner, $regs ) ) {      
      $owner = $regs[1];
      $ownerdom = $regs[2];
    } else {
    }

    $fldrcomp = array('user',$owner );
    if( $folder ) $fldrcomp[] = $folder;
    $fldr = join('/', $fldrcomp );
    if( $ownerdom && !$userdom ) $fldr .= '@'.$ownerdom;
    return $fldr;
  }

  // Date/Time value parsing, courtesy of the Horde iCalendar library
  function parseTime($text) {
    // There must be a trailing 'Z' on a time
    if (strlen($text) != 9) {
      return false;
    }

    $time['hour']  = intval(substr($text, 0, 2));
    $time['minute'] = intval(substr($text, 3, 2));
    $time['second']  = intval(substr($text, 6, 2));
    return $time;
  }

  function parseDate($text) {
    if (strlen($text) != 10) {
      return false;
    }
    
    $date['year']  = intval(substr($text, 0, 4));
    $date['month'] = intval(substr($text, 5, 2));
    $date['mday']  = intval(substr($text, 8, 2));
    
    return $date;
  }

  function parseDateTime($text) {
    $dateParts = split('T', $text);
    if (count($dateParts) != 2 && !empty($text)) {
      // Not a datetime field but may be just a date field.
      if (!$date = FreeBusy::parseDate($text)) {
	return $date;
      }
      return @gmmktime(0, 0, 0, $date['month'], $date['mday'], $date['year']);
    }
    
    if (!$date = FreeBusy::parseDate($dateParts[0])) {
      return $date;
    }
    if (!$time = FreeBusy::parseTime($dateParts[1])) {
      return $time;
    }
    
    return @gmmktime($time['hour'], $time['minute'], $time['second'],
                     $date['month'], $date['mday'], $date['year']);
  }

  function getEventHash($xml_text) {
    $xmldoc = domxml_open_mem($xml_text, DOMXML_LOAD_PARSING +
			       DOMXML_LOAD_COMPLETE_ATTRS + DOMXML_LOAD_SUBSTITUTE_ENTITIES +
			       DOMXML_LOAD_DONT_KEEP_BLANKS, $error);
    
    if (!empty($error)) {
      // There were errors parsing the XML data - abort
      myLog( "Error parsing \"$xml_txt\": $error", RM_LOG_ERROR);
      return false;
    }
    
    $noderoot = $xmldoc->document_element();
    $childnodes = $noderoot->child_nodes();
    
    $event_hash = array();
    
    // Build the event hash
    foreach ($childnodes as $value) {
      //myLog("Looking at tag ".($value->tagname), RM_LOG_DEBUG);
      if( $value->tagname == 'recurrence' ) {
	$rhash = array();
	$attrs = $value->attributes();
	foreach( $attrs as $attr ) {
	  //myLog("getEventHash setting rhash[".$attr->name."] = ".$attr->value, RM_LOG_DEBUG);
	  $rhash[$attr->name] = $attr->value;
	}
	foreach( $value->child_nodes() as $v ) {
	  if( $v->tagname == 'day' || $v->tagname == 'exclusion' ) {
	    $rhash[$v->tagname][] = $v->get_content();
	  } else {
	    $rhash[$v->tagname] = $v->get_content();
	    if( $v->tagname == 'range' && $v->has_attribute('type') ) {
	      $rhash['rangetype'] = $v->get_attribute('type');
	    }
	  }
	}	
	$event_hash[$value->tagname] = $rhash;
      } else {
	$event_hash[$value->tagname] = $value->get_content();
      }
    }
    
    //myLog("RAW Event: ".print_r($event_hash, true), RM_LOG_DEBUG);

    // Perform some sanity checks on the event
    if (
        empty($event_hash['uid']) ||
        empty($event_hash['start-date']) ||
        empty($event_hash['end-date'])
	) {
      return false;
    }
    

    if (empty($event_hash['sensitivity'])) {
      $event_hash['sensitivity'] = 'public';
    }
    
    // Set the summary if it's not present, so we don't get PHP warnings
    // about accessing non-present keys
    if (empty($event_hash['summary'])) {
      $event_hash['summary'] = '';
    }
    
    // Convert our date-time values to timestamps
    if( strpos( $event_hash['start-date'], 'T' ) === false &&
	strpos( $event_hash['end-date'], 'T' ) === false ) {
      $event_hash['allday'] = true;
    } else {
      $event_hash['allday'] = false;      
    }
    $event_hash['start-date'] = FreeBusy::parseDateTime($event_hash['start-date']);
    $event_hash['end-date'] = FreeBusy::parseDateTime($event_hash['end-date']);
    
    return $event_hash;
  }

  function clearExtra( $vFb ) {
    $vFb->_extraParams = array();
    return $vFb;
  }

  var $cache_dir;
  var $username;
  var $password;
  var $imaphost;
  var $imapport = 143;
  var $imapoptions;
  var $foldername;
  var $relevance;

  // Settings
  var $fbfuture;
  var $fbpast;
  var $default_domain = 'foo';
  var $week_starts_on_sunday = false;

  var $imap;
  var $imap_serverstring;  
};

?>

--- NEW FILE: freebusycache.class.php ---
<?php
/*
 *  Copyright (c) 2004 Klaraelvdalens Datakonsult AB
 *  Copyright (c) 2005 Intra2net AG
 *  Copyright (c) 2006 erfrakon Partnerschaftsgesellschaft
 *
 *    Written by Steffen Hansen <steffen at klaralvdalens-datakonsult.se>
 *    Written by Thomas Jarosch <thomas.jarosch at intra2net.com>
 *    Written by Martin Konold <martin.konold at erfrakon.de>
 *
 *  This  program is free  software; you can redistribute  it and/or
 *  modify it  under the terms of the GNU  General Public License as
 *  published by the  Free Software Foundation; either version 2, or
 *  (at your option) any later version.
 *
 *  This program is  distributed in the hope that it will be useful,
 *  but WITHOUT  ANY WARRANTY; without even the  implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 *  General Public License for more details.
 *
 *  You can view the  GNU General Public License, online, at the GNU
 *  Project's homepage; see <http://www.gnu.org/licenses/gpl.html>.
 */

/*! To load/store partial freebusy lists
    and their ACLs */
    
require_once('Kolab/Freebusy/misc.php');

class FreeBusyCache {

  function FreeBusyCache( $basedir, $dbformat, $params, $extended = false) {
    $this->basedir = $basedir;
    $this->dbformat = $dbformat;
    $this->params = $params;
    $this->extended = $extended;
	 /* make sure that a database really exists before accessing it */
    if( !file_exists( $this->basedir.'/pfbcache.db' ) ) {
    	$db = dba_open( $this->basedir.'/pfbcache.db', 'cd' );
		dba_close($db);
	 }
  }

  function store( $filename, $fbdata, $acl, $relevance ) {

    $fbfilename = $this->mkfbfilename($filename);
    myLog("FreeBusyCache::store( file=$fbfilename, acl=[ "
	  .str_replace("\n",", ",$this->aclToString($acl))
	  ."], relevance=$relevance )", RM_LOG_DEBUG);
    if( $fbdata === false ) {
      // false data means delete the pfb
      unlink($fbfilename);
      $oldacl = $this->loadACL( $filename );
      if( isset($this->params['dbtype']) ) {
	$db = dba_open( $this->basedir.'/pfbcache.db', 'cd', $this->params['dbtype'] );
      } else {
	$db = dba_open( $this->basedir.'/pfbcache.db', 'cd' );
      }
      if( $db === false ) return false;
      foreach( $oldacl as $ac ) {
	  if( dba_exists( $ac['USER'], $db ) ) {
	    $lst = dba_fetch( $ac['USER'], $db );
	    $lst = $this->decodeList( $lst );
	    $lst = array_diff( $lst, array($fbfilename));
	    myLog("(delete) dba_replace(".$uid.", \"".$this->encodeList($lst)."\")", RM_LOG_DEBUG);
	    dba_replace( $uid, $this->encodeList($lst), $db );
	  }
      }
      unlink($fbfilename.'.acl');
    } else {
      //myLog("Storing $filename with acl ".var_export($acl,true), RM_LOG_DEBUG);
      
      // Create directories if missing
      $fbdirname  = dirname( $fbfilename );
      if (!is_dir($fbdirname)) {
	if( !$this->mkdirhier($fbdirname) ) {
	  $this->error = _("Error creating dir $fbdirname");
	  return false;
	}
      }
      
      // Store the fb list
      $tmpn = tempnam($this->basedir, 'fb');
      $tmpf = fopen($tmpn, 'w');
      if( !$tmpf ) return false;
      fwrite($tmpf, $fbdata);
      if( !rename($tmpn, $fbfilename) ) {
	$this->error = _("Error renaming $tmpn to $fbfilename");
	return false;
      }
      fclose($tmpf);
      
      // Store the ACL
      $oldacl = $this->loadACL( $filename );
      if( !$this->storeACL( $filename, $acl ) ) return false;
      
      // Update overview db
      switch( $relevance ) {
      case 'admins':  $perm = 'a'; break;
      case 'readers': $perm = 'r'; break;
      case 'nobody':  $perm = 'false'; break;
      default: $perm = 'a';
      }

      if( isset($this->params['dbtype']) ) {
	$db = dba_open( $this->basedir.'/pfbcache.db', 'cd', $this->params['dbtype'] );
      } else {
	$db = dba_open( $this->basedir.'/pfbcache.db', 'cd' );
      }
      if( $db === false ) {
	myLog('Unable to open freebusy cache db '.$this->basedir.'/pfbcache.db',
	      RM_LOG_ERROR );
	return false;
      }
      foreach( $acl as $ac ) {
	$uid = $ac['USER'];
	if( dba_exists( $uid, $db ) ) {
	  $lst = dba_fetch( $uid, $db );
	  $lst = $this->decodeList( $lst );
	  $lst = array_diff( $lst, array($filename));
	  dba_replace( $uid, $this->encodeList($lst), $db );
	}
      }
      if( $perm !== false ) {
	foreach( $acl as $ac ) {
	  if( strpos( $ac['RIGHTS'], $perm ) !== false ) {
	    if( dba_exists( $ac['USER'], $db ) ) {
	      $lst = dba_fetch( $ac['USER'], $db );
	      $lst = $this->decodeList( $lst );
	      $lst[] = $filename;
	      dba_replace( $ac['USER'], $this->encodeList(array_unique($lst)), $db );
	    } else {
	      dba_insert( $ac['USER'], $filename, $db );
	    }
	  }
	}
      }
      dba_close($db);
      return true;
    }
  }

  function load( $filename, &$ts, &$acl ) {
    myLog("FreeBusyCache::load( $filename )", RM_LOG_DEBUG);
    $fbfilename = $this->mkfbfilename($filename);
    if( file_exists($fbfilename) ) {
      if( !is_null($ts)) $ts = filectime($fbfilename);
      $acl = $this->loadACL($filename);
      myLog("FreeBusyCache::load(): ts=$ts acl=[ "
	    .str_replace("\n",", ",$this->aclToString($acl))
	    ."] )", RM_LOG_DEBUG);
      return file_get_contents($fbfilename);
    }
    myLog("FreeBusyCache: file $fbfilename does not exist", RM_LOG_ERROR);
    return false;
  }

  function delete( $filename ) {
    $fbfilename = $this->mkfbfilename($filename);
    unlink($fbfilename);
    unlink($this->mkaclfilename($filename));    
    if( isset($this->params['dbtype']) ) {
      $db = dba_open( $this->basedir.'/pfbcache.db', 'cd', $this->params['dbtype'] );
    } else {
      $db = dba_open( $this->basedir.'/pfbcache.db', 'cd' );
    }
    if( $db === false ) {
	myLog("dba_open(".$this->basedir.'/pfbcache.db'.") failed", RM_LOG_ERROR);
	return false;
    }
    for( $uid = dba_firstkey($db); $uid !== false; $uid = dba_nextkey($db)) {
      $lst = dba_fetch( $uid, $db );
      $lst = $this->decodeList( $lst );
      $lst = array_diff( $lst, array($filename));
      myLog("(delete) dba_replace(".$uid.", \"".$this->encodeList($lst)."\")", RM_LOG_DEBUG);
      dba_replace( $uid, $this->encodeList($lst), $db );      
    }
    dba_close($db);
  }

  function findAll( $uid, $groups ) {
    $lst = array();
    if( isset($this->params['dbtype']) ) {
      $db = dba_open( $this->basedir.'/pfbcache.db', 'rd', $this->params['dbtype'] );
    } else {
      $db = dba_open( $this->basedir.'/pfbcache.db', 'rd' );
    }
    if( $db === false ) {
	myLog("dba_open(".$this->basedir.'/pfbcache.db'.") failed", RM_LOG_ERROR);
	return false;
    }
    $uids = $groups;
    for( $i = 0; $i < count($uids); $i++ ) $uids[$i] = 'group:'.$uids[$i];
    $uids[] = $uid;
    foreach( $uids as $uid ) {
      if( dba_exists( $uid, $db ) ) {
	$tmplst = dba_fetch( $uid, $db );
	myLog("Found ".$uid." := $tmplst", RM_LOG_DEBUG);
	$lst = array_merge((array) $lst,(array) $this->decodeList( $tmplst ) );
      } else {
	myLog("$uid not found", RM_LOG_DEBUG);	
      }
    }
    dba_close($db);
    $lst = array_unique($lst);
    myLog( "FreeBusyCache::findAll( $uid, [".join(', ', $groups).'] ) = ['.join(', ',$lst).']',
	   RM_LOG_DEBUG );
    return $lst;
  }

  /*************** Private API below this line *************/
  // a copy of this function exists in freebusyimapcache.class.php
  function mkdirhier( $dirname ) {
    $base = substr($dirname,0,strrpos($dirname,'/'));
    $base = str_replace(".", "^", $base);
    if( !empty( $base ) && !is_dir( $base ) ) {
      if( !$this->mkdirhier( $base ) ) return false;
    }
    if( !file_exists( $dirname ) ) return mkdir( $dirname, 0755 );
    return true;
  }

  function mkfbfilename( $fbfilename ) {
    $fbfilename = str_replace( '.', '^', $fbfilename );

    $fbfilename = str_replace( "\0", '', $fbfilename );    
    return $this->basedir.'/'.$fbfilename.($this->extended?'.xpfb':'.pfb');
  }

  function mkaclfilename( $fbfilename ) {
    $fbfilename = str_replace( '.', '^', $fbfilename );
    $fbfilename = str_replace( "\0", '', $fbfilename );    
    return $this->basedir.'/'.$fbfilename.($this->extended?'.xpfb':'.pfb').'.acl';
  }

  function aclToString( $acl ) {
    $aclstr = '';
    foreach( $acl as $ac ) {
      $aclstr .= $ac['USER'].' '.$ac['RIGHTS']."\n";
    }
    return $aclstr;
  }

  function aclFromString( $aclstr ) {
    $acl = array();
    foreach( split("\n", $aclstr ) as $ac ) {
      if( ereg("(.*) (.*)", $ac, $regs ) ) {
	$acl[] = array('USER' => $regs[1], 'RIGHTS' => $regs[2] );
      }
    }
    return $acl;
  }

  function loadACL( $filename ) {
    return $this->aclFromString( @file_get_contents($this->mkaclfilename($filename)) );    
  }

  function storeACL( $filename, $acl ) {
    $tmpn = tempnam($this->basedir, 'acl');
    $tmpf = fopen($tmpn, 'w');
    if( !$tmpf ) return false;
    fwrite($tmpf, $this->aclToString($acl) );

    $aclfilename = $this->mkaclfilename($filename);
    if( !rename($tmpn, $aclfilename) ) {
      $this->error = _("Error renaming $tmpn to $fbfilename");
      return false;
    }
    fclose($tmpf);    
    return true;
  }

  function aclDiff( $oldacl, $newacl ) {
    $newuids = array();
    foreach( $newacl as $ac ) {
      if( strpos( $ac['RIGHTS'], 'r' ) !== false ) {
	$newuids[$ac['USER']] = true;
      }
    }
    $deleteduids = array();
    foreach( $oldacl as $ac ) {
      if( !$newuids[$ac['USER']] ) $deleteduids[] = $ac['USER'];
    }
    return $deleteduids;
  }

  /** Returns an array with cyrus permission chars (lrsp...) as keys */
  function getRights( $acl, $uid, $groups ) {
    if( !isset($uid) ) return false;
    if( is_array( $groups ) ) $uids = $groups;
    else $uids = array();
    for( $i = 0; $i < count($uids); $i++ ) $uids[$i] = 'group:'.$uids[$i];
    $uids[] = $uid;
    $rights = array();
    $negacl = array();

    // Calc positive rights
    foreach( $acl as $ac ) {
      $r = $ac['RIGHTS'];
      $u = $ac['USER'];
      if( $r{0} == '-' ) {
	$negacl[] = array( 'USER' => $u, 'RIGHTS' => $r );
	continue;
      }
      if( in_array( $u, $uids ) ) {
	for( $i = 0; $i < strlen($r); $i++ ) {
	  $rights[$r{$i}] = true;
	}
      }
    }
    
    // Remove negative rights
    foreach( $negacl as $ac ) {
      $r = $ac['RIGHTS'];
      $u = $ac['USER'];
      if( in_array( $u, $uids ) ) {
	for( $i = 1; $i < strlen($r); $i++ ) {
	  unset($rights[$r{$i}]);
	}
      }
    }
    return $rights;
  }

  function decodeList( $str ) {
    myLog("FreeBusyCache::decodeList($str)", RM_LOG_DEBUG);
    return split( ',', $str );
  }
  function encodeList( $lst ) {
    return join(',',$lst);
  }

  function recursivedir( $dir ) {
    $dh = opendir( $dir );
    if( $dh === false ) return false;
    $dirs = array();
    while (($file = readdir($dh)) !== false) {
      if( is_dir($dir.'/'.$file) ) {
	if($file=='.' || $file=='..') continue;
	if( !ereg( ($this->extended?'/.*\.xpfb$/':'/.*\.pfb$/'), $file ) ) continue;
	$tmp = $this->recursivedir( $dir.'/'.$file );
	if( $tmp !== false ) $dirs = array_merge((array) $dirs,(array) $tmp );
      } else if( is_file($dir.'/'.$file) ) {
	$dirs[] = $dir.'/'.$file;
      }
    }
    closedir( $dh );
    return $dirs;
  }

  var $basedir;
  var $dbformat;  
  var $error;
};

?>

--- NEW FILE: freebusycollector.class.php ---
<?php
/*
 *  Copyright (c) 2004 Klaraelvdalens Datakonsult AB
 *
 *    Written by Steffen Hansen <steffen at klaralvdalens-datakonsult.se>
 *
 *  This  program is free  software; you can redistribute  it and/or
 *  modify it  under the terms of the GNU  General Public License as
 *  published by the  Free Software Foundation; either version 2, or
 *  (at your option) any later version.
 *
 *  This program is  distributed in the hope that it will be useful,
 *  but WITHOUT  ANY WARRANTY; without even the  implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 *  General Public License for more details.
 *
 *  You can view the  GNU General Public License, online, at the GNU
 *  Project's homepage; see <http://www.gnu.org/licenses/gpl.html>.
 */

define( 'FB_OK', 0 );
define( 'FB_TOO_OLD', 1 );
define( 'FB_PARSE_ERROR', 2 );

class FreeBusyCollector {

  function FreeBusyCollector( $organizer ) {
    $this->organizer = $organizer;
    $this->init();
  }

  function init() {
    require_once 'PEAR.php';
    require_once 'Horde/iCalendar.php';
    require_once 'Horde/MIME.php';
    require_once 'Horde/MIME/Message.php';
    require_once 'Horde/MIME/Structure.php';

    // Create the new iCalendar.
    $this->vCal = &new Horde_iCalendar();
    $this->vCal->setAttribute('PRODID', '-//proko2//freebusy 1.0//EN');
    $this->vCal->setAttribute('METHOD', 'PUBLISH');
  
    // Create new vFreebusy.
    $vFb = &Horde_iCalendar::newComponent('vfreebusy', $this->vCal);
    $vFb->setAttribute('ORGANIZER', 'MAILTO:' . $this->organizer);
  
    $vFb->setAttribute('DTSTAMP', time());
    //$vFb->setAttribute('DTSTART', $startstamp);
    //$vFb->setAttribute('DTEND', $endstamp);
    $vFb->setAttribute('URL', 'http://' . $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI']);
    $this->vCal->addComponent($vFb);
  }

  function addFreebusy( $text ) {
    $vCal = &new Horde_iCalendar();
    if( !$vCal->parsevCalendar($text) ) {
      trigger_error("Could not parse ical", E_USER_ERROR);
      return FB_PARSE_ERROR;
    }
    $vFb1 = &$this->vCal->findComponent( 'vfreebusy' );
    $vFb2 = &$vCal->findComponent( 'vfreebusy' );
    if( !$vFb2 ) {
      trigger_error("Could not find freebusy info in ical", E_USER_ERROR);      
      return FB_PARSE_ERROR;
    }

    if( $ets = $vFb2->getAttributeDefault( 'DTEND', false ) !== false ) {
      // PENDING(steffen): Make value configurable
      if( $ets < time() ) {
	// Not relevant anymore
	return FB_TOO_OLD;
      }
    }
    
    if( ($sts = $vFb1->getAttributeDefault('DTSTART', false)) === false ) {
      $vFb1->setAttribute('DTSTART', $vFb2->getAttribute('DTSTART'), array(), false );
    } else {
      $vFb1->setAttribute('DTSTART', min( $sts, $vFb2->getAttribute('DTSTART') ), array(), false );
    }
    if( ($ets = $vFb1->getAttributeDefault('DTEND', false)) === false ) {
      $vFb1->setAttribute('DTEND', $vFb2->getAttribute('DTEND'), array(), false );
    } else {
      $vFb1->setAttribute('DTEND', max( $ets, $vFb2->getAttribute('DTEND')), array(), false );
    }
    
    $vFb1->merge( $vFb2 );
    return FB_OK;
  }

  function exportvCalendar() {
    //$vFb->setAttribute('URL', 'http://' . $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI']);
    $vFb = &$this->vCal->findComponent( 'vfreebusy' );
    if( !(boolean)$vFb->getBusyPeriods() ) {
      /* No busy periods in fb list. We have to add a
       * dummy one to be standards compliant
       */
      $vFb->setAttribute('COMMENT', 'This is a dummy vfreebusy that indicates an empty calendar');
      $vFb->addBusyPeriod( 'BUSY', 0,0, null );
    }
    return $this->vCal->exportvCalendar();
  }

  function collect( $user, &$cache ) {
    
  }

  var $organizer;
  var $vCal;
};

?>

--- NEW FILE: freebusyimapcache.class.php ---
<?php
/*
 *  Copyright (c) 2005 Intra2net AG
 *  Copyright (c) 2006 erfrakon Partnerschaftsgesellschaft
 *
 *    Written by Thomas Jarosch <thomas.jarosch at intra2net.com>
 *    Written by Martin Konold <martin.konold at erfrakon.de>
 *
 *  This  program is free  software; you can redistribute  it and/or
 *  modify it  under the terms of the GNU  General Public License as
 *  published by the  Free Software Foundation; either version 2, or
 *  (at your option) any later version.
 *
 *  This program is  distributed in the hope that it will be useful,
 *  but WITHOUT  ANY WARRANTY; without even the  implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 *  General Public License for more details.
 *
 *  You can view the  GNU General Public License, online, at the GNU
 *  Project's homepage; see <http://www.gnu.org/licenses/gpl.html>.
 */

 /* Class to efficently cache kolab events stored on an IMAP server */

class FreeBusyIMAPCache {
  var $version;       // internal version of format
  var $store_prefix;  // prefix to prepend to all file operations
  var $owner;         // folder owner
  var $foldername;    // folder name
  var $cache_modified;      // indicates if we have to writeout the cache
  var $cache;         // cache data

  function FreeBusyIMAPCache($store_prefix, &$owner, &$foldername) {
    $this->store_prefix = $store_prefix;
    $this->owner = $owner;
    $this->foldername = $foldername;
    $this->version = 1;
    $this->reset_cache();
  }
  
  function reset_cache() {
    $this->cache = array();
    $this->cache["version"] = $this->version;
    $this->cache["uidvalidity"] = -1;
    $this->cache["uidnext"] = -1;
    $this->cache["incidences-for"] = "";
    $this->cache["imap2fb"] = array();
    $this->cache_modified = true;
  }

  function check_folder_changed($uidvalidity, $uidnext, $incidences_for, &$new_uids) {
    $changed = false;
    // uidvalidity changed?
    if ($uidvalidity != $this->cache["uidvalidity"]) {
      myLog("uidvalidity changed (old: ".$this->cache["uidvalidity"].", new: $uidvalidity), clearing cache", RM_LOG_DEBUG);
      $this->reset_cache();
      $changed = true;
    }

    // uidnext changed?
    if ($uidnext != $this->cache["uidnext"]) {
      myLog("uidnext on folder changed (old: ".$this->cache["uidnext"].", new: ".$uidnext.")", RM_LOG_DEBUG);
      $changed = true;
    }

    // incidences-for changed?
    if ($incidences_for != $this->cache["incidences-for"]) {
      myLog("incidences-for changed (old: ".$this->cache["incidences-for"].", new: $incidences_for), clearing cache", RM_LOG_DEBUG);
      $this->reset_cache();
      $changed = true;
    }

    $this->cache["uidvalidity"] = $uidvalidity;
    $this->cache["uidnext"] = $uidnext;
    $this->cache["incidences-for"] = $incidences_for;

    // deleted a message?
    $old_uids = array_keys($this->cache["imap2fb"]);
    while(list($key, $old_uid) = each($old_uids)) {
      if (!in_array($old_uid, $new_uids)) {
        unset($this->cache["imap2fb"][$old_uid]);
        $this->cache_modified = true;
        $changed = true;
      }
    }

    if (!$changed)
      myLog("check_changed: folder didn't change", RM_LOG_DEBUG);
    return $changed;
  }

  function check_uid_exists($uid) {
    return array_key_exists($uid, $this->cache["imap2fb"]);
  }

  function add_empty_imap2fb(&$imap_uid) {
    $this->cache["imap2fb"][$imap_uid] = array();
    $this->cache_modified = true;
  }

  function add_imap2fb(&$imap_uid, $fb_start, $fb_end, $fb_duration, $fb_extra) {
    /*
         Internal imap2fb array structure:
         0..n IMAP uid
         |----------- 0..n free/busy periods
                      |----------- start
                      |----------- end
                      |----------- duration
                      |----------- extra
    */
    myLog("added event to store: uid: $imap_uid, start: $fb_start, end: $fb_end, duration: $fb_duration", RM_LOG_DEBUG);

    $store = array();
    $store["start"] = $fb_start;
    $store["end"] = $fb_end;
    $store["duration"] = $fb_duration;
    $store["extra"] = $fb_extra;

    $this->cache["imap2fb"][$imap_uid][] = $store;
    $this->cache_modified = true;
  }

  function output_fb(&$vFb) {
    reset($this->cache["imap2fb"]);
    while(list($uid, $periods) = each($this->cache["imap2fb"]))
      while(list($key, $period) = each($periods))
        $vFb->addBusyPeriod('BUSY', $period["start"], $period["end"], $period["duration"], $period["extra"]);
  }


  function compute_filename() {
    $folder_parts = explode('/', $this->foldername);
    unset($folder_parts[0]);
    $folder_storename = join('/', $folder_parts);

    $folder_storename = str_replace(".", "^", $folder_storename);
    $folder_storename = str_replace("\0", "", $folder_storename);

    if( ereg( '(.*)@(.*)', $this->owner, $regs ) ) {
      $domain = $regs[2].'/';
	   $domain = str_replace(".", "^", $domain);
	   $domain = str_replace("\0", "", $domain);
    } else if( ereg('(.*)@(.*)', $folder_storename, $regs ) ) {
	// Folder-encoded domain, ie. domainless user
      $domain = $regs[2].'/';
      $folder_storename = $regs[1];
    } else $domain = '';
	
    $full_path = $this->store_prefix.$domain.$folder_storename.".imapcache";
    return $full_path;
  }

  function cache_load() {
    $filename = $this->compute_filename();

    myLog("Trying to load file: $filename", RM_LOG_DEBUG);

    if (!is_readable($filename))
      return false;

    $this->cache = unserialize(file_get_contents($filename));

    // Delete disc cache if it's from an old version
    if ($this->cache["version"] != $this->version) {
      myLog("Version mismatch (got: ".$this->cache["version"].", current: ".$this->version.", dropping cache", RM_LOG_WARN);
      $this->reset_cache();
    } 
    else $this->cache_modified = false;
    return true;
  }

  function cache_store($force=false) {
    if ($this->cache_modified || $force) {
      $filename = $this->compute_filename();
      myLog("Trying to save cache to file: $filename", RM_LOG_DEBUG);

      if (!$this->mkdirhier(dirname($filename))) {
        myLog("can't create director hierachy: ".dirname($filename), RM_LOG_ERROR);
        return;
      }

      $tmpname = tempnam(dirname($this->store_prefix), 'imapcache');
      $fp = fopen($tmpname, 'w');
      if(!$fp) return false;
        if (fwrite($fp, serialize($this->cache)) === false) {
          fclose ($fp);
          myLog("can't write to file: $tmpname. Out of discspace?", RM_LOG_ERROR);
          return;
        }

      if(!rename($tmpname, $filename)) {
         myLog("can't rename $tmpname to $filename", RM_LOG_ERROR);
         return false;
      }
      fclose($fp);
      $this->cache_modified = false;
    } 
    else {
      myLog("IMAPcache unmodified, not saving", RM_LOG_DEBUG);
    }	
  }

  function cache_delete() {
    unlink($this->compute_filename());
    $this->reset_cache();
  }

  function mkdirhier( $dirname ) {
    $base = substr($dirname,0,strrpos($dirname,'/'));
    $base = str_replace(".", "^", $base);
    if( !empty( $base ) && !is_dir( $base ) ) {
      if( !$this->mkdirhier( $base ) ) return false;
    }
    if( !file_exists( $dirname ) ) return mkdir( $dirname, 0755 );
    return true;
  }
};

?>

--- NEW FILE: freebusyldap.class.php ---
<?php
/*
 *  Copyright (c) 2004 Klaraelvdalens Datakonsult AB
 *
 *    Written by Steffen Hansen <steffen at klaralvdalens-datakonsult.se>
 *
 *  This  program is free  software; you can redistribute  it and/or
 *  modify it  under the terms of the GNU  General Public License as
 *  published by the  Free Software Foundation; either version 2, or
 *  (at your option) any later version.
 *
 *  This program is  distributed in the hope that it will be useful,
 *  but WITHOUT  ANY WARRANTY; without even the  implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 *  General Public License for more details.
 *
 *  You can view the  GNU General Public License, online, at the GNU
 *  Project's homepage; see <http://www.gnu.org/licenses/gpl.html>.
 */

class FreeBusyLDAP {
  function FreeBusyLDAP( $uri, $base ) {
    $this->is_bound = false;
    $this->uri = $uri;
    $this->base = $base;
    return $this->connection=ldap_connect($uri);    
  }

  function error() {
    return ldap_error( $this->connection );
  }

  function close() {
    $rc = ldap_close( $this->connection );
    $this->connection = false;
    return $rc;
  }

  function bind( $dn = false , $pw = '' ) {
    if( $dn ) return $this->is_bound = ldap_bind( $this->connection, $dn, $pw );
    else return $this->is_bound = ldap_bind( $this->connection);
  }

  function freeBusyPast() {
    $result = ldap_read( $this->connection, $this->base, 
			 '(&(objectClass=kolab)(k=kolab))',
			   array( 'kolabFreeBusyPast' ) );
    if( $result ) {
      $entries = ldap_get_entries( $this->connection, $result );
      if( $entries['count'] > 0 && !empty($entries[0]['kolabfreebusypast'][0]) ) {
	ldap_free_result($result);
	return $entries[0]['kolabfreebusypast'][0];
      }
    }
    return 0; // Default
  }

  // Return a hash of info about a user
  function userInfo( $uid ) {
    $result = ldap_search( $this->connection, $this->base, 
			   '(&(objectClass=kolabInetOrgPerson)(|(uid='.
			   $this->escape($uid).')(mail='.$this->escape($uid).')))',
			   array( 'dn','mail','uid','kolabHomeServer', 'kolabFreeBusyFuture' ) );
    if( $result ) {
      $entries = ldap_get_entries( $this->connection, $result );
      if( $entries['count'] > 0 && !empty($entries[0]['mail'][0]) ) {
	$hash = array();
	$hash['DN'] = $this->readLdapAttr( $entries[0], 'dn' );
	$hash['UID'] = $this->readLdapAttr( $entries[0], 'uid' );
	$hash['MAIL'] = $this->readLdapAttr( $entries[0], 'mail', $uid );
	$hash['HOMESERVER'] = $this->readLdapAttr( $entries[0], 'kolabhomeserver' );
	$hash['FBFUTURE'] = (int)($this->readLdapAttr( $entries[0], 'kolabfreebusyfuture', 60 ));
	ldap_free_result( $result );
	return $hash;
      }
      ldap_free_result( $result );
    }
    return false;
  }

  function mailForUid( $uid ) {
    return $this->_internalLookupMail('(&(objectClass=kolabInetOrgPerson)(uid='.$uid.'))',$uid);
  }

  function mailForUidOrAlias( $uid ) {
    return $this->_internalLookupMail('(&(objectClass=kolabInetOrgPerson)(|(uid='.$uid.')(alias='.$uid.')))',$uid);
  }

  function homeServer( $uid ) {
    $result = ldap_search( $this->connection, $this->base, 
			   '(&(objectClass=kolabInetOrgPerson)(|(uid='.$uid.')(mail='.$uid.')))',
			   array( 'kolabhomeserver' ) );
    if( $result ) {
      $entries = ldap_get_entries( $this->connection, $result );
      if( $entries['count'] > 0 && !empty($entries[0]['kolabhomeserver'][0]) ) 
	return $entries[0]['kolabhomeserver'][0];
    }
    return false;
  }

  function dn( $uid ) {
    $result = ldap_search( $this->connection, $this->base, 
			   '(&(objectClass=kolabInetOrgPerson)(|(uid='.$uid.')(mail='.$uid.')))',
			   array( 'dn' ) );
    if( $result ) {
      $entries = ldap_get_entries( $this->connection, $result );
      if( $entries['count'] > 0 ) { 
	return $entries[0]['dn'];
      }
    }
    return false;    
  }
  function distlists( $dn ) {
    $result = ldap_search( $this->connection, $this->base, 
			   '(&(objectClass=kolabGroupOfNames)(member='.FreeBusyLDAP::escape($dn).'))',
			   array( 'cn' ) );
    if( $result ) {
      $entries = ldap_get_entries( $this->connection, $result );
      $lst = array();
      for( $i = 0; $i < $entries['count']; $i++ ) {
	$lst[] = $entries[$i]['cn'][0];
      }
      myLog( "FreeBusyLDAP::distlists( $dn ) found ".count($lst)." entries", 
	     RM_LOG_DEBUG );
      return $lst;
    }
    myLog( "FreeBusyLDAP::distlists( $dn ) found nothing", 
	   RM_LOG_DEBUG );
    return false;
  }

  /**********/
  function escape( $str ) {
    /*
      From RFC-2254:

      If a value should contain any of the following characters

      Character       ASCII value
      ---------------------------
      *               0x2a
      (               0x28
      )               0x29
      \               0x5c
      NUL             0x00

     the character must be encoded as the backslash '\' character (ASCII
     0x5c) followed by the two hexadecimal digits representing the ASCII
     value of the encoded character. The case of the two hexadecimal
     digits is not significant.
     */
    $str = str_replace( '\\', '\\5c', $str );
    $str = str_replace( '*',  '\\2a', $str );
    $str = str_replace( '(',  '\\28', $str );
    $str = str_replace( ')',  '\\29', $str );
    $str = str_replace( '\0', '\\00', $str );
    return $str;
  }

  function readLdapAttr( $entry, $attrname, $default = false ) {
    $val = $default;
    if( !array_key_exists( $attrname, $entry ) ) return $default;
    else if( is_array( $entry[$attrname] ) ) {
      $val = $entry[$attrname][0];
    } else {
      $val = $entry[$attrname];
    }
    if( $val == '' ) $val = $default;
    return $val;
  }

  function _internalLookupMail( $filter, $default ) {
    if( !isset( $default ) ) return false;
    $result = ldap_search( $this->connection, $this->base, $filter,
			   array( 'mail' ) );
    if( $result ) {
      $entries = ldap_get_entries( $this->connection, $result );
      if( $entries['count'] > 0 && !empty($entries[0]['mail'][0]) ) return $entries[0]['mail'][0];
    }
    return $default;
  }

  var $connection;
  var $is_bound;
  var $uri;
  var $base;
};

?>
--- NEW FILE: freebusyldap_dummy.class.php ---
<?php
/*
 *  Copyright (c) 2005 Intra2net AG
 *  Copyright (c) 2006 erfrakon Partnerschaftsgesellschaft
 *
 *    Written by Thomas Jarosch <thomas.jarosch at intra2net.com>
 *    Written by Martin Konold <martin.konold at erfrakon.de>
 *
 *  This  program is free  software; you can redistribute  it and/or
 *  modify it  under the terms of the GNU  General Public License as
 *  published by the  Free Software Foundation; either version 2, or
 *  (at your option) any later version.
 *
 *  This program is  distributed in the hope that it will be useful,
 *  but WITHOUT  ANY WARRANTY; without even the  implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 *  General Public License for more details.
 *
 *  You can view the  GNU General Public License, online, at the GNU
 *  Project's homepage; see <http://www.gnu.org/licenses/gpl.html>.
 */

class FreeBusyLDAP {
  function FreeBusyLDAP( $uri, $base ) {
    return true;
  }

  function error() {
    return "LDAP::error not implemented";
  }

  function close() {
    return true;
  }

  function bind( $dn = false , $pw = '' ) {
    return true;
  }

  function freeBusyPast() {
    return 0;  // Default
  }

  // Return a hash of info about a user
  function userInfo( $uid ) {
    $rtn = array();

    $rtn["MAIL"] = $uid;
    $rtn["HOMESERVER"] = "";
    $rtn["FBFUTURE"] = 60;

    return $rtn;
  }

  function mailForUid( $uid ) {
    return $uid;
  }

  function homeServer( $uid ) {
    return "localhost";
  }

  function dn( $uid ) {
    return "";
  }

  function distlists( $dn ) {
    return array();
  }
};

?>
--- NEW FILE: misc.php ---
<?php
/*
 *  Copyright (c) 2004 Klaraelvdalens Datakonsult AB
 *
 *    Written by Steffen Hansen <steffen at klaralvdalens-datakonsult.se>
 *
 *  This  program is free  software; you can redistribute  it and/or
 *  modify it  under the terms of the GNU  General Public License as
 *  published by the  Free Software Foundation; either version 2, or
 *  (at your option) any later version.
 *
 *  This program is  distributed in the hope that it will be useful,
 *  but WITHOUT  ANY WARRANTY; without even the  implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 *  General Public License for more details.
 *
 *  You can view the  GNU General Public License, online, at the GNU
 *  Project's homepage; see <http://www.gnu.org/licenses/gpl.html>.
 */

function shutdown() {
    global $fb, $ldap;

    if (isset($fb) && $fb !== false) {
        $fb->imapDisconnect();
        $fb = false;
    }
    if (isset($ldap) && $ldap !== false) {
        $ldap->close();
        $ldap = false;
    }
    logClose();
}

function serverError($errortext) {
  myLog( $errortext, RM_LOG_ERROR );
  shutdown();

  header('HTTP/1.0 500 Server Error');

  echo '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>500 Server Error</title>
</head><body>
<h1>Error</h1>
<p>'.htmlentities($_SERVER['REQUEST_URI']) . ':</p>
';

  if (!empty($errortext)) {
    echo "<hr>
<pre>$errortext</pre>
";
  }

  echo '<hr>
' . $_SERVER['SERVER_SIGNATURE'] . '</body></html>';
    exit;
}

function notFound($errortext) {
  myLog( $errortext, RM_LOG_ERROR );
  shutdown();

  header('HTTP/1.0 404 Not Found');

  echo '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL ' . htmlentities($_SERVER['REQUEST_URI']) . ' was not found on this server.</p>
';

  if (!empty($errortext)) {
    echo "<hr>
<pre>$errortext</pre>
";
  }

  echo '<hr>
' . $_SERVER['SERVER_SIGNATURE'] . '</body></html>';
    exit;
}

function unauthorized($errortext = '') {
    global $params;
    myLog( $errortext, RM_LOG_ERROR );
    shutdown();

    header('WWW-Authenticate: Basic realm="freebusy-'.$params['email_domain'].'"');
    header('HTTP/1.0 401 Unauthorized');

    echo '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>401 Unauthorized</title>
</head><body>
<h1>Unauthorized</h1>
<p>You are not authorized to access the requested URL.</p>
';

    if (!empty($errortext)) {
        echo "<hr>
<pre>$errortext</pre>
";
    }

    echo '<hr>
' . $_SERVER['SERVER_SIGNATURE'] . '</body></html>
';

    exit;
}

// What myLog levels we can use
define('RM_LOG_SUPER',         -1);
define('RM_LOG_SILENT',         0);
define('RM_LOG_ERROR',          1);
define('RM_LOG_WARN',           2);
define('RM_LOG_INFO',           3);
define('RM_LOG_DEBUG',          4);

$logLevelPrefixes = array(
    RM_LOG_SUPER            => '',
    RM_LOG_SILENT           => '',
    RM_LOG_ERROR            => 'Error',
    RM_LOG_WARN             => 'Warning',
    RM_LOG_INFO             => '',
    RM_LOG_DEBUG            => 'Debug',
);
// What logging mechanisms are available for use
define('RM_LOG_SYSLOG',      1);
define('RM_LOG_FILE',        2);
define('RM_LOG_STDERR',      3);

$logType = 0;
$logPrefix = '';
$logDestination = NULL;

function logInit($name = '')
{
    global $params, $argv, $logType, $logPrefix, $logDestination;

    if (empty($name)) {
        $name = basename($argv[0]);
    }

    if (!array_key_exists('log', $params) || empty($params['log'])) {
        return;
    }

    $matches = array();
    if (preg_match('/(\w+):(.*)?/', $params['log'], $matches)) {
        switch ($matches[1]) {
        case 'syslog':
            $logType = RM_LOG_SYSLOG;
            $txtopts = preg_split('/[\s,]+/', $matches[2], -1, PREG_SPLIT_NO_EMPTY);
            $options = 0;
            foreach ($txtopts as $txtopt) {
                switch ($txtopt) {
                case 'cons': $options |= LOG_CONS; break;
                case 'ndelay': $options |= LOG_NDELAY; break;
                case 'odelay': $options |= LOG_ODELAY; break;
                case 'perror': $options |= LOG_PERROR; break;
                case 'pid': $options |= LOG_PID; break;
                }
            }
            openlog($name, $options, LOG_USER);
            break;

        case 'file':
            $logType = RM_LOG_FILE;
            $logPrefix = $name;
            $logDestination = fopen($matches[2], 'a');
            break;
	case 'stderr':
            $logType = RM_LOG_STDERR;
            $logPrefix = $name;
            $logDestination = fopen('php://stderr', 'a');
	    break;
        }
    }
}

function logClose()
{
    global $logType, $logDestination;

    switch ($logType) {
    case RM_LOG_SYSLOG:
        closelog();
        break;

    case RM_LOG_FILE:
        fclose($logDestination);
        $logDestination = NULL;
        break;
    }
}
function myLog($text, $priority = RM_LOG_INFO)
{
    global $params, $logLevelPrefixes, $logPrefix, $logType, $logDestination;

    if ($params['log_level'] >= $priority) {
        if (!empty($logLevelPrefixes[$priority])) {
            $text = $logLevelPrefixes[$priority] . ": $text";
        }

        switch ($logType) {
        case RM_LOG_SYSLOG:
            syslog(RM_LOG_INFO, $text);
            break;

        case RM_LOG_FILE:
            fwrite($logDestination, strftime('%B %d %T') . " ${logPrefix}[" . getmypid() . "]: $text\n");
            fflush($logDestination);
            break;
	case RM_LOG_STDERR:
            fwrite($logDestination, strftime('%B %d %T') . " ${logPrefix}[" . getmypid() . "]: $text\n");
            fflush($logDestination);
            break;	    
        }
    }
}

/** Helper function */
function assembleUri($parsed)
{
    if (!is_array($parsed)) return false;

    $uri = empty($parsed['scheme']) ? '' :
        $parsed['scheme'] . ':' . ((strtolower($parsed['scheme']) == 'mailto') ? '' : '//');

    $uri .= empty($parsed['user']) ? '' :
        ($parsed['user']) .
        (empty($parsed['pass']) ? '' : ':'.($parsed['pass']))
        . '@';

    $uri .= empty($parsed['host']) ? '' :
        $parsed['host'];
    $uri .= empty($parsed['port']) ? '' :
        ':' . $parsed['port'];

    $uri .= empty($parsed['path']) ? '' :
        $parsed['path'];
    $uri .= empty($parsed['query']) ? '' :
        '?' . $parsed['query'];
    $uri .= empty($parsed['anchor']) ? '' :
        '#' . $parsed['anchor'];

    return $uri;
}

function removePassword( $url ) {
  $parsed = parse_url($url);
  if( !empty($parsed['pass']) ) $parsed['pass'] = 'XXX';
  return assembleUri($parsed);
}

?>

--- NEW FILE: recurrence.class.php ---
<?php
/*
 *  Copyright (c) 2004 Klaraelvdalens Datakonsult AB
 *
 *    Written by Steffen Hansen <steffen at klaralvdalens-datakonsult.se>
 *
 *  This  program is free  software; you can redistribute  it and/or
 *  modify it  under the terms of the GNU  General Public License as
 *  published by the  Free Software Foundation; either version 2, or
 *  (at your option) any later version.
 *
 *  This program is  distributed in the hope that it will be useful,
 *  but WITHOUT  ANY WARRANTY; without even the  implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 *  General Public License for more details.
 *
 *  You can view the  GNU General Public License, online, at the GNU
 *  Project's homepage; see <http://www.gnu.org/licenses/gpl.html>.
 */

/**
 * Class for recurring event calculation
 *
 * Usage: Subclass and implement setBusy(...),
 *        Instantiate, call setter methods to fill
 *        in data, then call expand() to expand the
 *        recurrence. This will cause setBusy() to be 
 *        called for each busy period
 */
/*abstract*/ class Recurrence {

  /* public: */

  function Recurrence() {}
  function setStartDate( $ts ) { $this->initial_start = $ts; }
  function setEndDate( $ts ) { $this->initial_end = $ts; }

  function setCycletype( $ctype ) { $this->cycle_type = $ctype; }
  function setType( $type ) { $this->type = $type; }
  function setInterval( $interval ) { $this->interval = $interval; }
  function setDaynumber( $num ) { $this->daynumber = is_array($num)?$num:array($num); }
  function setDay( $day ) { $this->day = is_array($day)?$day:array($day); }
  function setMonth( $month ) { $this->month = is_array($month)?$month:array($month); }
  function setExclusionDates( $ex ) { 
    // Prescale dates for easy comparison
    $this->exclusion_dates = array();
    foreach( $ex as $e ) {
      $e = explode('-',$e);
      $e = intval(sprintf('%04d%02d%02d', $e[0],$e[1],$e[2]));
      $this->exclusion_dates[] = $e;
    } 
  }
  
  function setRangetype( $type ) { $this->range_type = $type; }
  function setRange( $range ) { $this->range = $range; }

  function ts2string($ts) {
    return gmdate("D, M d Y H:i:s",$ts).'Z';
  }
  
  function expand( $startstamp, $endstamp ) {
    myLog( 'Recurrence::expand( '.Recurrence::ts2string($startstamp).", "
	   .Recurrence::ts2string($endstamp).' ), cycletype='.$this->cycle_type, 
	   RM_LOG_DEBUG);
    switch( $this->cycle_type ) {
    case 'daily':
      $this->expand_daily( $startstamp, $endstamp );
      break;
    case 'weekly':
      $this->expand_weekly( $startstamp, $endstamp );
      break;
    case 'monthly':
      $this->expand_monthly( $startstamp, $endstamp );
      break;
    case 'yearly':
      $this->expand_yearly( $startstamp, $endstamp );
      break;
    default:
      myLog('Unknown cycletype '.$this->cycle_type, RM_LOG_ERROR);
    }
  }

  /* Abstract function, please override */
  function setBusy( $start, $end, $duration ) {
    mylog( "Warning: Abstract method Recurrence::setBusy( $start, $end, $duration ) called", RM_LOG_ERROR);
  }

  /* private: */

  function expand_daily( $startstamp, $endstamp ) {
    // Daily recurrence, every 'interval' days
    $duration = $this->initial_end-$this->initial_start;
    $count = 0;
    if( !$this->interval ) $this->interval = 1;
    for( $t = $this->initial_start; $t < $endstamp; $t = strtotime( '+'.$this->interval.' days',$t) ) {
      //myLog("Adding recurrence $t -> ".($t+$duration), RM_LOG_DEBUG );
      if( !$this->isExcluded( $t, $t+$duration) ) $this->setBusy( $t, null, $duration );
      $count++;
      if( $this->range_type == 'number' && $count > $this->range ) {
	break;
      } else if( $this->range_type == 'date' && $t > strtotime( '+1 day',$this->range ) ) {
	break;
      }
    }
  }

  function expand_weekly( $startstamp, $endstamp ) {
    // Weekly recurrence, every 'interval' weeks on 'day' days
    $duration = $this->initial_end-$this->initial_start;
    if( !$this->interval ) $this->interval = 1;
    $count = 0;
    $delta_days = (int)gmdate('w',$this->initial_start);
    for( $t =  strtotime("-$delta_days days", $this->initial_start); $t < $endstamp; 
	 $t = strtotime( '+'.$this->interval.' weeks', $t) ) {
      //myLog("t=".gmdate("D, M d Y H:i:s",$t), RM_LOG_DEBUG);
      foreach( $this->day as $day ) {
	$tmp = strtotime( '+'.$this->dayname2number($day).' days', $t);
	if( $tmp >= $this->initial_start && $tmp < $endstamp &&
	    !$this->isExcluded( $tmp, $tmp+$duration) ) {
	  myLog("Adding recurrence ".gmdate("D, M d Y H:i:s",$tmp)." -> "
		.gmdate("D, M d Y H:i:s",$tmp+$duration), RM_LOG_DEBUG );
	  $this->setBusy( $tmp, null, $duration );
	} else {
	  break;
	}
      }
      $count++;
      if( $this->range_type == 'number' && $count > $this->range ) {
	break;
      } else if( $this->range_type == 'date' && $t > strtotime( '+1 day',$this->range ) ) {
	break;
      }	    
    }
  }

  function expand_monthly( $startstamp, $endstamp ) {
    // Weekly recurrence, every 'interval' weeks
    $duration = $this->initial_end-$this->initial_start;
    if( !$this->interval ) $this->interval = 1;
    $count = 0;
    $delta_days = (int)gmdate('d',$this->initial_start);
    myLog("initial_start=".Recurrence::ts2string($this->initial_start), RM_LOG_DEBUG);
    $offset = 0;
    $first_of_month = gmdate("M 1 Y H:i:s+0000", $this->initial_start); 
    for( $t = strtotime( $first_of_month ); 
	 $t < $endstamp; $t = strtotime( '+'.$this->interval.' months', $t)) {
      if( $this->type == 'daynumber') {
	// On numbered days
	myLog('t = '.gmdate('M d Y H:i:s',$t), RM_LOG_DEBUG);
	foreach( $this->daynumber as $dayno ) {
	  $t_month = gmdate('m',strtotime("-$offset days", $t)); 
	  $tmp = strtotime( '+'.($dayno-$offset-1).' days', $t);
	  myLog('tmp = '.gmdate('M d Y H:i:s',$tmp), RM_LOG_DEBUG);
	  $tmp_month = gmdate('m',$tmp); // make sure same month
	  if( $tmp >= $this->initial_start && $tmp < $endstamp &&
	      $t_month == $tmp_month &&
	      !$this->isExcluded( $tmp, $tmp+$duration) ) {
	    myLog("Adding recurrence ".gmdate("D, M d Y H:i:s",$tmp)." -> "
		  .gmdate("D, M d Y H:i:s",$tmp+$duration), RM_LOG_DEBUG );
	    $this->setBusy( $tmp, null, $duration );
	  } else {
	    break;
	  }
	}
	$count++;
      } else if( $this->type == 'weekday' ) {
	// On named weekdays
	// Find beginning of first week
	$tmp = strtotime("-$offset days",$t);
	$firstday = (int)gmdate('w',$tmp);
	for( $i = 0; $i < count($this->day); $i++ ) {
	  $dayno = $this->daynumber[$i];
	  $wday  = $this->dayname2number($this->day[$i]);
	  $tmp_month = gmdate('m',$tmp); // make sure same month
	  if( $wday < $firstday ) $tmp2 = strtotime( '+'.($dayno*7+$wday-$firstday).' days', $tmp);
	  else $tmp2 = strtotime( '+'.(($dayno-1)*7+$wday-$firstday).' days', $tmp);
	  $tmp2_month = gmdate('m',$tmp2); // make sure same month
	  if( $tmp_month == $tmp2_month && $tmp2 >= $this->initial_start && $tmp2 < $endstamp 
	      && !$this->isExcluded( $tmp2, $tmp2+$duration) ) {
	    myLog("Adding recurrence ".gmdate("D, M d Y H:i:s",$tmp2)." -> "
		  .gmdate("D, M d Y H:i:s",$tmp2+$duration), RM_LOG_DEBUG );
	    $this->setBusy( $tmp2, null, $duration );
	  } else if($tmp2 >= $endstamp ) {
	    break;
	  }
	}
	$count++;
      }
      if( $this->range_type == 'number' && $count > $this->range ) {
	break;
      } else if( $this->range_type == 'date' 
		 && strtotime('-$offset days',$t) > strtotime( '+1 day',$this->range ) ) {
	break;
      }
    }
  }

  function expand_yearly( $startstamp, $endstamp ) {
    $duration = $this->initial_end-$this->initial_start;
    if( !$this->interval ) $this->interval = 1;
    $count = 0;
    $delta_days = (int)gmdate('z',$this->initial_start);
    for( $t = strtotime( "-$delta_days days", $this->initial_start ); $t < $endstamp; 
	 $t = strtotime( '+'.$this->interval.' years', $t) ) {
      //myLog("t= ".gmdate("M d Y H:i:s",$t), RM_LOG_DEBUG );
      if( $this->type == 'yearday') {
	foreach( $this->daynumber as $dayno ) {
	  $tmp = strtotime( '+'.($dayno-1).' days', $t);
	  if( $this->initial_start <= $tmp && $tmp < $endstamp
	      && !$this->isExcluded( $tmp, $tmp+$duration) ) {
	    myLog("Adding recurrence ".gmdate("D, M d Y H:i:s",$tmp)." -> "
		  .gmdate("D, M d Y H:i:s",$tmp+$duration), RM_LOG_DEBUG );
	    $this->setBusy( $tmp, null, $duration );
	  } else if($tmp >= $endstamp ) {
	    break;
	  }
	}
	$count++;
      } else if( $this->type == 'monthday' ) {
	for( $i = 0; $i < count($this->daynumber); $i++ ) {
	  $dayno = $this->daynumber[$i];
	  $month = $this->monthname2number($this->month[$i]);
	  $year = gmdate('Y', $t );
	  $time = gmdate('H:i:s', $t);
	  //myLog("setting tmp to $year-$month-$dayno $time+0000", RM_LOG_DEBUG );
	  $tmp =  strtotime( "$year-$month-$dayno $time+0000");
	  //myLog("tmp= ".gmdate("M d Y H:i:s",$tmp), RM_LOG_DEBUG );
	  if( $this->initial_start <= $tmp && $tmp < $endstamp 
	      && !$this->isExcluded( $tmp, $tmp+$duration) ) {
	    myLog("Adding recurrence ".gmdate("D, M d Y H:i:s",$tmp)." -> "
		  .gmdate("D, M d Y H:i:s",$tmp+$duration), RM_LOG_DEBUG );
	    $this->setBusy( $tmp, null, $duration );	      
	  } else {
	    break;
	  }
	}
	$count++;	      
      } else if( $this->type == 'weekday' ) {
	for( $i = 0; $i < count($this->daynumber); $i++ ) {
	  $dayno = $this->daynumber[$i];
	  $wday  = $this->day[$i];
	  $month = $this->month[$i];
	  $year  = gmdate('Y',$t);
	  $time  = gmdate('H:i:s',$t);
	  $tmp = strtotime( "$dayno $wday", strtotime( "1 $month $year") );
	  $tmp = strtotime( gmdate('Y-m-d',$tmp)." $time+0000");
	  if( $this->initial_start <= $tmp && $tmp < $endstamp 
	      && !$this->isExcluded( $tmp, $tmp+$duration) ) {
	    myLog("Adding recurrence ".gmdate("D, M d Y H:i:s",$tmp)." -> "
		  .gmdate("D, M d Y H:i:s",$tmp+$duration), RM_LOG_DEBUG );
	    $this->setBusy( $tmp, null, $duration );	      
	  } else {
	    break;
	  }
	}
	$count++;
      } 
      if( $this->range_type == 'number' && $count > $this->range ) {
	break;
      } else if( $this->range_type == 'date' && $t > strtotime( '+1 day',$this->range ) ) {
	break;
      }
    }
  }

  function dayname2number( $day ) {
    switch( strtolower($day) ) {
    case 'sunday': return 0;
    case 'monday': return 1;
    case 'tuesday': return 2;
    case 'wednesday': return 3;
    case 'thursday': return 4;
    case 'friday': return 5;
    case 'saturday': return 6;
    default:
      myLog("Recurrence::dayname2number($day): Invalid day", RM_LOG_ERROR);
      return -1;
    }
  }

  function monthname2number( $month ) {
    switch( $month ) {
    case 'january':  return 1;
    case 'february': return 2;
    case 'march':    return 3;
    case 'april':    return 4;
    case 'may':      return 5;
    case 'june':     return 6;
    case 'july':     return 7;
    case 'august':   return 8;
    case 'september':return 9;
    case 'october':  return 10;
    case 'november': return 11;
    case 'december': return 12;
    default:
      myLog("Recurrence::monthname2number($month): Invalid month", RM_LOG_ERROR);
      return -1;
    }
  }

  function isExcluded( $start, $end ) {
    $start = explode(' ', gmdate('Y m d', $start));
    $start = intval($start[0].$start[1].$start[2]);

    $end = explode(' ', gmdate('Y m d', $end));
    $end = intval($end[0].$end[1].$end[2]);
    
    foreach( $this->exclusion_dates as $e ) {
      if( $start <= $e && $end >= $e ) {
	myLog("$start-$end excluded!", RM_LOG_DEBUG);	
	return true;
      }
    }
    return false;
  }

  var $initial_start   = NULL; // timestamp
  var $initial_end     = NULL; // timestamp
  var $cycle_type      = NULL; // string { 'daily', 'weekly', 'monthly', 'yearly' }
  var $type            = NULL; // string { 'daynumber', 'weekday', 'monthday', 'yearday' }
  var $interval        = NULL; // int
  var $daynumber       = NULL; // array(int)
  var $day             = NULL; // array(string)
  var $month           = NULL; // array(int)

  var $range_type      = NULL; // string { 'number', 'date' }
  var $range           = NULL; // int or timestamp

  var $exclusion_dates = array();

};
?>




More information about the commits mailing list