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

cvs at kolab.org cvs at kolab.org
Thu Nov 22 17:53:07 CET 2007


Author: gunnar

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

Added Files:
	Cache.php Page.php 
Removed 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:
Fully restructured the perl package. Implements the newest free/busy concepts.

--- NEW FILE: Cache.php ---
<?php
/*  
 *  COPYRIGHT
 *  ---------
 *
 *  See ../AUTHORS file
 *
 *
 *  LICENSE
 *  -------
 *
 *  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 of the License, 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 should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  $Revision: 1.1 $
 *
 *  ABOUT
 *  -----
 *
 *  This provides the necessary utilities to handle the file based
 *  free/busy cache.
 *
 */

/* We require the iCalendar library to build the free/busy list */
require_once 'Horde/iCalendar.php';
require_once 'Horde/iCalendar/vfreebusy.php';

class FreeBusyCache {

    var $_cache_dir;

    function FreeBusyCache($cache_dir)
    {
        $this->_cache_dir = $cache_dir;
    }

    function store($access) 
    {
        $folder = $this->_getFolder($access);

        /* Now we really need the free/busy library */
        require_once('Horde/Kolab/Freebusy.php');

        $fb = &new Horde_Kolab_Freebusy();
        $result = $fb->connect($folder);
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        $vCal = $fb->generate(null, null, $access->fbpast, $access->fbfuture,
                              $access->owner);
        if (is_a($vCal, 'PEAR_Error')) {
            $vCal;
        }

        $fbfilename = $this->_getFilename($folder, $access->owner);
        
        $c_pvcal = &new FreeBusyCacheFile_pvcal($this->_cache_dir, $fbfilename);
        $c_acl   = &new FreeBusyCacheFile_acl($this->_cache_dir, $fbfilename);
        $c_xacl  = &new FreeBusyCacheFile_xacl($this->_cache_dir, $fbfilename);
        
        /* missing data means delete the cache files */
        if (empty($vCal)) {
            Horde::logMessage(sprintf(_("No events. Purging cache %s."), 
                                      $fbfilename),
                              __FILE__, __LINE__, PEAR_LOG_DEBUG);

            $result = $c_pvcal->purge();
            if (is_a($result, 'PEAR_Error')) {
                return $result;
            }
            $result = $c_acl->purge();
            if (is_a($result, 'PEAR_Error')) {
                return $result;
            }
            $result = $c_xacl->purge();
            if (is_a($result, 'PEAR_Error')) {
                return $result;
            }

        } else {

            $result = $c_pvcal->storePVcal($vCal);
            if (is_a($result, 'PEAR_Error')) {
                return $result;
            }

            $relevance = $fb->getRelevance();
            if (is_a($relevance, 'PEAR_Error')) {
                $relevance;
            }
            $acl = $fb->getACL();
            if (is_a($acl, 'PEAR_Error')) {
                $acl;
            }

            $result = $c_acl->storeACL($acl, $relevance);
            if (is_a($result, 'PEAR_Error')) {
                return $result;
            }
            
            $xacl = $fb->getExtendedACL();
            if (is_a($xacl, 'PEAR_Error')) {
                $xacl;
            }
            /* The owner may always look at the extended attributes */
            $xacl .= ' ' . $access->owner;
            $result = $c_xacl->storeXACL($xacl);
            if (is_a($result, 'PEAR_Error')) {
                return $result;
            }
    
            Horde::logMessage(sprintf(_("FreeBusyCache::store(file=%s, relevance=%s, acl=%s, xacl=%s)"), 
                                      $fbfilename, $relevance, $acl, $xacl), 
                              __FILE__, __LINE__, PEAR_LOG_DEBUG);
        }
    }

    function &loadPartial(&$access, $extended) 
    {
        $folder = $this->_getFolder($access);
        $file = $this->_getFilename($folder, $access->owner);
        $aclcache = &FreeBusyCacheDB_acl::singleton('acl', $this->_cache_dir);

        $relevant = false;
        foreach ($access->groups as $id) {
            if ($aclcache->has($file, $id)) {
                $relevant = true;
                break;
                }
        }

        if (!$relevant) {
            return PEAR::raiseError(sprintf(_("Folder %s is irrelevant for user %s."),
                                            $folder, $access->user));
        }

        if ($extended) {
            $extended = $this->_allowExtended($file, $access);
        }

        $c_pvcal = &new FreeBusyCacheFile_pvcal($this->_cache_dir, $file);
        $pvCal = $c_pvcal->loadPVcal($extended);
        if (is_a($pvCal, 'PEAR_Error')) {
            return $pvCal;
        }
        return $pvCal;
    }

