Add a timeobjects API-only application for providing a way to expose
authorMichael J. Rubinsky <mrubinsk@horde.org>
Sun, 7 Jun 2009 16:59:19 +0000 (12:59 -0400)
committerMichael J. Rubinsky <mrubinsk@horde.org>
Sun, 7 Jun 2009 16:59:19 +0000 (12:59 -0400)
data via the listTimeObjects API. Currently only a Weather driver is
available.

timeobjects/config/conf.php [new file with mode: 0644]
timeobjects/config/prefs.php [new file with mode: 0644]
timeobjects/config/timeobjects.php.registry.d [new file with mode: 0644]
timeobjects/lib/Driver.php [new file with mode: 0644]
timeobjects/lib/Driver/Weather.php [new file with mode: 0644]
timeobjects/lib/Exception.php [new file with mode: 0644]
timeobjects/lib/api.php [new file with mode: 0644]
timeobjects/lib/base.php [new file with mode: 0644]

diff --git a/timeobjects/config/conf.php b/timeobjects/config/conf.php
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/timeobjects/config/prefs.php b/timeobjects/config/prefs.php
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/timeobjects/config/timeobjects.php.registry.d b/timeobjects/config/timeobjects.php.registry.d
new file mode 100644 (file)
index 0000000..f18db03
--- /dev/null
@@ -0,0 +1,9 @@
+<?php
+// Edit to suit your needs. I use kronolith's fileroot here to start for
+// simplicity for now since they are both in hatchery.
+$this->applications['timeobjects'] = array(
+    'fileroot' => $this->applications['kronolith']['fileroot'] . '/../timeobjects',
+    'status' => 'hidden',
+    'name' => _("Time Objects"),
+    'provides' => 'timeobjects'
+);
\ No newline at end of file
diff --git a/timeobjects/lib/Driver.php b/timeobjects/lib/Driver.php
new file mode 100644 (file)
index 0000000..b59768b
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+/**
+ * @TODO
+ */
+class TimeObjects_Driver
+{
+    protected $_params = array();
+
+    public function __construct($params)
+    {
+        $this->_params = array_merge($params, $this->_params);
+    }
+
+    /**
+     * @abstract
+     * @param $start
+     * @param $end
+     * @return unknown_type
+     */
+    public function listTimeObjects($start, $end)
+    {
+    }
+
+    public function factory($name, $params = array())
+    {
+        $class = 'TimeObjects_Driver_' . basename($name);
+        if (class_exists($class)) {
+            $driver = &new $class($params);
+        } else {
+            $driver = PEAR::raiseError(sprintf(_("Unable to load the definition of %s."), $class));
+        }
+
+        return $driver;
+    }
+}
\ No newline at end of file
diff --git a/timeobjects/lib/Driver/Weather.php b/timeobjects/lib/Driver/Weather.php
new file mode 100644 (file)
index 0000000..509d5e0
--- /dev/null
@@ -0,0 +1,153 @@
+<?php
+/**
+ * TimeObjects driver for exposing weatherdotcom data via the listTimeObjects API
+ *
+ * @TODO: Inject any config items needed (proxy, partner ids etc...) instead of globaling
+ *        the $conf array.
+ *
+ *        Use Horde_Controller, Routes etc... for endpoints?
+ *
+ * Copyright 2009 The Horde Project (http://www.horde.org/)
+ *
+ * @author Michael J. Rubinsky <mrubinsk@horde.org>
+ * @package TimeObjects
+ */
+class TimeObjects_Driver_Weather extends TimeObjects_Driver
+{
+    protected $_params = array('location' => '08080',
+                               'units' => 'standard',
+                               'days' => 5);
+
+    /**
+     *
+     * @param mixed $start  The start time of the period
+     * @param mixed $time   The end time of the period
+     *
+     * @return array of listTimeObjects arrays.
+     */
+    public function listTimeObjects($start = null, $time = null)
+    {
+        global $conf;
+
+        if (!class_exists('Services_Weather') || !class_exists('Cache')) {
+            throw new TimeObjects_Exception('Services_Weather or PEAR Cache Classes not found.');
+        }
+
+        $options = array();
+        if (!empty($conf['http']['proxy']['proxy_host'])) {
+            $proxy = 'http://';
+            if (!empty($conf['http']['proxy']['proxy_user'])) {
+                $proxy .= urlencode($conf['http']['proxy']['proxy_user']);
+                if (!empty($conf['http']['proxy']['proxy_pass'])) {
+                    $proxy .= ':' . urlencode($conf['http']['proxy']['proxy_pass']);
+                }
+                $proxy .= '@';
+            }
+            $proxy .= $conf['http']['proxy']['proxy_host'];
+            if (!empty($conf['http']['proxy']['proxy_port'])) {
+                $proxy .= ':' . $conf['http']['proxy']['proxy_port'];
+            }
+
+            $options['httpProxy'] = $proxy;
+        }
+
+        if (empty($this->_params['location'])) {
+            throw new TimeObjects_Exception(_("No location is set."));
+        }
+
+        $weatherDotCom = &Services_Weather::service('WeatherDotCom', $options);
+        $weatherDotCom->setAccountData(
+            (isset($conf['weatherdotcom']['partner_id']) ? $conf['weatherdotcom']['partner_id'] : ''),
+            (isset($conf['weatherdotcom']['license_key']) ? $conf['weatherdotcom']['license_key'] : ''));
+
+        $cacheDir = Horde::getTempDir();
+        if (!$cacheDir) {
+            throw new TimeObjects_Exception(_("No temporary directory available for cache."));
+        } else {
+            $weatherDotCom->setCache('file', array('cache_dir' => ($cacheDir . '/')));
+        }
+        $weatherDotCom->setDateTimeFormat('m.d.Y', 'H:i');
+        $weatherDotCom->setUnitsFormat($this->_params['units']);
+        $units = $weatherDotCom->getUnitsFormat();
+
+        // If the user entered a zip code for the location, no need to
+        // search (weather.com accepts zip codes as location IDs).
+        // The location ID should already have been validated in
+        // getParams.
+        //
+        // @TODO: These need to all be changed to use exceptions
+        $search = (preg_match('/\b(?:\\d{5}(-\\d{5})?)|(?:[A-Z]{4}\\d{4})\b/',
+            $this->_params['location'], $matches) ?
+            $matches[0] :
+            $weatherDotCom->searchLocation($this->_params['location']));
+        if (is_a($search, 'PEAR_Error')) {
+            switch ($search->getCode()) {
+            case SERVICES_WEATHER_ERROR_SERVICE_NOT_FOUND:
+                throw new TimeObjects_Exception(_("Requested service could not be found."));
+            case SERVICES_WEATHER_ERROR_UNKNOWN_LOCATION:
+                throw new TimeObjects_Exception(_("Unknown location provided."));
+            case SERVICES_WEATHER_ERROR_WRONG_SERVER_DATA:
+                throw new TimeObjects_Exception(_("Server data wrong or not available."));
+            case SERVICES_WEATHER_ERROR_CACHE_INIT_FAILED:
+                throw new TimeObjects_Exception(_("Cache init was not completed."));
+            case SERVICES_WEATHER_ERROR_DB_NOT_CONNECTED:
+                throw new TimeObjects_Exception(_("MetarDB is not connected."));
+            case SERVICES_WEATHER_ERROR_UNKNOWN_ERROR:
+                throw new TimeObjects_Exception(_("An unknown error has occured."));
+            case SERVICES_WEATHER_ERROR_NO_LOCATION:
+                throw new TimeObjects_Exception(_("No location provided."));
+            case SERVICES_WEATHER_ERROR_INVALID_LOCATION:
+                throw new TimeObjects_Exception(_("Invalid location provided."));
+            case SERVICES_WEATHER_ERROR_INVALID_PARTNER_ID:
+                throw new TimeObjects_Exception(_("Invalid partner id."));
+            case SERVICES_WEATHER_ERROR_INVALID_PRODUCT_CODE:
+                throw new TimeObjects_Exception(_("Invalid product code."));
+            case SERVICES_WEATHER_ERROR_INVALID_LICENSE_KEY:
+               throw new TimeObjects_Exception(_("Invalid license key."));
+            default:
+                throw new TimeObjects_Exception($search->getMessage());
+            }
+        }
+        if (is_array($search)) {
+            throw new TimeObjects_Exception(_(sprintf("Several locations possible with the paramter: ", $this->_params['location'])));
+        }
+        $forecast = $weatherDotCom->getForecast($search, $this->_params['days']);
+        if (is_a($forecast, 'PEAR_Error')) {
+            throw new TimeObjects_Exception($forecast->getMessage());
+        }
+
+        $now = new Horde_Date(time());
+        $objects = array();
+        foreach ($forecast['days'] as $which => $data) {
+            $day = new Horde_Date($now);
+            $day->mday += $which;
+            $day_end = new Horde_Date($day);
+            $day_end->mday++;
+
+            // For day 0, the day portion isn't available after a certain time
+            // simplify and just check for it's presence or use night.
+            $title = sprintf("%s %d%s/%d%s", (!empty($data['day']['condition']) ? $data['day']['condition'] : $data['night']['condition']),
+                                             $data['temperatureHigh'],
+                                             $units['temp'],
+                                             $data['temperatureLow'],
+                                             $units['temp']);
+            $objects[] = array('id' => $day->timestamp(), //???
+                               'title' => $title,
+                               'start' => sprintf('%d-%02d-%02dT00:00:00',
+                                                  $day->year,
+                                                  $day->month,
+                                                  $day->mday),
+                               'end' => sprintf('%d-%02d-%02dT00:00:00',
+                                                $day_end->year,
+                                                $day_end->month,
+                                                $day_end->mday),
+                                'recurrence' => Horde_Date_Recurrence::RECUR_NONE,
+                                'params' => array(),
+                                'icon' =>  Horde::url($GLOBALS['registry']->getImageDir('horde') . '/block/weatherdotcom/23x23/' . ($data['day']['conditionIcon'] == '-' ? 'na' : $data['day']['conditionIcon']) . '.png', true, false)
+                        );
+       }
+
+        return $objects;
+    }
+
+}
\ No newline at end of file
diff --git a/timeobjects/lib/Exception.php b/timeobjects/lib/Exception.php
new file mode 100644 (file)
index 0000000..9aee528
--- /dev/null
@@ -0,0 +1,7 @@
+<?php
+/**
+ *
+ */
+class TimeObjects_Exception extends Exception
+{
+}
\ No newline at end of file
diff --git a/timeobjects/lib/api.php b/timeobjects/lib/api.php
new file mode 100644 (file)
index 0000000..f8ba3ba
--- /dev/null
@@ -0,0 +1,54 @@
+<?php
+/**
+ * API methods for exposing various bits of data via the listTimeObjects API
+ */
+$_services['listTimeObjectCategories'] = array(
+    'type' => '{urn:horde}stringArray'
+);
+
+$_services['listTimeObjects'] = array(
+    'args' => array('categories' => '{urn:horde}stringArray', 'start' => 'int', 'end' => 'int'),
+    'type' => '{urn:horde}hashHash'
+);
+
+// @TODO: Probably implement a URL endpoint or something so we can link
+//        to the correct external site depending on what time object category
+//        we are referring to.
+$_services['show'] = array(
+    'link' => '#',
+);
+
+/**
+ * Returns the available categories we provide.
+ *
+ * Right now, only providing weather data.
+ *
+ * @return array
+ */
+function _timeobjects_listTimeObjectCategories()
+{
+    return array('weather' => _("Weather"));
+}
+
+/**
+ * Obtain the timeObjects for the requested category
+ *
+ * @param array $time_categories  An array of categories to list
+ * @param mixed $start            The start of the time period to list for
+ * @param mixed $end              The end of the time period to list for
+ *
+ * @return An array of timeobject arrays.
+ */
+function _timeobjects_listTimeObjects($time_categories, $start, $end)
+{
+    require_once dirname(__FILE__) . '/base.php';
+
+    $return = array();
+    foreach ($time_categories as $category) {
+        $drv = TimeObjects_Driver::factory($category);
+        $new = $drv->listTimeObjects($start, $end);
+        $return = array_merge($return, $new);
+    }
+
+    return $return;
+}
\ No newline at end of file
diff --git a/timeobjects/lib/base.php b/timeobjects/lib/base.php
new file mode 100644 (file)
index 0000000..59a65f4
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+/**
+ * Base inclusion file
+ *
+ */
+$rto_dir = dirname(__FILE__);
+
+// Check for a prior definition of HORDE_BASE.
+if (!defined('HORDE_BASE')) {
+    /* Temporary fix - if horde does not live directly under the imp
+     * directory, the HORDE_BASE constant should be defined in
+     * imp/lib/base.local.php. */
+    if (file_exists($rto_dir . '/base.local.php')) {
+        include $rto_dir . '/base.local.php';
+    } else {
+        define('HORDE_BASE', $rto_dir . '/../..');
+    }
+}
+
+/* Load the Horde Framework core, and set up inclusion paths. */
+require_once HORDE_BASE . '/lib/core.php';
+Horde_Autoloader::addClassPath($rto_dir);
+Horde_Autoloader::addClassPattern('/^TimeObjects_/', $rto_dir);
+
+/* Registry. */
+$session_control = Util::nonInputVar('session_control');
+if ($session_control == 'none') {
+    $registry = &Registry::singleton(Registry::SESSION_NONE);
+} elseif ($session_control == 'readonly') {
+    $registry = &Registry::singleton(Registry::SESSION_READONLY);
+} else {
+    $registry = &Registry::singleton();
+}
+
+if (is_a(($pushed = $registry->pushApp('timeobjects', !defined('AUTH_HANDLER'))), 'PEAR_Error')) {
+    if ($pushed->getCode() == 'permission_denied') {
+        Horde::authenticationFailureRedirect();
+    }
+    Horde::fatal($pushed, __FILE__, __LINE__, false);
+}
+
+if (!defined('TIMEOBJECTS_BASE')) {
+    define('TIMEOBJECTS_BASE', $rto_dir . '/..');
+}
\ No newline at end of file