Started converting the Kolab Free/Busy application into a Horde MVC
authorGunnar Wrobel <p@rdus.de>
Fri, 11 Sep 2009 09:50:16 +0000 (11:50 +0200)
committerGunnar Wrobel <p@rdus.de>
Fri, 11 Sep 2009 09:50:16 +0000 (11:50 +0200)
based webapp. Also began to remove the use of singletons in order to
facilitate unit testing the system. Horde_Kolab_FreeBusy now serves as
Registry/ServiceLocator to the various services required.

framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy.php
framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/Controller/FreebusyController.php [new file with mode: 0644]
framework/Kolab_FreeBusy/package.xml
framework/Kolab_FreeBusy/test/Horde/Kolab/FreeBusy/DispatchTest.php [new file with mode: 0644]
framework/Kolab_FreeBusy/test/Horde/Kolab/FreeBusy/Mock/Controller/FreebusyController.php [new file with mode: 0644]
framework/Kolab_FreeBusy/www/Horde/Kolab/FreeBusy/freebusy.php

index 3211274..d9f75a5 100644 (file)
 <?php
 /**
- * The Kolab implementation of free/busy.
+ * The Kolab implementation of the free/busy system.
  *
- * $Horde: framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy.php,v 1.14 2009/07/14 00:28:33 mrubinsk Exp $
+ * PHP version 5
  *
- * @package Kolab_FreeBusy
+ * @category Kolab
+ * @package  Kolab_FreeBusy
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_FreeBusy
  */
 
-/** PEAR for raising errors */
-require_once 'PEAR.php';
-
-/** View classes for the result */
-require_once 'Horde/Kolab/FreeBusy/View.php';
-
-/** A class that handles access restrictions */
-require_once 'Horde/Kolab/FreeBusy/Access.php';
-
 /**
- * How to use this class
- *
- * require_once 'config.php';
- *
- * $fb = new Kolab_Freebusy();
- *
- * $fb->trigger();
- *
- * OR
- *
- * $fb->fetch();
+ * The Horde_Kolab_FreeBusy class serves as Registry aka ServiceLocator for the
+ * Free/Busy application. It also provides the entry point into the the Horde
+ * MVC system and allows to dispatch a request.
  *
- * $Horde: framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy.php,v 1.14 2009/07/14 00:28:33 mrubinsk Exp $
+ * Copyright 2009 The Horde Project (http://www.horde.org/)
  *
- * Copyright 2004-2008 Klarälvdalens Datakonsult AB
+ * See the enclosed file COPYING for license information (LGPL). If you did not
+ * receive this file, see
+ * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
  *
- * See the enclosed file COPYING for license information (LGPL). If you
- * did not receive this file, see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
- *
- * @since   Horde 3.2
- * @author  Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
- * @author  Gunnar Wrobel <wrobel@pardus.de>
- * @author  Thomas Arendsen Hein <thomas@intevation.de>
- * @package Kolab_FreeBusy
+ * @category Kolab
+ * @package  Kolab_FreeBusy
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_FreeBusy
+ * @since    Horde 3.2
  */