    function &load(&$access, $extended) 
    {
        /* Which files will we access? */
        $aclcache = &FreeBusyCacheDB_acl::singleton('acl', $this->_cache_dir);
        $files = array();
        foreach ($access->groups as $id) {
            $add = $aclcache->get($id);
            if (is_a($add, 'PEAR_Error')) {
                return $add;
            }
            $files = array_unique(array_merge($files, $add));
        }
        $filesnames = array();

        $c_file = $this->_getFilename($access->user, $access->owner);
        $c_vcal = &new FreeBusyCacheFile_vcal($this->_cache_dir, $c_file);

        /* If the current vCal cache did not expire, we can deliver it */
        if (!$c_vcal->expired($files)) {
            $vCal = $c_vcal->loadVcal();
            if (is_a($vCal, 'PEAR_Error')) {
                return $vCal;
            }
            return $vCal;
        }
        
        // 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:' . $access->owner);
        
        $vFb->setAttribute('DTSTAMP', time());
        $vFb->setAttribute('URL', 'http://' . $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI']);

        $mtimes = array();
        foreach ($files as $file) {
            if ($extended) {
                $extended = $this->_allowExtended($file, $access);
            }
            $c_pvcal = &new FreeBusyCacheFile_pvcal($this->_cache_dir, $file);
            $pvCal = $c_pvcal->loadPVcal($extended);
            if (is_a($pvCal, 'PEAR_Error')) {
                    Horde::logMessage(sprintf(_("Ignoring partial free/busy file %s: %s)"), 
                                              $file, $pvCal->getMessage()), 
                                      __FILE__, __LINE__, PEAR_LOG_INFO);
                    continue;
            }
            $pvFb = &$pvCal->findComponent('vfreebusy');
            if( !$pvFb ) {
                    Horde::logMessage(sprintf(_("Could not find free/busy info in file %s.)"), 
                                              $file), __FILE__, __LINE__, PEAR_LOG_INFO);
                    continue;
            }
            if ($ets = $pvFb->getAttributeDefault('DTEND', false) !== false) {
                // PENDING(steffen): Make value configurable
                if ($ets < time()) {
                    Horde::logMessage(sprintf(_("Free/busy info in file %s is too old.)"), 
                                              $file), __FILE__, __LINE__, PEAR_LOG_INFO);
                    $c_pvcal->purge();
                    continue;
                }
            }
            $vFb->merge($pvFb);

            /* Store last modification time */
            $mtimes[$file] = $c_pvcal->getMtime();
        }

        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);
        }

        $vCal->addComponent($vFb);

        $c_vcal->storeVcal($vCal, $mtimes);

        return $vCal;
    }

    function _allowExtended($file, &$access) 
    {
        $xaclcache = &FreeBusyCacheDB_xacl::singleton('xacl', $this->_cache_dir);
            
        foreach ($access->groups as $id) {
            if ($xaclcache->has($file, $id)) {
                return true;
            }
        }
        Horde::logMessage(sprintf(_("Extended attributes on folder %s disallowed for user %s."),
                                  $access->folder, $access->user), __FILE__, __LINE__, PEAR_LOG_DEBUG);
        return false;
    }

    /*************** Private API below this line *************/
    function _getFilename($folder, $owner)
    {
        if (ereg('(.*)@(.*)', $owner, $regs)) {
            $owner = $regs[2] . '/' . $regs[1];
        }
        if ($folder) {
            if (ereg('(.*)@(.*)', $folder, $regs)) {
                $folder = $regs[2] . '/' . $regs[1];
            }
            $filename = $owner . '/' . $folder;
        }
        
        return str_replace("\0", '', str_replace('.', '^', $filename));
    }

    function _getFolder($access) 
    {
        $userdom = false;
        $ownerdom = false;
        if (ereg( '(.*)@(.*)', $access->user, $regs)) {
            // Regular user
            $user = $regs[1];
            $userdom  = $regs[2];
        }
        if(ereg( '(.*)@(.*)', $access->owner, $regs)) {      
            // Regular owner
            $owner = $regs[1];
            $ownerdom = $regs[2];
        }

        $fldrcomp = array();
        if ($user == $owner) {
            $fldrcomp[] = 'INBOX';
        } else {
            $fldrcomp[] = 'user';
            $owner[] = 'user';
        }
        
        if (!empty($access->folder)) {
            $fldrcomp[] = $access->folder;
        }
    
        $folder = join('/', $fldrcomp);
        if ($ownerdom && !$userdom) {
            $folder .= '@' . $ownerdom;
        }
        return $folder;
    }
};

class FreeBusyCacheDB {

    var $_cache_dir;
    var $_db;
    var $_dbformat;
    var $_type = '';

    function FreeBusyCacheDB($cache_dir) {
        global $conf;

        $this->_cache_dir = $cache_dir;
    
        if (!empty($conf['fb']['dbformat'])) {
            $this->_dbformat = $conf['fb']['dbformat'];
        } else {
            $this->_dbformat = 'db4';
        }

        /* make sure that a database really exists before accessing it */
        if (!file_exists($this->_cache_dir . '/' . $this->_type . 'cache.db')) {
            $result = $this->_open();
            if (is_a($result, 'PEAR_Error')) {
                return $result;
            }
            $this->_close();
        }

    }

    function _open() 
    {
        if ($this->_db) {
            return;
        }

        $dbfile = $this->_cache_dir . '/' . $this->_type . 'cache.db';
        $this->_db = @dba_open($dbfile, 'cd', $this->_dbformat);
        if ($this->_db === false) {
            return PEAR::raiseError(sprintf(_("Unable to open freebusy cache db %s"), $dbfile));
        }
    }
    
    function _close() 
    {
        @dba_close($this->_db);
        $this->_db = null;
    }

    function _remove($filename, $uid) 
    {
        $result = $this->_open();
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        if (dba_exists($uid, $this->_db)) {
            $lst = dba_fetch($uid, $this->_db);
            $lst = split(',', $lst);
            $lst = array_diff($lst, array($filename));
            $result = dba_replace($uid, join(',', $lst), $this->_db);
            if ($result === false) {
                return PEAR::raiseError(sprintf(_("Unable to set db value for uid %s"), $uid));
            }
        }
    }

    function _add($filename, $uid) 
    {
        $result = $this->_open();
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        if (dba_exists($uid, $this->_db)) {
            $lst = dba_fetch($uid, $this->_db);
            $lst = split(',', $lst);
            $lst[] = $filename;
            $result = dba_replace($uid, join(',', array_unique($lst)), $this->_db);
            if ($result === false) {
                return PEAR::raiseError(sprintf(_("Unable to set db value for uid %s"), $uid));
            }
        } else {
            $result = dba_insert($uid, $filename, $this->_db);
            if ($result === false) {
                return PEAR::raiseError(sprintf(_("Unable to set db value for uid %s"), $uid));
            }
        }
    }

    function has($filename, $uid) 
    {
        $result = $this->_open();

        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        if (dba_exists($uid, $this->_db)) {
            $lst = dba_fetch($uid, $this->_db);
            $lst = split(',', $lst);
            return in_array($filename, $lst);
        }
        return false;
    }
    
    function get($uid) 
    {
        $result = $this->_open();
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        if (dba_exists($uid, $this->_db)) {
            $lst = dba_fetch($uid, $this->_db);
            return split(',', $lst);
        }
        return array();
    }
    
    /**
     * Attempts to return a reference to a concrete FreeBusyACLCache
     * instance. It will only create a new instance if no
     * FreeBusyACLCache instance currently exists.
     *
     * This method must be invoked as:
     *   <code>$var = &FreeBusyACLCache::singleton($cache_dir);</code>
     *
     * @static
     *
     * @param string $type       The type of the cache.
     * @param string $cache_dir  The directory for storing the cache.
     *
     * @return FreeBusyACLCache The concrete FreeBusyACLCache
     *                          reference, or false on an error.
     */
    function &singleton($type, $cache_dir)
    {
        static $cachedb = array();

        $signature = $type . $cache_dir;

        if (empty($cachedb[$signature])) {
            $class = 'FreeBusyCacheDB_' . $type;
            $cachedb[$signature] = &new $class($cache_dir);
        }

        return $cachedb[$signature];
    }
}

class FreeBusyCacheDB_acl extends FreeBusyCacheDB {

    var $_type = 'acl';

    function store($filename, $acl, $oldacl, $perm)
    {
        /* We remove the filename from all users listed in the old ACL first */
        foreach ($oldacl as $ac) {
            $result = $this->_remove($filename, $ac[0]);
            if (is_a($result, 'PEAR_Error')) {
                return $result;
            }
        }

        /* Now add the filename for all users with the correct permissions */
        if ($perm !== false ) {
            foreach ($acl as $user => $ac) {
                if (strpos($ac, $perm) !== false) {
                    if (!empty($user)) {
                        $result = $this->_add($filename, $user);
                        if (is_a($result, 'PEAR_Error')) {
                            return $result;
                        }
                    }
                }
            }
        }

        $this->_close();
    }
}

class FreeBusyCacheDB_xacl extends FreeBusyCacheDB {

    var $_type = 'xacl';

    function store($filename, $xacl, $oldxacl)
    {
        $xacl = split(' ', $xacl);
        $oldxacl = split(' ', $oldxacl);
        $both = array_intersect($xacl, $oldxacl);

        /* Removed access rights */
        foreach (array_diff($oldxacl, $both) as $uid) {
            if (!empty($uid)) {
                $result = $this->_remove($filename, $uid);
                if (is_a($result, 'PEAR_Error')) {
                    return $result;
                }
            }
        }

        /* Added access rights */
        foreach (array_diff($xacl, $both) as $uid) {
            if (!empty($uid)) {
                $result = $this->_add($filename, $uid);
                if (is_a($result, 'PEAR_Error')) {
                    return $result;
                }
            }
        }

        $this->_close();
    }
}