-class Horde_Kolab_FreeBusy {
-
+class Horde_Kolab_FreeBusy
+{
     /**
-     * Parameters provided to this class.
+     * Singleton value.
      *
-     * @var array
+     * @var Horde_Kolab_FreeBusy
      */
-    var $_params;
+    static protected $instance;
 
     /**
-     * Link to the cache.
+     * The object representing the request.
      *
-     * @var Horde_Kolab_FreeBusy_Cache
+     * @var Horde_Controller_Request_Base
      */
-    var $_cache;
+    private $_request;
 
     /**
-     * Setup the cache.
+     * The object representing the request<->controller mapping.
+     *
+     * @var Horde_Routes_Mapper
      */
-    function _initCache()
-    {
-        global $conf;
+    private $_mapper;
 
-        /* Load the cache class now */
-        require_once 'Horde/Kolab/FreeBusy/Cache.php';
-
-        /* Where is the cache data stored? */
-        if (!empty($conf['fb']['cache_dir'])) {
-            $cache_dir = $conf['fb']['cache_dir'];
-        } else {
-            if (class_exists('Horde')) {
-                $cache_dir = Horde::getTempDir();
-            } else {
-                $cache_dir = '/tmp';
-            }
-        }
+    /**
+     * The request dispatcher.
+     *
+     * @var Horde_Controller_Dispatcher
+     */
+    private $_dispatcher;
 
-        $this->_cache = new Horde_Kolab_FreeBusy_Cache($cache_dir);
+    /**
+     * Constructor.
+     *
+     * @param array $params The parameters required to initialize the
+     *                      application.
+     */
+    public function __construct($params = array())
+    {
+        $this->_params = $params;
     }
 
     /**
-     * Trigger regeneration of free/busy data in a calender.
+     * Returns a reference to the global Horde_Kolab_FreeBusy object,
+     * only creating it if it doesn't already exist.
+     *
+     * This method must be invoked as:
+     *   $registry = Horde_Kolab_FreeBusy::singleton()
+     *
+     * We expect this method to be invoked *once* on application startup. At
+     * that point the parameters need to be set to correctly initialize the
+     * system.
+     *
+     * The singleton should then later be only used by the Controllers to access
+     * different system components (by using the MVC system we loose connection
+     * to this class within the controllers so we need global state here).
+     *
+     * @param array $params The parameters required to initialize the
+     *                      application.
+     * <pre>
+     * 'script'  - (string) Script name in relation to the document root.
+     *                      [optional]
+     *
+     * 'config'  - (array)  Indicates where to find configuration options.
+     *                      [optional]
+     *
+     *     'dir'      - (string) Configuration files can be found in this
+     *                           directory.
+     *
+     * 'request' - (array)  Options for the request object. [optional]
+     *
+     *     'class'    - (string) The class of request object to use (should
+     *                           obviously match the request type).
+     *     'params'   - (array)  Additional parameters to use on request
+     *                           object construction.
+     *
+     * 'mapper'  - (array)  Options for the mapper object. [optional]
+     *
+     *     'params'   - (array)  Additional parameters to use on mapper
+     *                           object construction.
+     *
+     * 'dispatch'- (array)  Options for the dispatcher object. [optional]
+     *
+     *     'controllerDir' - (string) The directory holding controllers.
+     *     'viewsDir'      - (string) The directory holding views.
+     *
+     * </pre>
+     *
+     * @return Horde_Kolab_FreeBusy  The Horde_Registry instance.
      */
-    function &trigger()
+    static public function singleton($params = array())
     {
-        global $conf;
-
-        /* Get the folder name */
-        $req_folder = Horde_Util::getFormData('folder', '');
-
-        Horde::logMessage(sprintf("Starting generation of partial free/busy data for folder %s",
-                                  $req_folder), __FILE__, __LINE__, PEAR_LOG_DEBUG);
-
-        /* Validate folder access */
-        $access = new Horde_Kolab_FreeBusy_Access();
-        $result = $access->parseFolder($req_folder);
-        if (is_a($result, 'PEAR_Error')) {
-            $error = array('type' => FREEBUSY_ERROR_NOTFOUND,
-                           'error' => $result);
-            $view = new Horde_Kolab_FreeBusy_View_error($error);
-            return $view;
+        if (!isset(self::$instance)) {
+            self::$instance = new Horde_Kolab_FreeBusy($params);
         }
 
-        Horde::logMessage(sprintf("Partial free/busy data of owner %s on server %s requested by user %s.",
-                                  $access->owner, $access->freebusyserver, $access->user),
-                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
-
-        /* Get the cache request variables */
-        $req_cache    = Horde_Util::getFormData('cache', false);
-        $req_extended = Horde_Util::getFormData('extended', false);
-
-        /* Try to fetch the data if it is stored on a remote server */
-        $result = $access->fetchRemote(true, $req_extended);
-        if (is_a($result, 'PEAR_Error')) {
-            $error = array('type' => FREEBUSY_ERROR_UNAUTHORIZED,
-                           'error' => $result);
-            $view = new Horde_Kolab_FreeBusy_View_error($error);
-            return $view;
-        }
+        return self::$instance;
+    }
 
-        $this->_initCache();
+    /**
+     * Destroy the application context.
+     *
+     * @return NULL
+     */
+    static public function destroy()
+    {
+        self::$instance = null;
+    }
 
-        if (!$req_cache) {
-            /* User wants to regenerate the cache */
+    /**
+     * Inject the request object into the application context.
+     *
+     * @param Horde_Controller_Request_Base $request The object that should
+     *                                               represent the current
+     *                                               request.
+     *
+     * @return NULL
+     */
+    public function setRequest(Horde_Controller_Request_Base $request)
+    {
+        $this->_request = $request;
+    }
 
-            /* Here we really need an authenticated IMAP user */
-            $result = $access->authenticated();
-            if (is_a($result, 'PEAR_Error')) {
-                $error = array('type' => FREEBUSY_ERROR_UNAUTHORIZED,
-                               'error' => $result);
-                $view = new Horde_Kolab_FreeBusy_View_error($error);
-                return $view;
+    /**
+     * Return the object representing the current request.
+     *
+     * @return Horde_Controller_Request_Base The current request.
+     *
+     * @throws Horde_Exception
+     */
+    public function getRequest()
+    {
+        if (!isset($this->_request)) {
+            if (!empty($this->_params['request']['class'])) {
+                $request_class = $this->_params['request']['class'];
+            } else {
+                $request_class = 'Horde_Controller_Request_Http';
             }
-
-            if (empty($access->owner)) {
-                $message = sprintf(_("No such account %s!"),
-                                   htmlentities($access->req_owner));
-                $error = array('type' => FREEBUSY_ERROR_NOTFOUND,
-                               'error' => PEAR::raiseError($message));
-                $view = new Horde_Kolab_FreeBusy_View_error($error);
-                return $view;
+            if (!empty($this->_params['request']['params'])) {
+                $params = $this->_params['request']['params'];
+            } else {
+                $params = array();
             }
-
-            /* Update the cache */
-            $result = $this->_cache->store($access);
-            if (is_a($result, 'PEAR_Error')) {
-                $error = array('type' => FREEBUSY_ERROR_NOTFOUND,
-                               'error' => $result);
-                $view = new Horde_Kolab_FreeBusy_View_error($error);
-                return $view;
+            // Set up our request and routing objects
+            $this->_request = new $request_class($params);
+            /**
+             * The HTTP request object would hide errors. Display them.
+             */
+            if (isset($this->request->_exception)) {
+                throw $this->request->_exception;
             }
         }
 
-        /* Load the cache data */
-        $vfb = $this->_cache->loadPartial($access, $req_extended);
-        if (is_a($vfb, 'PEAR_Error')) {
-            $error = array('type' => FREEBUSY_ERROR_NOTFOUND,
-                           'error' => $vfb);
-            $view = new Horde_Kolab_FreeBusy_View_error($error);
-            return $view;
-        }
-
-        Horde::logMessage("Delivering partial free/busy data.", __FILE__, __LINE__, PEAR_LOG_DEBUG);
-
-        /* Generate the renderer */
-        $data = array('fb' => $vfb, 'name' => $access->owner . '.ifb');
-        $view = new Horde_Kolab_FreeBusy_View_vfb($data);
-
-        /* Finish up */
-        Horde::logMessage("Free/busy generation complete.", __FILE__, __LINE__, PEAR_LOG_DEBUG);
-
-        return $view;
+        return $this->_request;
     }
 
     /**
-     * Fetch the free/busy data for a user.
+     * Inject the mapper object into the application context.
+     *
+     * @param Horde_Route_Mapper $mapper The object that handles mapping.
+     *
+     * @return NULL
      */
-    function &fetch()
+    public function setMapper(Horde_Route_Mapper $mapper)
     {
-        global $conf;
-
-        /* Get the user requsted */
-        $req_owner = Horde_Util::getFormData('uid');
-
-        Horde::logMessage(sprintf("Starting generation of free/busy data for user %s",
-                                  $req_owner), __FILE__, __LINE__, PEAR_LOG_DEBUG);
-
-        /* Validate folder access */
-        $access = new Horde_Kolab_FreeBusy_Access();
-        $result = $access->parseOwner($req_owner);
-        if (is_a($result, 'PEAR_Error')) {
-            $error = array('type' => FREEBUSY_ERROR_NOTFOUND, 'error' => $result);
-            $view = new Horde_Kolab_FreeBusy_View_error($error);
-            return $view;
-        }
-
-        Horde::logMessage(sprintf("Free/busy data of owner %s on server %s requested by user %s.",
-                                  $access->owner, $access->freebusyserver, $access->user),
-                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
-
-        $req_extended = Horde_Util::getFormData('extended', false);
-
-        /* Try to fetch the data if it is stored on a remote server */
-        $result = $access->fetchRemote(false, $req_extended);
-        if (is_a($result, 'PEAR_Error')) {
-            $error = array('type' => FREEBUSY_ERROR_UNAUTHORIZED, 'error' => $result);
-            $view = new Horde_Kolab_FreeBusy_View_error($error);
-            return $view;
-        }
-
-        $this->_initCache();
-
-        $result = $this->_cache->load($access, $req_extended);
-        if (is_a($result, 'PEAR_Error')) {
-            $error = array('type' => FREEBUSY_ERROR_NOTFOUND, 'error' => $result);
-            $view = new Horde_Kolab_FreeBusy_View_error($error);
-            return $view;
-        }
-
-        Horde::logMessage("Delivering complete free/busy data.", __FILE__, __LINE__, PEAR_LOG_DEBUG);
-
-        /* Generate the renderer */
-        $data = array('fb' => $result, 'name' => $access->owner . '.vfb');
-        $view = new Horde_Kolab_FreeBusy_View_vfb($data);
-
-        /* Finish up */
-        Horde::logMessage("Free/busy generation complete.", __FILE__, __LINE__, PEAR_LOG_DEBUG);
-
-        return $view;
+        $this->_mapper = $mapper;
     }
 
     /**
-     * Regenerate the free/busy cache.
+     * Return the mapper.
+     *
+     * @return Horde_Route_Mapper The mapper.
+     *
+     * @throws Horde_Exception
      */
-    function &regenerate($reporter)
+    public function getMapper()
     {
-        $access = new Horde_Kolab_FreeBusy_Access();
-        $result = $access->authenticated();
-        if (is_a($result, 'PEAR_Error')) {
-            return $result->getMessage();
-        }
-
-        /* Load the required Kolab libraries */
-        require_once "Horde/Kolab/Storage/List.php";
-
-        $list = &Kolab_List::singleton();
-        $calendars = $list->getByType('event');
-        if (is_a($calendars, 'PEAR_Error')) {
-            return $calendars->getMessage();
-        }
-
-        $this->_initCache();
-
-        $lines = array();
+        if (!isset($this->_mapper)) {
+            if (!empty($this->_params['mapper']['params'])) {
+                $params = $this->_params['mapper']['params'];
+            } else {
+                $params = array();
+            }
+            $this->_mapper = new Horde_Routes_Mapper($params);
 
-        foreach ($calendars as $calendar) {
             /**
-             * We are using imap folders for our calendar list but
-             * the library expects us to follow the trigger format
-             * used by pfb.php
+             * Application routes are relative only to the application. Let the
+             * mapper know where they start.
              */
-            $req_domain = explode('@', $calendar->name);
-            if (isset($req_domain[1])) {
-                $domain = $req_domain[1];
+            if (!empty($this->_params['script'])) {
+                $this->_mapper->prefix = dirname($this->_params['script']);
             } else {
-                $domain = null;
+                $this->_mapper->prefix = dirname($_SERVER['PHP_SELF']);
+            }
+
+            // Check for route definitions.
+            if (!empty($this->_params['config']['dir'])) {
+                $routeFile = $this->_params['config']['dir'] . '/routes.php';
             }
-            $req_folder = explode('/', $req_domain[0]);
-            if ($req_folder[0] == 'user') {
-                unset($req_folder[0]);
-                $owner = $req_folder[1];
-                unset($req_folder[1]);
-            } else if ($req_folder[0] == 'INBOX') {
-                $owner = $access->user;
-                unset($req_folder[0]);
+            if (empty($this->_params['config']['dir'])
+                || !file_exists($routeFile)) {
+                $this->_mapper->connect('*(id).:(type)',
+                                        array('controller'   => 'freebusy',
+                                              'action'       => 'fetch',
+                                              'requirements' => array('type' => '(i|x|v|p|px)fb')
+                                        ));
+
+                $this->_mapper->connect('trigger/:id/:folder',
+                                        array('controller' => 'freebusy',
+                                              'action'     => 'trigger'));
+
+                $this->_mapper->connect('delete/:id',
+                                        array('controller' => 'freebusy',
+                                              'action'     => 'delete'));
+
+                $this->_mapper->connect('regenerate/:id',
+                                        array('controller' => 'freebusy',
+                                              'action'     => 'delete'));
+            } else {
+                // Load application routes.
+                include $routeFile;
             }
+        }
 
-            $trigger = $owner . ($domain ? '@' . $domain : '') . '/' . join('/', $req_folder);
-            $trigger = Horde_String::convertCharset($trigger, 'UTF7-IMAP', 'UTF-8');
+        return $this->_mapper;
+    }
 
-            /* Validate folder access */
-            $result = $access->parseFolder($trigger);
-            if (is_a($result, 'PEAR_Error')) {
-                $reporter->failure($calendar->name, $result->getMessage());
-                continue;
-            }
+    /**
+     * Inject the dispatcher object into the application context.
+     *
+     * @param Horde_Controller_Dispatcher $dispatcher The object that handles
+     *                                                dispatching.
+     *
+     * @return NULL
+     */
+    public function setDispatcher(Horde_Controller_Dispatcher $dispatcher)
+    {
+        $this->_dispatcher = $dispatcher;
+    }
 
-            /* Hack for allowing manager access */
-            if ($access->user == 'manager') {
-                $imapc = &Horde_Kolab_IMAP::singleton($GLOBALS['conf']['kolab']['imap']['server'],
-                                                      $GLOBALS['conf']['kolab']['imap']['port']);
-                $result = $imapc->connect($access->user, Horde_Auth::getCredential('password'));
-                if (is_a($result, 'PEAR_Error')) {
-                    $reporter->failure($calendar->name, $result->getMessage());
-                    continue;
-                }
-                $acl = $imapc->getACL($calendar->name);
-                if (is_a($acl, 'PEAR_Error')) {
-                    $reporter->failure($calendar->name, $result->getMessage());
-                    continue;
-                }
-                $oldacl = '';
-                if (isset($acl['manager'])) {
-                    $oldacl = $acl['manager'];
-                }
-                $result = $imapc->setACL($calendar->name, 'manager', 'lrs');
-                if (is_a($result, 'PEAR_Error')) {
-                    $reporter->failure($calendar->name, $result->getMessage());
-                    continue;
-                }
+    /**
+     * Return the dispatcher.
+     *
+     * @return Horde_Controller_Dispatcher The dispatcher.
+     *
+     * @throws Horde_Exception
+     */
+    public function getDispatcher()
+    {
+        if (!isset($this->_dispatcher)) {
+            if (empty($this->_params['dispatch']['controllerDir'])) {
+                $controllerDir = dirname(__FILE__) . '/FreeBusy/Controller';
+            } else {
+                $controllerDir = $this->_params['dispatch']['controllerDir'];
             }
 
-            /* Update the cache */
-            $result = $this->_cache->store($access);
-            if (is_a($result, 'PEAR_Error')) {
-                $reporter->failure($calendar->name, $result->getMessage());
-                continue;
+            if (empty($this->_params['dispatch']['viewsDir'])) {
+                $viewsDir = dirname(__FILE__) . '/FreeBusy/View';
+            } else {
+                $viewsDir = $this->_params['dispatch']['viewsDir'];
             }
 
-            /* Revert the acl  */
-            if ($access->user == 'manager' && $oldacl) {
-                $result = $imapc->setACL($calendar->name, 'manager', $oldacl);
-                if (is_a($result, 'PEAR_Error')) {
-                    $reporter->failure($calendar->name, $result->getMessage());
-                    continue;
-                }
-            }
+            $context = array(
+                'mapper' => $this->getMapper(),
+                'controllerDir' => $controllerDir,
+                'viewsDir' => $viewsDir,
+                // 'logger' => '',
+            );
 
-            $reporter->success($calendar->name);
+            $this->_dispatcher = Horde_Controller_Dispatcher::singleton($context);
+        }
 
+        return $this->_dispatcher;
+    }
+
+    /**
+     * Handle the current request.
+     *
+     * @return NULL
+     */
+    public function dispatch()
+    {
+        try {
+            $this->getDispatcher()->dispatch($this->getRequest());
+        } catch (Exception $e) {
+            //@todo: Error view
+            throw $e;
         }
-        return $lines;
     }
 }
-
-
diff --git a/framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/Controller/FreebusyController.php b/framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/Controller/FreebusyController.php
new file mode 100644 (file)
index 0000000..6902ea1
--- /dev/null
@@ -0,0 +1,347 @@
+<?php
+/**
+ * The Kolab implementation of the free/busy system.
+ *
+ * PHP version 5
+ *
+ * @category Kolab
+ * @package  Kolab_FreeBusy
+ * @author   Thomas Arendsen Hein <thomas@intevation.de>
+ * @author   Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_FreeBusy
+ */
+
+/**
+ * The Autoloader allows us to omit "require/include" statements.
+ */
+require_once 'Horde/Autoloader.php';
+
+/**
+ * The core Controller handling the different request types.
+ *
+ * Copyright 2004-2009 Klarälvdalens Datakonsult AB
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did not
+ * receive this file, see
+ * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+ *
+ * @category Kolab
+ * @package  Kolab_FreeBusy
+ * @author   Thomas Arendsen Hein <thomas@intevation.de>
+ * @author   Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_FreeBusy
+ * @since    Horde 3.2
+ */
+class FreeBusyController extends Horde_Controller_Base
+{
+    /**
+     * Parameters provided to this class.
+     *
+     * @var array
+     */
+    var $_params;
+
+    /**
+     * Link to the cache.
+     *
+     * @var Horde_Kolab_FreeBusy_Cache
+     */
+    var $_cache;
+
+    /**
+     * Setup the cache.
+     */
+    function _initCache()
+    {
+        global $conf;
+
+        /* Load the cache class now */
+        require_once 'Horde/Kolab/FreeBusy/Cache.php';
+
+        /* Where is the cache data stored? */
+        if (!empty($conf['fb']['cache_dir'])) {
+            $cache_dir = $conf['fb']['cache_dir'];
+        } else {
+            if (class_exists('Horde')) {
+                $cache_dir = Horde::getTempDir();
+            } else {
+                $cache_dir = '/tmp';
+            }
+        }
+
+        $this->_cache = new Horde_Kolab_FreeBusy_Cache($cache_dir);
+    }
+
+    /**
+     * Trigger regeneration of free/busy data in a calender.
+     *
+     * @return NULL
+     */
+    function &trigger()
+    {
+        global $conf;
+
+        /* Get the folder name */
+        $req_folder = Horde_Util::getFormData('folder', '');
+
+        Horde::logMessage(sprintf("Starting generation of partial free/busy data for folder %s",
+                                  $req_folder), __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        /* Validate folder access */
+        $access = new Horde_Kolab_FreeBusy_Access();
+        $result = $access->parseFolder($req_folder);
+        if (is_a($result, 'PEAR_Error')) {
+            $error = array('type' => FREEBUSY_ERROR_NOTFOUND,
+                           'error' => $result);
+            $view = new Horde_Kolab_FreeBusy_View_error($error);
+            return $view;
+        }
+
+        Horde::logMessage(sprintf("Partial free/busy data of owner %s on server %s requested by user %s.",
+                                  $access->owner, $access->freebusyserver, $access->user),
+                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        /* Get the cache request variables */
+        $req_cache    = Horde_Util::getFormData('cache', false);
+        $req_extended = Horde_Util::getFormData('extended', false);
+
+        /* Try to fetch the data if it is stored on a remote server */
+        $result = $access->fetchRemote(true, $req_extended);
+        if (is_a($result, 'PEAR_Error')) {
+            $error = array('type' => FREEBUSY_ERROR_UNAUTHORIZED,
+                           'error' => $result);
+            $view = new Horde_Kolab_FreeBusy_View_error($error);
+            return $view;
+        }
+
+        $this->_initCache();
+
+        if (!$req_cache) {
+            /* User wants to regenerate the cache */
+
+            /* Here we really need an authenticated IMAP user */
+            $result = $access->authenticated();
+            if (is_a($result, 'PEAR_Error')) {
+                $error = array('type' => FREEBUSY_ERROR_UNAUTHORIZED,
+                               'error' => $result);
+                $view = new Horde_Kolab_FreeBusy_View_error($error);
+                return $view;
+            }
+
+            if (empty($access->owner)) {
+                $message = sprintf(_("No such account %s!"),
+                                   htmlentities($access->req_owner));
+                $error = array('type' => FREEBUSY_ERROR_NOTFOUND,
+                               'error' => PEAR::raiseError($message));
+                $view = new Horde_Kolab_FreeBusy_View_error($error);
+                return $view;
+            }
+
+            /* Update the cache */
+            $result = $this->_cache->store($access);
+            if (is_a($result, 'PEAR_Error')) {
+                $error = array('type' => FREEBUSY_ERROR_NOTFOUND,
+                               'error' => $result);
+                $view = new Horde_Kolab_FreeBusy_View_error($error);
+                return $view;
+            }
+        }
+
+        /* Load the cache data */
+        $vfb = $this->_cache->loadPartial($access, $req_extended);
+        if (is_a($vfb, 'PEAR_Error')) {
+            $error = array('type' => FREEBUSY_ERROR_NOTFOUND,
+                           'error' => $vfb);
+            $view = new Horde_Kolab_FreeBusy_View_error($error);
+            return $view;
+        }
+
+        Horde::logMessage("Delivering partial free/busy data.", __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        /* Generate the renderer */
+        $data = array('fb' => $vfb, 'name' => $access->owner . '.ifb');
+        $view = new Horde_Kolab_FreeBusy_View_vfb($data);
+
+        /* Finish up */
+        Horde::logMessage("Free/busy generation complete.", __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        return $view;
+    }
+
+    /**
+     * Fetch the free/busy data for a user.
+     *
+     * @return NULL
+     */
+    function &fetch()
+    {
+        global $conf;
+
+        /* Get the user requsted */
+        $req_owner = Horde_Util::getFormData('uid');
+
+        Horde::logMessage(sprintf("Starting generation of free/busy data for user %s",
+                                  $req_owner), __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        /* Validate folder access */
+        $access = new Horde_Kolab_FreeBusy_Access();
+        $result = $access->parseOwner($req_owner);
+        if (is_a($result, 'PEAR_Error')) {
+            $error = array('type' => FREEBUSY_ERROR_NOTFOUND, 'error' => $result);
+            $view = new Horde_Kolab_FreeBusy_View_error($error);
+            return $view;
+        }
+
+        Horde::logMessage(sprintf("Free/busy data of owner %s on server %s requested by user %s.",
+                                  $access->owner, $access->freebusyserver, $access->user),
+                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        $req_extended = Horde_Util::getFormData('extended', false);
+
+        /* Try to fetch the data if it is stored on a remote server */
+        $result = $access->fetchRemote(false, $req_extended);
+        if (is_a($result, 'PEAR_Error')) {
+            $error = array('type' => FREEBUSY_ERROR_UNAUTHORIZED, 'error' => $result);
+            $view = new Horde_Kolab_FreeBusy_View_error($error);
+            return $view;
+        }
+
+        $this->_initCache();
+
+        $result = $this->_cache->load($access, $req_extended);
+        if (is_a($result, 'PEAR_Error')) {
+            $error = array('type' => FREEBUSY_ERROR_NOTFOUND, 'error' => $result);
+            $view = new Horde_Kolab_FreeBusy_View_error($error);
+            return $view;
+        }
+
+        Horde::logMessage("Delivering complete free/busy data.", __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        /* Generate the renderer */
+        $data = array('fb' => $result, 'name' => $access->owner . '.vfb');
+        $view = new Horde_Kolab_FreeBusy_View_vfb($data);
+
+        /* Finish up */
+        Horde::logMessage("Free/busy generation complete.", __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        return $view;
+    }
+
+    /**
+     * Regenerate the free/busy cache data.
+     *
+     * @return NULL
+     */
+    function &regenerate($reporter)
+    {
+        $access = new Horde_Kolab_FreeBusy_Access();
+        $result = $access->authenticated();
+        if (is_a($result, 'PEAR_Error')) {
+            return $result->getMessage();
+        }
+
+        /* Load the required Kolab libraries */
+        require_once "Horde/Kolab/Storage/List.php";
+
+        $list = &Kolab_List::singleton();
+        $calendars = $list->getByType('event');
+        if (is_a($calendars, 'PEAR_Error')) {
+            return $calendars->getMessage();
+        }
+
+        $this->_initCache();
+
+        $lines = array();
+
+        foreach ($calendars as $calendar) {
+            /**
+             * We are using imap folders for our calendar list but
+             * the library expects us to follow the trigger format
+             * used by pfb.php
+             */
+            $req_domain = explode('@', $calendar->name);
+            if (isset($req_domain[1])) {
+                $domain = $req_domain[1];
+            } else {
+                $domain = null;
+            }
+            $req_folder = explode('/', $req_domain[0]);
+            if ($req_folder[0] == 'user') {
+                unset($req_folder[0]);
+                $owner = $req_folder[1];
+                unset($req_folder[1]);
+            } else if ($req_folder[0] == 'INBOX') {
+                $owner = $access->user;
+                unset($req_folder[0]);
+            }
+
+            $trigger = $owner . ($domain ? '@' . $domain : '') . '/' . join('/', $req_folder);
+            $trigger = Horde_String::convertCharset($trigger, 'UTF7-IMAP', 'UTF-8');
+
+            /* Validate folder access */
+            $result = $access->parseFolder($trigger);
+            if (is_a($result, 'PEAR_Error')) {
+                $reporter->failure($calendar->name, $result->getMessage());
+                continue;
+            }
+
+            /* Hack for allowing manager access */
+            if ($access->user == 'manager') {
+                $imapc = &Horde_Kolab_IMAP::singleton($GLOBALS['conf']['kolab']['imap']['server'],
+                                                      $GLOBALS['conf']['kolab']['imap']['port']);
+                $result = $imapc->connect($access->user, Horde_Auth::getCredential('password'));
+                if (is_a($result, 'PEAR_Error')) {
+                    $reporter->failure($calendar->name, $result->getMessage());
+                    continue;
+                }
+                $acl = $imapc->getACL($calendar->name);
+                if (is_a($acl, 'PEAR_Error')) {
+                    $reporter->failure($calendar->name, $result->getMessage());
+                    continue;
+                }
+                $oldacl = '';
+                if (isset($acl['manager'])) {
+                    $oldacl = $acl['manager'];
+                }
+                $result = $imapc->setACL($calendar->name, 'manager', 'lrs');
+                if (is_a($result, 'PEAR_Error')) {
+                    $reporter->failure($calendar->name, $result->getMessage());
+                    continue;
+                }
+            }
+
+            /* Update the cache */
+            $result = $this->_cache->store($access);
+            if (is_a($result, 'PEAR_Error')) {
+                $reporter->failure($calendar->name, $result->getMessage());
+                continue;
+            }
+
+            /* Revert the acl  */
+            if ($access->user == 'manager' && $oldacl) {
+                $result = $imapc->setACL($calendar->name, 'manager', $oldacl);
+                if (is_a($result, 'PEAR_Error')) {
+                    $reporter->failure($calendar->name, $result->getMessage());
+                    continue;
+                }
+            }
+
+            $reporter->success($calendar->name);
+
+        }
+        return $lines;
+    }
+
+    /**
+     * Delete data for a specific user.
+     *
+     * @return NULL
+     */
+    public function delete()
+    {
+    }
+}
\ No newline at end of file
index 3fc93e9..212f160 100644 (file)
@@ -64,6 +64,9 @@ http://pear.php.net/dtd/package-2.0.xsd">
       <dir name="FreeBusy">
        <file name="Access.php" role="php" />
        <file name="Cache.php" role="php" />
+       <dir name="Controller">
+        <file name="FreebusyController.php" role="php" />
+       </dir> <!-- /lib/Horde/Kolab/FreeBusy/Controller -->
        <file name="Imap.php" role="php" />
        <file name="Report.php" role="php" />
        <file name="View.php" role="php" />
@@ -79,8 +82,14 @@ http://pear.php.net/dtd/package-2.0.xsd">
      <dir name="Kolab">
       <dir name="FreeBusy">
        <file name="AllTests.php" role="test" />
+       <file name="DispatchTest.php" role="test" />
        <file name="FreeBusyTest.php" role="test" />
        <file name="FreeBusyScenarioTest.php" role="test" />
+       <dir name="Mock">
+        <dir name="Controller">
+         <file name="FreebusyController.php" role="test" />
+        </dir> <!-- /test/Horde/Kolab/FreeBusy/Mock/Controller -->
+       </dir> <!-- /test/Horde/Kolab/FreeBusy/Mock -->
       </dir> <!-- /test/Horde/Kolab/FreeBusy -->
      </dir> <!-- /test/Horde/Kolab -->
     </dir> <!-- /test/Horde -->
@@ -136,12 +145,15 @@ http://pear.php.net/dtd/package-2.0.xsd">
    <install name="lib/Horde/Kolab/FreeBusy.php" as="Horde/Kolab/FreeBusy.php" />
    <install name="lib/Horde/Kolab/FreeBusy/Access.php" as="Horde/Kolab/FreeBusy/Access.php" />
    <install name="lib/Horde/Kolab/FreeBusy/Cache.php" as="Horde/Kolab/FreeBusy/Cache.php" />
+   <install name="lib/Horde/Kolab/FreeBusy/Controller/FreebusyController.php" as="Horde/Kolab/FreeBusy/Controller/FreebusyController.php" />
    <install name="lib/Horde/Kolab/FreeBusy/Imap.php" as="Horde/Kolab/FreeBusy/Imap.php" />
    <install name="lib/Horde/Kolab/FreeBusy/Report.php" as="Horde/Kolab/FreeBusy/Report.php" />
    <install name="lib/Horde/Kolab/FreeBusy/View.php" as="Horde/Kolab/FreeBusy/View.php" />
    <install name="lib/Horde/Kolab/Test/FreeBusy.php" as="Horde/Kolab/Test/FreeBusy.php" />
    <install name="test/Horde/Kolab/FreeBusy/AllTests.php" as="Horde/Kolab/FreeBusy/AllTests.php" />
+   <install name="test/Horde/Kolab/FreeBusy/DispatchTest.php" as="Horde/Kolab/FreeBusy/DispatchTest.php" />
    <install name="test/Horde/Kolab/FreeBusy/FreeBusyTest.php" as="Horde/Kolab/FreeBusy/FreeBusyTest.php" />
+   <install name="test/Horde/Kolab/FreeBusy/Mock/Controller/FreebusyController.php" as="Horde/Kolab/FreeBusy/Mock/Controller/FreebusyController.php" />
    <install name="test/Horde/Kolab/FreeBusy/FreeBusyScenarioTest.php" as="Horde/Kolab/FreeBusy/FreeBusyScenarioTest.php" />
    <install name="www/Horde/Kolab/FreeBusy/config.php" as="config.php" />
    <install name="www/Horde/Kolab/FreeBusy/freebusy.php" as="freebusy.php" />
diff --git a/framework/Kolab_FreeBusy/test/Horde/Kolab/FreeBusy/DispatchTest.php b/framework/Kolab_FreeBusy/test/Horde/Kolab/FreeBusy/DispatchTest.php
new file mode 100644 (file)
index 0000000..2818748
--- /dev/null
@@ -0,0 +1,137 @@
+<?php
+/**
+ * Test the dispatcher of the free/busy system.
+ *
+ * PHP version 5
+ *
+ * @category Kolab
+ * @package  Kolab_FreeBusy
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_FreeBusy
+ */
+
+/**
+ * The Autoloader allows us to omit "require/include" statements.
+ */
+require_once 'Horde/Autoloader.php';
+
+/**
+ * Test dispatching calls in the free/busy system.
+ *
+ * Copyright 2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @category Kolab
+ * @package  Kolab_FreeBusy
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_FreeBusy
+ */
+class Horde_Kolab_FreeBusy_DispatchTest extends Horde_Kolab_Test_FreeBusy
+{
+
+    /**
+     * Test setup.
+     */
+    public function setUp()
+    {
+        /**
+         * The controller automatically starts a session. But we don't want to
+         * send out cookie headers since we are running in PHPUnit.
+         */
+        ini_set('session.use_cookies', 0);
+        ini_set('session.use_only_cookies', 0);
+        session_cache_limiter(null);
+    }
+
+    /**
+     * Test destruction.
+     */
+    public function tearDown()
+    {
+        Horde_Kolab_FreeBusy::destroy();
+    }
+
+    /**
+     * Test dispatching a "fetch" call.
+     *
+     * @return NULL
+     */
+    public function testFetch()
+    {
+        $urls = array(
+            '/freebusy/test@example.com.ifb' => array(
+                'ifb', 'test@example.com'),
+            '/freebusy/test@example.com.vfb' => array(
+                'vfb', 'test@example.com'),
+            '/freebusy/test@example.com.xfb' => array(
+                'xfb', 'test@example.com'),
+            '/freebusy/test@example.com.pfb' => array(
+                'pfb', 'test@example.com'),
+            '/freebusy/test@example.com.pxfb' => array(
+                'pxfb', 'test@example.com'),
+            '/freebusy/test@example.com.zfb' => false,
+        );
+
+        foreach ($urls as $key => $result) {
+            $params = array(
+                'script' => '/freebusy/freebusy.php',
+                'request' => array(
+                    'params' => array(
+                        'server' => array(
+                            'REQUEST_URI' => $key
+                        )
+                    )
+                ),
+                'dispatch' => array(
+                    'controllerDir' => dirname(__FILE__) . '/Mock/Controller',
+                )
+            );
+            $application = Horde_Kolab_FreeBusy::singleton($params);
+            if (empty($result)) {
+                try {
+                    $application->dispatch();
+                } catch (Horde_Controller_Exception $e) {
+                    $this->assertEquals('No routes match the path: "' . substr($key, 1) . '"', $e->getMessage());
+                }
+            } else {
+                ob_start();
+                $application->dispatch();
+                $output = ob_get_contents();
+                ob_end_clean();
+                $this->assertEquals('fetched "' . $result[0] . '" data for user "' . $result[1] . '"', $output);
+            }
+            Horde_Kolab_FreeBusy::destroy();
+        }
+    }
+
+    /**
+     * Test dispatching a "trigger" call.
+     *
+     * @return NULL
+     */
+    public function testTrigger()
+    {
+    }
+
+    /**
+     * Test dispatching a "regenerate" call.
+     *
+     * @return NULL
+     */
+    public function testRegenerate()
+    {
+    }
+
+    /**
+     * Test dispatching a "delete" call.
+     *
+     * @return NULL
+     */
+    public function testDelete()
+    {
+    }
+}
\ No newline at end of file
diff --git a/framework/Kolab_FreeBusy/test/Horde/Kolab/FreeBusy/Mock/Controller/FreebusyController.php b/framework/Kolab_FreeBusy/test/Horde/Kolab/FreeBusy/Mock/Controller/FreebusyController.php
new file mode 100644 (file)
index 0000000..7cb1bb6
--- /dev/null
@@ -0,0 +1,76 @@
+<?php
+/**
+ * Tests for the Kolab implementation of the free/busy system.
+ *
+ * PHP version 5
+ *
+ * @category Kolab
+ * @package  Kolab_FreeBusy
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_FreeBusy
+ */
+
+/**
+ * The Autoloader allows us to omit "require/include" statements.
+ */
+require_once 'Horde/Autoloader.php';
+
+/**
+ * A mockup for the main application controller.
+ *
+ * Copyright 2004-2009 Klarälvdalens Datakonsult AB
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did not
+ * receive this file, see
+ * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+ *
+ * @category Kolab
+ * @package  Kolab_FreeBusy
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_FreeBusy
+ * @since    Horde 3.2
+ */
+class FreeBusyController extends Horde_Controller_Base
+{
+    /**
+     * Trigger regeneration of free/busy data in a calender.
+     *
+     * @return NULL
+     */
+    public function trigger()
+    {
+        $this->renderText('triggered');
+    }
+
+    /**
+     * Fetch the free/busy data for a user.
+     *
+     * @return NULL
+     */
+    public function fetch()
+    {
+        $this->renderText('fetched "' . $this->params->type . '" data for user "' . $this->params->id . '"');
+    }
+
+    /**
+     * Regenerate the free/busy cache data.
+     *
+     * @return NULL
+     */
+    public function regenerate()
+    {
+        $this->renderText('regenerated');
+    }
+
+    /**
+     * Delete data for a specific user.
+     *
+     * @return NULL
+     */
+    public function delete()
+    {
+        $this->renderText('deleted');
+    }
+}
\ No newline at end of file
index 0e28802..a9b72b0 100644 (file)
@@ -1,24 +1,30 @@
 <?php
 /**
- * A script for fetching the Kolab Free/Busy information.
- *
- * $Horde: framework/Kolab_FreeBusy/www/Horde/Kolab/FreeBusy/freebusy.php,v 1.6 2009/07/14 00:28:33 mrubinsk Exp $
+ * The web entry point for the Kolab free/busy system.
  *
  * Copyright 2004-2009 Klarälvdalens Datakonsult AB
  *
- * @author  Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
- * @author  Gunnar Wrobel <p@rdus.de>
- * @author  Thomas Arendsen Hein <thomas@intevation.de>
- * @package Kolab_FreeBusy
+ * See the enclosed file COPYING for license information (LGPL). If you did not
+ * receive this file, see
+ * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+ *
+ * PHP version 5
+ *
+ * @category Kolab
+ * @package  Kolab_FreeBusy
+ * @author   Thomas Arendsen Hein <thomas@intevation.de>
+ * @author   Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_FreeBusy
  */
 
-/** Load the required free/busy library */
-require_once 'Horde/Kolab/FreeBusy.php';
-
-/** Load the configuration */
-require_once 'config.php';
-
-$fb = new Horde_Kolab_FreeBusy();
-$view = $fb->fetch();
-$view->render();
+/**
+ * The Autoloader allows us to omit "require/include" statements.
+ */
+require_once 'Horde/Autoloader.php';
 
+/** Dispatch the request. */
+$params = array('config' => array('dir' => dirname(__FILE__) . '/config'));
+$application = Horde_Kolab_FreeBusy::singleton($params);
+$application->dispatch();