class FreeBusyCacheFile {

    var $_suffix = '';
    var $_filename;
    var $_file;
    var $_version = 1;

    function FreeBusyCacheFile($cache_dir, $filename, $suffix = null)
    {
        if (!empty($suffix)) {
            $this->_suffix = $suffix;
        }

        $this->_cache_dir = $cache_dir;
        $this->_filename  = $filename;
        $this->_file = $this->_cache_dir . '/' . $this->_filename . '.' . $this->_suffix;
    }

    function purge()
    {
        if (file_exists($this->_file)) {
            $result = @unlink($this->_file);
            if (!$result) {
                return PEAR::raiseError(sprintf(_("Failed removing file %s"),
                                                $this->_file));
            }
        }
    }

    function store(&$data) 
    {
        /* Create directories if missing */
        $fbdirname = dirname($this->_file);
        if (!is_dir($fbdirname)) {
            $result = $this->_makeTree($fbdirname);
            if (is_a($result, 'PEAR_Error')) {
                return $result;
            }
        }

        /* Store the cache data */
        $fh = fopen($this->_file, 'w');
        if (!$fh) {
            return PEAR::raiseError(sprintf(_("Failed creating cache file %s!"), 
                                            $this->_file));
        }
        fwrite($fh, serialize(array('version' => $this->_version,
                                    'data' => $data)));
        fclose($fh);
    }

    function &load()
    {
        $file = @file_get_contents($this->_file);
        if ($file === false) {
            return PEAR::raiseError(sprintf(_("%s failed reading cache file %s!"), 
                                            get_class($this), $this->_file));
        }
        $cache = @unserialize($file);
        if ($cache === false) {
            return PEAR::raiseError(sprintf(_("%s failed to unserialize cache data from file %s!"), 
                                            get_class($this), $this->_file));
        }
        if (!isset($cache['version'])) {
            return PEAR::raiseError(sprintf(_("Cache file %s lacks version data!"), 
                                            $this->_file));
        }
        if (!isset($cache['data'])) {
            return PEAR::raiseError(sprintf(_("Cache file %s lacks data!"), 
                                            $this->_file));
        }
        if ($cache['version'] != $this->_version) {
            return PEAR::raiseError(sprintf(_("Cache file %s has version %s while %s is required!"), 
                                            $this->_file, $cache['version'], $this->_version));
        }
        return $cache['data'];
    }
    
    function _maketree($dirname)
    {
        $base = substr($dirname, 0, strrpos($dirname, '/'));
        $base = str_replace(".", "^", $base);
        if (!empty($base) && !is_dir($base)) {
            $result = $this->_maketree($base);
            if (is_a($result, 'PEAR_Error')) {
                return $result;
            }
        }
        if (!file_exists($dirname)) {
            $result = @mkdir($dirname, 0755);
            if (!$result) {
                return PEAR::raiseError(sprintf(_("Error creating directory %s"), $dirname));
            }
        }
    }
}

class FreeBusyCacheFile_pvcal extends FreeBusyCacheFile {

    var $_suffix = 'pvc';

    function storePVcal(&$pvcal)
    {
        return $this->store($pvcal);
    }

    function &loadPVcal($extended)
    {
        $pvcal = $this->load();
        if (is_a($pvcal, 'PEAR_Error')) {
            return $pvcal;
        }
        if (!$extended) {
            $components = &$pvcal->getComponents();
            foreach ($components as $component) {
                if ($component->getType() == 'vFreebusy') {
                    $component->_extraParams = array();
                }
            }
        }
        return $pvcal;
    }

    function getMtime() 
    {
        return filemtime($this->_file);
    }
}

class FreeBusyCacheFile_vcal extends FreeBusyCacheFile {

    var $_suffix = 'vc';

    var $_data;

    function storeVcal(&$vcal, &$mtimes) 
    {
        $data = array('vcal' => $vcal,
                      'mtimes' => $mtimes);
        return $this->store($data);
    }
    
    function &loadVcal()
    {
        if ($this->_data) {
            return $this->_data;
        }
        
        $result = $this->load();
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        $this->_data = $result['vcal'];

        return $this->_data;
    }

    function expired($files)
    {
        $result = $this->load();
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        $this->_data = $result['vcal'];

        /* Files changed? */
        $keys = array_keys($result['mtimes']);
        $changes = array_diff($keys, $files);        
        if (!empty($changes)) {
            return true;
        }

        /* Check the file ctimes */
        foreach ($files as $file) {
            if (filemtime($this->_cache_dir . '/' . $file) != $result['mtimes'][$file]) {
                return true;
            }
        }

        /* Older than three days? */
        $components = $this->_data->getComponents();
        foreach ($components as $component) {
            if ($component->getType() == 'vFreebusy') {
                $attr = $component->getAttribute('DTSTAMP');
                if (!empty($attr) && !is_a($attr, 'PEAR_Error')) {
                    //Should be configurable
                    if (time() - (int)$attr > 259200) {
                        return true;
                    }
                }
            }
        }


        return false;
    }
}

class FreeBusyCacheFile_acl extends FreeBusyCacheFile {

    var $_suffix = 'acl';

    var $_acls;
    
    function FreeBusyCacheFile_acl($cache_dir, $filename) 
    {
        $this->_acls = &FreeBusyCacheDB::singleton('acl', $cache_dir);
        parent::FreeBusyCacheFile($cache_dir, $filename, 'acl');
    }

    function purge()
    {
        $oldacl = $this->load();
        if (is_a($oldacl, 'PEAR_Error')) {
            $oldacl = array();
        }
        
        $result = $this->_acls->store($this->_filename, array(), $oldacl, false);
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        return parent::purge();
    }
    
    function storeACL(&$acl, $relevance) 
    {
        $oldacl = $this->load();
        if (is_a($oldacl, 'PEAR_Error')) {
            $oldacl = array();
        }

        /* Handle relevance */
        switch ($relevance) {
        case 'readers':
            $perm = 'r';
            break;
        case 'nobody':
            $perm = false;
            break;
        case 'admins':  
        default: 
            $perm = 'a';
        }

        $result = $this->_acls->store($this->_filename, $acl, $oldacl, $perm);
        if (is_a($oldacl, 'PEAR_Error')) {
            return $oldacl;
        }

        $result = $this->store($acl);
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }
    }
}

class FreeBusyCacheFile_xacl extends FreeBusyCacheFile {

    var $_suffix = 'xacl';

    var $_xacls;
    
    function FreeBusyCacheFile_xacl($cache_dir, $filename) 
    {
        $this->_xacls = &FreeBusyCacheDB::singleton('xacl', $cache_dir);
        parent::FreeBusyCacheFile($cache_dir, $filename, 'xacl');
    }

    function purge()
    {
        $oldxacl = $this->load();
        if (is_a($oldxacl, 'PEAR_Error')) {
            $oldxacl = '';
        }
        
        $result = $this->_xacls->store($this->_filename, '', $oldxacl);
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        return parent::purge();
    }
    
    function storeXACL(&$xacl) 
    {
        $oldxacl = $this->load();
        if (is_a($oldxacl, 'PEAR_Error')) {
            $oldxacl = '';
        }

        $result = $this->_xacls->store($this->_filename, $xacl, $oldxacl);
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        $result = $this->store($xacl);
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }
    }
}

?>

--- NEW FILE: Page.php ---
<?php
/*  
 *  COPYRIGHT
 *  ---------
 *
 *  See docs/AUTHORS file
 *
 *
 *  LICENSE
 *  -------
 *
 *  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 of the License, 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 should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  $Revision: 1.1 $
 *
 *  ABOUT
 *  -----
 *
 *  This provides error pages for the Kolab free/busy system.
 *
 */

/* Load the required PEAR libraries */ 
require_once 'PEAR.php';

/* Load the required Horde libraries */ 
require_once 'Horde.php';
require_once "Horde/String.php";
require_once "Horde/Util.php";
require_once "Horde/Kolab/LDAP.php";

/**
 * The Kolab_Freebusy_Error:: class provides error pages for the 
 * Kolab free/busy system.
 *
 * @author  Gunnar Wrobel <p at rdus.de>
 * @author  Steffen Hansen <steffen at klaralvdalens-datakonsult.se>
 * @package Kolab_Freebusy
 */
class Kolab_Freebusy_Error {

    /**
     * Deliver a "Not Found" page
     *
     * @param PEAR_Error $error    The error.
     */
    function notFound($error)
    {
        $headers = array('HTTP/1.0 404 Not Found');
        $url = htmlentities($_SERVER['REQUEST_URI']);
        $message = sprintf(_("The requested URL %s was not found on this server."), $url);
        
        Kolab_Freebusy_Error::_errorPage($error, $headers, _("404 Not Found"), _("Not found"), $message);
    }
    
    /**
     * Deliver a "Unauthorized" page
     *
     * @param PEAR_Error $error    The error.
     */
    function unauthorized($error) {
        global $conf;
        
        if (!empty($conf['fb']['email_domain'])) {
            $email_domain = $conf['fb']['email_domain'];
        } else {
            $email_domain = 'localhost';
        }

        $headers = array('WWW-Authenticate: Basic realm="freebusy-' . $email_domain . '"',
                         'HTTP/1.0 401 Unauthorized');
        
        Kolab_Freebusy_Error::_errorPage($error, $headers, _("401 Unauthorized"), _("Unauthorized"),
                  _("You are not authorized to access the requested URL."));
    }
    
    /**
     * Deliver a "Server Error" page
     *
     * @param PEAR_Error $error    The error.
     */
    function serverError($error) {
        $headers = array('HTTP/1.0 500 Server Error');
        Kolab_Freebusy_Error::_errorPage($error, $headers, _("500 Server Error"), _("Error"),
                  htmlentities($_SERVER['REQUEST_URI']));
    }

    /**
     * Deliver an error page
     *
     * @param PEAR_Error $error    The error.
     * @param array      $headers  The HTTP headers to deliver with the response
     * @param string     $title    The page title
     * @param string     $headline The headline of the page
     * @param string     $body     The message to display on the page
     */
    function _errorPage($error, $headers, $title, $headline, $body) {

        /* Print the headers */
        foreach ($headers as $line) {
            header($line);
        }
        
        /* Print the page */
        echo "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n";
        echo "<html><head><title>" . $title . "</title></head>\n";
        echo "<body>\n";
        echo "<h1>" . $headline . "</h1>\n";
        echo "<p>" . $body . "</p>\n";
        if (!empty($error)) {
            echo "<hr><pre>" . $error->getMessage() . "</pre>\n";
            Horde::logMessage($error, __FILE__, __LINE__, PEAR_LOG_ERR);
        }
        echo "<hr>\n";
        echo $_SERVER['SERVER_SIGNATURE'] . "\n";
        echo "</body></html>\n";
        exit;
    }
}

/**
 * The Timer:: class provides a high precision timer for calculating
 * page generation time.
 *
 * @author  Gunnar Wrobel <p at rdus.de>
 * @author  Steffen Hansen <steffen at klaralvdalens-datakonsult.se>
 * @package Kolab_Freebusy
 */
class Timer
{
    var $_start;

    function Timer()
    {
        $this->_start = $this->_microtime_float();
    }
    
    function stop()
    {
        return $this->_microtime_float() - $this->_start;
    }
    
    function _microtime_float() 
    {
        list($usec, $sec) = explode(" ", microtime());
        return (float) $usec + (float) $sec;
    }
}

/**
 * The FolderAccess:: class provides functionality to check free/busy
 * access rights for the specified folder.
 *
 * @author  Gunnar Wrobel <p at rdus.de>
 * @author  Steffen Hansen <steffen at klaralvdalens-datakonsult.se>
 * @package Kolab_Freebusy
 */
class FolderAccess {

    /**
     * The user calling the script
     *
     * @var string
     */
    var $user;

    /**
     * The password of the user calling the script
     *
     * @var string
     */
    var $pass;

    /**
     * The folder we try to access
     *
     * @var string
     */
    var $folder;

    /**
     * The requested owner
     *
     * @var string
     */
    var $req_owner;

    /**
     * The owner of that folder
     *
     * @var string
     */
    var $owner;

    /**
     * The homeserver for that folder
     *
     * @var string
     */
    var $homeserver;

    /**
     * The groups of the user calling the script (this includes the
     * primary user id)
     *
     * @var string
     */
    var $groups;

    /**
     * The number of days to calculate free/busy into the past
     *
     * @var int
     */
    var $fbpast;

    /**
     * The number of days to calculate free/busy into the future
     *
     * @var int
     */
    var $fbfuture = 60;

    function FolderAccess()
    {
        $this->_parseUser();
    }
    
    function parseFolder($req_folder = '')
    {
        /* Handle the owner/folder name and make sure the owner part is in lower case */
        $req_folder = String::convertCharset($req_folder, 'UTF-8', 'UTF7-IMAP');
        $folder = explode('/', $req_folder);
        if (count($folder) < 2) {
            return PEAR::raiseError(sprintf(_("No such folder %s"), $req_folder));
        }

        $folder[0] = strtolower($folder[0]);
        $req_folder = implode('/', $folder);
        $this->req_owner = $folder[0];
        unset($folder[0]);
        $this->folder = join('/', $folder);

        $result = $this->_process();
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }
    }

    function parseOwner($req_owner = '')
    {
        $this->req_owner = $req_owner;

        $result = $this->_process();
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }
    }
    
    function fetchRemote()
    {
        global $conf;

        if (!empty($conf['fb']['server'])) {
            $server = $conf['fb']['server'];
        } else {
            $server = 'localhost';
        }
        if (!empty($conf['fb']['redirect'])) {
            $do_redirect = $conf['fb']['redirect'];
        } else {
            $do_redirect = false;
        }

        /* Check if we are on the right server and redirect if appropriate */
        if ($this->homeserver && $this->homeserver != $server) {
            $redirect = 'https://' . $this->homeserver . $_SERVER['REQUEST_URI'];
            Horde::logMessage(sprintf(_("Found remote user, redirecting to %s"), 
                                      $this->homeserver), __FILE__, __LINE__, PEAR_LOG_ERR);
            if ($do_redirect) {
                header("Location: $redirect");
            } else {
                header("X-Redirect-To: $redirect");
                $redirect = 'https://' . urlencode($this->user) . ':' . urlencode($this->pass)
                    . '@' . $this->homeserver . $_SERVER['REQUEST_URI'];
                if (!@readfile($redirect)) {
                    $message = sprintf(_("Unable to read free/busy information from %s"), 
                                       'https://' . urlencode($this->user) . ':XXX'
                                       . '@' . $this->homeserver . $_SERVER['REQUEST_URI']);
                    return PEAR::raiseError($message);
                }
            }
            exit;
        }
    }

    function authenticate()
    {
        if (empty($this->user)) {
            return PEAR::raiseError(_("Please authenticate!"));
        }
        
        /* Load the authentication libraries */
        require_once "Horde/Auth.php";
        require_once 'Horde/Secret.php';

        $auth = &Auth::singleton('kolab');

        if (!$auth->authenticate($this->user, array('password' => $this->pass), false)) {
            return PEAR::raiseError(sprintf(_("Invalid Kolab authentication for user %s!"), 
                                            $this->user));
        }
        session_start();
        $_SESSION['__auth'] = array(
            'authenticated' => true,
            'userId' => $this->user,
            'timestamp' => time(),
            'remote_addr' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null,
        );
        Auth::setCredential('password', $this->pass);
    }

    function _parseUser()
    {
        $this->user = isset($_SERVER['PHP_AUTH_USER'])?$_SERVER['PHP_AUTH_USER']:false;
        $this->pass = isset($_SERVER['PHP_AUTH_PW'])?$_SERVER['PHP_AUTH_PW']:false;

        // This part allows you to use the PHP scripts with CGI rather than as
        // an apache module. This will of course slow down things but on the
        // other hand it allows you to reduce the memory footprint of the 
        // apache server. The default is to use PHP as a module and the CGI 
        // version requires specific Apache configuration.
        //
        // The line you need to add to your configuration of the /freebusy 
        // location of your server looks like this:
        //
        //    RewriteRule .* - [E=REMOTE_USER:%{HTTP:Authorization}]
        //
        // The complete section will probably look like this then:
        //
        //  <IfModule mod_rewrite.c>
        //    RewriteEngine On
        //    # FreeBusy list handling
        //    RewriteBase /freebusy
        //    RewriteRule .* - [E=REMOTE_USER:%{HTTP:Authorization}]
        //    RewriteRule ^([^/]+)\.ifb       freebusy.php?uid=$1		    [L]
        //    RewriteRule ^([^/]+)\.vfb       freebusy.php?uid=$1		    [L]
        //    RewriteRule ^([^/]+)\.xfb       freebusy.php?uid=$1&extended=1        [L]
        //    RewriteRule ^trigger/(.+)\.pfb  pfb.php?folder=$1&cache=0             [L]
        //    RewriteRule ^(.+)\.pfb          pfb.php?folder=$1&cache=1             [L]
        //    RewriteRule ^trigger/(.+)\.xpfb pfb.php?folder=$1&cache=0&extended=1  [L]
        //    RewriteRule ^(.+)\.xpfb         pfb.php?folder=$1&cache=1&extended=1  [L]
        //  </IfModule>
        if (empty($this->user) && isset($_ENV['REDIRECT_REDIRECT_REMOTE_USER'])) {
            $a = base64_decode(substr($_ENV['REDIRECT_REDIRECT_REMOTE_USER'], 6)) ;
            if ((strlen($a) != 0) && (strcasecmp($a, ":" ) == 0)) {
                list($this->user, $this->pass) = explode(':', $a, 2);
            }
        }
    }

    function _process() 
    {
        /* Bind to LDAP so that we can request user data */
        $ldap = Horde_Kolab_LDAP::singleton();

        if (!$ldap->is_bound) {
            $result = $ldap->bind();
            if (is_a($result, 'PEAR_Error')) {
                return $result;
            }
        }

        /* Does not really belong here but since we are connected now,
         we should fetch that config info */
        $this->fbpast = $ldap->freeBusyPast();

        /* Fetch the user info of the calling user */
        $userinfo = $ldap->userInfo($this->user);
        if (is_a($userinfo, 'PEAR_Error')) {
            Horde::logMessage($userinfo, __FILE__, __LINE__, PEAR_LOG_ERROR);
            $userinfo = null;
        }

        /* Possibly rewrite the calling UID into the primary mail address */
        if ($userinfo && isset($userinfo['MAIL']) && !empty($userinfo['MAIL'])) {
            $this->user = $userinfo['MAIL'];
        }

        /* Fetch the user info of the requested folder owner */
        $uinfo = $ldap->userInfo($this->req_owner);
        if (is_a($uinfo, 'PEAR_Error')) {
            Horde::logMessage($uinfo, __FILE__, __LINE__, PEAR_LOG_ERROR);
            $uinfo = null;
        }

        /* Possibly rewrite the owner UID into the primary mail address */
        if (!empty($uinfo) && isset($uinfo['MAIL']) && !empty($uinfo['MAIL'])) {
            $this->owner = $userinfo['MAIL'];
        }

        /* If we were unable to determine the owner we will finally
         * try to append the domain name of the calling user. */
        if (empty($uinfo) || empty($this->owner) ||
            strpos($this->owner, '@') === false) {
            /* try guessing the domain */
            $idx = strpos($this->user, '@');
            if($idx !== false) {
                $domain = substr($this->user, $idx+1);
                Horde::logMessage(sprintf(_("Trying to append %s to %s"), 
                                          $domain, $this->req_owner),
                                  __FILE__, __LINE__, PEAR_LOG_DEBUG);
                $uinfo = $ldap->userInfo($this->req_owner . '@' . $domain);
                if (is_a($uinfo, 'PEAR_Error')) {
                    Horde::logMessage($uinfo, __FILE__, __LINE__, PEAR_LOG_ERROR);
                    $uinfo = null;
                }
            }
        }

        /* Get the owner mail address and the home server */
        if ($uinfo) {
            if (!empty($uinfo['MAIL'])) {
                $this->owner = $uinfo['MAIL'];
            }
            if (!empty($uinfo['HOMESERVER'])) {
                $this->homeserver = $uinfo['HOMESERVER'];  
            }
            if (!empty($uinfo['FBFUTURE'])) {
                $this->fbfuture = $uinfo['FBFUTURE'];  
            }
            if (!empty($uinfo['GROUPS'])) {
                $this->groups = $uinfo['GROUPS'];  
            }
        }
        if (empty($this->groups)) {
            $this->groups = array($this->user);
        } else {
            $this->groups[] = $this->user;
        }
    }
}

class FreeBusyView {

    var $_vfb;

    function FreeBusyView($vfb)
    {
        $this->_vfb = $vfb->exportvCalendar();

        $ts = time();
        
        $components = &$vfb->getComponents();
        foreach ($components as $component) {
            if ($component->getType() == 'vFreebusy') {
                $attr = $component->getAttribute('DTSTAMP');
                if (!empty($attr) && !is_a($attr, 'PEAR_Error')) {
                    $ts = $attr;
                    break;
                }
            }
        }

        $this->_ts  = $ts;
    }
    
    function render($content = '')
    {
        global $conf;

        if (!empty($conf['fb']['send_content_type'])) {
            $send_content_type = $conf['fb']['send_content_type'];
        } else {
            $send_content_type = false;
        }
        
        if (!empty($conf['fb']['send_content_length'])) {
            $send_content_length = $conf['fb']['send_content_length'];
        } else {
            $send_content_length = false;
        }
        
        if (!empty($conf['fb']['send_content_disposition'])) {
            $send_content_disposition = $conf['fb']['send_content_disposition'];
        } else {
            $send_content_disposition = false;
        }
        
        /* Ensure that the data doesn't get cached along the way */
        header('Cache-Control: no-store, no-cache, must-revalidate');
        header('Cache-Control: post-check=0, pre-check=0', false);
        header('Pragma: no-cache');
        header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
        header('Last-Modified: ' . gmdate("D, d M Y H:i:s", $this->_ts) . ' GMT');
        header('Pragma: public');
        header('Content-Transfer-Encoding: none');
        if ($send_content_type) {
            header('Content-Type: text/calendar');
        }
        if ($send_content_length) {
            header('Content-Length: ' . strlen($this->_vfb));
        }
        if ($send_content_disposition) {
            header('Content-Disposition: attachment; filename="' . $content . '"');
        }

        echo $this->_vfb;
    }
    
}

--- domxml-php4-to-php5.php DELETED ---

--- freebusy.class.php DELETED ---

--- freebusycache.class.php DELETED ---

--- freebusycollector.class.php DELETED ---

--- freebusyimapcache.class.php DELETED ---

--- freebusyldap.class.php DELETED ---

--- freebusyldap_dummy.class.php DELETED ---

--- misc.php DELETED ---

--- recurrence.class.php DELETED ---





More information about the commits mailing list